From 5728a89e4ffca0431ff3b67340e5dd0c0724797a Mon Sep 17 00:00:00 2001 From: Godwin Date: Mon, 16 Jan 2017 07:53:47 -0800 Subject: [PATCH 01/45] Added missing Spanish translations --- config/locales/es.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/locales/es.yml b/config/locales/es.yml index fe1ec17..80529cd 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -753,6 +753,8 @@ es: conferences: Bike!Bike! conferencias se llevan a cabo nacionalmente una vez al año en una ciudad diferente en América del Norte; Las conferencias regionales pueden celebrarse en cualquier lugar y en cualquier momento. conference_registration: headings: + Pre_Register: Pre-Register for %{title} + Register: Register for %{title} Workshops_Looking_For_Facilitators: Talleres que necesitan facilitadorxs Add_Workshop: Proponer un taller workshops: Talleres From bdf540664d53e5e661a784171f45d1644052234e Mon Sep 17 00:00:00 2001 From: Godwin Date: Mon, 16 Jan 2017 07:56:49 -0800 Subject: [PATCH 02/45] Actually translated the English to Spanish --- config/locales/es.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/es.yml b/config/locales/es.yml index 80529cd..684c2f5 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -753,8 +753,8 @@ es: conferences: Bike!Bike! conferencias se llevan a cabo nacionalmente una vez al año en una ciudad diferente en América del Norte; Las conferencias regionales pueden celebrarse en cualquier lugar y en cualquier momento. conference_registration: headings: - Pre_Register: Pre-Register for %{title} - Register: Register for %{title} + Pre_Register: Pre-Registrarse para %{title} + Register: Registrarse para %{title} Workshops_Looking_For_Facilitators: Talleres que necesitan facilitadorxs Add_Workshop: Proponer un taller workshops: Talleres From ee647904b4234d8d8f70e3671f275de45dd50c8e Mon Sep 17 00:00:00 2001 From: Godwin Date: Mon, 16 Jan 2017 08:21:40 -0800 Subject: [PATCH 03/45] Fixed JS error reporting --- app/assets/javascripts/main.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index d467b08..feb2bb4 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -1,15 +1,15 @@ (function() { - window.onerror = function(message, url, lineNumber) { + window.onerror = function(message, url, lineNumber) { //save error and send to server for example. var request = new XMLHttpRequest(); - request.open('POST', '/js_error', true); - request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); - request.send( + var data = 'message=' + encodeURI(message) + '&url=' + encodeURI(url) + '&lineNumber=' + encodeURI(lineNumber) + - '&location=' + encodeURI(window.location.href) - ); + '&location=' + encodeURI(window.location.href); + request.open('GET', '/js_error?' + data, true); + request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); + request.send(); return false; }; window.forEach = function(a, f) { Array.prototype.forEach.call(a, f) }; From 8b9a11575c01ba6a6ca139204df4347c9e9bf16d Mon Sep 17 00:00:00 2001 From: Godwin Date: Thu, 19 Jan 2017 09:33:48 -0800 Subject: [PATCH 04/45] Fixed an issue with dataset in IE10 --- app/assets/javascripts/main.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index feb2bb4..9dd46da 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -67,8 +67,8 @@ if (msg) { dlg.querySelector('.message').innerHTML = msg.innerHTML } - if (link.dataset.infoTitle) { - dlg.querySelector('.title').innerHTML = decodeURI(link.dataset.infoTitle); + if (link.getAttribute('data-infoTitle')) { + dlg.querySelector('.title').innerHTML = decodeURI(link.getAttribute('data-infoTitle')); } confirmBtn = dlg.querySelector('.confirm'); if (confirmBtn) { @@ -133,7 +133,7 @@ var htmlNode = document.documentElement; document.addEventListener('keydown', function(event) { - if (htmlNode.dataset.input != 'kb' && + if (htmlNode.getAttribute('data-input') != 'kb' && ((["input", "textarea", "select", "option"].indexOf(event.target.nodeName.toLowerCase()) < 0 && !event.target.attributes.contenteditable) || event.key == "Tab")) { htmlNode.setAttribute('data-input', 'kb'); @@ -141,7 +141,7 @@ }); document.addEventListener('mousemove', function(event) { - if (htmlNode.dataset.input != 'mouse' && (event.movementX || event.movementY)) { + if (htmlNode.getAttribute('data-input') != 'mouse' && (event.movementX || event.movementY)) { htmlNode.setAttribute('data-input', 'mouse'); } }); From 888f186a34ea43dd7db011cf4b6ac6f23db5126d Mon Sep 17 00:00:00 2001 From: Godwin Date: Sun, 19 Feb 2017 20:00:04 -0800 Subject: [PATCH 05/45] Moved to GMail and fixed administration errors --- Gemfile | 87 ++++---- .../stylesheets/bumbleberry-settings.json | 4 +- app/controllers/application_controller.rb | 12 +- .../conference_administration_controller.rb | 47 ++-- app/helpers/application_helper.rb | 3 +- app/models/event_location.rb | 27 ++- app/views/application/home.html.haml | 4 +- .../_broadcast.html.haml | 48 +++-- .../_registration_status.html.haml | 14 +- .../_registrations.html.haml | 38 ++-- .../_stats.html.haml | 46 ++-- .../administration_step.html.haml | 2 +- .../stats.html.haml | 126 +++++------ .../stats.xlsx.haml | 2 +- config/environments/preview.rb | 155 +++++++------- config/environments/production.rb | 201 +++++++++--------- config/locales/en.yml | 4 + db/schema.rb | 9 - 18 files changed, 431 insertions(+), 398 deletions(-) rename app/views/{conferences => conference_administration}/stats.html.haml (97%) rename app/views/{conferences => conference_administration}/stats.xlsx.haml (96%) diff --git a/Gemfile b/Gemfile index 851267c..1d0c2bd 100644 --- a/Gemfile +++ b/Gemfile @@ -5,15 +5,18 @@ gem 'pg' gem 'rake', '11.1.2' gem 'ruby_dep', '1.3.1' # Lock at 1.3.1 since 1.4 requires ruby 2.5. We should unlock once we upgrade the ruby version on our server +# gem 'bcrypt-ruby', '3.0.0', require: 'bcrypt' +# gem 'bcrypt', '3.1.9' +# gem 'bcrypt', require: :ruby gem 'rack-mini-profiler' gem 'haml' gem 'nokogiri', '~> 1.6.8.rc2' if Dir.exists?('../lingua_franca') - gem 'lingua_franca', :path => '../lingua_franca' + gem 'lingua_franca', :path => '../lingua_franca' else - gem 'lingua_franca', :git => 'git://github.com/lingua-franca/lingua_franca.git' + gem 'lingua_franca', :git => 'git://github.com/lingua-franca/lingua_franca.git' end gem 'tzinfo-data' @@ -21,15 +24,15 @@ gem 'sass' gem 'sass-rails' if Dir.exists?('../bumbleberry') - gem 'bumbleberry', :path => "../bumbleberry" + gem 'bumbleberry', :path => "../bumbleberry" else - gem 'bumbleberry', :git => 'git://github.com/bumbleberry/bumbleberry.git' + gem 'bumbleberry', :git => 'git://github.com/bumbleberry/bumbleberry.git' end if Dir.exists?('../paypal-express') - gem 'paypal-express', :path => "../paypal-express" + gem 'paypal-express', :path => "../paypal-express" else - gem 'paypal-express', :git => 'git://github.com/bikebike/paypal-express.git' + gem 'paypal-express', :git => 'git://github.com/bikebike/paypal-express.git' end gem 'uglifier', '>= 1.3.0' @@ -51,52 +54,52 @@ gem 'launchy' gem 'to_spreadsheet', :git => 'git://github.com/glebm/to_spreadsheet.git' group :test do - gem 'rspec' - gem 'rspec-rails' + gem 'rspec' + gem 'rspec-rails' end group :development do - gem 'better_errors' - gem 'binding_of_caller' - gem 'meta_request' - - gem 'capistrano', '~> 3.1' - gem 'capistrano-rails', '~> 1.1' - gem 'capistrano-faster-assets', '~> 1.0' - - gem 'eventmachine', :github => 'krzcho/eventmachine', :branch => 'master' - gem 'thin'# , :github => 'krzcho/thin', :branch => 'master' + gem 'better_errors' + gem 'binding_of_caller' + gem 'meta_request' + + gem 'capistrano', '~> 3.1' + gem 'capistrano-rails', '~> 1.1' + gem 'capistrano-faster-assets', '~> 1.0' + + gem 'eventmachine', :github => 'krzcho/eventmachine', :branch => 'master' + gem 'thin'# , :github => 'krzcho/thin', :branch => 'master' end group :test do - gem 'gherkin3', '>= 3.1.0' - gem 'cucumber' - gem 'cucumber-core' - gem 'cucumber-rails' - - gem 'poltergeist' - gem 'guard-rspec' - gem 'factory_girl_rails' - gem 'coveralls', require: false - gem 'selenium-webdriver' - gem 'simplecov', require: false - gem 'webmock', require: false - gem 'database_cleaner' - gem 'mocha' -end - -group :staging, :production, :preview do - gem 'rails_12factor' + gem 'gherkin3', '>= 3.1.0' + gem 'cucumber' + gem 'cucumber-core' + gem 'cucumber-rails' + + gem 'poltergeist' + gem 'guard-rspec' + gem 'factory_girl_rails' + gem 'coveralls', require: false + gem 'selenium-webdriver' + gem 'simplecov', require: false + gem 'webmock', require: false + gem 'database_cleaner' + gem 'mocha' end group :production, :preview do - gem 'unicorn' - gem 'daemon-spawn' - gem 'daemons' + gem 'rails_12factor' + gem 'daemon-spawn' + gem 'daemons' + + platforms :ruby do + gem 'unicorn' + end end platforms 'mswin', 'mingw' do - group :test do - gem 'wdm', '>= 0.1.0' - end + group :test do + gem 'wdm', '>= 0.1.0' + end end diff --git a/app/assets/stylesheets/bumbleberry-settings.json b/app/assets/stylesheets/bumbleberry-settings.json index c0bafee..0ca48d3 100644 --- a/app/assets/stylesheets/bumbleberry-settings.json +++ b/app/assets/stylesheets/bumbleberry-settings.json @@ -5,8 +5,8 @@ "chrome": ["51"] }, "development": { - "and_chr": ["55"], - "chrome": ["55"], + "and_chr": ["56"], + "chrome": ["56"], "edge": ["13"], "firefox": ["50"], "ie": ["11"], diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7f6039b..131f5da 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -78,11 +78,15 @@ class ApplicationController < LinguaFrancaApplicationController end def home - @workshops = Workshop.where(:conference_id => @conference.id) + @workshops = [] - if @conference.workshop_schedule_published - @event_dlg = true - get_scheule_data(false) + if @conference.present? + @workshops = Workshop.where(conference_id: @conference.id) + + if @conference.workshop_schedule_published + @event_dlg = true + get_scheule_data(false) + end end end diff --git a/app/controllers/conference_administration_controller.rb b/app/controllers/conference_administration_controller.rb index e64d547..193edb3 100644 --- a/app/controllers/conference_administration_controller.rb +++ b/app/controllers/conference_administration_controller.rb @@ -62,7 +62,10 @@ class ConferenceAdministrationController < ApplicationController set_flash_messages # redirect to the step unless the method handled redirection itself - unless self.send(method_name) + case self.send(method_name) + when true + administration_step(@admin_step) + when false redirect_to administration_step_path(@this_conference.slug, @admin_step) end end @@ -100,6 +103,9 @@ class ConferenceAdministrationController < ApplicationController end def administrate_broadcast + if @this_conference.start_date.blank? || @this_conference.end_date.blank? + @warning_message = :no_date_warning + end end def administrate_broadcast_sent @@ -120,6 +126,9 @@ class ConferenceAdministrationController < ApplicationController end def administrate_registration_status + if @this_conference.start_date.blank? || @this_conference.end_date.blank? + @warning_message = :no_date_warning + end end def administrate_organizations @@ -165,6 +174,11 @@ class ConferenceAdministrationController < ApplicationController end def administrate_registrations + if @this_conference.start_date.blank? || @this_conference.end_date.blank? + @warning_message = :no_date_warning + return + end + get_stats(!request.format.xlsx?) if request.format.xlsx? @@ -200,6 +214,11 @@ class ConferenceAdministrationController < ApplicationController end def administrate_stats + if @this_conference.start_date.blank? || @this_conference.end_date.blank? + @warning_message = :no_date_warning + return + end + get_stats(!request.format.xlsx?) if request.format.xlsx? @@ -736,7 +755,7 @@ class ConferenceAdministrationController < ApplicationController end else do_404 - return true + return nil end return false @@ -945,7 +964,7 @@ class ConferenceAdministrationController < ApplicationController do_404 end - return true + return nil end def admin_update_housing @@ -985,7 +1004,7 @@ class ConferenceAdministrationController < ApplicationController do_404 end - return true + return nil end def admin_update_broadcast @@ -1007,7 +1026,7 @@ class ConferenceAdministrationController < ApplicationController end end redirect_to administration_step_path(@this_conference.slug, :broadcast_sent) - return true + return nil elsif params[:button] == 'preview' @send_to_count = view_context.broadcast_to(@send_to).size @broadcast_step = :preview @@ -1024,7 +1043,7 @@ class ConferenceAdministrationController < ApplicationController end @send_to_count = view_context.broadcast_to(@send_to).size end - return false + return true end def admin_update_locations @@ -1087,14 +1106,14 @@ class ConferenceAdministrationController < ApplicationController end do_404 - return true + return nil end def admin_update_events case params[:button] when 'edit' redirect_to edit_event_path(@this_conference.slug, params[:id]) - return true + return nil when 'save' if params[:id].present? event = Event.find_by!(conference_id: @this_conference.id, id: params[:id]) @@ -1124,7 +1143,7 @@ class ConferenceAdministrationController < ApplicationController end do_404 - return true + return nil end def admin_update_workshop_times @@ -1141,7 +1160,7 @@ class ConferenceAdministrationController < ApplicationController end do_404 - return true + return nil end def admin_update_schedule @@ -1155,7 +1174,6 @@ class ConferenceAdministrationController < ApplicationController @entire_page = false get_scheule_data render partial: 'schedule' - return true when 'get-workshop-list' get_scheule_data(true) @@ -1171,7 +1189,6 @@ class ConferenceAdministrationController < ApplicationController end render partial: 'select_workshop_table' - return true when 'set-workshop' workshop = Workshop.find_by!(conference_id: @this_conference.id, id: params[:workshop].to_i) workshop.event_location_id = params[:location] @@ -1183,11 +1200,11 @@ class ConferenceAdministrationController < ApplicationController get_scheule_data render partial: 'schedule' - return true + else + do_404 end - do_404 - return true + return nil end def admin_update_schedule diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 1fbd759..9c9797b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -991,7 +991,8 @@ module ApplicationHelper def administration_sub_steps { location_edit: :locations, - event_edit: :events + event_edit: :events, + broadcast_sent: :broadcast } end diff --git a/app/models/event_location.rb b/app/models/event_location.rb index bb09ffb..a179995 100644 --- a/app/models/event_location.rb +++ b/app/models/event_location.rb @@ -4,22 +4,21 @@ require 'geocoder/railtie' Geocoder::Railtie.insert class EventLocation < ActiveRecord::Base - belongs_to :conference - geocoded_by :full_address + belongs_to :conference + geocoded_by :full_address - reverse_geocoded_by :latitude, :longitude, :address => :full_address - after_validation :geocode, if: ->(obj){ obj.address_changed? } + reverse_geocoded_by :latitude, :longitude, :address => :full_address + after_validation :geocode, if: ->(obj){ obj.address_changed? } - def full_address - l = conference.location - [address, l.city, l.territory, l.country].join(', ') - end + def full_address + [address, conference.city.city, conference.city.territory, conference.city.country].join(', ') + end - def self.all_spaces - Workshop.all_spaces + [:event_space] - end + def self.all_spaces + Workshop.all_spaces + [:event_space] + end - def self.all_amenities - Workshop.all_needs - end + def self.all_amenities + Workshop.all_needs + end end diff --git a/app/views/application/home.html.haml b/app/views/application/home.html.haml index 8e827c1..4d3d522 100644 --- a/app/views/application/home.html.haml +++ b/app/views/application/home.html.haml @@ -1,5 +1,5 @@ - content_for :og_image do - = @conference.poster.full.url || image_path('default_poster.jpg') -- if @conferences + = (@conference.present? ? @conference.poster.full.url : nil) || image_path('default_poster.jpg') +- if @conferences.present? - @conferences.each do | conference | = render 'conferences/conference', conference: conference, links: [ :read_more, :register ] diff --git a/app/views/conference_administration/_broadcast.html.haml b/app/views/conference_administration/_broadcast.html.haml index 2990e25..ff1ae50 100644 --- a/app/views/conference_administration/_broadcast.html.haml +++ b/app/views/conference_administration/_broadcast.html.haml @@ -1,23 +1,27 @@ -= columns(medium: 12) do - = admin_update_form do - - if @broadcast_step == :preview || @broadcast_step == :test - = hidden_field_tag :subject, @subject - = hidden_field_tag :body, @body - = hidden_field_tag :send_to, @send_to - - if @broadcast_step == :preview - %p= _'articles.conference_registration.paragraphs.admin.broadcast.test', vars: { send_to_count: "#{(@send_to_count || 0)}".html_safe } +- if @warning_message + = columns(medium: 12) do + .warning-info=_"articles.admin.registrations.#{@warning_message}" +- else + = columns(medium: 12) do + = admin_update_form do + - if @broadcast_step == :preview || @broadcast_step == :test + = hidden_field_tag :subject, @subject + = hidden_field_tag :body, @body + = hidden_field_tag :send_to, @send_to + - if @broadcast_step == :preview + %p= _'articles.conference_registration.paragraphs.admin.broadcast.test', vars: { send_to_count: "#{(@send_to_count || 0)}".html_safe } + - else + .warning-info.make-room= _'articles.conference_registration.paragraphs.admin.broadcast.preview', vars: { send_to_count: "#{(@send_to_count || 0)}".html_safe } + .test-preview + %h3=@subject + = richtext @body, 4 + .actions.right + = button_tag :test, value: :test, class: :secondary if @broadcast_step == :preview + = button_with_confirmation :send, (_'modals.admin.broadcast.confirm', vars: { number: "#{(@send_to_count || 0)}".html_safe }), value: :send, class: :delete if @broadcast_step == :test + = button_tag :edit, value: :edit - else - .warning-info.make-room= _'articles.conference_registration.paragraphs.admin.broadcast.preview', vars: { send_to_count: "#{(@send_to_count || 0)}".html_safe } - .test-preview - %h3=@subject - = richtext @body, 4 - .actions.right - = button_tag :test, value: :test, class: :secondary if @broadcast_step == :preview - = button_with_confirmation :send, (_'modals.admin.broadcast.confirm', vars: { number: "#{(@send_to_count || 0)}".html_safe }), value: :send, class: :delete if @broadcast_step == :test - = button_tag :edit, value: :edit - - else - = selectfield :send_to, nil, broadcast_options, full: true - = textfield :subject, @subject, required: true, big: true - = textarea :body, @body, lang: @this_conference.locale, edit_on: :focus - .actions.right - = button_tag :preview, value: :preview + = selectfield :send_to, nil, broadcast_options, full: true + = textfield :subject, @subject, required: true, big: true + = textarea :body, @body, lang: @this_conference.locale, edit_on: :focus + .actions.right + = button_tag :preview, value: :preview diff --git a/app/views/conference_administration/_registration_status.html.haml b/app/views/conference_administration/_registration_status.html.haml index 7558d58..7fef8de 100644 --- a/app/views/conference_administration/_registration_status.html.haml +++ b/app/views/conference_administration/_registration_status.html.haml @@ -1,5 +1,9 @@ -= columns(medium: 12) do - = form_tag administration_update_path(@this_conference.slug, @admin_step) do - = selectfield :registration_status, @this_conference.registration_status || 'closed', registration_status_options_list, inline_label: true - .actions.left - = button_tag :save, value: :save +- if @warning_message + = columns(medium: 12) do + .warning-info=_"articles.admin.registrations.#{@warning_message}" +- else + = columns(medium: 12) do + = form_tag administration_update_path(@this_conference.slug, @admin_step) do + = selectfield :registration_status, @this_conference.registration_status || 'closed', registration_status_options_list, inline_label: true + .actions.left + = button_tag :save, value: :save diff --git a/app/views/conference_administration/_registrations.html.haml b/app/views/conference_administration/_registrations.html.haml index 25f07e0..c6a24fa 100644 --- a/app/views/conference_administration/_registrations.html.haml +++ b/app/views/conference_administration/_registrations.html.haml @@ -1,17 +1,21 @@ -- add_inline_script :registrations -= columns(medium: 12) do - .goes-fullscreen#registrations-table - .flex-column - = searchfield :search, nil, big: true, stretch: true - %a.button{data: { expands: 'registrations-table' }}='expand' - %a.button.delete{data: { contracts: 'registrations-table' }}='close' - %a.button.modify{data: { 'opens-modal': 'new-registration' }}='+' - .table-scroller - = html_table @excel_data, registrations_table_options - = admin_update_form id: 'new-registration', class: 'modal-edit' do - .modal-edit-overlay{data: { 'closes-modal': 'new-registration' }} - .modal-edit-content - = html_edit_table @excel_data, registrations_edit_table_options - .actions.right - %a.button.subdued{data: { 'closes-modal': 'new-registration' }}='Cancel' - = button_tag :save, value: :save, class: :modify +- if @warning_message + = columns(medium: 12) do + .warning-info=_"articles.admin.registrations.#{@warning_message}" +- else + - add_inline_script :registrations + = columns(medium: 12) do + .goes-fullscreen#registrations-table + .flex-column + = searchfield :search, nil, big: true, stretch: true + %a.button{data: { expands: 'registrations-table' }}='expand' + %a.button.delete{data: { contracts: 'registrations-table' }}='close' + %a.button.modify{data: { 'opens-modal': 'new-registration' }}='+' + .table-scroller + = html_table @excel_data, registrations_table_options + = admin_update_form id: 'new-registration', class: 'modal-edit' do + .modal-edit-overlay{data: { 'closes-modal': 'new-registration' }} + .modal-edit-content + = html_edit_table @excel_data, registrations_edit_table_options + .actions.right + %a.button.subdued{data: { 'closes-modal': 'new-registration' }}='Cancel' + = button_tag :save, value: :save, class: :modify diff --git a/app/views/conference_administration/_stats.html.haml b/app/views/conference_administration/_stats.html.haml index 5cd1972..da621f1 100644 --- a/app/views/conference_administration/_stats.html.haml +++ b/app/views/conference_administration/_stats.html.haml @@ -1,21 +1,25 @@ -= columns(medium: 12) do - .details - = data_set(:h3, 'articles.admin.stats.headings.completed_registrations') do - = (@completed_registrations || 0).to_s - = data_set(:h3, 'articles.admin.stats.headings.incomplete_registrations') do - = ((@registration_count - @completed_registrations) || 0).to_s - = data_set(:h3, 'articles.admin.stats.headings.bikes') do - = (@completed_registrations || 0) > 0 ? "#{@bikes} (#{number_to_percentage(@bikes / @completed_registrations.to_f * 100.0)})" : "0" - = data_set(:h3, 'articles.admin.stats.headings.food.meat') do - = (@food[:all] || 0) > 0 ? "#{@food[:meat]} (#{number_to_percentage(@food[:meat] / @food[:all].to_f * 100.0)})" : "0" - = data_set(:h3, 'articles.admin.stats.headings.food.vegetarian') do - = (@food[:all] || 0) > 0 ? "#{@food[:vegetarian]} (#{number_to_percentage(@food[:vegetarian] / @food[:all].to_f * 100.0)})" : "0" - = data_set(:h3, 'articles.admin.stats.headings.food.vegan') do - = (@food[:all] || 0) > 0 ? "#{@food[:vegan]} (#{number_to_percentage(@food[:vegan] / @food[:all].to_f * 100.0)})" : "0" - = data_set(:h3, 'articles.admin.stats.headings.donation_count') do - = (@completed_registrations || 0) > 0 ? "#{@donation_count} (#{number_to_percentage(@donation_count / @completed_registrations.to_f * 100.0)})" : "0" - = data_set(:h3, 'articles.admin.stats.headings.donation_total') do - = "$#{@donations || 0.00}" - .actions - = link_to (_'links.download.Excel'), administration_step_path(@this_conference.slug, :stats, :format => :xlsx), class: [:button, :download] - = link_to (_'links.download.Organizations_Excel'), administration_step_path(@this_conference.slug, :organizations, :format => :xlsx), class: [:button, :download, :subdued] +- if @warning_message + = columns(medium: 12) do + .warning-info=_"articles.admin.registrations.#{@warning_message}" +- else + = columns(medium: 12) do + .details + = data_set(:h3, 'articles.admin.stats.headings.completed_registrations') do + = (@completed_registrations || 0).to_s + = data_set(:h3, 'articles.admin.stats.headings.incomplete_registrations') do + = ((@registration_count - @completed_registrations) || 0).to_s + = data_set(:h3, 'articles.admin.stats.headings.bikes') do + = (@completed_registrations || 0) > 0 ? "#{@bikes} (#{number_to_percentage(@bikes / @completed_registrations.to_f * 100.0)})" : "0" + = data_set(:h3, 'articles.admin.stats.headings.food.meat') do + = (@food[:all] || 0) > 0 ? "#{@food[:meat]} (#{number_to_percentage(@food[:meat] / @food[:all].to_f * 100.0)})" : "0" + = data_set(:h3, 'articles.admin.stats.headings.food.vegetarian') do + = (@food[:all] || 0) > 0 ? "#{@food[:vegetarian]} (#{number_to_percentage(@food[:vegetarian] / @food[:all].to_f * 100.0)})" : "0" + = data_set(:h3, 'articles.admin.stats.headings.food.vegan') do + = (@food[:all] || 0) > 0 ? "#{@food[:vegan]} (#{number_to_percentage(@food[:vegan] / @food[:all].to_f * 100.0)})" : "0" + = data_set(:h3, 'articles.admin.stats.headings.donation_count') do + = (@completed_registrations || 0) > 0 ? "#{@donation_count} (#{number_to_percentage(@donation_count / @completed_registrations.to_f * 100.0)})" : "0" + = data_set(:h3, 'articles.admin.stats.headings.donation_total') do + = "$#{@donations || 0.00}" + .actions + = link_to (_'links.download.Excel'), administration_step_path(@this_conference.slug, :stats, :format => :xlsx), class: [:button, :download] + = link_to (_'links.download.Organizations_Excel'), administration_step_path(@this_conference.slug, :organizations, :format => :xlsx), class: [:button, :download, :subdued] diff --git a/app/views/conference_administration/administration_step.html.haml b/app/views/conference_administration/administration_step.html.haml index b35ea89..b45ea15 100644 --- a/app/views/conference_administration/administration_step.html.haml +++ b/app/views/conference_administration/administration_step.html.haml @@ -35,4 +35,4 @@ = columns(medium: 12) do %p=((_"articles.admin.#{@admin_group}.descriptions.#{@admin_step}", :s)) unless @hide_description === true = row do - = render @admin_step + = render @admin_step.to_s diff --git a/app/views/conferences/stats.html.haml b/app/views/conference_administration/stats.html.haml similarity index 97% rename from app/views/conferences/stats.html.haml rename to app/views/conference_administration/stats.html.haml index 98e9848..ba4bd86 100644 --- a/app/views/conferences/stats.html.haml +++ b/app/views/conference_administration/stats.html.haml @@ -1,63 +1,63 @@ -= render 'page_header', :page_key => 'Registration_Stats' -%article - = row do - = columns(medium: 12) do - %h2=_'articles.conference_registration.headings.Stats' - %p=_'articles.conference_registration.paragraphs.Stats', :p - = link_to (_'links.download.Excel','Download Data in Excel Format'), stats_path(@this_conference.slug, :format => :xls), {:class => :button} - = columns(medium: 6) do - %ul.stats - %li - %h3=_'articles.conference_registration.terms.Total_Registrations' - .stat.important=_!@total_registrations - %li.money - %h3=_'articles.conference_registration.terms.Total_Donations' - .stat=money @total_donations - %li - %h3=_'articles.conference_registration.terms.Donation_Count' - .stat - = _!"#{@donation_count} / #{@total_registrations}" - %li - %h3=_'articles.conference_registration.terms.Housing' - .breakdown - - @housing.each do |h, v| - - unless h == :none - .stat-with-label - .label=_"articles.conference_registration.questions.housing.#{h}" - .stat=_!v - %li - %h3=_'articles.conference_registration.terms.Bikes' - .breakdown - .stat=_!"#{@bike_count} / #{@total_registrations}" - - @bikes.each do |h, v| - - unless h == :none - .stat-with-label - .label=_"articles.conference_registration.questions.bike.#{h}" - .stat.percent=_!percent(v.to_f / @bike_count) - %li - %h3=_'articles.conference_registration.terms.Food' - .breakdown - - @food.each do |h, v| - .stat-with-label - .label=_"articles.conference_registration.questions.food.#{h}" - .stat.percent=_!percent(v.to_f / @total_registrations) - %li - %h3=_'articles.conference_registration.terms.Languages' - .breakdown - - @languages.each do |h, v| - .stat-with-label - .label=_"languages.#{h}" - .stat.percent=_!percent(v.to_f / @total_registrations) - = columns(medium: 6) do - .allergies - %h3=_'articles.conference_registration.headings.Allergies' - %ul - - @allergies.each do |a| - %li - %p=_!a - .other - %h3=_'articles.conference_registration.headings.other' - %ul - - @other.each do |o| - %li - %p=_!o += render 'page_header', :page_key => 'Registration_Stats' +%article + = row do + = columns(medium: 12) do + %h2=_'articles.conference_registration.headings.Stats' + %p=_'articles.conference_registration.paragraphs.Stats', :p + = link_to (_'links.download.Excel','Download Data in Excel Format'), stats_path(@this_conference.slug, :format => :xls), {:class => :button} + = columns(medium: 6) do + %ul.stats + %li + %h3=_'articles.conference_registration.terms.Total_Registrations' + .stat.important=_!@total_registrations + %li.money + %h3=_'articles.conference_registration.terms.Total_Donations' + .stat=money @total_donations + %li + %h3=_'articles.conference_registration.terms.Donation_Count' + .stat + = _!"#{@donation_count} / #{@total_registrations}" + %li + %h3=_'articles.conference_registration.terms.Housing' + .breakdown + - @housing.each do |h, v| + - unless h == :none + .stat-with-label + .label=_"articles.conference_registration.questions.housing.#{h}" + .stat=_!v + %li + %h3=_'articles.conference_registration.terms.Bikes' + .breakdown + .stat=_!"#{@bike_count} / #{@total_registrations}" + - @bikes.each do |h, v| + - unless h == :none + .stat-with-label + .label=_"articles.conference_registration.questions.bike.#{h}" + .stat.percent=_!percent(v.to_f / @bike_count) + %li + %h3=_'articles.conference_registration.terms.Food' + .breakdown + - @food.each do |h, v| + .stat-with-label + .label=_"articles.conference_registration.questions.food.#{h}" + .stat.percent=_!percent(v.to_f / @total_registrations) + %li + %h3=_'articles.conference_registration.terms.Languages' + .breakdown + - @languages.each do |h, v| + .stat-with-label + .label=_"languages.#{h}" + .stat.percent=_!percent(v.to_f / @total_registrations) + = columns(medium: 6) do + .allergies + %h3=_'articles.conference_registration.headings.Allergies' + %ul + - @allergies.each do |a| + %li + %p=_!a + .other + %h3=_'articles.conference_registration.headings.other' + %ul + - @other.each do |o| + %li + %p=_!o diff --git a/app/views/conferences/stats.xlsx.haml b/app/views/conference_administration/stats.xlsx.haml similarity index 96% rename from app/views/conferences/stats.xlsx.haml rename to app/views/conference_administration/stats.xlsx.haml index 7490ac9..54d73d1 100644 --- a/app/views/conferences/stats.xlsx.haml +++ b/app/views/conference_administration/stats.xlsx.haml @@ -1 +1 @@ -= excel_table(@excel_data) += excel_table(@excel_data) diff --git a/config/environments/preview.rb b/config/environments/preview.rb index 8c77f27..f39d999 100644 --- a/config/environments/preview.rb +++ b/config/environments/preview.rb @@ -1,105 +1,104 @@ BikeBike::Application.configure do - # Settings specified here will take precedence over those in config/application.rb. + # Settings specified here will take precedence over those in config/application.rb. - # Code is not reloaded between requests. - config.cache_classes = true + # Code is not reloaded between requests. + config.cache_classes = true - # Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both thread web servers - # and those relying on copy on write to perform better. - # Rake tasks automatically ignore this option for performance. - config.eager_load = true + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both thread web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true - # Full error reports are disabled and caching is turned on. - config.consider_all_requests_local = false - config.action_controller.perform_caching = true + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true - # Enable Rack::Cache to put a simple HTTP cache in front of your application - # Add `rack-cache` to your Gemfile before enabling this. - # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. - # config.action_dispatch.rack_cache = true + # Enable Rack::Cache to put a simple HTTP cache in front of your application + # Add `rack-cache` to your Gemfile before enabling this. + # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. + # config.action_dispatch.rack_cache = true - # Disable Rails's static asset server (Apache or nginx will already do this). - config.serve_static_files = true + # Disable Rails's static asset server (Apache or nginx will already do this). + config.serve_static_files = true - # Compress JavaScripts and CSS. - config.assets.js_compressor = :uglifier - # config.assets.css_compressor = :sass + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass - # Do not fallback to assets pipeline if a precompiled asset is missed. - #config.assets.compile = true + # Do not fallback to assets pipeline if a precompiled asset is missed. + #config.assets.compile = true - # Generate digests for assets URLs. - config.assets.digest = true + # Generate digests for assets URLs. + config.assets.digest = true - # Version of your assets, change this if you want to expire all your assets. - config.assets.version = '1.0101' + # Version of your assets, change this if you want to expire all your assets. + config.assets.version = '1.0101' - # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache - # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx - # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - #config.force_ssl = true + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + #config.force_ssl = true - # Set to :debug to see everything in the log. - config.log_level = :info + # Set to :debug to see everything in the log. + config.log_level = :info - #config.cache_classes = true - #config.serve_static_assets = true - #config.assets.compile = true - # config.assets.digest = true + #config.cache_classes = true + #config.serve_static_assets = true + #config.assets.compile = true + # config.assets.digest = true - # Prepend all log lines with the following tags. - # config.log_tags = [ :subdomain, :uuid ] + # Prepend all log lines with the following tags. + # config.log_tags = [ :subdomain, :uuid ] - # Use a different logger for distributed setups. - # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + # Use a different logger for distributed setups. + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) - # Use a different cache store in production. - # config.cache_store = :mem_cache_store + # Use a different cache store in production. + # config.cache_store = :mem_cache_store - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.action_controller.asset_host = "http://assets.example.com" + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = "http://assets.example.com" - # Precompile additional assets. - # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. - # config.assets.precompile += %w( search.js ) - config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif) - #config.action_controller.asset_host = "https://preview-cdn.bikebike.org" + # Precompile additional assets. + # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. + # config.assets.precompile += %w( search.js ) + config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif) + #config.action_controller.asset_host = "https://preview-cdn.bikebike.org" - # Ignore bad email addresses and do not raise email delivery errors. - # Set this to true and configure the email server for immediate delivery to raise delivery errors. - # config.action_mailer.raise_delivery_errors = false + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation can not be found). - config.i18n.fallbacks = true + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation can not be found). + config.i18n.fallbacks = true - # Send deprecation notices to registered listeners. - config.active_support.deprecation = :notify + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify - # Disable automatic flushing of the log to improve performance. - # config.autoflush_log = false + # Disable automatic flushing of the log to improve performance. + # config.autoflush_log = false - # Use default logging formatter so that PID and timestamp are not suppressed. - config.log_formatter = ::Logger::Formatter.new + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new - config.action_mailer.delivery_method = :smtp - config.action_mailer.smtp_settings = { - :address => 'mail.bikebike.org', - :domain => 'preview.bikebike.org', - :port => 587, - :authentication => :plain, - :enable_starttls_auto => true, - :openssl_verify_mode => 'none', - :user_name => 'info@preview.bikebike.org', - :password => 'test' - } - config.action_mailer.raise_delivery_errors = true - config.action_mailer.perform_deliveries = true + config.action_mailer.delivery_method = :smtp + config.action_mailer.smtp_settings = { + :address => 'smtp.gmail.com', + :domain => 'localhost:3000', + :port => 587, + :authentication => :plain, + :enable_starttls_auto => true, + :user_name => ENV['MAILER_USER'], + :password => ENV['MAILER_PASSWORD'] + } + config.action_mailer.raise_delivery_errors = true + config.action_mailer.perform_deliveries = true - I18n.config.language_detection_method = I18n::Config::DETECT_LANGUAGE_FROM_SUBDOMAIN - I18n.config.subdomain_format = 'preview-%' - I18n.config.host_locale_regex = /^preview\-([a-z]{2})\.[^\.]+\..*$/ + I18n.config.language_detection_method = I18n::Config::DETECT_LANGUAGE_FROM_SUBDOMAIN + I18n.config.subdomain_format = 'preview-%' + I18n.config.host_locale_regex = /^preview\-([a-z]{2})\.[^\.]+\..*$/ end diff --git a/config/environments/production.rb b/config/environments/production.rb index c10c435..82fd268 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,103 +1,102 @@ BikeBike::Application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # Code is not reloaded between requests. - config.cache_classes = true - - # Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both thread web servers - # and those relying on copy on write to perform better. - # Rake tasks automatically ignore this option for performance. - config.eager_load = true - - # Full error reports are disabled and caching is turned on. - config.consider_all_requests_local = false - config.action_controller.perform_caching = true - - # Enable Rack::Cache to put a simple HTTP cache in front of your application - # Add `rack-cache` to your Gemfile before enabling this. - # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. - # config.action_dispatch.rack_cache = true - - # Disable Rails's static asset server (Apache or nginx will already do this). - config.serve_static_files = true - - # Compress JavaScripts and CSS. - config.assets.js_compressor = :uglifier - # config.assets.css_compressor = :sass - - # Do not fallback to assets pipeline if a precompiled asset is missed. - #config.assets.compile = true - - # Generate digests for assets URLs. - config.assets.digest = true - - # Version of your assets, change this if you want to expire all your assets. - config.assets.version = '1.01' - - # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache - # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx - - # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - #config.force_ssl = true - - # Set to :debug to see everything in the log. - config.log_level = :info - - #config.cache_classes = true - #config.serve_static_assets = true - #config.assets.compile = true - # config.assets.digest = true - - # Prepend all log lines with the following tags. - # config.log_tags = [ :subdomain, :uuid ] - - # Use a different logger for distributed setups. - # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) - - # Use a different cache store in production. - # config.cache_store = :mem_cache_store - - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.action_controller.asset_host = "http://assets.example.com" - - # Precompile additional assets. - # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. - # config.assets.precompile += %w( search.js ) - #config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif) - config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif *.svg) - #config.action_controller.asset_host = "https://cdn.bikebike.org" - - # Ignore bad email addresses and do not raise email delivery errors. - # Set this to true and configure the email server for immediate delivery to raise delivery errors. - # config.action_mailer.raise_delivery_errors = false - - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation can not be found). - config.i18n.fallbacks = true - - # Send deprecation notices to registered listeners. - config.active_support.deprecation = :notify - - # Disable automatic flushing of the log to improve performance. - # config.autoflush_log = false - - # Use default logging formatter so that PID and timestamp are not suppressed. - config.log_formatter = ::Logger::Formatter.new - - config.action_mailer.delivery_method = :smtp - config.action_mailer.smtp_settings = { - :address => 'mail.bikebike.org', - :domain => 'preview.bikebike.org', - :port => 587, - :authentication => :plain, - :enable_starttls_auto => true, - :openssl_verify_mode => 'none', - :user_name => 'info@preview.bikebike.org', - :password => 'test' - } - config.action_mailer.raise_delivery_errors = true - config.action_mailer.perform_deliveries = true - I18n.config.language_detection_method = I18n::Config::DETECT_LANGUAGE_FROM_SUBDOMAIN + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both thread web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Enable Rack::Cache to put a simple HTTP cache in front of your application + # Add `rack-cache` to your Gemfile before enabling this. + # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. + # config.action_dispatch.rack_cache = true + + # Disable Rails's static asset server (Apache or nginx will already do this). + config.serve_static_files = true + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + #config.assets.compile = true + + # Generate digests for assets URLs. + config.assets.digest = true + + # Version of your assets, change this if you want to expire all your assets. + config.assets.version = '1.01' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + #config.force_ssl = true + + # Set to :debug to see everything in the log. + config.log_level = :info + + #config.cache_classes = true + #config.serve_static_assets = true + #config.assets.compile = true + # config.assets.digest = true + + # Prepend all log lines with the following tags. + # config.log_tags = [ :subdomain, :uuid ] + + # Use a different logger for distributed setups. + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = "http://assets.example.com" + + # Precompile additional assets. + # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. + # config.assets.precompile += %w( search.js ) + #config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif) + config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif *.svg) + #config.action_controller.asset_host = "https://cdn.bikebike.org" + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation can not be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Disable automatic flushing of the log to improve performance. + # config.autoflush_log = false + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + config.action_mailer.delivery_method = :smtp + config.action_mailer.smtp_settings = { + :address => 'smtp.gmail.com', + :domain => 'localhost:3000', + :port => 587, + :authentication => :plain, + :enable_starttls_auto => true, + :user_name => ENV['MAILER_USER'], + :password => ENV['MAILER_PASSWORD'] + } + config.action_mailer.raise_delivery_errors = true + config.action_mailer.perform_deliveries = true + I18n.config.language_detection_method = I18n::Config::DETECT_LANGUAGE_FROM_SUBDOMAIN end diff --git a/config/locales/en.yml b/config/locales/en.yml index 7183d34..3bb98c0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -961,6 +961,8 @@ en: completed_registrations: Number of registrations incomplete_registrations: Incomplete registrations Registrations: Registrations + registrations: + no_date_warning: Before users can register, you must first set your conference start and end dates. meals: description: On this page you can schedule the meals that you will be serving. no_locations_warning: Before you can add meals, you must first add locations. @@ -1054,12 +1056,14 @@ en: stats: Statistics registrations: Modify Registrations broadcast: Contact Users + broadcast_sent: You email has been dispatched description: Open or close registration, view registration statistics, modify information subbmitted by registratnts and contact users. descriptions: registration_status: Open or close registration to your conference. stats: View a breakdown of statictics, how many users have registered, how much money have been collected, etc. registrations: View and edit all data collected through the registration process. broadcast: Send emails to targeted subsets of users. + broadcast_sent: Your email has been queued to send, depending on the number of users you have sent to, it may take several hours to send to everyone. broadcast: heading: Broadcast description: The broadcast tool is used to contact users through email. You can send messages en masse to select groups of users. diff --git a/db/schema.rb b/db/schema.rb index 0edeb71..f2e36ba 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -16,15 +16,6 @@ ActiveRecord::Schema.define(version: 20170111172147) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" - create_table "applications", force: :cascade do |t| - t.string "slug" - t.string "name" - t.string "path" - t.string "url" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - create_table "authentications", force: :cascade do |t| t.integer "user_id", null: false t.string "provider", null: false From 89af3c5aaeed8c8f51a6638f20170febd54eb11c Mon Sep 17 00:00:00 2001 From: Godwin Date: Tue, 21 Feb 2017 17:32:35 -0800 Subject: [PATCH 06/45] Fixed error on event creation page --- app/helpers/application_helper.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9c9797b..56ca8a7 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1560,6 +1560,7 @@ module ApplicationHelper def textfield(name, value, options = {}) html = '' id = unique_id(name) + html_name = name.to_s + (options[:index] ? "[#{options[:index]}]" : '') description_id = nil if options[:heading].present? @@ -1635,14 +1636,14 @@ module ApplicationHelper if options[:required] && options[:options].first.present? && options[:options].first.last.present? option_list = ('' + option_list).html_safe end - html += select_tag(name, option_list, input_options) + html += select_tag(html_name, option_list, input_options) when :file add_inline_script :filefield input_options[:tabindex] = '-1' - html += off_screen(file_field_tag name, input_options) + html += off_screen(file_field_tag html_name, input_options) else input_options[:autocomplete] = 'off' if options[:type] == :search - html += send("#{(options[:type] || :text).to_s}_field_tag", name, value, input_options) + html += send("#{(options[:type] || :text).to_s}_field_tag", html_name, value, input_options) end if options[:after].present? From 572e0d6644b728b89d84e3bb7ba286909ee52138 Mon Sep 17 00:00:00 2001 From: Godwin Date: Mon, 27 Feb 2017 17:26:27 -0800 Subject: [PATCH 07/45] Fixed workshop times and schedule --- .../conference_administration_controller.rb | 51 ++++-- .../_schedule.html.haml | 165 +++++++++--------- app/views/conferences/_conference.html.haml | 2 +- app/views/conferences/view.html.haml | 2 +- config/locales/en.yml | 7 + 5 files changed, 126 insertions(+), 101 deletions(-) diff --git a/app/controllers/conference_administration_controller.rb b/app/controllers/conference_administration_controller.rb index 193edb3..733efd4 100644 --- a/app/controllers/conference_administration_controller.rb +++ b/app/controllers/conference_administration_controller.rb @@ -1149,13 +1149,25 @@ class ConferenceAdministrationController < ApplicationController def admin_update_workshop_times case params[:button] when 'save_block' + empty_param = empty_params(:time, :time_span, :days) + if empty_param.present? + set_error_message "save_block_#{empty_param}_required".to_sym + else + @this_conference.workshop_blocks ||= [] + @this_conference.workshop_blocks[params[:workshop_block].to_i] = { + 'time' => params[:time], + 'length' => params[:time_span], + 'days' => params[:days].keys + } + @this_conference.save + set_success_message :block_saved + end + return false + when 'delete_block' @this_conference.workshop_blocks ||= [] - @this_conference.workshop_blocks[params[:workshop_block].to_i] = { - 'time' => params[:time], - 'length' => params[:time_span], - 'days' => params[:days].keys - } + @this_conference.workshop_blocks.delete_at(params[:workshop_block].to_i) @this_conference.save + set_success_message :block_deleted return false end @@ -1163,6 +1175,19 @@ class ConferenceAdministrationController < ApplicationController return nil end + def admin_update_publish_schedule + case params[:button] + when 'publish' + @this_conference.workshop_schedule_published = !@this_conference.workshop_schedule_published + @this_conference.save + set_success_message "schedule_#{@this_conference.workshop_schedule_published ? '' : 'un'}published".to_sym + return false + end + + do_404 + return false + end + def admin_update_schedule case params[:button] when 'deschedule_workshop' @@ -1207,18 +1232,6 @@ class ConferenceAdministrationController < ApplicationController return nil end - def admin_update_schedule - case params[:button] - when 'publish' - @this_conference.workshop_schedule_published = !@this_conference.workshop_schedule_published - @this_conference.save - return false - end - - do_404 - return false - end - def admin_update_providers case params[:button] when 'save_distance' @@ -1243,4 +1256,8 @@ class ConferenceAdministrationController < ApplicationController end return nil end + + def empty_params(*args) + get_empty(params, args) + end end diff --git a/app/views/conference_administration/_schedule.html.haml b/app/views/conference_administration/_schedule.html.haml index 03ffdbe..8a05083 100644 --- a/app/views/conference_administration/_schedule.html.haml +++ b/app/views/conference_administration/_schedule.html.haml @@ -1,85 +1,86 @@ -= columns(medium: 12) do - - conference = @this_conference || @conference - - if conference.event_locations.blank? && @entire_page - .warning-info=_'articles.admin.schedule.no_locations_warning' - - else - - add_inline_script :schedule if @entire_page - #schedule-preview - - @schedule.each do | day, data | - %h4=date(day, :weekday) - %table.schedule{class: [data[:locations].present? ? 'has-locations' : 'no-locations', "locations-#{data[:num_locations]}"]} - - if data[:locations].present? && data[:locations].values.first != :add - %thead - %tr - %th.corner - - data[:locations].each do | id, location | - %th=location.is_a?(Symbol) ? '' : location.title - %tbody - - data[:times].each do | time, time_data | - %tr - - rowspan = (time_data[:length] * 2).to_i - %th=time(time) - - if time_data[:type] == :workshop += row do + = columns(medium: 12) do + - conference = @this_conference || @conference + - if conference.event_locations.blank? && @entire_page + .warning-info=_'articles.admin.schedule.no_locations_warning' + - else + - add_inline_script :schedule if @entire_page + #schedule-preview + - @schedule.each do | day, data | + %h4=date(day, :weekday) + %table.schedule{class: [data[:locations].present? ? 'has-locations' : 'no-locations', "locations-#{data[:num_locations]}"]} + - if data[:locations].present? && data[:locations].values.first != :add + %thead + %tr + %th.corner - data[:locations].each do | id, location | - - if time_data[:item][:workshops][id].present? - - workshop = time_data[:item][:workshops][id][:workshop] - - status = time_data[:item][:workshops][id][:status] - - else - - workshop = status = nil - %td{class: [time_data[:type], workshop.present? ? :filled : :open], rowspan: rowspan, data: workshop.present? ? nil : { block: time_data[:item][:block], day: day, location: id }} - - if workshop.present? && workshop.event_location.present? - = link_to view_workshop_path(@conference.slug, workshop.id), class: 'event-detail-link' do - .details - .title=workshop.title - %template.event-details{data: { href: view_workshop_path(@conference.slug, workshop.id) }} - %h1.title=workshop.title - %p.address - = workshop.event_location.title + _!(': ') - = location_link workshop.event_location - .workshop-description= richtext workshop.info, 1 - - if @can_edit - = form_tag administration_update_path(conference.slug, @admin_step), class: 'deschedule-workshop' do - .status - .conflict-score - %span.title Conflicts: - %span.value="#{status[:conflict_score]} / #{workshop.interested.size}" - - if status[:errors].present? - .errors - - status[:errors].each do | error | - .error=_"errors.messages.schedule.#{error[:name].to_s}", vars: error[:i18nVars] - = hidden_field_tag :id, workshop.id - = button_tag :deschedule, value: :deschedule_workshop, class: [:delete, :small] - - elsif @can_edit - .title="Block #{time_data[:item][:block] + 1}" - - elsif time_data[:type] != :nil - %td{class: time_data[:type], rowspan: rowspan, colspan: data[:locations].present? ? data[:locations].size : 1} - - case time_data[:type] - - when :meal - - location = EventLocation.where(id: time_data[:item]['location'].to_i).first - - if location.present? - %a.event-detail-link + %th=location.is_a?(Symbol) ? '' : location.title + %tbody + - data[:times].each do | time, time_data | + %tr + - rowspan = (time_data[:length] * 2).to_i + %th=time(time) + - if time_data[:type] == :workshop + - data[:locations].each do | id, location | + - if time_data[:item][:workshops][id].present? + - workshop = time_data[:item][:workshops][id][:workshop] + - status = time_data[:item][:workshops][id][:status] + - else + - workshop = status = nil + %td{class: [time_data[:type], workshop.present? ? :filled : :open], rowspan: rowspan, data: workshop.present? ? nil : { block: time_data[:item][:block], day: day, location: id }} + - if workshop.present? && workshop.event_location.present? + = link_to view_workshop_path(@conference.slug, workshop.id), class: 'event-detail-link' do .details - .title= time_data[:item]['title'] - .location= location.title - %template.event-details - %h1.title=time_data[:item]['title'] + .title=workshop.title + %template.event-details{data: { href: view_workshop_path(@conference.slug, workshop.id) }} + %h1.title=workshop.title %p.address - = location.title + _!(': ') - = location_link location - - when :event - - if time_data[:item].event_location.present? - %a.event-detail-link - .details - .title= time_data[:item][:title] - .location= time_data[:item].event_location.title - %template.event-details - %h1.title=time_data[:item][:title] - %p.address - = time_data[:item].event_location.title + _!(': ') - = location_link time_data[:item].event_location - = richtext time_data[:item][:info], 1 - - if @entire_page - #workshop-selector - = form_tag administration_update_path(@this_conference.slug, @admin_step), class: 'workshop-dlg', id: 'workshop-table-form' do - %h3 Select a Workshop - #table + = workshop.event_location.title + _!(': ') + = location_link workshop.event_location + .workshop-description= richtext workshop.info, 1 + - if @can_edit + = form_tag administration_update_path(conference.slug, @admin_step), class: 'deschedule-workshop' do + .status + .conflict-score + %span.title Conflicts: + %span.value="#{status[:conflict_score]} / #{workshop.interested.size}" + - if status[:errors].present? + .errors + - status[:errors].each do | error | + .error=_"errors.messages.schedule.#{error[:name].to_s}", vars: error[:i18nVars] + = hidden_field_tag :id, workshop.id + = button_tag :deschedule, value: :deschedule_workshop, class: [:delete, :small] + - elsif @can_edit + .title="Block #{time_data[:item][:block] + 1}" + - elsif time_data[:type] != :nil + %td{class: time_data[:type], rowspan: rowspan, colspan: data[:locations].present? ? data[:locations].size : 1} + - case time_data[:type] + - when :meal + - location = EventLocation.where(id: time_data[:item]['location'].to_i).first + - if location.present? + %a.event-detail-link + .details + .title= time_data[:item]['title'] + .location= location.title + %template.event-details + %h1.title=time_data[:item]['title'] + %p.address + = location.title + _!(': ') + = location_link location + - when :event + - if time_data[:item].event_location.present? + %a.event-detail-link + .details + .title= time_data[:item][:title] + .location= time_data[:item].event_location.title + %template.event-details + %h1.title=time_data[:item][:title] + %p.address + = time_data[:item].event_location.title + _!(': ') + = location_link time_data[:item].event_location + = richtext time_data[:item][:info], 1 + - if @entire_page + #workshop-selector + = form_tag administration_update_path(@this_conference.slug, @admin_step), class: 'workshop-dlg', id: 'workshop-table-form' do + %h3 Select a Workshop + #table diff --git a/app/views/conferences/_conference.html.haml b/app/views/conferences/_conference.html.haml index eb41317..ec0b923 100644 --- a/app/views/conferences/_conference.html.haml +++ b/app/views/conferences/_conference.html.haml @@ -21,7 +21,7 @@ - if conference.workshop_schedule_published - add_inline_script :home_schedule %h3=_'articles.workshops.headings.Schedule' - = render 'conferences/admin/schedule' + = render 'conference_administration/schedule' - else %h3=_'articles.workshops.headings.Proposed_Workshops' %p=_'articles.workshops.paragraphs.Proposed_Workshops' diff --git a/app/views/conferences/view.html.haml b/app/views/conferences/view.html.haml index 55eca23..4c7e5a4 100644 --- a/app/views/conferences/view.html.haml +++ b/app/views/conferences/view.html.haml @@ -2,4 +2,4 @@ = @this_conference.poster.full.url || image_path('default_poster.jpg') - content_for :title do =@this_conference.title -= render 'conferences/conference', conference: @this_conference, links: @links += render 'conferences/conference', conference: @this_conference, links: @links, sections: [:info, :workshops] diff --git a/config/locales/en.yml b/config/locales/en.yml index 3bb98c0..dee6f17 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -202,6 +202,9 @@ en: error_removing_org_member: Error removing user from organization error_adding_administrator: Error adding administrator error_removing_administrator: Error removing administrator + save_block_time_required: Time is required + save_block_time_span_required: Length is required + save_block_days_required: Please select at least one day template: body: 'There were problems with the following fields:' header: @@ -244,6 +247,10 @@ en: administrator_added: Administrator added to conference administrator_removed: Administrator removed from conference distance_saved: Provider options updated + block_saved: Workshop time has been saved + block_deleted: Workshop time has been deleted + schedule_published: Your schedule has been published and should be visible on your conference page + schedule_unpublished: Your schedule has been un-published and should no longer be visible to users helpers: select: prompt: Please select From 8f852b102bd26b7b93f9aafa5d98510f6aa57350 Mon Sep 17 00:00:00 2001 From: Godwin Date: Sun, 9 Apr 2017 11:37:16 -0700 Subject: [PATCH 08/45] 2017 refactor --- .gitignore | 4 - Gemfile | 82 +- Gemfile.lock | 633 +++++ Guardfile | 25 + README.md | 5 + Rakefile | 31 + app/assets/images/test-poster.png | Bin 0 -> 77534 bytes app/assets/javascripts/userfield.js | 1 + app/assets/stylesheets/_application.scss | 23 +- .../stylesheets/bumbleberry-settings.json | 194 +- app/assets/stylesheets/user-mailer.scss | 398 +-- ...roller.rb => administration_controller.rb} | 102 +- app/controllers/application_controller.rb | 468 ++-- .../conference_administration_controller.rb | 100 +- app/controllers/conferences_controller.rb | 71 +- app/controllers/oauths_controller.rb | 101 - app/controllers/workshops_controller.rb | 8 +- app/helpers/admin_helper.rb | 163 ++ app/helpers/application_helper.rb | 2221 +---------------- app/helpers/bike_bike_form_helper.rb | 407 --- app/helpers/form_helper.rb | 729 ++++++ app/helpers/geocoder_helper.rb | 100 + app/helpers/i18n_helper.rb | 74 + app/helpers/page_helper.rb | 88 + app/helpers/registration_helper.rb | 30 +- app/helpers/table_helper.rb | 366 +++ app/helpers/widgets_helper.rb | 288 +++ app/mailers/user_mailer.rb | 4 +- app/models/authentication.rb | 3 - app/models/city.rb | 166 -- app/models/city_cache.rb | 21 - app/models/comment.rb | 37 - app/models/conference.rb | 160 -- app/models/conference_admin.rb | 2 - app/models/conference_administrator.rb | 4 - app/models/conference_host_organization.rb | 4 - app/models/conference_registration.rb | 74 - .../conference_registration_form_field.rb | 4 - .../conference_registration_response.rb | 5 - app/models/conference_type.rb | 7 - app/models/email_confirmation.rb | 40 - app/models/event.rb | 20 - app/models/event_location.rb | 24 - app/models/event_type.rb | 2 - app/models/location.rb | 21 - app/models/locations_organization.rb | 6 - app/models/organization.rb | 77 - app/models/organization_status.rb | 2 - app/models/registration_form_field.rb | 75 - app/models/user.rb | 68 - app/models/user_organization_relationship.rb | 11 - app/models/version.rb | 2 - app/models/workshop.rb | 180 -- app/models/workshop_facilitator.rb | 2 - app/models/workshop_interest.rb | 4 - app/models/workshop_presentation_style.rb | 2 - app/models/workshop_requested_resource.rb | 2 - app/models/workshop_resource.rb | 2 - app/models/workshop_stream.rb | 2 - app/uploaders/avatar_uploader.rb | 104 - app/uploaders/cover_uploader.rb | 54 - app/uploaders/logo_uploader.rb | 57 - app/uploaders/poster_uploader.rb | 69 - app/views/admin/new.html.haml | 2 +- app/views/application/500.html.haml | 3 +- app/views/application/_contact.html.haml | 2 +- app/views/application/_login.html.haml | 2 +- .../application/_login_confirm.html.haml | 2 +- .../_login_confirmation_sent.html.haml | 6 +- .../application/_not_a_translator.html.haml | 2 +- .../application/_translator_login.html.haml | 2 +- app/views/application/contact.html.haml | 16 +- app/views/application/home.html.haml | 7 +- .../application/permission_denied.html.haml | 16 +- app/views/application/update_user.html.haml | 2 +- app/views/application/user_settings.html.haml | 2 +- .../_administrators.html.haml | 10 +- .../_broadcast.html.haml | 17 + .../_dates.html.haml | 2 +- .../_description.html.haml | 2 +- .../_events.html.haml | 4 +- .../_locations.html.haml | 6 +- .../_meals.html.haml | 4 +- .../_payment_message.html.haml | 2 +- .../_paypal.html.haml | 2 +- .../_poster.html.haml | 2 +- .../_providers.html.haml | 2 +- .../_publish_schedule.html.haml | 4 +- .../_registration_status.html.haml | 14 +- .../_registrations.html.haml | 38 +- .../_schedule.html.haml | 40 +- .../_suggested_amounts.html.haml | 2 +- .../_workshop_times.html.haml | 4 +- .../conferences/_administration.html.haml | 6 - app/views/conferences/_conference.html.haml | 27 +- .../conferences/_confirm_email.html.haml | 20 +- app/views/conferences/_contact_info.html.haml | 4 +- app/views/conferences/_done.html.haml | 2 +- .../conferences/_email_confirm.html.haml | 10 +- app/views/conferences/_hosting.html.haml | 4 +- app/views/conferences/_payment.html.haml | 9 +- .../conferences/_paypal_confirm.html.haml | 8 +- app/views/conferences/_policy.html.haml | 10 +- app/views/conferences/_questions.html.haml | 2 +- app/views/conferences/_register.html.haml | 2 +- .../_registration_register.html.haml | 45 - app/views/conferences/_workshops.html.haml | 36 +- app/views/conferences/broadcast.html.haml | 14 +- app/views/conferences/edit.html.haml | 44 +- app/views/conferences/hosts.html.haml | 13 - app/views/conferences/list.html.haml | 2 +- app/views/conferences/register.html.haml | 2 +- app/views/conferences/registration.html.haml | 14 - app/views/layouts/application.html.haml | 2 +- app/views/layouts/user_mailer.html.haml | 50 +- app/views/schedule/edit.html.haml | 8 +- app/views/shared/_footer.html.haml | 7 - app/views/shared/_navbar.html.haml | 4 +- .../shared/_translation_control.html.haml | 52 +- app/views/user_mailer/contact.html.haml | 8 +- .../user_mailer/contact_details.html.haml | 54 +- .../user_mailer/email_confirmation.html.haml | 3 +- .../user_mailer/workshop_comment.html.haml | 4 +- .../workshop_facilitator_request.html.haml | 6 +- ...hop_facilitator_request_approved.html.haml | 4 +- ...kshop_facilitator_request_denied.html.haml | 4 +- .../user_mailer/workshop_translated.html.haml | 4 +- .../user_mailer/workshop_translated.text.haml | 25 - app/views/workshops/_show.html.haml | 14 +- .../workshops/_workshop_previews.html.haml | 2 +- app/views/workshops/delete.html.haml | 16 +- app/views/workshops/edit.html.haml | 6 - app/views/workshops/facilitate.html.haml | 28 +- .../facilitate_request_sent.html.haml | 18 +- app/views/workshops/index.html.haml | 9 +- app/views/workshops/new.html.haml | 4 +- config/application.rb | 41 +- config/environments/development.rb | 111 +- config/environments/test.rb | 96 +- config/locales/en.yml | 2145 +++++++++++----- config/locales/fr.yml | 2 + config/routes.rb | 138 +- config/unicorn.rb | 18 +- db/migrate/20131102214949_sorcery_core.rb | 16 - .../20131102214950_sorcery_remember_me.rb | 15 - .../20131102214951_sorcery_reset_password.rb | 17 - .../20131102214952_sorcery_user_activation.rb | 17 - ...02214953_sorcery_brute_force_protection.rb | 13 - db/migrate/20131102214954_sorcery_external.rb | 14 - db/migrate/20140208035124_create_locations.rb | 12 - .../20140208231633_add_country_to_location.rb | 5 - ...0140208231738_add_territory_to_location.rb | 5 - .../20140208231802_add_city_to_location.rb | 5 - .../20140208231828_add_street_to_location.rb | 5 - ...40208231913_add_postal_code_to_location.rb | 5 - ...0208232052_remove_address_from_location.rb | 5 - .../20140209001432_add_index_to_location.rb | 5 - .../20140209005651_create_conference_types.rb | 10 - ...40209005813_add_slug_to_conference_type.rb | 5 - ...327_create_workshop_presentation_styles.rb | 11 - ...0140209012344_create_workshop_resources.rb | 11 - .../20140209012408_create_workshop_streams.rb | 11 - ...0209015644_create_organization_statuses.rb | 11 - .../20140209021801_create_translations.rb | 13 - db/migrate/20140212022115_create_versions.rb | 14 - .../20140212054019_add_value_to_versions.rb | 5 - .../20140217225420_add_avatar_to_users.rb | 5 - .../20140219020355_create_organizations.rb | 21 - ...8_remove_location_id_from_organizations.rb | 5 - ...222202640_create_organization_locations.rb | 8 - ...primary_key_for_locations_organizations.rb | 5 - ..._create_user_organization_relationships.rb | 10 - ...140224034434_add_cover_to_organizations.rb | 5 - ...dd_id_to_organization_user_relationship.rb | 5 - .../20140301202157_create_conferences.rb | 21 - ...23_create_conference_host_organizations.rb | 11 - ...20140302212924_create_conference_admins.rb | 10 - ...302225518_add_text_fields_to_conference.rb | 7 - ...3010316_create_registration_form_fields.rb | 14 - ...eate_conference_registraton_form_fields.rb | 11 - ...3010926_create_conference_registrations.rb | 11 - ...reate_conference_registration_responses.rb | 11 - ..._to_conference_registration_form_fields.rb | 5 - ...rom_conference_registration_form_fields.rb | 5 - ..._to_conference_registration_form_fields.rb | 5 - db/migrate/20140314025647_create_workshops.rb | 18 - ...0315175914_create_workshop_facilitators.rb | 11 - ...222_create_workshop_requested_resources.rb | 11 - .../20140315183121_create_event_types.rb | 10 - db/migrate/20140315183241_create_events.rb | 16 - .../20140524050931_add_about_me_to_users.rb | 5 - .../20140524052048_add_role_to_users.rb | 5 - ...d_cover_attribution_id_to_organizations.rb | 5 - ...cover_attribution_name_to_organizations.rb | 5 - ..._cover_attribution_src_to_organizations.rb | 5 - ...140525165547_add_phone_to_organizations.rb | 5 - ...organization_status_id_to_organizations.rb | 5 - ...15_add_cover_attribution_to_conferences.rb | 7 - ...er_attribution_user_id_to_organizations.rb | 5 - ...over_attribution_user_id_to_conferences.rb | 5 - ...is_confirmed_to_conference_registraions.rb | 5 - ..._participant_to_conference_registraions.rb | 5 - ...is_volunteer_to_conference_registraions.rb | 5 - .../20140714012739_add_firstname_to_users.rb | 5 - .../20140714012811_add_lastname_to_users.rb | 5 - .../20140714013645_add_sessions_table.rb | 12 - ...ation_token_to_conference_registrations.rb | 5 - ...52_add_data_to_conference_registrations.rb | 5 - ...9_add_email_to_conference_registrations.rb | 5 - ...is_complete_to_conference_registrations.rb | 5 - ...d_completed_to_conference_registrations.rb | 5 - ...mation_token_to_conference_registration.rb | 5 - ...payment_info_to_conference_registration.rb | 5 - ...723182548_add_order_to_workshop_streams.rb | 5 - ...d_order_to_workshop_presentation_styles.rb | 5 - ...on_fees_paid_to_conference_registration.rb | 5 - ...20150314031359_add_locale_to_conference.rb | 5 - ...0150314031361_create_translation_tables.rb | 26 - ...225815_add_email_address_to_conferences.rb | 5 - ...add_paypal_email_address_to_conferences.rb | 5 - ...150412203317_create_email_confirmations.rb | 12 - ...150412203357_add_is_translator_to_users.rb | 5 - ...47_add_city_to_conference_registrations.rb | 10 - ...04032547_add_paypal_info_to_conferences.rb | 7 - ...0150807002150_add_info_to_registrations.rb | 7 - ...150809011815_make_username_not_nullable.rb | 5 - ...453_make_username_actually_not_nullable.rb | 5 - .../20150811234700_create_delayed_jobs.rb | 22 - ...150819042431_add_languages_to_workshops.rb | 9 - .../20150821005002_add_notes_to_workshops.rb | 5 - ...0150912010105_create_workshop_interests.rb | 10 - .../20150914230507_add_locale_to_workshops.rb | 5 - .../20150920233402_create_event_locations.rb | 14 - ...3710_add_event_location_id_to_workshops.rb | 5 - ...0233755_add_event_location_id_to_events.rb | 5 - .../20150925000217_add_type_to_events.rb | 5 - ...0927010559_add_day_parts_to_conferences.rb | 5 - ...60304021005_add_status_to_organizations.rb | 5 - ..._add_registration_status_to_conferences.rb | 9 - ...ighest_step_to_conference_registrations.rb | 5 - ...s_completed_to_conference_registrations.rb | 5 - ...849_add_needs_facilitators_to_workshops.rb | 5 - .../20160529225253_add_languages_to_users.rb | 5 - ...e_format_in_dynamic_translation_records.rb | 5 - ...ide_housing_to_conference_registrations.rb | 5 - ...ousing_data_to_conference_registrations.rb | 5 - .../20160604221432_add_locale_to_users.rb | 5 - ...0621020123_add_space_to_event_locations.rb | 5 - ...20160621051912_add_meals_to_conferences.rb | 5 - .../20160622011811_add_locale_to_events.rb | 5 - ...3219_add_workshop_blocks_to_conferences.rb | 5 - .../20160703044620_add_block_to_workshops.rb | 5 - ...160707034050_add_is_subscribed_to_users.rb | 5 - db/migrate/20160708042302_create_comments.rb | 11 - .../20160708042511_add_user_id_to_comments.rb | 5 - ...00814_add_payment_message_to_conference.rb | 5 - ...00940_add_payment_amounts_to_conference.rb | 5 - .../20161006021205_add_fb_id_to_user.rb | 5 - .../20161008210246_add_type_to_conference.rb | 5 - db/migrate/20161009004744_create_cities.rb | 14 - .../20161009005122_create_city_matches.rb | 10 - .../20161009194821_add_year_to_conference.rb | 5 - ...0161009200252_add_city_id_to_conference.rb | 5 - ...61129010755_add_is_public_to_conference.rb | 9 - ...129014652_add_is_featured_to_conference.rb | 9 - .../20161129035601_rename_conference_type.rb | 9 - .../20161130042227_rename_city_matches.rb | 39 - .../20161201011655_add_place_id_to_city.rb | 7 - .../20161201015559_add_city_id_to_location.rb | 5 - ...201021349_add_spanish_city_translations.rb | 4 - .../20161201043930_set_conference_types.rb | 20 - ...021236_create_conference_administrators.rb | 10 - ...17_change_conference_datetimes_to_dates.rb | 6 - .../20161211065022_replace_missing_locales.rb | 19 - ...add_city_id_to_conference_registrations.rb | 5 - ...7_add_provider_conditions_to_conference.rb | 5 - db/schema.rb | 80 +- features/conferences.feature | 113 + features/contact_us.feature | 40 + features/landing_page.feature | 61 +- features/registration.feature | 606 +++++ features/schedule.feature | 72 + features/sign_in.feature | 86 + features/static_pages.feature | 33 +- features/step_definitions/conferences.rb | 102 + features/step_definitions/emails.rb | 54 + features/step_definitions/interface_steps.rb | 626 ++--- features/step_definitions/navigation.rb | 70 + features/step_definitions/registration.rb | 93 + features/step_definitions/users.rb | 93 + features/step_definitions/workshops.rb | 107 + .../assets/images/posters/Brooklyn2025.png | Bin 0 -> 77534 bytes .../assets/images/posters/NewOrleans2013.png | Bin 0 -> 1212897 bytes .../assets/images/posters/Northwest2025.png | Bin 0 -> 132821 bytes features/support/env.rb | 185 +- features/support/factory_girl.rb | 164 +- features/support/helper.rb | 194 ++ features/support/location_cache.yml | 479 +++- features/support/paths.rb | 53 +- features/support/state.rb | 112 + features/workshops.feature | 323 +++ 301 files changed, 8361 insertions(+), 7878 deletions(-) create mode 100644 Gemfile.lock create mode 100644 app/assets/images/test-poster.png rename app/controllers/{admin_controller.rb => administration_controller.rb} (94%) delete mode 100644 app/controllers/oauths_controller.rb create mode 100644 app/helpers/admin_helper.rb delete mode 100644 app/helpers/bike_bike_form_helper.rb create mode 100644 app/helpers/form_helper.rb create mode 100644 app/helpers/geocoder_helper.rb create mode 100644 app/helpers/i18n_helper.rb create mode 100644 app/helpers/page_helper.rb create mode 100644 app/helpers/table_helper.rb create mode 100644 app/helpers/widgets_helper.rb delete mode 100644 app/models/authentication.rb delete mode 100644 app/models/city.rb delete mode 100644 app/models/city_cache.rb delete mode 100644 app/models/comment.rb delete mode 100644 app/models/conference.rb delete mode 100644 app/models/conference_admin.rb delete mode 100644 app/models/conference_administrator.rb delete mode 100644 app/models/conference_host_organization.rb delete mode 100644 app/models/conference_registration.rb delete mode 100644 app/models/conference_registration_form_field.rb delete mode 100644 app/models/conference_registration_response.rb delete mode 100644 app/models/conference_type.rb delete mode 100644 app/models/email_confirmation.rb delete mode 100644 app/models/event.rb delete mode 100644 app/models/event_location.rb delete mode 100644 app/models/event_type.rb delete mode 100644 app/models/location.rb delete mode 100644 app/models/locations_organization.rb delete mode 100644 app/models/organization.rb delete mode 100644 app/models/organization_status.rb delete mode 100644 app/models/registration_form_field.rb delete mode 100644 app/models/user.rb delete mode 100644 app/models/user_organization_relationship.rb delete mode 100644 app/models/version.rb delete mode 100644 app/models/workshop.rb delete mode 100644 app/models/workshop_facilitator.rb delete mode 100644 app/models/workshop_interest.rb delete mode 100644 app/models/workshop_presentation_style.rb delete mode 100644 app/models/workshop_requested_resource.rb delete mode 100644 app/models/workshop_resource.rb delete mode 100644 app/models/workshop_stream.rb delete mode 100644 app/uploaders/avatar_uploader.rb delete mode 100644 app/uploaders/cover_uploader.rb delete mode 100644 app/uploaders/logo_uploader.rb delete mode 100644 app/uploaders/poster_uploader.rb delete mode 100644 app/views/conferences/_administration.html.haml delete mode 100644 app/views/conferences/_registration_register.html.haml delete mode 100644 app/views/conferences/hosts.html.haml delete mode 100644 app/views/conferences/registration.html.haml delete mode 100644 app/views/user_mailer/workshop_translated.text.haml delete mode 100644 app/views/workshops/edit.html.haml create mode 100644 config/locales/fr.yml delete mode 100644 db/migrate/20131102214949_sorcery_core.rb delete mode 100644 db/migrate/20131102214950_sorcery_remember_me.rb delete mode 100644 db/migrate/20131102214951_sorcery_reset_password.rb delete mode 100644 db/migrate/20131102214952_sorcery_user_activation.rb delete mode 100644 db/migrate/20131102214953_sorcery_brute_force_protection.rb delete mode 100644 db/migrate/20131102214954_sorcery_external.rb delete mode 100644 db/migrate/20140208035124_create_locations.rb delete mode 100644 db/migrate/20140208231633_add_country_to_location.rb delete mode 100644 db/migrate/20140208231738_add_territory_to_location.rb delete mode 100644 db/migrate/20140208231802_add_city_to_location.rb delete mode 100644 db/migrate/20140208231828_add_street_to_location.rb delete mode 100644 db/migrate/20140208231913_add_postal_code_to_location.rb delete mode 100644 db/migrate/20140208232052_remove_address_from_location.rb delete mode 100644 db/migrate/20140209001432_add_index_to_location.rb delete mode 100644 db/migrate/20140209005651_create_conference_types.rb delete mode 100644 db/migrate/20140209005813_add_slug_to_conference_type.rb delete mode 100644 db/migrate/20140209012327_create_workshop_presentation_styles.rb delete mode 100644 db/migrate/20140209012344_create_workshop_resources.rb delete mode 100644 db/migrate/20140209012408_create_workshop_streams.rb delete mode 100644 db/migrate/20140209015644_create_organization_statuses.rb delete mode 100644 db/migrate/20140209021801_create_translations.rb delete mode 100644 db/migrate/20140212022115_create_versions.rb delete mode 100644 db/migrate/20140212054019_add_value_to_versions.rb delete mode 100644 db/migrate/20140217225420_add_avatar_to_users.rb delete mode 100644 db/migrate/20140219020355_create_organizations.rb delete mode 100644 db/migrate/20140222202258_remove_location_id_from_organizations.rb delete mode 100644 db/migrate/20140222202640_create_organization_locations.rb delete mode 100644 db/migrate/20140222212830_create_primary_key_for_locations_organizations.rb delete mode 100644 db/migrate/20140223004805_create_user_organization_relationships.rb delete mode 100644 db/migrate/20140224034434_add_cover_to_organizations.rb delete mode 100644 db/migrate/20140227012532_add_id_to_organization_user_relationship.rb delete mode 100644 db/migrate/20140301202157_create_conferences.rb delete mode 100644 db/migrate/20140302212723_create_conference_host_organizations.rb delete mode 100644 db/migrate/20140302212924_create_conference_admins.rb delete mode 100644 db/migrate/20140302225518_add_text_fields_to_conference.rb delete mode 100644 db/migrate/20140303010316_create_registration_form_fields.rb delete mode 100644 db/migrate/20140303010547_create_conference_registraton_form_fields.rb delete mode 100644 db/migrate/20140303010926_create_conference_registrations.rb delete mode 100644 db/migrate/20140303011229_create_conference_registration_responses.rb delete mode 100644 db/migrate/20140307035356_rename_conference_registraton_form_fields_to_conference_registration_form_fields.rb delete mode 100644 db/migrate/20140308173154_remove_order_from_conference_registration_form_fields.rb delete mode 100644 db/migrate/20140308173325_add_position_to_conference_registration_form_fields.rb delete mode 100644 db/migrate/20140314025647_create_workshops.rb delete mode 100644 db/migrate/20140315175914_create_workshop_facilitators.rb delete mode 100644 db/migrate/20140315181222_create_workshop_requested_resources.rb delete mode 100644 db/migrate/20140315183121_create_event_types.rb delete mode 100644 db/migrate/20140315183241_create_events.rb delete mode 100644 db/migrate/20140524050931_add_about_me_to_users.rb delete mode 100644 db/migrate/20140524052048_add_role_to_users.rb delete mode 100644 db/migrate/20140524202858_add_cover_attribution_id_to_organizations.rb delete mode 100644 db/migrate/20140524202938_add_cover_attribution_name_to_organizations.rb delete mode 100644 db/migrate/20140524203006_add_cover_attribution_src_to_organizations.rb delete mode 100644 db/migrate/20140525165547_add_phone_to_organizations.rb delete mode 100644 db/migrate/20140525174815_add_organization_status_id_to_organizations.rb delete mode 100644 db/migrate/20140525212115_add_cover_attribution_to_conferences.rb delete mode 100644 db/migrate/20140712190647_add_cover_attribution_user_id_to_organizations.rb delete mode 100644 db/migrate/20140712190815_add_cover_attribution_user_id_to_conferences.rb delete mode 100644 db/migrate/20140713213158_add_is_confirmed_to_conference_registraions.rb delete mode 100644 db/migrate/20140713213502_add_is_participant_to_conference_registraions.rb delete mode 100644 db/migrate/20140713213534_add_is_volunteer_to_conference_registraions.rb delete mode 100644 db/migrate/20140714012739_add_firstname_to_users.rb delete mode 100644 db/migrate/20140714012811_add_lastname_to_users.rb delete mode 100644 db/migrate/20140714013645_add_sessions_table.rb delete mode 100644 db/migrate/20140716001857_add_confirmation_token_to_conference_registrations.rb delete mode 100644 db/migrate/20140716002152_add_data_to_conference_registrations.rb delete mode 100644 db/migrate/20140719172749_add_email_to_conference_registrations.rb delete mode 100644 db/migrate/20140719185914_add_is_complete_to_conference_registrations.rb delete mode 100644 db/migrate/20140719204949_add_completed_to_conference_registrations.rb delete mode 100644 db/migrate/20140723174550_add_payment_confirmation_token_to_conference_registration.rb delete mode 100644 db/migrate/20140723174648_add_payment_info_to_conference_registration.rb delete mode 100644 db/migrate/20140723182548_add_order_to_workshop_streams.rb delete mode 100644 db/migrate/20140723183557_add_order_to_workshop_presentation_styles.rb delete mode 100644 db/migrate/20140725001300_add_registration_fees_paid_to_conference_registration.rb delete mode 100644 db/migrate/20150314031359_add_locale_to_conference.rb delete mode 100644 db/migrate/20150314031361_create_translation_tables.rb delete mode 100644 db/migrate/20150315225815_add_email_address_to_conferences.rb delete mode 100644 db/migrate/20150315225844_add_paypal_email_address_to_conferences.rb delete mode 100644 db/migrate/20150412203317_create_email_confirmations.rb delete mode 100644 db/migrate/20150412203357_add_is_translator_to_users.rb delete mode 100644 db/migrate/20150802234647_add_city_to_conference_registrations.rb delete mode 100644 db/migrate/20150804032547_add_paypal_info_to_conferences.rb delete mode 100644 db/migrate/20150807002150_add_info_to_registrations.rb delete mode 100644 db/migrate/20150809011815_make_username_not_nullable.rb delete mode 100644 db/migrate/20150809012453_make_username_actually_not_nullable.rb delete mode 100644 db/migrate/20150811234700_create_delayed_jobs.rb delete mode 100644 db/migrate/20150819042431_add_languages_to_workshops.rb delete mode 100644 db/migrate/20150821005002_add_notes_to_workshops.rb delete mode 100644 db/migrate/20150912010105_create_workshop_interests.rb delete mode 100644 db/migrate/20150914230507_add_locale_to_workshops.rb delete mode 100644 db/migrate/20150920233402_create_event_locations.rb delete mode 100644 db/migrate/20150920233710_add_event_location_id_to_workshops.rb delete mode 100644 db/migrate/20150920233755_add_event_location_id_to_events.rb delete mode 100644 db/migrate/20150925000217_add_type_to_events.rb delete mode 100644 db/migrate/20150927010559_add_day_parts_to_conferences.rb delete mode 100644 db/migrate/20160304021005_add_status_to_organizations.rb delete mode 100644 db/migrate/20160515210708_add_registration_status_to_conferences.rb delete mode 100644 db/migrate/20160521230653_add_highest_step_to_conference_registrations.rb delete mode 100644 db/migrate/20160522022034_add_steps_completed_to_conference_registrations.rb delete mode 100644 db/migrate/20160526044849_add_needs_facilitators_to_workshops.rb delete mode 100644 db/migrate/20160529225253_add_languages_to_users.rb delete mode 100644 db/migrate/20160530175805_change_date_format_in_dynamic_translation_records.rb delete mode 100644 db/migrate/20160604221121_add_can_provide_housing_to_conference_registrations.rb delete mode 100644 db/migrate/20160604221248_add_housing_data_to_conference_registrations.rb delete mode 100644 db/migrate/20160604221432_add_locale_to_users.rb delete mode 100644 db/migrate/20160621020123_add_space_to_event_locations.rb delete mode 100644 db/migrate/20160621051912_add_meals_to_conferences.rb delete mode 100644 db/migrate/20160622011811_add_locale_to_events.rb delete mode 100644 db/migrate/20160630233219_add_workshop_blocks_to_conferences.rb delete mode 100644 db/migrate/20160703044620_add_block_to_workshops.rb delete mode 100644 db/migrate/20160707034050_add_is_subscribed_to_users.rb delete mode 100644 db/migrate/20160708042302_create_comments.rb delete mode 100644 db/migrate/20160708042511_add_user_id_to_comments.rb delete mode 100644 db/migrate/20160814000814_add_payment_message_to_conference.rb delete mode 100644 db/migrate/20160814000940_add_payment_amounts_to_conference.rb delete mode 100644 db/migrate/20161006021205_add_fb_id_to_user.rb delete mode 100644 db/migrate/20161008210246_add_type_to_conference.rb delete mode 100644 db/migrate/20161009004744_create_cities.rb delete mode 100644 db/migrate/20161009005122_create_city_matches.rb delete mode 100644 db/migrate/20161009194821_add_year_to_conference.rb delete mode 100644 db/migrate/20161009200252_add_city_id_to_conference.rb delete mode 100644 db/migrate/20161129010755_add_is_public_to_conference.rb delete mode 100644 db/migrate/20161129014652_add_is_featured_to_conference.rb delete mode 100644 db/migrate/20161129035601_rename_conference_type.rb delete mode 100644 db/migrate/20161130042227_rename_city_matches.rb delete mode 100644 db/migrate/20161201011655_add_place_id_to_city.rb delete mode 100644 db/migrate/20161201015559_add_city_id_to_location.rb delete mode 100644 db/migrate/20161201021349_add_spanish_city_translations.rb delete mode 100644 db/migrate/20161201043930_set_conference_types.rb delete mode 100644 db/migrate/20161207021236_create_conference_administrators.rb delete mode 100644 db/migrate/20161210212817_change_conference_datetimes_to_dates.rb delete mode 100644 db/migrate/20161211065022_replace_missing_locales.rb delete mode 100644 db/migrate/20170110061048_add_city_id_to_conference_registrations.rb delete mode 100644 db/migrate/20170111172147_add_provider_conditions_to_conference.rb create mode 100644 features/conferences.feature create mode 100644 features/contact_us.feature create mode 100644 features/registration.feature create mode 100644 features/schedule.feature create mode 100644 features/sign_in.feature create mode 100644 features/step_definitions/conferences.rb create mode 100644 features/step_definitions/emails.rb create mode 100644 features/step_definitions/navigation.rb create mode 100644 features/step_definitions/registration.rb create mode 100644 features/step_definitions/users.rb create mode 100644 features/step_definitions/workshops.rb create mode 100644 features/support/assets/images/posters/Brooklyn2025.png create mode 100644 features/support/assets/images/posters/NewOrleans2013.png create mode 100644 features/support/assets/images/posters/Northwest2025.png create mode 100644 features/support/helper.rb create mode 100644 features/support/state.rb create mode 100644 features/workshops.feature diff --git a/.gitignore b/.gitignore index cc90ddb..61d62ed 100644 --- a/.gitignore +++ b/.gitignore @@ -47,10 +47,6 @@ rerun.txt pickle-email-*.html .project -# Ignore Gemfile.lock so that we always get the most up to date gems -# We'll be testing in 'preview' to avoid clashes -Gemfile.lock - # Ignore Redis' dataset snapshot dump.rdb diff --git a/Gemfile b/Gemfile index 1d0c2bd..0fec91c 100644 --- a/Gemfile +++ b/Gemfile @@ -5,58 +5,42 @@ gem 'pg' gem 'rake', '11.1.2' gem 'ruby_dep', '1.3.1' # Lock at 1.3.1 since 1.4 requires ruby 2.5. We should unlock once we upgrade the ruby version on our server -# gem 'bcrypt-ruby', '3.0.0', require: 'bcrypt' -# gem 'bcrypt', '3.1.9' -# gem 'bcrypt', require: :ruby gem 'rack-mini-profiler' gem 'haml' -gem 'nokogiri', '~> 1.6.8.rc2' +gem 'nokogiri' -if Dir.exists?('../lingua_franca') - gem 'lingua_franca', :path => '../lingua_franca' -else - gem 'lingua_franca', :git => 'git://github.com/lingua-franca/lingua_franca.git' -end - -gem 'tzinfo-data' gem 'sass' gem 'sass-rails' - -if Dir.exists?('../bumbleberry') - gem 'bumbleberry', :path => "../bumbleberry" -else - gem 'bumbleberry', :git => 'git://github.com/bumbleberry/bumbleberry.git' -end - -if Dir.exists?('../paypal-express') - gem 'paypal-express', :path => "../paypal-express" -else - gem 'paypal-express', :git => 'git://github.com/bikebike/paypal-express.git' -end - gem 'uglifier', '>= 1.3.0' -gem 'sorcery', '>= 0.8.1' -gem 'oauth2', '~> 0.8.0' +# replace this once these changes are merged in sorcery +gem 'sorcery', git: 'https://github.com/tg90nor/sorcery.git', branch: 'make-facebook-provider-use-json-token-parser' gem 'carrierwave' gem 'carrierwave-imageoptimizer' gem 'mini_magick' -gem 'geocoder' -gem 'paper_trail', '~> 3.0.5' -gem 'sitemap_generator' gem 'activerecord-session_store' -gem 'sass-json-vars' gem 'premailer-rails' -gem 'redcarpet' gem 'sidekiq' gem 'letter_opener' gem 'launchy' -gem 'to_spreadsheet', :git => 'git://github.com/glebm/to_spreadsheet.git' -group :test do - gem 'rspec' - gem 'rspec-rails' -end +# Bike Collectives gems, when developing locally execute: +# bundle config local.bikecollectives_core ../bikecollectives_core +# bundle config local.bumbleberry ../bumbleberry +# bundle config local.lingua_franca ../lingua_franca +# bundle config local.marmara ../marmara +gem 'bikecollectives_core', git: 'https://github.com/bikebike/bikecollectives_core.git', branch: 'master' +gem 'bumbleberry', git: 'https://github.com/bumbleberry/bumbleberry.git', branch: '2017' +gem 'lingua_franca', git: 'https://github.com/lingua-franca/lingua_franca.git', branch: '2017' +gem 'marmara', git: 'https://github.com/lingua-franca/marmara.git', branch: 'master' + +# Bike!Bike! specific stuff +gem 'ianfleeton-paypal-express', require: 'paypal/express' +gem 'geocoder' +gem 'sitemap_generator' +gem 'sass-json-vars' +gem 'redcarpet' +gem 'to_spreadsheet', git: 'https://github.com/glebm/to_spreadsheet.git' group :development do gem 'better_errors' @@ -67,17 +51,23 @@ group :development do gem 'capistrano-rails', '~> 1.1' gem 'capistrano-faster-assets', '~> 1.0' - gem 'eventmachine', :github => 'krzcho/eventmachine', :branch => 'master' - gem 'thin'# , :github => 'krzcho/thin', :branch => 'master' + gem 'eventmachine', git: 'https://github.com/krzcho/eventmachine', :branch => 'master' + gem 'thin' + gem 'rubocop', require: false + gem 'haml-lint', require: false end group :test do + gem 'rspec' + gem 'rspec-rails' gem 'gherkin3', '>= 3.1.0' gem 'cucumber' gem 'cucumber-core' - gem 'cucumber-rails' + gem 'cucumber-rails', require: false + gem 'guard-cucumber' gem 'poltergeist' + gem 'capybara-email' gem 'guard-rspec' gem 'factory_girl_rails' gem 'coveralls', require: false @@ -90,16 +80,22 @@ end group :production, :preview do gem 'rails_12factor' - gem 'daemon-spawn' - gem 'daemons' - +end + +group :production, :preview do platforms :ruby do - gem 'unicorn' + gem 'unicorn', require: false end + + gem 'daemon-spawn' + gem 'daemons' end platforms 'mswin', 'mingw' do + gem 'tzinfo-data' + group :test do gem 'wdm', '>= 0.1.0' + gem 'win32console', require: false end end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..e52b085 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,633 @@ +GIT + remote: https://github.com/glebm/to_spreadsheet.git + revision: 4c08455646dd18de51cc1ec05717fbb240c78a68 + specs: + to_spreadsheet (1.0.6) + axlsx + chronic + nokogiri + rails + responders + +GIT + remote: https://github.com/ianfleeton/paypal-express + revision: 629749621de4c65dd6651649f98410315520fb3d + specs: + paypal-express (0.8.1) + activesupport (>= 2.3) + attr_required (>= 0.0.5) + rest-client + +GIT + remote: https://github.com/krzcho/eventmachine + revision: 651a35ee9df9826e048c3b3721e2c6b415c5a328 + branch: master + specs: + eventmachine (1.2.1) + +GIT + remote: https://github.com/tg90nor/sorcery.git + revision: 79b69a87ce168c47fab76921874aa7e8cb727002 + branch: make-facebook-provider-use-json-token-parser + specs: + sorcery (0.10.3) + bcrypt (~> 3.1) + oauth (~> 0.4, >= 0.4.4) + oauth2 (~> 1.0, >= 0.8.0) + +PATH + remote: ../bikecollectives_core + specs: + bikecollectives_core (0.1.0) + activerecord-session_store + carrierwave + carrierwave-imageoptimizer + haml + launchy + letter_opener + mini_magick + pg + premailer-rails + rails (~> 4.2.0) + redcarpet + sass + sass-json-vars + sass-rails + sidekiq + uglifier (>= 1.3.0) + +PATH + remote: ../bumbleberry + specs: + bumbleberry (0.0.1) + blockspring + cairo + railties + rsvg2 + sass-json-vars + sass-rails + +PATH + remote: ../lingua_franca + specs: + lingua_franca (0.0.1) + diffy + forgery + http_accept_language + i18n + rails (~> 4.2.0.rc2) + rails-i18n + rubyzip + +PATH + remote: ../marmara + specs: + marmara (1.0.2) + css_parser (>= 1.5.0.pre) + +GEM + remote: http://rubygems.org/ + specs: + actionmailer (4.2.0) + actionpack (= 4.2.0) + actionview (= 4.2.0) + activejob (= 4.2.0) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.0) + actionview (= 4.2.0) + activesupport (= 4.2.0) + rack (~> 1.6.0) + rack-test (~> 0.6.2) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.1) + actionview (4.2.0) + activesupport (= 4.2.0) + 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.0) + activesupport (= 4.2.0) + globalid (>= 0.3.0) + activemodel (4.2.0) + activesupport (= 4.2.0) + builder (~> 3.1) + activerecord (4.2.0) + activemodel (= 4.2.0) + activesupport (= 4.2.0) + arel (~> 6.0) + activerecord-session_store (1.0.0) + actionpack (>= 4.0, < 5.1) + activerecord (>= 4.0, < 5.1) + multi_json (~> 1.11, >= 1.11.2) + rack (>= 1.5.2, < 3) + railties (>= 4.0, < 5.1) + activesupport (4.2.0) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + addressable (2.5.1) + public_suffix (~> 2.0, >= 2.0.2) + airbrussh (1.1.2) + sshkit (>= 1.6.1, != 1.7.0) + arel (6.0.4) + ast (2.3.0) + attr_required (1.0.1) + axlsx (2.0.1) + htmlentities (~> 4.3.1) + nokogiri (>= 1.4.1) + rubyzip (~> 1.0.0) + bcrypt (3.1.11-x64-mingw32) + bcrypt (3.1.11-x86-mingw32) + better_errors (2.1.1) + coderay (>= 1.0.0) + erubis (>= 2.6.6) + rack (>= 0.9.0) + binding_of_caller (0.7.2) + debug_inspector (>= 0.0.1) + blockspring (0.1.4) + rest-client (> 1.6.7) + builder (3.2.3) + cairo (1.15.5-x64-mingw32) + pkg-config (>= 1.1.5) + cairo (1.15.5-x86-mingw32) + pkg-config (>= 1.1.5) + callsite (0.0.11) + capistrano (3.8.0) + airbrussh (>= 1.0.0) + i18n + rake (>= 10.0.0) + sshkit (>= 1.9.0) + capistrano-bundler (1.2.0) + capistrano (~> 3.1) + sshkit (~> 1.2) + capistrano-faster-assets (1.0.2) + capistrano (>= 3.1) + capistrano-rails (1.2.3) + capistrano (~> 3.1) + capistrano-bundler (~> 1.1) + capybara (2.13.0) + addressable + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + capybara-email (2.5.0) + capybara (~> 2.4) + mail + carrierwave (1.0.0) + activemodel (>= 4.0.0) + activesupport (>= 4.0.0) + mime-types (>= 1.16) + carrierwave-imageoptimizer (1.4.0) + carrierwave (>= 0.8, < 2.0) + image_optimizer (~> 1.6) + childprocess (0.6.3) + ffi (~> 1.0, >= 1.0.11) + chronic (0.10.2) + cliver (0.3.2) + coderay (1.1.1) + concurrent-ruby (1.0.5) + connection_pool (2.2.1) + coveralls (0.8.20) + json (>= 1.8, < 3) + simplecov (~> 0.14.1) + term-ansicolor (~> 1.3) + thor (~> 0.19.4) + tins (~> 1.6) + crack (0.4.3) + safe_yaml (~> 1.0.0) + css_parser (1.5.0.pre2) + addressable + cucumber (2.4.0) + builder (>= 2.1.2) + cucumber-core (~> 1.5.0) + cucumber-wire (~> 0.0.1) + diff-lcs (>= 1.1.3) + gherkin (~> 4.0) + multi_json (>= 1.7.5, < 2.0) + multi_test (>= 0.1.2) + cucumber-core (1.5.0) + gherkin (~> 4.0) + cucumber-rails (1.4.5) + capybara (>= 1.1.2, < 3) + cucumber (>= 1.3.8, < 4) + mime-types (>= 1.16, < 4) + nokogiri (~> 1.5) + railties (>= 3, < 5.1) + cucumber-wire (0.0.1) + daemon-spawn (0.4.2) + daemons (1.2.4) + database_cleaner (1.5.3) + debug_inspector (0.0.2) + diff-lcs (1.3) + diffy (3.2.0) + docile (1.1.5) + domain_name (0.5.20170404) + unf (>= 0.0.5, < 1.0.0) + erubis (2.7.0) + execjs (2.7.0) + factory_girl (4.8.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.8.0) + factory_girl (~> 4.8.0) + railties (>= 3.0.0) + faraday (0.11.0) + multipart-post (>= 1.2, < 3) + ffi (1.9.18-x64-mingw32) + ffi (1.9.18-x86-mingw32) + forgery (0.6.0) + formatador (0.2.5) + gdk_pixbuf2 (3.1.1-x64-mingw32) + gio2 (= 3.1.1) + gdk_pixbuf2 (3.1.1-x86-mingw32) + gio2 (= 3.1.1) + geocoder (1.4.3) + gherkin (4.1.1) + gherkin3 (3.1.2) + gio2 (3.1.1-x64-mingw32) + glib2 (= 3.1.1) + gobject-introspection (= 3.1.1) + gio2 (3.1.1-x86-mingw32) + glib2 (= 3.1.1) + gobject-introspection (= 3.1.1) + git-version-bump (0.15.1) + glib2 (3.1.1-x64-mingw32) + cairo (>= 1.12.8) + pkg-config + glib2 (3.1.1-x86-mingw32) + cairo (>= 1.12.8) + pkg-config + globalid (0.3.7) + activesupport (>= 4.1.0) + gobject-introspection (3.1.1-x64-mingw32) + glib2 (= 3.1.1) + gobject-introspection (3.1.1-x86-mingw32) + glib2 (= 3.1.1) + guard (2.14.1) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (~> 1.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.9.12) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-cucumber (2.1.2) + cucumber (~> 2.0) + guard-compat (~> 1.0) + nenv (~> 0.1) + guard-rspec (4.7.3) + guard (~> 2.1) + guard-compat (~> 1.1) + rspec (>= 2.99.0, < 4.0) + haml (4.0.7) + tilt + haml-lint (0.999.999) + haml_lint + haml_lint (0.24.0) + haml (>= 4.0, < 5.1) + rainbow + rake (>= 10, < 13) + rubocop (>= 0.47.0) + sysexits (~> 1.1) + hashdiff (0.3.2) + htmlentities (4.3.4) + http-cookie (1.0.3) + domain_name (~> 0.5) + http_accept_language (2.1.0) + i18n (0.8.1) + image_optimizer (1.7.0) + json (1.8.6) + jwt (1.5.6) + kgio (2.11.0) + launchy (2.4.3) + addressable (~> 2.3) + letter_opener (1.4.1) + launchy (~> 2.2) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + loofah (2.0.3) + nokogiri (>= 1.5.9) + lumberjack (1.0.11) + mail (2.6.4) + mime-types (>= 1.16, < 4) + meta_request (0.4.0) + callsite (~> 0.0, >= 0.0.11) + rack-contrib (~> 1.1) + railties (>= 3.0.0, < 5.1.0) + metaclass (0.0.4) + method_source (0.8.2) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_magick (4.7.0) + mini_portile2 (2.1.0) + minitest (5.10.1) + mocha (1.2.1) + metaclass (~> 0.0.1) + multi_json (1.12.1) + multi_test (0.1.2) + multi_xml (0.6.0) + multipart-post (2.0.0) + nenv (0.3.0) + net-scp (1.2.1) + net-ssh (>= 2.6.5) + net-ssh (4.1.0) + netrc (0.11.0) + nokogiri (1.6.8.1-x64-mingw32) + mini_portile2 (~> 2.1.0) + nokogiri (1.6.8.1-x86-mingw32) + mini_portile2 (~> 2.1.0) + notiffany (0.1.1) + nenv (~> 0.1) + shellany (~> 0.0) + oauth (0.5.1) + oauth2 (1.3.1) + faraday (>= 0.8, < 0.12) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + pango (3.1.1-x64-mingw32) + cairo (>= 1.14.0) + glib2 (= 3.1.1) + pango (3.1.1-x86-mingw32) + cairo (>= 1.14.0) + glib2 (= 3.1.1) + parser (2.4.0.0) + ast (~> 2.2) + pg (0.20.0-x64-mingw32) + pg (0.20.0-x86-mingw32) + pkg-config (1.1.7) + poltergeist (1.14.0) + capybara (~> 2.1) + cliver (~> 0.3.1) + websocket-driver (>= 0.2.0) + powerpack (0.1.1) + premailer (1.10.2) + addressable + css_parser (>= 1.4.10) + htmlentities (>= 4.0.0) + premailer-rails (1.9.5) + actionmailer (>= 3, < 6) + premailer (~> 1.7, >= 1.7.9) + pry (0.10.4) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + public_suffix (2.0.5) + rack (1.6.5) + rack-contrib (1.4.0) + git-version-bump (~> 0.15) + rack (~> 1.4) + rack-mini-profiler (0.10.2) + rack (>= 1.2.0) + rack-protection (1.5.3) + rack + rack-test (0.6.3) + rack (>= 1.0) + rails (4.2.0) + actionmailer (= 4.2.0) + actionpack (= 4.2.0) + actionview (= 4.2.0) + activejob (= 4.2.0) + activemodel (= 4.2.0) + activerecord (= 4.2.0) + activesupport (= 4.2.0) + bundler (>= 1.3.0, < 2.0) + railties (= 4.2.0) + sprockets-rails + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.8) + activesupport (>= 4.2.0.beta, < 5.0) + nokogiri (~> 1.6) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.3) + loofah (~> 2.0) + rails-i18n (4.0.9) + i18n (~> 0.7) + railties (~> 4.0) + rails_12factor (0.0.3) + rails_serve_static_assets + rails_stdout_logging + rails_serve_static_assets (0.0.5) + rails_stdout_logging (0.0.5) + railties (4.2.0) + actionpack (= 4.2.0) + activesupport (= 4.2.0) + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rainbow (2.2.1) + raindrops (0.18.0) + rake (11.1.2) + rb-fsevent (0.9.8) + rb-inotify (0.9.8) + ffi (>= 0.5.0) + redcarpet (3.4.0) + redis (3.3.3) + responders (2.3.0) + railties (>= 4.2.0, < 5.1) + rest-client (2.0.1-x64-mingw32) + ffi (~> 1.9) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + rest-client (2.0.1-x86-mingw32) + ffi (~> 1.9) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + rspec (3.5.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-core (3.5.4) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-mocks (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-rails (3.5.2) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-support (~> 3.5.0) + rspec-support (3.5.0) + rsvg2 (3.1.1-x64-mingw32) + cairo (>= 1.12.8) + gdk_pixbuf2 (= 3.1.1) + pango (>= 3.1.1) + rsvg2 (3.1.1-x86-mingw32) + cairo (>= 1.12.8) + gdk_pixbuf2 (= 3.1.1) + pango (>= 3.1.1) + rubocop (0.48.1) + parser (>= 2.3.3.1, < 3.0) + powerpack (~> 0.1) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + ruby-progressbar (1.8.1) + ruby_dep (1.3.1) + rubyzip (1.0.0) + safe_yaml (1.0.4) + sass (3.4.23) + sass-json-vars (0.3.3) + sass (>= 3.1) + sass-rails (5.0.6) + railties (>= 4.0.0, < 6) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + selenium-webdriver (3.3.0) + childprocess (~> 0.5) + rubyzip (~> 1.0) + websocket (~> 1.0) + shellany (0.0.1) + sidekiq (4.2.10) + concurrent-ruby (~> 1.0) + connection_pool (~> 2.2, >= 2.2.0) + rack-protection (>= 1.5.0) + redis (~> 3.2, >= 3.2.1) + simplecov (0.14.1) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + sitemap_generator (5.3.1) + builder (~> 3.0) + slop (3.6.0) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.0) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sshkit (1.13.1) + net-scp (>= 1.1.2) + net-ssh (>= 2.8.0) + sysexits (1.2.0) + term-ansicolor (1.5.0) + tins (~> 1.0) + thin (1.7.0) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0, >= 1.0.4) + rack (>= 1, < 3) + thor (0.19.4) + thread_safe (0.3.6) + tilt (2.0.7) + tins (1.13.2) + tzinfo (1.2.3) + thread_safe (~> 0.1) + tzinfo-data (1.2017.2) + tzinfo (>= 1.0.0) + uglifier (3.1.13) + execjs (>= 0.3.0, < 3) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.2-x64-mingw32) + unf_ext (0.0.7.2-x86-mingw32) + unicode-display_width (1.1.3) + unicorn (5.3.0) + kgio (~> 2.6) + raindrops (~> 0.7) + wdm (0.1.1) + webmock (2.3.2) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff + websocket (1.2.4) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) + win32console (1.3.2-x86-mingw32) + xpath (2.0.0) + nokogiri (~> 1.3) + +PLATFORMS + x64-mingw32 + x86-mingw32 + +DEPENDENCIES + activerecord-session_store + better_errors + bikecollectives_core! + binding_of_caller + bumbleberry! + capistrano (~> 3.1) + capistrano-faster-assets (~> 1.0) + capistrano-rails (~> 1.1) + capybara-email + carrierwave + carrierwave-imageoptimizer + coveralls + cucumber + cucumber-core + cucumber-rails + daemon-spawn + daemons + database_cleaner + eventmachine! + factory_girl_rails + geocoder + gherkin3 (>= 3.1.0) + guard-cucumber + guard-rspec + haml + haml-lint + launchy + letter_opener + lingua_franca! + marmara! + meta_request + mini_magick + mocha + nokogiri (~> 1.6.8.rc2) + paypal-express! + pg + poltergeist + premailer-rails + rack-mini-profiler + rails (= 4.2.0) + rails_12factor + rake (= 11.1.2) + redcarpet + rspec + rspec-rails + rubocop + ruby_dep (= 1.3.1) + sass + sass-json-vars + sass-rails + selenium-webdriver + sidekiq + simplecov + sitemap_generator + sorcery! + thin + to_spreadsheet! + tzinfo-data + uglifier (>= 1.3.0) + unicorn + wdm (>= 0.1.0) + webmock + win32console + +BUNDLED WITH + 1.14.4 diff --git a/Guardfile b/Guardfile index e6e7c78..e5db626 100644 --- a/Guardfile +++ b/Guardfile @@ -22,3 +22,28 @@ guard :rspec do watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } end + +cucumber_options = { + # Below are examples overriding defaults + + # cmd: 'bin/cucumber', + # cmd_additional_args: '--profile guard', + + # all_after_pass: false, + # all_on_start: false, + # keep_failed: false, + # feature_sets: ['features/frontend', 'features/experimental'], + + # run_all: { cmd_additional_args: '--profile guard_all' }, + # focus_on: { 'wip' }, # @wip + # notification: false +} + +guard "cucumber", cucumber_options do + watch(%r{^features/.+\.feature$}) + watch(%r{^features/support/.+$}) { "features" } + + watch(%r{^features/step_definitions/(.+)_steps\.rb$}) do |m| + Dir[File.join("**/#{m[1]}.feature")][0] || "features" + end +end diff --git a/README.md b/README.md index 10a69b3..7ce286e 100644 --- a/README.md +++ b/README.md @@ -150,3 +150,8 @@ On hold until our design team determines a director for our identity. ## Testing Practices ## Our focus will be on integration testing using Capybara. While testing the app records all translations that it finds, whether or not they exist, and which pages that they were found on. + +## Attribution ## + +### Icons ### + diff --git a/Rakefile b/Rakefile index b9a026c..ffec68e 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,8 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. +require 'rubygems' +require 'cucumber' +require 'cucumber/rake/task' require File.expand_path('../config/application', __FILE__) @@ -61,3 +64,31 @@ task update_cities_fr: :environment do c.save! end end + +task :i18n do + LinguaFranca.test LinguaFranca::TestModes::RECORD do + Rake::Task[:cucumber].execute + end +end + +task :css do + ENV['CSS_TEST'] = '1' + Rake::Task[:cucumber].execute + ENV['CSS_TEST'] = nil +end + +task :a11y do + ENV['TEST_A11Y'] = '1' + Rake::Task[:cucumber].execute + ENV['TEST_A11Y'] = nil +end + +task "cucumber:debug" do + ENV['TEST_DEBUG'] = '1' + Rake::Task[:cucumber].execute + ENV['TEST_DEBUG'] = nil +end + +Cucumber::Rake::Task.new(:cucumber) do |t| + t.cucumber_opts = "features --format pretty" +end diff --git a/app/assets/images/test-poster.png b/app/assets/images/test-poster.png new file mode 100644 index 0000000000000000000000000000000000000000..29d20c1b7c42ef63f57bb45a48bc2f5b6e97d6b5 GIT binary patch literal 77534 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 literal 0 HcmV?d00001 diff --git a/app/assets/javascripts/userfield.js b/app/assets/javascripts/userfield.js index 3ee54d8..593e15a 100644 --- a/app/assets/javascripts/userfield.js +++ b/app/assets/javascripts/userfield.js @@ -21,6 +21,7 @@ var field = fields[i]; var input = field.getElementsByTagName('input')[0]; var name = field.getElementsByClassName('user-name')[0]; + } }, false); })(); diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss index fb851df..d4121c4 100644 --- a/app/assets/stylesheets/_application.scss +++ b/app/assets/stylesheets/_application.scss @@ -1710,17 +1710,17 @@ ul.warnings { a.policy { background-color: $colour-5; - @include _(order, 1); + // @include _(order, 1); } a.about { background-color: $colour-3; - @include _(order, 1); + // @include _(order, 1); } a.conferences { background-color: $colour-2; - @include _(order, 1); + // @include _(order, 1); } form, .register { @@ -1728,7 +1728,8 @@ ul.warnings { } a, form { - width: 33%; + width: 33.333%; + float: left; @include _(flex, 1); } @@ -2948,12 +2949,11 @@ a.logo { .conference-banner { text-align: center; padding: 0; - margin: 0 -1em 2em; - width: auto; + margin: 0; .title { font-size: 5vw; - margin: 1em auto 2em; + margin: 1em auto 0; h1, h2 { margin: 0; @@ -2971,6 +2971,7 @@ a.logo { img { max-width: 100%; + margin-top: 2em; } } @@ -3311,8 +3312,11 @@ body.error-locale-not-available { body { #primary-content { @include _(transition, 'filter 250ms ease-in-out, -webkit-filter 250ms ease-in-out'); - display: -webkit-flex; - flex-direction: column; + + @if capable_of(flexbox, true) { + display: -webkit-flex; + flex-direction: column; + } min-height: 100vh; overflow: hidden; } @@ -4617,6 +4621,7 @@ html[data-ontop] { .nav a { &[class] { width: auto; + float: none; overflow: visible; margin-left: 0.725em; padding: 0.25em 0.5em; diff --git a/app/assets/stylesheets/bumbleberry-settings.json b/app/assets/stylesheets/bumbleberry-settings.json index 0ca48d3..5a67c7c 100644 --- a/app/assets/stylesheets/bumbleberry-settings.json +++ b/app/assets/stylesheets/bumbleberry-settings.json @@ -1,103 +1,105 @@ { - "stylesheets": ["application", "editor"], - "precompile": { - "test": { - "chrome": ["51"] - }, - "development": { - "and_chr": ["56"], - "chrome": ["56"], - "edge": ["13"], - "firefox": ["50"], - "ie": ["11"], - "ios_saf": ["8", "9"] - } + "stylesheets": ["application", "editor", "admin"], + "precompile": { + "test": { + "safari": ["5"], + "chrome": ["55"] }, - "background-color": "#FFFEFE", - "breakpoint-unit": "px", - "unresponsive-width": 1000, - "row-width": 1000, - "total-columns": 12, - "platforms": { - "mobile": { - "range": [0,700] - }, - "desktop": { - "range": [0,0] - } + "development": { + "safari": ["5", "5.1"], + "and_chr": ["57"], + "chrome": ["57"], + "edge": ["13"], + "firefox": ["50"], + "ie": ["11"], + "ios_saf": ["8", "9"] + } + }, + "background-color": "#FFFEFE", + "breakpoint-unit": "px", + "unresponsive-width": 1000, + "row-width": 1000, + "total-columns": 12, + "platforms": { + "mobile": { + "range": [0,700] + }, + "desktop": { + "range": [0,0] + } + }, + "breakpoints": { + "small": { + "range": [0], + "grid": "y", + "offset": "n", + "reset-order": "n" }, - "breakpoints": { - "small": { - "range": [0], - "grid": "y", - "offset": "n", - "reset-order": "n" - }, - "medium": { - "range": [680], - "grid": "y", - "offset": "n", - "reset-order": "n" - }, - "large": { - "range": [1024], - "grid": "y", - "offset": "n", - "reset-order": "n" - }, - "small-only": { - "range": [0,319], - "grid": "n", - "offset": "n", - "reset-order": "n" - }, - "medium-only": { - "range": [320,1023], - "grid": "n", - "offset": "n", - "reset-order": "n" - } + "medium": { + "range": [680], + "grid": "y", + "offset": "n", + "reset-order": "n" }, - "grid-push": [1, 2], - "grid-pull": [1, 2], - "font-loading-method": "http2", - "fonts": { - "primary": { - "name": "Sanchez Light", - "location": "Sanchez", - "svg_id": "wf", - "ttf_type": "ttf", - "fallback": ["Helvetica Neue", "Helvetica", "Arial", "Lucida Grande", "sans-serif"] - }, - "secondary": { - "name": "AlteHaasGroteskBold", - "location": "AlteHaasGroteskBold", - "svg_id": "alte_haas_groteskbold", - "ttf_type": "ttf", - "fallback": ["Helvetica Neue", "Helvetica", "Arial", "Lucida Grande", "sans-serif"] - }, - "monospace": { - "name": "NotCourierSans", - "location": "NotCourierSans", - "svg_id": "notcouriersans", - "ttf_type": "ttf", - "fallback": ["Courier New", "monospace"], - "auto_load": "n" - }, - "monospace-bold": { - "name": "NotCourierSans", - "location": "NotCourierSans-Bold", - "svg_id": "notcouriersansbold", - "ttf_type": "ttf", - "fallback": ["Courier New", "monospace"], - "auto_load": "n", - "weight": "bold" - } + "large": { + "range": [1024], + "grid": "y", + "offset": "n", + "reset-order": "n" }, - "sprites": { - "icons": { - "bb-icon-logo": [0, 0, "182px", "149px"], - "bb-icon-logo-text": ["182px", 0, "136px", "149px"] - } + "small-only": { + "range": [0,319], + "grid": "n", + "offset": "n", + "reset-order": "n" + }, + "medium-only": { + "range": [320,1023], + "grid": "n", + "offset": "n", + "reset-order": "n" + } + }, + "grid-push": [1, 2], + "grid-pull": [1, 2], + "font-loading-method": "http2", + "fonts": { + "primary": { + "name": "Sanchez Light", + "location": "Sanchez", + "svg_id": "wf", + "ttf_type": "ttf", + "fallback": ["Helvetica Neue", "Helvetica", "Arial", "Lucida Grande", "sans-serif"] + }, + "secondary": { + "name": "AlteHaasGroteskBold", + "location": "AlteHaasGroteskBold", + "svg_id": "alte_haas_groteskbold", + "ttf_type": "ttf", + "fallback": ["Helvetica Neue", "Helvetica", "Arial", "Lucida Grande", "sans-serif"] + }, + "monospace": { + "name": "NotCourierSans", + "location": "NotCourierSans", + "svg_id": "notcouriersans", + "ttf_type": "ttf", + "fallback": ["Courier New", "monospace"], + "auto_load": "n" + }, + "monospace-bold": { + "name": "NotCourierSans", + "location": "NotCourierSans-Bold", + "svg_id": "notcouriersansbold", + "ttf_type": "ttf", + "fallback": ["Courier New", "monospace"], + "auto_load": "n", + "weight": "bold" + } + }, + "sprites": { + "icons": { + "bb-icon-logo": [0, 0, "182px", "149px"], + "bb-icon-logo-text": ["182px", 0, "136px", "149px"] } + } } diff --git a/app/assets/stylesheets/user-mailer.scss b/app/assets/stylesheets/user-mailer.scss index af6e781..b312e63 100644 --- a/app/assets/stylesheets/user-mailer.scss +++ b/app/assets/stylesheets/user-mailer.scss @@ -1,268 +1,268 @@ @import "settings"; body { - width: 100% !important; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - margin: 1em; - padding: 0; + width: 100% !important; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + margin: 1em; + padding: 0; } .ExternalClass { - width: 100%; - line-height: 100%; + width: 100%; + line-height: 100%; - p, span, font, td, div { - line-height: 100%; - } + p, span, font, td, div { + line-height: 100%; + } } #backgroundTable { - margin: 0; - padding: 0; - width: 100% !important; - max-width: 100% !important; - line-height: 100% !important; + margin: 0; + padding: 0; + width: 100% !important; + max-width: 100% !important; + line-height: 100% !important; } img { - outline: none; - text-decoration: none; - -ms-interpolation-mode: bicubic; + outline: none; + text-decoration: none; + -ms-interpolation-mode: bicubic; - a & { - border: none; - } + a & { + border: none; + } } .image_fix { - display: block; - max-width: 100%; + display: block; + max-width: 100%; } a { - color: $colour-1; - - #outlook & { - padding: 0; - } - - &:link { color: $colour-1; } - &:visited { color: $colour-1; } - &:hover { color: $colour-5; } - - @media only screen and (max-device-width: 480px) { - &[href^="tel"], &[href^="sms"] { - text-decoration: none; - color: $black; - pointer-events: none; - cursor: default; - } - - .mobile_link &[href^="tel"], .mobile_link &[href^="sms"] { - text-decoration: default; - color: $colour-1 !important; - pointer-events: auto; - cursor: default; - } - } + color: $colour-1; + + #outlook & { + padding: 0; + } + + &:link { color: $colour-1; } + &:visited { color: $colour-1; } + &:hover { color: $colour-5; } + + @media only screen and (max-device-width: 480px) { + &[href^="tel"], &[href^="sms"] { + text-decoration: none; + color: $black; + pointer-events: none; + cursor: default; + } + + .mobile_link &[href^="tel"], .mobile_link &[href^="sms"] { + text-decoration: default; + color: $colour-1 !important; + pointer-events: auto; + cursor: default; + } + } } p { - color: $black !important; - font-size: 1.5em; + color: $black !important; + font-size: 1.5em; } p, blockquote { - margin: 1em; - line-height: 1.3333em; + margin: 1em; + line-height: 1.3333em; } blockquote { - font-size: 1em; + font-size: 1em; } h1 { - font-size: 2.5em; - line-height: 1.25em; - padding: 1em 0; - - &:first-child { - padding-top: 0; - } + font-size: 2.5em; + line-height: 1.25em; + padding: 1em 0; + + &:first-child { + padding-top: 0; + } } h2 { - font-size: 1.8em; - line-height: 1.25em; - padding-bottom: 1em; + font-size: 1.8em; + line-height: 1.25em; + padding-bottom: 1em; } blockquote { - font-style: italic; - margin-bottom: 2em; - color: #666 !important; - border: 0.1em solid #CCC; - padding: 1em; - border-style: none none solid solid; - background-color: #F8F8F8; + font-style: italic; + margin-bottom: 2em; + color: #666 !important; + border: 0.1em solid #CCC; + padding: 1em; + border-style: none none solid solid; + background-color: #F8F8F8; } h1, h2, h3, h4, h5, h6 { - color: $black !important; - - a { - color: $colour-1 !important; - - &:active { - color: $colour-4 !important; - } - - &:visited { - color: $colour-2 !important; - } - } + color: $black !important; + + a { + color: $colour-1 !important; + + &:active { + color: $colour-4 !important; + } + + &:visited { + color: $colour-2 !important; + } + } } table { - border-collapse: collapse; - max-width: 512px; - min-width: 280px; - mso-table-lspace: 0pt; - mso-table-rspace: 0pt; - - td { - border-collapse: collapse; - } - - th { - text-align: left; - } + border-collapse: collapse; + max-width: 512px; + min-width: 280px; + mso-table-lspace: 0pt; + mso-table-rspace: 0pt; + + td { + border-collapse: collapse; + } + + th { + text-align: left; + } } table#bb_full_width, table#ecxbb_full_width { - &, table { - max-width: none; - width: 100%; - } + &, table { + max-width: none; + width: 100%; + } } .error-report { - width: 100%; - max-width: 100%; - border: 0.15em solid #CCC; - background-color: #EEE; - font-size: 1.25em; - margin-bottom: 2em; - - th { - background-color: #CCC; - } - - td, th { - padding: 0.25em 0.5em; - - &:last-child { - border-left: 0.15em solid #CCC; - min-width: 40em; - } - } - - td:last-child { - font-family: monospace; - word-break: break-word; - background-color: #FFF; - font-size: 0.75em; - } + width: 100%; + max-width: 100%; + border: 0.15em solid #CCC; + background-color: #EEE; + font-size: 1.25em; + margin-bottom: 2em; + + th { + background-color: #CCC; + } + + td, th { + padding: 0.25em 0.5em; + + &:last-child { + border-left: 0.15em solid #CCC; + min-width: 40em; + } + } + + td:last-child { + font-family: monospace; + word-break: break-word; + background-color: #FFF; + font-size: 0.75em; + } } code { - color: #C33; - font-size: 0.9em; + color: #C33; + font-size: 0.9em; } pre { - font-size: 1.5em; - padding: 1em; - background-color: #333; - color: antiquewhite; - word-break: break-word; + font-size: 1.5em; + padding: 1em; + background-color: #333; + color: antiquewhite; + word-break: break-word; } .diff, .ecxdiff { - margin: 1em 0 5em 1em; - overflow: auto; - - ul { - list-style: none; - padding: 0; - margin: 0; - position: relative; - } - - li { - width: 40%; - display: inline-block; - padding: 0.5em; - margin: 0; - border-bottom: 1px solid #DDD; - //white-space: nowrap; - - &:nth-child(odd) { - margin-right: -3px; - float: left; - clear: left; - } - - ins { - color: #080;//lighten($colour-3, 33%); - } - - del { - color: #800; - } - } + margin: 1em 0 5em 1em; + overflow: auto; + + ul { + list-style: none; + padding: 0; + margin: 0; + position: relative; + } + + li { + width: 40%; + display: inline-block; + padding: 0.5em; + margin: 0; + border-bottom: 1px solid #DDD; + //white-space: nowrap; + + &:nth-child(odd) { + margin-right: -3px; + float: left; + clear: left; + } + + ins { + color: #080;//lighten($colour-3, 33%); + } + + del { + color: #800; + } + } } // Buttons h3 { - b { - padding: 10px 20px; - line-height: 50px; - - a, a:visited { - color: #FFF !important; - background-color: $colour-5; - text-decoration: none !important; - border-radius: 4px; - padding: 10px 15px; - margin-left: 20px; - border-bottom: 3px solid darken($colour-5, 10%); - -webkit-box-shadow: 0 0.5em 1.5em -0.75em #000; - box-shadow: 0 0.5em 1.5em -0.75em #000; - cursor: pointer !important; - } - - a:hover { - background-color: darken($colour-5, 10%); - } - } + b { + padding: 10px 20px; + line-height: 50px; + + a, a:visited { + color: #FFF !important; + background-color: $colour-5; + text-decoration: none !important; + border-radius: 4px; + padding: 10px 15px; + margin-left: 20px; + border-bottom: 3px solid darken($colour-5, 10%); + -webkit-box-shadow: 0 0.5em 1.5em -0.75em #000; + box-shadow: 0 0.5em 1.5em -0.75em #000; + cursor: pointer !important; + } + + a:hover { + background-color: darken($colour-5, 10%); + } + } } @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) { - a[href^="tel"], a[href^="sms"] { - text-decoration: none; - color: $colour-1; - pointer-events: none; - cursor: default; - - .mobile_link & { - text-decoration: default; - color: $colour-1 !important; - pointer-events: auto; - cursor: default; - } - } + a[href^="tel"], a[href^="sms"] { + text-decoration: none; + color: $colour-1; + pointer-events: none; + cursor: default; + + .mobile_link & { + text-decoration: default; + color: $colour-1 !important; + pointer-events: auto; + cursor: default; + } + } } diff --git a/app/controllers/admin_controller.rb b/app/controllers/administration_controller.rb similarity index 94% rename from app/controllers/admin_controller.rb rename to app/controllers/administration_controller.rb index 1c03f0d..4c5d980 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/administration_controller.rb @@ -1,51 +1,51 @@ -require 'geocoder/calculations' - -class AdminController < ApplicationController - def new - return do_404 unless logged_in? && current_user.administrator? - @this_conference = Conference.new - @main_title = @page_title = 'articles.conferences.headings.new' - end - - def edit - return do_404 unless logged_in? && current_user.administrator? - @this_conference = Conference.find_by!(slug: params[:slug]) - @page_title = 'articles.conferences.headings.edit' - @main_title_vars = { vars: { title: @this_conference.title } } - render 'new' - end - - def save - conference = params[:id].present? ? Conference.find_by!(id: params[:id]) : Conference.new - - if params[:button] == 'save' - city = City.search(params[:city]) - conference.city_id = city.id - conference.conferencetype = params[:type] - conference.year = params[:year].to_i - conference.is_public = params[:is_public].present? - conference.is_featured = params[:is_featured].present? - conference.make_slug(true) - conference.save! - elsif params[:button] == 'delete' - conference.destroy - return redirect_to conferences_url - end - - redirect_to conference_url(conference.slug) - end - - rescue_from ActiveRecord::PremissionDenied do |exception| - if logged_in? - redirect_to :register - else - @register_template = :confirm_email - @page_title = "articles.conference_registration.headings.#{@this_conference.registration_status == :open ? '': 'Pre_'}Registration_Details" - render :register - end - end - - rescue_from ActiveRecord::RecordNotFound do |exception| - do_404 - end -end +require 'geocoder/calculations' + +class AdministrationController < ApplicationController + def new + return do_404 unless logged_in? && current_user.administrator? + @this_conference = Conference.new + @main_title = @page_title = 'articles.conferences.headings.new' + end + + def edit + return do_404 unless logged_in? && current_user.administrator? + @this_conference = Conference.find_by!(slug: params[:slug]) + @page_title = 'articles.conferences.headings.edit' + @main_title_vars = { vars: { title: @this_conference.title } } + render 'new' + end + + def save + conference = params[:id].present? ? Conference.find_by!(id: params[:id]) : Conference.new + + if params[:button] == 'save' + city = City.search(params[:city]) + conference.city_id = city.id + conference.conferencetype = params[:type] + conference.year = params[:year].to_i + conference.is_public = params[:is_public].present? + conference.is_featured = params[:is_featured].present? + conference.make_slug(true) + conference.save! + elsif params[:button] == 'delete' + conference.destroy + return redirect_to conferences_url + end + + redirect_to conference_url(conference.slug) + end + + rescue_from ActiveRecord::PremissionDenied do |exception| + if logged_in? + redirect_to :register + else + @register_template = :confirm_email + @page_title = "articles.conference_registration.headings.#{@this_conference.registration_status == :open ? '': 'Pre_'}Registration_Details" + render :register + end + end + + rescue_from ActiveRecord::RecordNotFound do |exception| + do_404 + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 131f5da..8dc06bd 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,21 +1,19 @@ -module ActiveRecord - class PremissionDenied < RuntimeError - end -end - -class ApplicationController < LinguaFrancaApplicationController - # Prevent CSRF attacks by raising an exception. - # For APIs, you may want to use :null_session instead. +class ApplicationController < BaseController protect_from_forgery with: :exception, :except => [:do_confirm, :js_error, :admin_update] before_filter :capture_page_info helper_method :protect - @@test_host - @@test_location + # @@test_host + # @@test_location + + def default_url_options + { host: "#{request.protocol}#{request.host_with_port}", trailing_slash: true } + end def capture_page_info + # capture request info in case an error occurs if request.method == "GET" && (params[:controller] != 'application' || params[:action] != 'contact') session[:last_request] request_info = { @@ -27,14 +25,11 @@ class ApplicationController < LinguaFrancaApplicationController 'env' => Hash.new } } - request.env.each do | key, value | + request.env.each do |key, value| request_info['request']['env'][key.to_s] = value.to_s end session['request_info'] = request_info end - # set the translator to the current user if we're logged in - I18n.config.translator = current_user - I18n.config.callback = self # get the current conferences and set them globally status_hierarchy = { @@ -50,18 +45,16 @@ class ApplicationController < LinguaFrancaApplicationController @conference = @conferences.first # add some style sheets - @stylesheets ||= Array.new + @stylesheets ||= Set.new # add the translations stylesheet if translating @stylesheets << params[:controller] if params[:controller] == 'translations' - @_inline_scripts ||= [] + @_inline_scripts ||= Set.new @_inline_scripts << Rails.application.assets.find_asset('main.js').to_s - ActionMailer::Base.default_url_options = {:host => "#{request.protocol}#{request.host_with_port}"} - - if request.post? && params[:action] == 'do_confirm' - halt_redirection! - end + ActionMailer::Base.default_url_options = { + host: "#{request.protocol}#{request.host_with_port}" + } @alt_lang_urls = {} I18n.backend.enabled_locales.each do |locale| @@ -72,15 +65,10 @@ class ApplicationController < LinguaFrancaApplicationController # give each environment a different icon and theme colour so that we can easily see where we are. See https://css-tricks.com/give-development-domain-different-favicon-production @favicon = Rails.env.development? || Rails.env.preview? ? "favicon-#{Rails.env.to_s}.ico" : 'favicon.ico' @theme_colour = Rails.env.preview? ? '#EF57B4' : (Rails.env.development? ? '#D89E59' : '#00ADEF') - - # call the base method to detect the language - super end def home - @workshops = [] - - if @conference.present? + if @conference.present? && @conference.id.present? @workshops = Workshop.where(conference_id: @conference.id) if @conference.workshop_schedule_published @@ -94,54 +82,17 @@ class ApplicationController < LinguaFrancaApplicationController @is_policy_page = true end - def robots - render :text => File.read("config/robots-#{Rails.env.production? ? 'live' : 'dev'}.txt"), :content_type => 'text/plain' - end + # def self.set_host(host) + # @@test_host = host + # end - def humans - render :text => File.read("config/humans.txt"), :content_type => 'text/plain' - end + # def self.set_location(location) + # @@test_location = location + # end - def self.set_host(host) - @@test_host = host - end - - def self.set_location(location) - @@test_location = location - end - - def self.get_location() - @@test_location - end - - def do_404 - error_404(status: 404) - end - - def error_404(args = {}) - params[:_original_action] = params[:action] - params[:action] = 'error-404' - @page_title = 'page_titles.404.Page_Not_Found' - @main_title = 'error.404.title' - render 'application/404', args - end - - def do_403(template = nil) - @template = template - @page_title ||= 'page_titles.403.Access_Denied' - @main_title ||= @page_title - params[:_original_action] = params[:action] - params[:action] = 'error-403' - render 'application/permission_denied', status: 403 - end - - def error_500(exception = nil) - @page_title = 'page_titles.500.An_Error_Occurred' - @main_title = 'error.500.title' - params[:_original_action] = params[:action] - params[:action] = 'error-500' - render 'application/500', status: 500 - end + # def self.get_location() + # @@test_location + # end def js_error # send and email if this is production @@ -154,20 +105,30 @@ class ApplicationController < LinguaFrancaApplicationController begin # log the error - logger.info "Javascript exception: #{params[:message]}" - - UserMailer.send_mail(:error_report) do - [ - "A JavaScript error has occurred", - report, - params[:message], - nil, - request, - params, - current_user, - Time.now.strftime("%d/%m/%Y %H:%M") - ] - end# if Rails.env.preview? || Rails.env.production? + logger.info "A JavaScript error has occurred on #{params[:location]}:#{params[:lineNumber]}: #{params[:message]}" + + if Rails.env.preview? || Rails.env.production? + requestHash = { + 'remote_ip' => arg.remote_ip, + 'uuid' => arg.uuid, + 'original_url' => arg.original_url, + 'env' => Hash.new + } + request.env.each do | key, value | + requestHash['env'][key.to_s] = value.to_s + end + + send_mail(:error_report, + "A JavaScript error has occurred", + report, + params[:message], + nil, + requestHash, + params, + current_user, + Time.now.strftime("%d/%m/%Y %H:%M") + ) + end rescue Exception => exception2 logger.info exception2.to_s logger.info exception2.backtrace.join("\n") @@ -175,31 +136,27 @@ class ApplicationController < LinguaFrancaApplicationController render json: {} end - rescue_from ActiveRecord::RecordNotFound do |exception| - do_404 - end - - rescue_from ActiveRecord::PremissionDenied do |exception| - do_403 - end + def confirmation_sent(user) + template = 'login_confirmation_sent' + @page_title ||= 'page_titles.403.Please_Check_Email' - rescue_from AbstractController::ActionNotFound do |exception| - @banner_image = 'grafitti.jpg' - - if current_user - @page_title = nil#'page_titles.Please_Login' - do_403 'not_a_translator' - #return - else - @page_title = 'page_titles.403.Please_Login' - do_403 'translator_login' + if (request.present? && request.referrer.present? && conference = /^\/conferences\/(\w+)\/register\/?$/.match(request.referrer.gsub(/^https?:\/\/.*?\//, '/'))) + @this_conference = Conference.find_by!(slug: conference[1]) + @banner_image = @this_conference.cover_url + template = 'conferences/email_confirm' end + + do_403 template end def locale_not_enabled!(locale = nil) locale_not_available!(locale) end + def locale_not_available + locale_not_available!(params[:locale]) + end + def locale_not_available!(locale = nil) set_default_locale params[:_original_action] = params[:action] @@ -210,11 +167,13 @@ class ApplicationController < LinguaFrancaApplicationController render 'application/locale_not_available', status: 404 end - rescue_from StandardError do |exception| - handle_exception exception + unless Rails.env.test? + rescue_from StandardError do |exception| + handle_exception exception - # show the error page - error_500 exception + # show the error page + error_500 exception + end end def handle_exception(exception) @@ -223,19 +182,28 @@ class ApplicationController < LinguaFrancaApplicationController logger.info exception.backtrace.join("\n") # send and email if this is production - suppress(Exception) do - UserMailer.send_mail(:error_report) do - [ - "An error has occurred in #{Rails.env}", - nil, - exception.to_s, - exception.backtrace.join("\n"), - request, - params, - current_user, - Time.now.strftime("%d/%m/%Y %H:%M") - ] - end if Rails.env.preview? || Rails.env.production? + if Rails.env.preview? || Rails.env.production? + suppress(Exception) do + requestHash = { + 'remote_ip' => arg.remote_ip, + 'uuid' => arg.uuid, + 'original_url' => arg.original_url, + 'env' => Hash.new + } + request.env.each do | key, value | + requestHash['env'][key.to_s] = value.to_s + end + send_mail(:error_report, + "An error has occurred in #{Rails.env}", + nil, + exception.to_s, + exception.backtrace.join("\n"), + requestHash, + params, + current_user, + Time.now.strftime("%d/%m/%Y %H:%M") + ) + end end # raise the error if we are in development so that we can debug it @@ -250,37 +218,6 @@ class ApplicationController < LinguaFrancaApplicationController end end - def generate_confirmation(user, url, expiry = nil) - if user.is_a? String - user = User.find_user(user) - - # if the user doesn't exist, just show them a 403 - do_403 unless user.present? - end - expiry ||= (Time.now + 12.hours) - session[:confirm_uid] = user.id - - unless user.locale.present? - user.locale = I18n.locale - user.save - end - - # send the confirmation email and make sure it get sent as quickly as possible - UserMailer.send_mail :email_confirmation do - EmailConfirmation.create(user_id: user.id, expiry: expiry, url: url) - end - end - - def user_settings - @conferences = Array.new - if logged_in? - Conference.all.each do | conference | - @conferences << conference if conference.host? current_user - end - end - @main_title = @page_title = 'page_titles.user_settings.Your_Account' - end - def contact @main_title = @page_title = 'page_titles.contact.Contact_Us' end @@ -290,32 +227,28 @@ class ApplicationController < LinguaFrancaApplicationController if params[:reason] == 'conference' - @conference.organizations.each do | org | - org.users.each do | user | - email_list << user.named_email + @conference.organizations.each do |org| + org.users.each do |user| + # email_list << user.named_email end end end - UserMailer.send_mail(:contact) do - [ + send_mail(:contact, current_user || params[:email], params[:subject], params[:message], email_list - ] - end + ) request_info = session['request_info'] || { 'request' => request, 'params' => params } - UserMailer.send_mail(:contact_details) do - [ + send_mail(:contact_details, current_user || params[:email], params[:subject], params[:message], request_info['request'], request_info['params'] - ] - end + ) redirect_to contact_sent_path end @@ -326,31 +259,8 @@ class ApplicationController < LinguaFrancaApplicationController render 'contact' end - def update_user_settings - return do_403 unless logged_in? - current_user.firstname = params[:name] - current_user.lastname = nil - current_user.languages = (params[:languages] || { I18n.locale.to_s => true }).keys - current_user.is_subscribed = params[:email_subscribe].present? - current_user.save - redirect_to settings_path - end - - def do_confirm(settings = nil) - settings ||= {:template => 'login_confirmation_sent'} + def confirm_user if params[:email] - # see if we've already sent the confirmation email and are just confirming - # the email address - if params[:token] - user = User.find_user(params[:email]) - confirm(user) - return - end - user = User.get(params[:email]) - - # generate the confirmation, send the email and show the 403 - referrer = params[:dest] || (request.present? && request.referer.present? ? request.referer.gsub(/^.*?\/\/.*?\//, '/') : settings_path) - generate_confirmation(params[:email], referrer) template = 'login_confirmation_sent' @page_title ||= 'page_titles.403.Please_Check_Email' @@ -365,75 +275,44 @@ class ApplicationController < LinguaFrancaApplicationController @banner_image ||= 'grafitti.jpg' @page_title ||= 'page_titles.403.Please_Login' - do_403 (template || 'translator_login') + do_403 template else do_404 end end - def confirm(uid = nil) - @confirmation = EmailConfirmation.find_by_token(params[:token]) - - unless @confirmation.present? - @token_not_found = true - return do_404 - end - - confirm_user = nil - if uid.is_a?(User) - confirm_user = uid - uid = confirm_user.id - end - # check to see if we were given a user id to confirm against - # if we were, make sure it was the same one - if (uid ||= (params[:uid] || session[:confirm_uid])) - if uid == @confirmation.user_id - session[:uid] = nil - confirm_user ||= User.find uid - auto_login(confirm_user) - else - @confirmation.delete - end - - redirect_to (@confirmation.url || '/') - return - end - - @banner_image = 'grafitti.jpg' - @page_title = 'page_titles.403.Please_Confirm_Email' - do_403 'login_confirm' + def error_404(args = {}) + params[:_original_action] = params[:action] + params[:action] = 'error-404' + @page_title = 'page_titles.404.Page_Not_Found' + @main_title = 'error.404.title' + super(args) end - def translator_request + def do_403(template = nil) @banner_image = 'grafitti.jpg' - @page_title = 'page_titles.403.Translator_Request_Sent' - do_403 'translator_request_sent' - end - - def user_logout - logout() - redirect_to (params[:url] || '/') - end + + unless current_user + @page_title = 'page_titles.403.Please_Login' + end - def find_user - user = User.find_user(params[:e]) + @template = template + @page_title ||= 'page_titles.403.Access_Denied' + @main_title ||= @page_title + params[:_original_action] = params[:action] + params[:action] = 'error-403' - if user.present? - return render json: { - name: user.name, - email: user.email, - exists: true - } - end - render json: { - name: I18n.t('user.not_found'), - email: nil, - exists: false - } + super(template) end - def login_user(u) - auto_login(u) + def error_500(exception = nil) + @page_title = 'page_titles.500.An_Error_Occurred' + @main_title = 'error.500.title' + params[:_original_action] = params[:action] + params[:action] = 'error-500' + @exception = exception + + super(exception) end def on_translation_change(object, data, locale, translator_id) @@ -443,8 +322,8 @@ class ApplicationController < LinguaFrancaApplicationController if object.respond_to?(:get_translators) object.get_translators(data, locale).each do |id, user| if user.id != current_user.id && user.id != translator_id - UserMailer.send_mail mailer, user.locale do - { :args => [object, data, locale, user, translator] } + LinguaFranca.with_locale user.locale do + send_mail(:send, mailer, object.id, data, locale, user.id, translator.id) end end end @@ -457,8 +336,8 @@ class ApplicationController < LinguaFrancaApplicationController if object.respond_to?(:get_translators) object.get_translators(data).each do |id, user| if user.id != current_user.id - UserMailer.send_mail mailer, user.locale do - { :args => [object, data, user, current_user] } + LinguaFranca.with_locale user.locale do + send_mail(:send, mailer, object.id, data, user.id, current_user.id) end end end @@ -469,23 +348,32 @@ class ApplicationController < LinguaFrancaApplicationController # log it logger.info "Missing translation found for: #{key}" - # send and email if this is production - begin - UserMailer.send_mail(:error_report) do - [ - "A missing translation found in #{Rails.env}", - "

A translation for #{key} in #{locale.to_s} was found. The text that was rendered to the user was:

#{str || 'nil'}
", - exception.to_s, - nil, - request, - params, - current_user, - Time.now.strftime("%d/%m/%Y %H:%M") - ] - end if Rails.env.preview? || Rails.env.production? - rescue Exception => exception2 - logger.info exception2.to_s - logger.info exception2.backtrace.join("\n") + # send an email if this is production + if Rails.env.preview? || Rails.env.production? + begin + requestHash = { + 'remote_ip' => arg.remote_ip, + 'uuid' => arg.uuid, + 'original_url' => arg.original_url, + 'env' => Hash.new + } + request.env.each do | key, value | + requestHash['env'][key.to_s] = value.to_s + end + send_mail(:error_report, + "A missing translation found in #{Rails.env}", + "

A translation for #{key} in #{locale.to_s} was found. The text that was rendered to the user was:

#{str || 'nil'}
", + exception.to_s, + nil, + requestHash, + params, + current_user.id, + Time.now.strftime("%d/%m/%Y %H:%M") + ) + rescue Exception => exception2 + logger.info exception2.to_s + logger.info exception2.backtrace.join("\n") + end end end @@ -545,8 +433,8 @@ class ApplicationController < LinguaFrancaApplicationController @schedule = {} day_1 = conference.start_date.wday - @workshop_blocks.each_with_index do | info, block | - info['days'].each do | block_day | + @workshop_blocks.each_with_index do |info, block| + info['days'].each do |block_day| day_diff = block_day.to_i - day_1 day_diff += 7 if day_diff < 0 day = (conference.start_date + day_diff.days).to_date @@ -559,9 +447,10 @@ class ApplicationController < LinguaFrancaApplicationController end end - @workshops.each do | workshop | + @workshops.each do |workshop| if workshop.block.present? block = @workshop_blocks[workshop.block['block'].to_i] + day_diff = workshop.block['day'].to_i - day_1 day_diff += 7 if day_diff < 0 day = (conference.start_date + day_diff.days).to_date @@ -573,7 +462,7 @@ class ApplicationController < LinguaFrancaApplicationController end end - @meals.each do | time, meal | + @meals.each do |time, meal| day = meal['day'].to_date time = meal['time'].to_f @schedule[day] ||= {} @@ -584,7 +473,7 @@ class ApplicationController < LinguaFrancaApplicationController @schedule[day][:times][time][:item] = meal end - @events.each do | event | + @events.each do |event| if event.present? && event.start_time.present? && event.end_time.present? day = event.start_time.midnight.to_date time = event.start_time.hour.to_f + (event.start_time.min / 60.0) @@ -598,13 +487,13 @@ class ApplicationController < LinguaFrancaApplicationController end @schedule = @schedule.sort.to_h - @schedule.each do | day, data | + @schedule.each do |day, data| @schedule[day][:times] = data[:times].sort.to_h end - @schedule.each do | day, data | + @schedule.each do |day, data| last_event = nil - data[:times].each do | time, time_data | + data[:times].each do |time, time_data| if last_event.present? @schedule[day][:times][last_event][:next_event] = time end @@ -613,8 +502,8 @@ class ApplicationController < LinguaFrancaApplicationController @schedule[day][:num_locations] = (data[:locations] || []).size end - @schedule.deep_dup.each do | day, data | - data[:times].each do | time, time_data | + @schedule.deep_dup.each do |day, data| + data[:times].each do |time, time_data| if time_data[:next_event].present? || time_data[:length] > 0.5 span = 0.5 length = time_data[:next_event].present? ? time_data[:next_event] - time : time_data[:length] @@ -631,7 +520,7 @@ class ApplicationController < LinguaFrancaApplicationController @schedule = @schedule.sort.to_h - @schedule.each do | day, data | + @schedule.each do |day, data| @schedule[day][:times] = data[:times].sort.to_h @schedule[day][:locations] ||= {} @@ -639,19 +528,19 @@ class ApplicationController < LinguaFrancaApplicationController @schedule[day][:locations][0] = :add if do_analyze || @schedule[day][:locations].empty? if do_analyze - data[:times].each do | time, time_data | + data[:times].each do |time, time_data| if time_data[:type] == :workshop && time_data[:item].present? && time_data[:item][:workshops].present? ids = time_data[:item][:workshops].keys - (0..ids.length).each do | i | + (0..ids.length).each do |i| if time_data[:item][:workshops][ids[i]].present? workshop_i = time_data[:item][:workshops][ids[i]][:workshop] conflicts = {} - (i+1..ids.length).each do | j | + (i+1..ids.length).each do |j| workshop_j = time_data[:item][:workshops][ids[j]].present? ? time_data[:item][:workshops][ids[j]][:workshop] : nil if workshop_i.present? && workshop_j.present? - workshop_i.active_facilitators.each do | facilitator_i | - workshop_j.active_facilitators.each do | facilitator_j | + workshop_i.active_facilitators.each do |facilitator_i| + workshop_j.active_facilitators.each do |facilitator_j| if facilitator_i.id == facilitator_j.id @schedule[day][:times][time][:status] ||= {} @schedule[day][:times][time][:item][:workshops][ids[j]][:status][:errors] << { @@ -673,7 +562,7 @@ class ApplicationController < LinguaFrancaApplicationController needs = JSON.parse(workshop_i.needs || '[]').map &:to_sym amenities = JSON.parse(location.amenities || '[]').map &:to_sym - needs.each do | need | + needs.each do |need| @schedule[day][:times][time][:item][:workshops][ids[i]][:status][:errors] << { name: :need_not_available, need: need, @@ -689,7 +578,7 @@ class ApplicationController < LinguaFrancaApplicationController # collect common interested users interests = [] - (0..ids.length).each do | j | + (0..ids.length).each do |j| workshop_j = time_data[:item][:workshops][ids[j]].present? ? time_data[:item][:workshops][ids[j]][:workshop] : nil if workshop_i.present? && workshop_j.present? && workshop_i.id != workshop_j.id interests = interests | workshop_j.interested.map { | u | u.user_id } @@ -744,4 +633,17 @@ class ApplicationController < LinguaFrancaApplicationController def html_value(value) return value.present? && ActionView::Base.full_sanitizer.sanitize(value).strip.present? ? value : nil end + + # send the confirmation email and make sure it get sent as quickly as possible + def send_confirmation(confirmation) + send_mail(:email_confirmation, confirmation.id) + end + + def send_mail(*args) + if Rails.env.preview? || Rails.env.production? + UserMailer.delay(queue: Rails.env.to_s).send(*args) + else + UserMailer.send(*args).deliver_now + end + end end diff --git a/app/controllers/conference_administration_controller.rb b/app/controllers/conference_administration_controller.rb index 733efd4..236c0c8 100644 --- a/app/controllers/conference_administration_controller.rb +++ b/app/controllers/conference_administration_controller.rb @@ -151,7 +151,7 @@ class ConferenceAdministrationController < ApplicationController }, data: [], } - @organizations.each do | org | + @organizations.each do |org| if org.present? address = org.locations.first @excel_data[:data] << { @@ -167,7 +167,7 @@ class ConferenceAdministrationController < ApplicationController } end end - return respond_to do | format | + return respond_to do |format| format.xlsx { render xlsx: :stats, filename: "organizations" } end end @@ -183,7 +183,7 @@ class ConferenceAdministrationController < ApplicationController if request.format.xlsx? logger.info "Generating stats.xls" - return respond_to do | format | + return respond_to do |format| format.xlsx { render xlsx: :stats, filename: "stats-#{DateTime.now.strftime('%Y-%m-%d')}" } end end @@ -194,7 +194,7 @@ class ConferenceAdministrationController < ApplicationController @donation_count = 0 @donations = 0 @food = { meat: 0, vegan: 0, vegetarian: 0, all: 0 } - @registrations.each do | r | + @registrations.each do |r| if view_context.registration_status(r) == :registered @completed_registrations += 1 @@ -223,7 +223,7 @@ class ConferenceAdministrationController < ApplicationController if request.format.xlsx? logger.info "Generating stats.xls" - return respond_to do | format | + return respond_to do |format| format.xlsx { render xlsx: :stats, filename: "stats-#{DateTime.now.strftime('%Y-%m-%d')}" } end else @@ -233,7 +233,7 @@ class ConferenceAdministrationController < ApplicationController @donation_count = 0 @donations = 0 @food = { meat: 0, vegan: 0, vegetarian: 0, all: 0 } - @registrations.each do | r | + @registrations.each do |r| if view_context.registration_status(r) == :registered @completed_registrations += 1 @@ -275,7 +275,7 @@ class ConferenceAdministrationController < ApplicationController }, data: [], } - @hosts.each do | id, host | + @hosts.each do |id, host| data = (host.housing_data || {}) host_data = { name: host.user.name, @@ -283,7 +283,7 @@ class ConferenceAdministrationController < ApplicationController email: host.user.email, phone: data['phone'], availability: data['availability'].present? && data['availability'][1].present? ? view_context.date_span(data['availability'][0].to_date, data['availability'][1].to_date) : '', - considerations: ((data['considerations'] || []).map { | consideration | view_context._"articles.conference_registration.host.considerations.#{consideration}" }).join(', '), + considerations: ((data['considerations'] || []).map { |consideration| view_context._"articles.conference_registration.host.considerations.#{consideration}" }).join(', '), empty: '', guests: { columns: [:name, :area, :email, :arrival_departure, :allergies, :food, :companion, :city], @@ -304,8 +304,8 @@ class ConferenceAdministrationController < ApplicationController } } - @housing_data[id][:guests].each do | space, space_data | - space_data.each do | guest_id, guest_data | + @housing_data[id][:guests].each do |space, space_data| + space_data.each do |guest_id, guest_data| guest = guest_data[:guest] if guest.present? companion = view_context.companion(guest) @@ -326,7 +326,7 @@ class ConferenceAdministrationController < ApplicationController @excel_data[:data] << host_data end - return respond_to do | format | + return respond_to do |format| format.xlsx { render xlsx: :stats, filename: "housing" } end end @@ -458,7 +458,7 @@ class ConferenceAdministrationController < ApplicationController }, data: [] } - User.AVAILABLE_LANGUAGES.each do | l | + User.AVAILABLE_LANGUAGES.each do |l| @excel_data[:keys]["language_#{l}".to_sym] = "languages.#{l.to_s}" end ConferenceRegistration.all_spaces.each do |s| @@ -468,7 +468,7 @@ class ConferenceAdministrationController < ApplicationController ConferenceRegistration.all_considerations.each do |c| @excel_data[:keys][c] = "articles.conference_registration.host.considerations.#{c.to_s}" end - @registrations.each do | r | + @registrations.each do |r| user = r.user_id ? User.where(id: r.user_id).first : nil if user.present? companion = view_context.companion(r) @@ -528,7 +528,7 @@ class ConferenceAdministrationController < ApplicationController last_day: availability[1].present? ? view_context.date(availability[1].to_date, :span_same_year_date_1) : '' } } - User.AVAILABLE_LANGUAGES.each do | l | + User.AVAILABLE_LANGUAGES.each do |l| can_speak = ((user.languages || []).include? l.to_s) data["language_#{l}".to_sym] = (can_speak ? (view_context._'articles.conference_registration.questions.bike.yes') : '') data[:raw_values]["language_#{l}".to_sym] = can_speak @@ -573,12 +573,12 @@ class ConferenceAdministrationController < ApplicationController first_day: view_context.conference_days_options_list(:before), last_day: view_context.conference_days_options_list(:after) } - User.AVAILABLE_LANGUAGES.each do | l | + User.AVAILABLE_LANGUAGES.each do |l| @column_options["language_#{l}".to_sym] = [ [(view_context._"articles.conference_registration.questions.bike.yes"), true] ] end - ConferenceRegistration.all_considerations.each do | c | + ConferenceRegistration.all_considerations.each do |c| @column_options[c.to_sym] = [ [(view_context._"articles.conference_registration.questions.bike.yes"), true] ] @@ -589,7 +589,7 @@ class ConferenceAdministrationController < ApplicationController def get_housing_data @hosts = {} @guests = {} - ConferenceRegistration.where(:conference_id => @this_conference.id).each do | registration | + ConferenceRegistration.where(:conference_id => @this_conference.id).each do |registration| if registration.can_provide_housing @hosts[registration.id] = registration elsif registration.housing.present? && registration.housing != 'none' @@ -603,11 +603,11 @@ class ConferenceAdministrationController < ApplicationController @housing_data = {} @hosts_affected_by_guests = {} - @hosts.each do | id, host | + @hosts.each do |id, host| @hosts[id].housing_data ||= {} @housing_data[id] = { guests: {}, space: {} } @hosts[id].housing_data['space'] ||= {} - @hosts[id].housing_data['space'].each do | s, size | + @hosts[id].housing_data['space'].each do |s, size| size = (size || 0).to_i @housing_data[id][:guests][s.to_sym] = {} @housing_data[id][:space][s.to_sym] = size @@ -616,7 +616,7 @@ class ConferenceAdministrationController < ApplicationController @guests_housed = 0 - @guests.each do | guest_id, guest | + @guests.each do |guest_id, guest| data = guest.housing_data || {} @hosts_affected_by_guests[guest_id] ||= [] @@ -651,7 +651,7 @@ class ConferenceAdministrationController < ApplicationController end companions = data['companions'] || [] - companions.each do | companion | + companions.each do |companion| user = User.find_user(companion) if user.present? reg = ConferenceRegistration.find_by( @@ -678,10 +678,10 @@ class ConferenceAdministrationController < ApplicationController end end - @hosts.each do | id, host | + @hosts.each do |id, host| host_data = host.housing_data - @hosts[id].housing_data['space'].each do | space, size | + @hosts[id].housing_data['space'].each do |space, size| # make sure the host isn't overbooked space = space.to_sym space_available = (size || 0).to_i @@ -755,7 +755,7 @@ class ConferenceAdministrationController < ApplicationController end else do_404 - return nil + return true end return false @@ -790,7 +790,7 @@ class ConferenceAdministrationController < ApplicationController end def admin_update_description - params[:info].each do | locale, value | + params[:info].each do |locale, value| @this_conference.set_column_for_locale(:info, locale, html_value(value)) end @this_conference.save @@ -811,7 +811,7 @@ class ConferenceAdministrationController < ApplicationController def admin_update_payment_message begin - params[:payment_message].each do | locale, value | + params[:payment_message].each do |locale, value| @this_conference.set_column_for_locale(:payment_message, locale, html_value(value)) end @this_conference.save @@ -883,7 +883,7 @@ class ConferenceAdministrationController < ApplicationController end user_changed = false - params.each do | key, value | + params.each do |key, value| case key.to_sym when :city if value.present? @@ -964,7 +964,7 @@ class ConferenceAdministrationController < ApplicationController do_404 end - return nil + return true end def admin_update_housing @@ -1004,7 +1004,7 @@ class ConferenceAdministrationController < ApplicationController do_404 end - return nil + return true end def admin_update_broadcast @@ -1014,36 +1014,32 @@ class ConferenceAdministrationController < ApplicationController @send_to = params[:send_to] @register_template = :administration if params[:button] == 'send' - view_context.broadcast_to(@send_to).each do | user | - UserMailer.send_mail :broadcast do - [ + view_context.broadcast_to(@send_to).each do |user| + send_mail(:broadcast, "#{request.protocol}#{request.host_with_port}", @subject, @body, - user, - @this_conference - ] - end + user.id, + @this_conference.id + ) end redirect_to administration_step_path(@this_conference.slug, :broadcast_sent) - return nil + return true elsif params[:button] == 'preview' @send_to_count = view_context.broadcast_to(@send_to).size @broadcast_step = :preview elsif params[:button] == 'test' @broadcast_step = :test - UserMailer.send_mail :broadcast do - [ + send_mail(:broadcast, "#{request.protocol}#{request.host_with_port}", @subject, @body, - current_user, - @this_conference - ] - end + current_user.id, + @this_conference.id + ) @send_to_count = view_context.broadcast_to(@send_to).size end - return true + return false end def admin_update_locations @@ -1106,14 +1102,14 @@ class ConferenceAdministrationController < ApplicationController end do_404 - return nil + return true end def admin_update_events case params[:button] when 'edit' redirect_to edit_event_path(@this_conference.slug, params[:id]) - return nil + return true when 'save' if params[:id].present? event = Event.find_by!(conference_id: @this_conference.id, id: params[:id]) @@ -1127,11 +1123,11 @@ class ConferenceAdministrationController < ApplicationController event.end_time = event.start_time + params[:time_span].to_f.hours # save translations - (params[:info] || {}).each do | locale, value | + (params[:info] || {}).each do |locale, value| event.set_column_for_locale(:title, locale, html_value(value), current_user.id) unless value = event._title(locale) end - (params[:title] || {}).each do | locale, value | + (params[:title] || {}).each do |locale, value| event.set_column_for_locale(:info, locale, value, current_user.id) unless value = event._info(locale) end @@ -1143,9 +1139,9 @@ class ConferenceAdministrationController < ApplicationController end do_404 - return nil + return true end - + def admin_update_workshop_times case params[:button] when 'save_block' @@ -1209,7 +1205,7 @@ class ConferenceAdministrationController < ApplicationController @location = params[:location] @event_location = @location.present? && @location.to_i > 0 ? EventLocation.find(@location.to_i) : nil - @workshops.sort { |a, b| a.title.downcase <=> b.title.downcase }.each do | workshop | + @workshops.sort { |a, b| a.title.downcase <=> b.title.downcase }.each do |workshop| @ordered_workshops[workshop.id] = workshop end @@ -1251,7 +1247,7 @@ class ConferenceAdministrationController < ApplicationController def get_empty(hash, keys) keys = [keys] unless keys.is_a?(Array) - keys.each do | key | + keys.each do |key| return key unless hash[key].present? end return nil diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index 803c77e..1a23fe1 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -2,6 +2,7 @@ require 'geocoder/calculations' require 'rest_client' class ConferencesController < ApplicationController + def list @page_title = 'articles.conferences.headings.Conference_List' @conference_list = { future: [], passed: [] } @@ -17,7 +18,7 @@ class ConferencesController < ApplicationController set_conference do_403 unless @this_conference.is_public || @this_conference.host?(current_user) - @workshops = Workshop.where(:conference_id => @conference.id) + @workshops = Workshop.where(:conference_id => @this_conference.id) if @this_conference.workshop_schedule_published @event_dlg = true @@ -59,7 +60,7 @@ class ConferencesController < ApplicationController steps = nil return do_404 unless registration_steps.present? - @register_template = :administration if params[:admin_step].present? + # @register_template = :administration if params[:admin_step].present? @errors = {} @warnings = [] @@ -72,8 +73,19 @@ class ConferencesController < ApplicationController @register_template = steps[steps.find_index($1.to_sym) - 1] elsif form_step == :paypal_confirm if @registration.present? && @registration.payment_confirmation_token == params[:confirmation_token] - @amount = PayPal!.details(params[:token]).amount.total - @registration.payment_info = {:payer_id => params[:PayerID], :token => params[:token], :amount => @amount}.to_yaml + if Rails.env.test? + @amount = params[:amount].to_f + info = YAML.load(@registration.payment_info) + info[:amount] = @amount + @registration.payment_info = info.to_yaml + else + @amount = PayPal!.details(params[:token]).amount.total + @registration.payment_info = { + payer_id: params[:PayerID], + token: params[:token], + amount: @amount + }.to_yaml + end @amount = (@amount * 100).to_i.to_s.gsub(/^(.*)(\d\d)$/, '\1.\2') @@ -86,7 +98,7 @@ class ConferencesController < ApplicationController info = YAML.load(@registration.payment_info) @amount = nil status = nil - if ENV['RAILS_ENV'] == 'test' + if Rails.env.test? status = info[:status] @amount = info[:amount] else @@ -114,7 +126,7 @@ class ConferencesController < ApplicationController case form_step when :confirm_email - return do_confirm + return confirm_email(params[:email], params[:token], register_path(@this_conference.slug)) when :contact_info if params[:name].present? && params[:name].gsub(/[\s\W]/, '').present? current_user.firstname = params[:name].squish @@ -188,18 +200,23 @@ class ConferencesController < ApplicationController amount = params[:amount].to_f if amount > 0 - @registration.payment_confirmation_token = ENV['RAILS_ENV'] == 'test' ? 'token' : Digest::SHA256.hexdigest(rand(Time.now.to_f * 1000000).to_i.to_s) - @registration.save - - host = "#{request.protocol}#{request.host_with_port}" - response = PayPal!.setup( - PayPalRequest(amount), - register_paypal_confirm_url(@this_conference.slug, :paypal_confirm, @registration.payment_confirmation_token), - register_paypal_confirm_url(@this_conference.slug, :paypal_cancel, @registration.payment_confirmation_token), - noshipping: true, - version: 204 - ) - if ENV['RAILS_ENV'] != 'test' + # we can't really test paypal integration in our tests, so we'll fake it instead + if Rails.env.test? + @registration.payment_confirmation_token = 'token' + @registration.payment_info = {amount: amount}.to_yaml + @registration.save! + redirect_to 'https://www.paypal.com' + else + @registration.payment_confirmation_token = Digest::SHA256.hexdigest(rand(Time.now.to_f * 1000000).to_i.to_s) + @registration.save! + pp = PayPal! + response = pp.setup( + PayPalRequest(amount), + register_paypal_confirm_url(@this_conference.slug, :paypal_confirm, @registration.payment_confirmation_token), + register_paypal_confirm_url(@this_conference.slug, :paypal_cancel, @registration.payment_confirmation_token), + noshipping: true, + version: 204 + ) redirect_to response.redirect_uri end return @@ -219,7 +236,7 @@ class ConferencesController < ApplicationController # this step is only completed if a payment has been made if form_step != :payment || (@registration.registration_fees_paid || 0) > 0 @registration.steps_completed ||= [] - @registration.steps_completed << form_step + @registration.steps_completed << form_step.to_s @registration.steps_completed.uniq! end end @@ -334,9 +351,7 @@ class ConferencesController < ApplicationController steps -= [:questions] # if this is a housing provider that is not attending the conference, remove these steps - if @registration.is_attending == 'n' - steps -= [:payment, :workshops] - end + steps -= [:payment, :workshops] if @registration.is_attending == 'n' else steps -= [:hosting] end @@ -344,8 +359,6 @@ class ConferencesController < ApplicationController steps -= [:hosting, :questions] end - steps += [:administration] if conference.host?(current_user) - return steps end @@ -394,11 +407,11 @@ class ConferencesController < ApplicationController def PayPalRequest(amount) Paypal::Payment::Request.new( - :currency_code => 'USD', # if nil, PayPal use USD as default - :description => 'Conference Registration', # item description - :quantity => 1, # item quantity - :amount => amount.to_f, # item value - :custom_fields => { + currency_code: 'USD', # if nil, PayPal use USD as default + description: 'Conference Registration', # item description + quantity: 1, # item quantity + amount: amount.to_f, # item value + custom_fields: { CARTBORDERCOLOR: "00ADEF", LOGOIMG: "https://en.bikebike.org/assets/bblogo-paypal.png" } diff --git a/app/controllers/oauths_controller.rb b/app/controllers/oauths_controller.rb deleted file mode 100644 index decccbb..0000000 --- a/app/controllers/oauths_controller.rb +++ /dev/null @@ -1,101 +0,0 @@ -class OauthsController < ApplicationController - skip_before_filter :require_login - - # sends the user on a trip to the provider, - # and after authorizing there back to the callback url. - def oauth - set_callback - session[:oauth_last_url] = params[:dest] || request.referer - login_at(auth_params[:provider]) - end - - def callback - set_callback - - user_info = (sorcery_fetch_user_hash auth_params[:provider] || {})[:user_info] - - email = user_info['email'] - fb_id = user_info['id'] - - # try to find the user by facebook id - user = User.find_by_fb_id(fb_id) - - # otherwise find the user by email - unless user.present? - # only look if the email address is present - user = User.find_user(email) if email.present? - end - - # create the user if the email is not recognized - if user.nil? - if email.present? - user = User.create(email: email, firstname: user_info['name'], fb_id: fb_id, locale: I18n.locale) - else - session[:oauth_update_user_info] = user_info - return redirect_to oauth_update_path - end - elsif user.fb_id.blank? || user.email.blank? - user.email = email - user.fb_id = fb_id - user.save! - end - - if user.present? && user.email.present? - # log in the user - auto_login(user) - end - - oauth_last_url = (session[:oauth_last_url] || home_path) - session.delete(:oauth_last_url) - redirect_to oauth_last_url - end - - def update - @main_title = @page_title = 'articles.conference_registration.headings.email_confirm' - @errors = { email: flash[:error] } if flash[:error].present? - render 'application/update_user' - end - - def save - unless params[:email].present? - return redirect_to oauth_update_path - end - - user = User.find_user(params[:email]) - - if user.present? - flash[:error] = :exists - return redirect_to oauth_update_path - end - - # create the user - user = User.new(email: params[:email], firstname: session[:oauth_update_user_info]['name'], fb_id: session[:oauth_update_user_info]['id']) - user.save! - - # log in - auto_login(user) - - # clear out the session - oauth_last_url = (session[:oauth_last_url] || home_path) - session.delete(:oauth_last_url) - session.delete(:oauth_update_user_info) - - # go to our final destination - redirect_to oauth_last_url - end - - private - def auth_params - params.permit(:code, :provider) - end - - def set_callback - # force https for prod - protocol = Rails.env.preview? || Rails.env.production? ? 'https://' : request.protocol - - # build the callback url - Sorcery::Controller::Config.send(params[:provider]).callback_url = - "#{protocol}#{request.env['HTTP_HOST']}/oauth/callback?provider=facebook" - end - -end \ No newline at end of file diff --git a/app/controllers/workshops_controller.rb b/app/controllers/workshops_controller.rb index 04b0409..ea7a64b 100644 --- a/app/controllers/workshops_controller.rb +++ b/app/controllers/workshops_controller.rb @@ -1,10 +1,10 @@ - class WorkshopsController < ApplicationController + def workshops set_conference set_conference_registration! - @workshops = Workshop.where(:conference_id => @this_conference.id) - @my_workshops = Workshop.joins(:workshop_facilitators).where(:workshop_facilitators => {:user_id => current_user.id}, :conference_id => @this_conference.id) + @workshops = Workshop.where(conference_id: @this_conference.id) + @my_workshops = @workshops.select { |w| w.active_facilitator?(current_user) } render 'workshops/index' end @@ -58,7 +58,7 @@ class WorkshopsController < ApplicationController @is_translating ||= false if @is_translating - return do_404 if @translation.to_s == @workshop.locale.to_s || !I18n.backend.enabled_locales.include?(@translation.to_s) + return do_404 unless @translation.to_s != @workshop.locale.to_s && LinguaFranca.locale_enabled?(@translation.to_sym) return do_403 unless @workshop.can_translate?(current_user, @translation) @title = @workshop._title(@translation) diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb new file mode 100644 index 0000000..a9b1979 --- /dev/null +++ b/app/helpers/admin_helper.rb @@ -0,0 +1,163 @@ +module AdminHelper + + def administration_steps + { + info: [:administrators, :dates, :description, :poster], + payment: [:payment_message, :suggested_amounts, :paypal], + registration: [:registration_status, :stats, :registrations, :broadcast], + housing: [:providers, :housing], + events: [:locations, :meals, :events], + schedule: [:workshop_times, :schedule, :publish_schedule] + } + end + + def administration_sub_steps + { + location_edit: :locations, + event_edit: :events + } + end + + def get_administration_group(administration_step) + admin_step = administration_step.to_sym + admin_step = administration_sub_steps[admin_step] if administration_sub_steps[admin_step].present? + administration_steps.each do | group, steps | + steps.each do | step | + return group if step == admin_step + end + end + + return nil + end + + def broadcast_to(to, conference = nil) + conference ||= @this_conference || @conference + + users = [] + + case to.to_sym + when :registered + ConferenceRegistration.where(conference_id: conference.id).each do | r | + users << r.user if ((r.steps_completed || []).include? 'questions') && r.user.present? && r.is_attending != 'n' + end + when :pre_registered + ConferenceRegistration.where(conference_id: conference.id).each do | r | + users << r.user if registration_status(r) == :preregistered && r.is_attending != 'n' + end + when :workshop_facilitators + user_hash = {} + Workshop.where(conference_id: conference.id).each do | w | + w.active_facilitators.each do | u | + user_hash[u.id] ||= u if u.present? + end + end + users = user_hash.values + when :unregistered + ConferenceRegistration.where(conference_id: conference.id).each do | r | + users << r.user if registration_status(r) == :unregistered && r.is_attending != 'n' + end + when :housing_providers + ConferenceRegistration.where(conference_id: conference.id, can_provide_housing: true).each do | r | + users << r.user if r.user.present? + end + when :guests + ConferenceRegistration.where(conference_id: conference.id, housing: 'house').each do | r | + users << r.user if r.user.present? && r.is_attending != 'n' + end + when :all + User.all.each do | u | + users << u if u.present? && (u.is_subscribed.nil? || u.is_subscribed) + end + end + + return users + end + + def get_housing_match(host, guest, space) + housing_data = guest.housing_data || [] + + if housing_data['host'].present? + if housing_data['host'] == host.id + return space == housing_data['space'] ? :selected_space : :other_space + end + + return :other_host + end + + if space_matches?(space, guest.housing) && available_dates_match?(host, guest) + return :good_match + end + + return :bad_match + end + + def get_workshop_match(workshop, day, block, location) + if workshop.event_location_id.present? && workshop.present? + if (Date.parse params[:day]).wday == workshop.block['day'] && block == workshop.block['block'].to_i + return :selected_space + end + + if location.present? && location.id == workshop.event_location_id + return :other_space + end + + return :other_host + end + + if location.present? + needs = JSON.parse(workshop.needs || '[]').map &:to_sym + amenities = JSON.parse(location.amenities || '[]').map &:to_sym + + if (needs & amenities).length < needs.length + return :bad_match + end + end + + (((((@schedule[@day] || {})[:times] || {})[@time] || {})[:item] || {})[:workshops] || {}).each do | l, w | + if w[:workshop].id != workshop.id + f_a = w[:workshop].active_facilitators.map { | f | f.id } + f_b = workshop.active_facilitators.map { | f | f.id } + if (f_a & f_b).present? + return :bad_match + end + end + end + + # housing_data = guest.housing_data || [] + + # if housing_data['host'].present? + # if housing_data['host'] == host.id + # return space == housing_data['space'] ? :selected_space : :other_space + # end + + # return :other_host + # end + + # if space_matches?(space, guest.housing) && available_dates_match?(host, guest) + # return :good_match + # end + + # return :bad_match + return :good_match + end + + def space_matches?(host_space, guest_space) + return false unless host_space.present? && guest_space.present? + + if host_space.to_s == 'bed_space' || host_space.to_s == 'floor_space' + return guest_space.to_s == 'house' + end + + return host_space.to_s == 'tent_space' && guest_space.to_s == 'tent' + end + + def available_dates_match?(host, guest) + return false unless host.housing_data['availability'].present? && host.housing_data['availability'][1].present? + if host.housing_data['availability'][0] <= guest.arrival && + host.housing_data['availability'][1] >= guest.departure + return true + end + + return false + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 56ca8a7..563131d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,521 +1,14 @@ -require 'redcarpet' +require 'lingua_franca/form_helper' module ApplicationHelper + include PageHelper include RegistrationHelper - - @@keyQueue = nil - @@translationsOnThisPage = nil - @@lastTranslation = nil - @@allTranslations = nil - @@no_banner = true - @@banner_attribution_details = nil - @@banner_image = nil - @@has_content = true - @@front_page = false - @@body_class = nil - @@test_location = nil - - def init_vars - @@keyQueue = nil - @@no_banner = true - @@banner_attribution_details = nil - @@banner_image = nil - @@has_content = true - @@front_page = false - @@body_class = nil - end - - def this_is_the_front_page - @@front_page = true - end - - def header_is_fixed - @fixed_header = true - end - - def is_header_fixed? - @fixed_header ||= false - end - - def is_this_the_front_page? - return @@front_page - end - - def header_classes - classes = Array.new - classes << 'fixed' if is_header_fixed? - return classes - end - - def ThereAreTranslationsOnThisPage? - @@translationsOnThisPage - end - - def get_all_translations - @@allTranslations - end - - def title(page_title) - content_for(:title) { page_title.to_s } - end - - def description(page_description) - content_for(:description) { page_description.to_s } - end - - def banner_image(banner_image, name: nil, id: nil, user_id: nil, src: nil) - @@no_banner = false - @@banner_image = banner_image - if (name || id || user_id || src) - @@banner_attribution_details = {:name => name, :id => id, :user_id => user_id, :src => src} - end - content_for(:banner_image) { banner_image.to_s } - end - - def banner_attrs(banner_image) - @@no_banner = false - if banner_image.length > 0 - @@banner_image = banner_image - return {style: 'background-image: url(' + banner_image + ');', class: 'has-image' } - end - {class: 'no-image'} - end - - def has_banner? - !@@no_banner - end - - def has_content? - @@has_content - end - - def has_no_content - @@has_content = false - end - - def banner_title(banner_title) - @@no_banner = false - content_for(:banner) { ('

' + banner_title.to_s + '

').html_safe } - end - - def add_stylesheet(sheet) - @stylesheets ||= [] - @stylesheets << sheet unless @stylesheets.include?(sheet) - end - - def stylesheets - html = '' - Rack::MiniProfiler.step('inject_css') do - html += inject_css! - end - (@stylesheets || []).each do |css| - Rack::MiniProfiler.step("inject_css #{css}") do - html += inject_css! css.to_s - end - end - html += stylesheet_link_tag 'i18n-debug' if request.params['i18nDebug'] - return html.html_safe - end - - def add_inline_script(script) - @_inline_scripts ||= [] - script = Rails.application.assets.find_asset("#{script.to_s}.js").to_s - @_inline_scripts << script unless @_inline_scripts.include?(script) - end - - def inline_scripts - return '' unless @_inline_scripts.present? - "".html_safe - end - - def banner_attribution - if @@banner_image && @@banner_attribution_details - src = @@banner_attribution_details[:src] - attribution = '
' - if src == 'panoramio' - attribution += '© ' + - _('Banner_image_provided_by_panoramio_user') + - ' ' + @@banner_attribution_details[:name] + '' + - '' + _('Photos_provided_by_Panoramio_are_under_the_copyright_of_their_owners') + '' - end - attribution += '
' - attribution.html_safe - end - end - - def dom_ready(&block) - content_for(:dom_ready, &block) - end - - def body_class(c) - @@body_class ||= Array.new - @@body_class << (c.is_a?(Array) ? c.join(' ') : c) - end - - def page_style - classes = Array.new - - classes << 'has-translations' if ThereAreTranslationsOnThisPage? - classes << 'no-content' unless @@has_content - classes << 'has-banner-image' if @@banner_image - classes << @@body_class.join(' ') if @@body_class - classes << 'fixed-banner' if is_header_fixed? - - if params[:controller] - classes << params[:action] - unless params[:controller] == 'application' - classes << params[:controller] - - if params[:action] - classes << "#{params[:controller]}-#{params[:action]}" - end - end - end - return classes - end - - def yield_or_default(section, default = '') - content_for?(section) ? content_for(section) : default - end - - def _translate_me(translation) - @@translationsOnThisPage = true - datakeys = '' - translation['vars'].each { |key, value| datakeys += ' data-var-' + key.to_s + '="' + value.to_s.gsub('"', '"') + '"' } - ('' + (translation['html'] || translation['untranslated']) + '').to_s.html_safe - end - - def _do_translate(key, vars, behavior, behavior_size, locale) - translation = {'key' => key, 'lang' => '0', 'vars' => vars} - v = vars.dup - begin - v[:raise] = true - options = {:raise => true} - if locale - options[:locale] = locale.to_sym - end - translation['untranslated'] = I18n.translate(key, v, options) - translation['lang'] = locale.to_s - translation['is_translated'] = true - - hash = Hash.new - translations = Translation.where(["locale = ? AND key LIKE ?", locale.to_s, key + '%']).take(6).each { |o| hash[o.key] = o.value } - translation['translated'] = hash.to_json.gsub('"', '"') - rescue I18n::MissingTranslationData - default_translation = I18n::MissingTranslationExceptionHandler.note(key, behavior, behavior_size) - translation['untranslated'] = default_translation - end - return translation - end - - def _can_translate?() - false - end - - def off_screen(text, id = nil) - content_tag(:span, text.html_safe, id: id, class: 'screen-reader-text') - end - - def url_for_locale(locale, url = nil) - return url unless locale.present? - - unless url.present? - new_params = params.merge({action: (params[:_original_action] || params[:action])}) - new_params.delete(:_original_action) - - if Rails.env.development? || Rails.env.test? - return url_for(new_params.merge({lang: locale.to_s})) - end - - subdomain = Rails.env.preview? ? "preview-#{locale.to_s}" : locale.to_s - return url_for(new_params.merge(host: "#{subdomain}.bikebike.org")) - end - - return url if Rails.env.development? || Rails.env.test? - return "https://preview-#{locale.to_s}.bikebike.org#{url}" if Rails.env.preview? - "https://#{locale.to_s}.bikebike.org#{url}" - end - - def registration_steps(conference = @conference) - { - pre: [:policy, :contact_info, :workshops], - open: [:policy, :contact_info, :questions, :hosting, :payment, :workshops] - }[@this_conference.registration_status] - end - - def registration_status(registration) - return :unregistered if registration.nil? - return registration.status - end - - def sortable(objects, id = 'id', url: nil, &block) - result = '
    ' - objects.each_index do |i| - @this = objects[i] - result += '
  • ' - result += hidden_field_tag (id + "[#{i}]"), objects[i][id] - result += hidden_field_tag ('position' + "[#{i}]"), i, :class => 'sortable-position' - if block_given? - result += capture(objects[i], &block) - end - result += '
  • ' - end - result += '' - result.html_safe - end - - def tabs object, tabs - type = object.class.name.downcase - tab_list = '' - - tabs.each do |tab| - link = nil - if self.respond_to?(type + '_' + tab.to_s + '_path') - link = self.send(type + '_' + tab.to_s + '_path', object) - elsif self.respond_to?(tab.to_s + '_' + type + '_path') - link = self.send(tab.to_s + '_' + type + '_path', object) - end - - c = ['tab', 'tab-' + (link ? tab.to_s : 'show')] - if params[:action] == tab.to_s - c << 'current' - end - link_html = '' - if tab.is_a?(Hash) - func = tab.keys[0] - val = tab[func] - args = val ? (val.is_a?(Array) ? (val.collect { |v| object[v] } ) : [object[val]] ) : nil - - link_html = link_to func.to_s.gsub(/_path$/, ''), args ? self.send(func, args) : self.send(func), :class => c - else - #x - #link_html = link_to tab, link || object, :class => c - end - tab_list += link_html - end - ('').html_safe - end - - def tabs! - object = nil - tabs = nil - case params[:controller] - when 'organizations' - object = @organization - tabs = OrganizationsHelper::TABS - when 'conferences' - object = @conference - tabs = ConferencesHelper::TABS - when 'workshops' - object = [@conference, @workshop] - tabs = WorkshopsHelper::TABS - end - - if object && tabs - return tabs object, tabs - end - end - - def sub_tabs object, tabs - type = object.class.name.downcase - tab_list = '' - - tabs.each do |tab| - link = nil - if self.respond_to?(type + '_' + tab.to_s + '_path') - link = self.send(type + '_' + tab.to_s + '_path', object) - elsif self.respond_to?(tab.to_s + '_' + type + '_path') - link = self.send(tab.to_s + '_' + type + '_path', object) - end - - c = ['sub-tab', 'sub-tab-' + (link ? tab.to_s : 'show')] - if current_page?(link) - c << 'current' - end - tab_list += link_to tab, link || object, :class => c - end - ('').html_safe - end - - def sub_tabs! - object = nil - tabs = nil - case params[:controller] - when 'organizations' - object = @organization - tabs = OrganizationsHelper::SUB_TABS - when 'conferences' - object = @conference - tabs = ConferencesHelper::SUB_TABS - end - - if object && tabs - return sub_tabs object, tabs - end - end - - def m(*args) - _(*args) { |t| - markdown(t) - } - end - - def markdown(object, attribute = nil) - return '' unless object - content = attribute ? object.send(attribute.to_s) : object - @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML.new({ - filter_html: true, - hard_wrap: true, - space_after_headers: true, - fenced_code_blocks: true, - link_attributes: { target: "_blank" } - }), { - autolink: true, - disable_indented_code_blocks: true, - superscript: true - }) - @markdown.render(content).html_safe - end - - def paragraph(object, attribute = nil) - return '' unless object - content = attribute ? object.send(attribute.to_s) : object - result = '' - if content =~ /<(p|span|h\d|div)[^>]*>/ - result = content.gsub(/\s*(style|class|id|width|height|font)=\".*?\"/, '') - .gsub(/ /, ' ') - .gsub(/<(\/)?\s*h\d\s*>/, '<\1h3>') - .gsub(/

    (.*?)\s*()+/, '

    \1

    ') - .gsub(/]*>\s*(.*?)\s*<\/span>/, '\1') - .gsub(/

    \s*<\/p>/, '') - .gsub(/<(\/)?div>/, '<\1p>') - if !(result =~ /]*>/) - result = '

    ' + result + '

    ' - end - else - result = markdown(object, attribute) - end - result.html_safe - end - - def form_field(f, response = nil) - id = 'field_' + f.id.to_s - html = p(f, 'title') - - options = JSON.parse(f.options) - if f.field_type == 'multiple' - if f.help - html += ('
    ' + p(f, 'help') + '
    ').html_safe - end - - opts = Hash.new - options['options'].split(/\s*\n\s*/).each do |value| - kv = value.split(/\s*\|\s*/, 2) - opts[kv[0]] = kv[1] - end - - val = response ? ActiveSupport::JSON.decode(response.data) : Hash.new - - if f.repeats? - is_array = f.is_array? - opts.each do |key, value| - n = (id + (is_array ? ('_' + key) : '')).to_sym - v = is_array ? (val ? val[key] : nil) : key - o = {:label => value} - if f.required - options[:required] = true - end - html += _form_field(options['selection_type'], n, v, o) - end - else - html += _form_field(options['selection_type'], id.to_sym, options_for_select(opts.invert, val), {}) - end - else - #html += field(id.to_sym, options['input_type'] + '_tag', label: false, placeholder: f.help, value: response ? ActiveSupport::JSON.decode(response.data) : nil, required: f.required) - opts = {label: false, placeholder: f.help && f.help.length > 0 ? f.help : false} - if f.required - opts[:required] = true - end - html += _form_field(options['input_type'], id.to_sym, response ? ActiveSupport::JSON.decode(response.data) : nil, opts) - end - - html.html_safe - end - - def t(*a) - _(*a) - end - - def lookup_ip - if request.remote_ip == '127.0.0.1' || request.remote_ip == '::1' - session['remote_ip'] || (session['remote_ip'] = open("http://checkip.dyndns.org").first.gsub(/^.*\s([\d\.]+).*$/s, '\1').gsub(/[^\.\d]/, '')) - else - request.remote_ip - end - end - - def get_remote_location - Geocoder.search(session['remote_ip'] || (session['remote_ip'] = open("http://checkip.dyndns.org").first.gsub(/^.*\s([\d\.]+).*$/s, '\1').gsub(/[^\.\d]/, '')), language: 'en').first - end - - def lookup_ip_location - begin - if is_test? && ApplicationController::get_location.present? - Geocoder.search(ApplicationController::get_location, language: 'en').first - elsif request.remote_ip == '127.0.0.1' || request.remote_ip == '::1' - get_remote_location - else - request.location || get_remote_location - end - rescue - nil - end - end - - def hash_to_html_attributes(hash, prefix = '') - attributes = '' - if hash - hash.each { |k,v| - k = k.to_s - if v - if v.is_a?(Hash) - attributes += hash_to_html_attributes(v, 'data-') - else - attributes += " #{k}=\"" + (v.is_a?(Array) ? v.join(' ') : v) + '"' - end - end - } - end - attributes - end - - def icon(id, attributes = nil) - ('').html_safe - end - - def static_map(location, zoom, width, height) - require 'fileutils' - local_file_name = "#{location}-#{width}x#{height}z#{zoom}.png" - file = File.join("public", "maps/#{local_file_name}") - FileUtils.mkdir_p("public/maps") unless File.directory?("public/maps") - if !File.exist?(file) - url = "https://maps.googleapis.com/maps/api/staticmap?center=#{location}&zoom=#{zoom}&size=#{width}x#{height}&maptype=roadmap&markers=size:small%7C#{location}&key=AIzaSyAH7U8xUUb8IwDPy1wWuYGprzxf4E1Jj4o" - require 'open-uri' - open(file, 'wb') do |f| - f << open(url).read - end - end - - cdn("/maps/#{local_file_name}") - end - - def cdn(file) - (Rails.application.config.action_controller.asset_host || '') + file - end + include FormHelper + include I18nHelper + include WidgetsHelper + include GeocoderHelper + include TableHelper + include AdminHelper def is_production? Rails.env == 'production' || Rails.env == 'preview' @@ -525,1705 +18,7 @@ module ApplicationHelper Rails.env == 'test' end - def subdomain - request.env['SERVER_NAME'].gsub(/^(\w+)\..*$/, '\1') - end - - def is_test_server? - subdomain == 'test' - end - - def rand_hash(length = 16, model = nil, field = nil) - if field - hash = rand_hash(length) - while !model.to_s.to_s.singularize.classify.constantize.find_by(field => hash).nil? - hash = rand_hash(length) - end - end - rand(36**length).to_s(36) - end - - def get_panoramio_image(location) - if is_test? - params[:image] = 'panoramio.jpg' - params[:attribution_id] = 1234 - params[:attribution_user_id] = 5678 - params[:attribution_name] = 'Some Guy' - params[:attribution_src] = 'panoramio' - return params - end - - location = location.city + ', ' + (location.territory ? location.territory + ' ' : '') + location.country - $panoramios ||= Hash.new - $panoramios[location] ||= 0 - $panoramios[location] += 1 - result = Geocoder.search(location, language: 'en').first - if result - points = Geocoder::Calculations.bounding_box([result.latitude, result.longitude], 5, { :unit => :km }) - options = {:set => :public, :size => :original, :from => 0, :to => 20, :mapfilter => false, :miny => points[0], :minx => points[1], :maxy => points[2], :maxx => points[3]} - url = 'http://www.panoramio.com/map/get_panoramas.php?' + options.to_query - response = JSON.parse(open(url).read) - response['photos'].each { |img| - if img['width'].to_i > 980 - if Organization.find_by(:cover_attribution_id => img['photo_id'], :cover_attribution_src => 'panoramio').nil? && Conference.find_by(:cover_attribution_id => img['photo_id'], :cover_attribution_src => 'panoramio').nil? - params[:image] = img['photo_file_url'] - params[:attribution_id] = img['photo_id'] - params[:attribution_user_id] = img['owner_id'] - params[:attribution_name] = img['owner_name'] - params[:attribution_src] = 'panoramio' - return params - end - end - } - end - return nil - end - - def get_secure_info(name) - YAML.load(File.read(Rails.root.join("config/#{name.to_s}.yml")))[Rails.env].symbolize_keys - end - - def location(location, locale = I18n.locale) - return nil if location.blank? - - city = nil - region = nil - country = nil - if location.is_a?(Location) || location.is_a?(City) - country = location.country - region = location.territory - city = location.city - elsif location.data.present? && location.data['address_components'].present? - component_map = { - 'locality' => :city, - 'administrative_area_level_1' => :region, - 'country' => :country - } - location.data['address_components'].each do | component | - types = component['types'] - country = component['short_name'] if types.include? 'country' - region = component['short_name'] if types.include? 'administrative_area_level_1' - city = component['long_name'] if types.include? 'locality' - end - else - country = location.data['country_code'] - region = location.data['region_code'] - city = location.data['city'] - end - - # we need cities for our logic, don't let this continue if we don't have one - return nil unless city.present? - - hash = Hash.new - region_translation = region.present? && country.present? ? _("geography.subregions.#{country}.#{region}", locale: locale) : '' - country_translation = country.present? ? _("geography.countries.#{country}", locale: locale) : '' - hash[:city] = _!(city) if city.present? - hash[:region] = region_translation if region_translation.present? - hash[:country] = country_translation if country_translation.present? - - # return the formatted location or the first value if we only have one value - return hash.length > 1 ? _("geography.formats.#{hash.keys.join('_')}", locale: locale, vars: hash) : hash.values.first - end - - def location_link(location) - return '' unless location.present? && location.address.present? - content_tag(:a, (_!location.address), href: "http://www.google.com/maps/place/#{location.latitude},#{location.longitude}") - end - - def same_city?(location1, location2) - return false unless location1.present? && location2.present? - - location1 = location(location1) unless location1.is_a?(String) - location2 = location(location2) unless location2.is_a?(String) - - location1.eql? location2 - end - - def show_errors(field, value = nil) - return '' unless @errors && @errors[field].present? - - error_txt = _"errors.messages.fields.#{field.to_s}.#{@errors[field]}", :s, vars: { value: value } - - "
    #{error_txt}
    ".html_safe - end - - def nav_link(link, title = nil, class_name = nil) - if title.nil? && link.is_a?(Symbol) - title = link - link = send("#{link.to_s}_path") - end - if class_name.nil? && title.is_a?(Symbol) - class_name = title - end - title = _"page_titles.#{title.to_s.titlecase.gsub(/\s/, '_')}" - classes = [] - classes << class_name if class_name.present? - classes << "strlen-#{strip_tags(title).length}" - classes << 'current' if request.fullpath.start_with?(link.gsub(/^(.*?)\/$/, '\1')) - link_to "#{title}".html_safe, link, :class => classes - end - - def date(date, format = :long) - I18n.l(date.is_a?(String) ? Date.parse(date) : date, :format => format) - end - - def time(time, format = :short) - if time.is_a?(String) - time = Date.parse(time) - elsif time.is_a?(Float) || time.is_a?(Integer) - time = DateTime.now.midnight + time.hours - end - - I18n.l(time, :format => format) - end - - def date_span(date1, date2) - key = 'same_month' - if date1.year != date2.year - key = 'different_year' - elsif date1.month != date2.month - key = 'same_year' - end - d1 = I18n.l(date1.to_date, format: "span_#{key}_date_1".to_sym) - d2 = I18n.l(date2.to_date, format: "span_#{key}_date_2".to_sym) - _('date.date_span', vars: {:date_1 => d1, :date_2 => d2}) - end - - def time_length(length) - hours = length.to_i - minutes = ((length - hours) * 60).to_i - hours = hours > 0 ? (I18n.t 'datetime.distance_in_words.x_hours', count: hours) : nil - minutes = minutes > 0 ? (I18n.t 'datetime.distance_in_words.x_minutes', count: minutes) : nil - return hours.present? ? (minutes.present? ? (I18n.t 'datetime.distance_in_words.x_and_y', x: hours, y: minutes) : hours) : minutes - end - - def hour_span(time1, time2) - (time2 - time1) / 3600 - end - - def hours(time1, time2) - time_length hour_span(time1, time2) - end - def generate_confirmation(user, url, expiry = nil) ApplicationController::generate_confirmation(user, url, expiry) end - - def money(amount) - return _!('$0.00') if amount == 0 - _!((amount * 100).to_i.to_s.gsub(/^(.*)(\d\d)$/, '$\1.\2')) - end - - def percent(p) - return _!('0.00%') if p == 0 - _!((p * 10000).to_i.to_s.gsub(/^(.*)(\d\d)$/, '\1.\2%')) - end - - def data_set(header_type, header_key, attributes = {}, &block) - raw_data_set(header_type, _(header_key), attributes, &block) - end - - def raw_data_set(header_type, header, attributes = {}, &block) - attributes[:class] = attributes[:class].split(' ') if attributes[:class].is_a?(String) - attributes[:class] = [attributes[:class].to_s] if attributes[:class].is_a?(Symbol) - attributes[:class] ||= [] - attributes[:class] << 'data-set' - content_tag(:div, attributes) do - content_tag(header_type, header, class: 'data-set-key') + - content_tag(:div, class: 'data-set-value', &block) - end - end - - def conference_days_options(conference = nil) - conference ||= @this_conference || @conference - return [] unless conference - - dates = [] - day = conference.start_date - 7.days - last_day = conference.end_date + 7.days - - while day <= last_day - dates << day - day += 1.day - end - - return dates - end - - def conference_days_options_list(period, conference = nil, format = nil) - conference ||= @this_conference || @conference - return [] unless conference - - days = [] - - conference_days_options(conference).each do |day| - belongs_to_periods = [] - belongs_to_periods << :before if day <= conference.start_date - belongs_to_periods << :after if day >= conference.end_date - belongs_to_periods << :before_plus_one if day <= (conference.start_date + 1.day) - belongs_to_periods << :after_minus_one if day >= (conference.end_date - 1.day) - belongs_to_periods << :during if day >= conference.start_date && day <= conference.end_date - days << [date(day.to_date, format || :span_same_year_date_1), day.to_date] if belongs_to_periods.include?(period) - end - return days - end - - def registration_status_options_list(conference = nil) - conference ||= @this_conference || @conference - return [] unless conference - - options = Array.new - [:closed, :pre, :open].each do | opt | - options << [(_"forms.labels.generic.registration_statuses.#{opt}"), opt] - end - - return options - end - - def month_select(value = nil, args = {}) - options = (1..12).to_a.map { |month| [ (I18n.t "date.#{args[:format] || 'month_names'}")[month], month ] } - selectfield args[:name] || :month, value, options, args - end - - def month_day_select(value = nil, args = {}) - options = (1..31).to_a.map { |day| [ day, day ] } - selectfield args[:name] || :month_day, value, options, args - end - - def day_select(value = nil, args = {}) - selectfield :day, value, conference_days_options_list(:during, nil, args[:format]), args - end - - def hour_select(value = nil, args = {}, start_time = 8, end_time = 23.5, step = 0.5) - time = start_time - times = [] - while time <= end_time - times << [time(DateTime.now.midnight + time.hours), time] - time += step - end - selectfield :time, value, times, args - end - - def length_select(value = nil, args = {}, min_length = 0.5, max_length = 6, step = 0.5) - length = min_length - lengths = [] - while length <= max_length - lengths << [time_length(length), length] - length += step - end - selectfield :time_span, value, lengths, args - end - - def contact_reason_select - reasons = [] - [:website, :conference].each do | reason | - reasons << [ _("forms.labels.generic.reasons.#{reason.to_s}"), reason ] - end - [['Something about the website', :website]] - selectfield :reason, nil, reasons, required: true, heading: 'articles.contact.headings.reason', label: false, full: true - end - - def block_select(value = nil, args = {}) - blocks = {} - @workshop_blocks.each_with_index do | info, block | - info['days'].each do | day | - blocks[(day.to_i * 10) + block] = [ "#{(I18n.t 'date.day_names')[day.to_i]} Block #{block + 1}", "#{day}:#{block}" ] - end - end - selectfield :workshop_block, value, blocks.sort.to_h.values, args - end - - def location_select(value = nil, args = {}) - locations = [] - if @this_conference.event_locations.present? - @this_conference.event_locations.each do | location | - locations << [ location.title, location.id ] unless ((args[:invalid_locations] || []).include? location.id) - end - end - selectfield :event_location, value, locations, args - end - - def location_name(id) - begin - location = EventLocation.find(id) - rescue - return '' - end - return '' unless location.present? - return location.title - end - - def host_options_list(hosts) - options = [[nil, nil]] - hosts.each do | id, registration | - options << [registration.user.name, id] - end - return options - end - - def registration_step_menu - steps = current_registration_steps(@registration) - return '' unless steps.present? && steps.length > 1 - - pre_registration_steps = '' - post_registration_steps = '' - post_registration = false - - steps.each do | step | - text = _"articles.conference_registration.headings.#{step[:name].to_s}" - - if step[:name] == :workshops - post_registration = true - end - - h = content_tag :li, class: [step[:enabled] ? :enabled : nil, @register_template == step[:name] ? :current : nil, post_registration ? :post : :pre].compact do - if step[:enabled] - content_tag :div, (link_to text, register_step_path(@this_conference.slug, step[:name])).html_safe, class: :step - else - content_tag :div, text, class: :step - end - end - - if post_registration - post_registration_steps += h.html_safe - else - pre_registration_steps += h.html_safe - end - end - - html = ( - row class: 'flow-steps' do - columns do - (content_tag :ul, id: 'registration-steps' do - pre_registration_steps.html_safe + - post_registration_steps.html_safe - end).html_safe - end - end - ) - - return html.html_safe - end - - def broadcast_to(to, conference = nil) - conference ||= @this_conference || @conference - - users = [] - - case to.to_sym - when :registered - ConferenceRegistration.where(conference_id: conference.id).each do | r | - users << r.user if ((r.steps_completed || []).include? 'questions') && r.user.present? && r.is_attending != 'n' - end - when :pre_registered - ConferenceRegistration.where(conference_id: conference.id).each do | r | - users << r.user if registration_status(r) == :preregistered && r.is_attending != 'n' - end - when :workshop_facilitators - user_hash = {} - Workshop.where(conference_id: conference.id).each do | w | - w.active_facilitators.each do | u | - user_hash[u.id] ||= u if u.present? - end - end - users = user_hash.values - when :unregistered - ConferenceRegistration.where(conference_id: conference.id).each do | r | - users << r.user if registration_status(r) == :unregistered && r.is_attending != 'n' - end - when :housing_providers - ConferenceRegistration.where(conference_id: conference.id, can_provide_housing: true).each do | r | - users << r.user if r.user.present? - end - when :guests - ConferenceRegistration.where(conference_id: conference.id, housing: 'house').each do | r | - users << r.user if r.user.present? && r.is_attending != 'n' - end - when :all - User.all.each do | u | - users << u if u.present? && (u.is_subscribed.nil? || u.is_subscribed) - end - end - - puts "Users:" - users.each do | u | - puts "\t#{u.id}\t#{u.email}" - end - return users - end - - def broadcast_options(conference = nil) - conference ||= @this_conference || @conference - - options = [ - :registered, - :pre_registered, - :workshop_facilitators, - :unregistered, - :housing_providers, - :guests, - :all - ] - - if conference.registration_status != :open - options -= [:registered, :guests] - options -= [:pre_registered] unless conference.registration_status != :pre - end - - return options - end - - def admin_steps # deprecated - [:stats, :edit, :payment, :broadcast, :housing, :locations, :meals, :events, :workshop_times, :schedule] - end - - def administration_steps - { - info: [:administrators, :dates, :description, :poster], - payment: [:payment_message, :suggested_amounts, :paypal], - registration: [:registration_status, :stats, :registrations, :broadcast], - housing: [:providers, :housing], - events: [:locations, :meals, :events], - schedule: [:workshop_times, :schedule, :publish_schedule] - } - end - - def administration_sub_steps - { - location_edit: :locations, - event_edit: :events, - broadcast_sent: :broadcast - } - end - - def get_administration_group(administration_step) - admin_step = administration_step.to_sym - admin_step = administration_sub_steps[admin_step] if administration_sub_steps[admin_step].present? - administration_steps.each do | group, steps | - steps.each do | step | - return group if step == admin_step - end - end - - return nil - end - - def valid_admin_steps # deprecated - admin_steps + [:broadcast_sent, :organizations] - end - - def admin_menu - steps = '' - first = true - admin_steps.each do | step | - steps += content_tag(:li, class: (step.to_s == @admin_step ? :current : nil)) do - link_to _("menu.submenu.admin.#{step.to_s.titlecase.gsub(/\s/, '_')}"), first ? - register_step_path(@this_conference.slug, :administration) : - administration_step_path(@this_conference.slug, step.to_s) - end - first = false - end - content_tag :ul, steps.html_safe, id: 'registration-admin-menu' - end - - def admin_update_form(options = {}, &block) - form_tag(administration_update_path(@this_conference.slug, @admin_step), options, &block) - end - - def interest_button(workshop) - interested = workshop.interested?(current_user) ? :remove_interest : :show_interest - id = "#{interested.to_s.gsub('_', '-')}-#{workshop.id}" - return (off_screen (_"forms.actions.aria.#{interested.to_s}"), id) + - (button_tag interested, :value => :toggle_interest, :class => (workshop.interested?(current_user) ? :delete : :add), aria: { labelledby: id }) - end - - def interest_text(workshop) - if workshop.interested?(current_user) - return _'articles.workshops.info.you_are_interested_count', :vars => {:count => (workshop.interested_count - 1)} - end - - return _'articles.workshops.info.interested_count', :vars => {:count => workshop.interested_count} - end - - def host_guests_table(registration) - id = registration.id - html = '' - - @housing_data[id][:guests].each do | area, guests | - guest_rows = '' - guests.each do | guest_id, guest | - status_html = '' - - @housing_data[id][:guest_data][guest_id][:errors].each do | error, value | - if value.is_a?(Array) - value.each do | v | - status_html += content_tag(:li, _("errors.messages.housing.space.#{error.to_s}", vars: v)) - end - else - status_html += content_tag(:li, _("errors.messages.housing.space.#{error.to_s}", vars: value)) - end - end - - @housing_data[id][:guest_data][guest_id][:warnings].each do | error, value | - if value.is_a?(Array) - value.each do | v | - status_html += content_tag(:li, _("warnings.messages.housing.space.#{error.to_s}", v)) - end - else - status_html += content_tag(:li, _("warnings.messages.housing.space.#{error.to_s}", vars: value)) - end - end - - if status_html.present? - status_html = content_tag(:ul, status_html.html_safe) - end - - guest_rows += content_tag :tr, id: "hosted-guest-#{guest_id}" do - (content_tag :td, guest[:guest].user.name) + - (content_tag :td do - (guest[:guest].city + - (content_tag :a, (_'actions.workshops.Remove'), href: '#', class: 'remove-guest', data: { guest: guest_id })).html_safe - end) + - (content_tag :td, status_html.html_safe, class: [:state, status_html.present? ? :unhappy : :happy]) - end - end - - space_size = (@housing_data[id][:space][area] || 0) - - # add empty rows to represent empty guest spots - for i in guests.size...space_size - guest_rows += content_tag :tr, class: 'empty-space' do - (content_tag :td, ' '.html_safe, colspan: 2) + - (content_tag :td) - end - end - - status_html = '' - if @housing_data[id][:warnings].present? && @housing_data[id][:warnings][:space].present? && @housing_data[id][:warnings][:space][area].present? - @housing_data[id][:warnings][:space][area].each do | w | - status_html += content_tag(:li, _("warnings.messages.housing.space.#{w.to_s}")) - end - end - if status_html.present? - status_html = content_tag(:ul, status_html.html_safe) - end - - html += content_tag :tr do - (content_tag :th, (_"forms.labels.generic.#{area}"), colspan: 2) + - (content_tag :th, status_html.html_safe, class: [:state, status_html.present? ? :unhappy : :happy]) - end - html += guest_rows - html += content_tag :tr, class: 'place-guest' do - content_tag :td, class: guests.size >= space_size ? 'full' : nil, colspan: 3 do - content_tag :a, (_'forms.actions.generic.place_guest'), class: 'select-guest', href: '#', data: { host: id, space: area } - end - end - end - - content_tag :table, html.html_safe, class: 'host-table' - end - - def get_housing_match(host, guest, space) - housing_data = guest.housing_data || [] - - if housing_data['host'].present? - if housing_data['host'] == host.id - return space == housing_data['space'] ? :selected_space : :other_space - end - - return :other_host - end - - if space_matches?(space, guest.housing) && available_dates_match?(host, guest) - return :good_match - end - - return :bad_match - end - - def get_workshop_match(workshop, day, block, location) - if workshop.event_location_id.present? && workshop.present? - if (Date.parse params[:day]).wday == workshop.block['day'] && block == workshop.block['block'].to_i - return :selected_space - end - - if location.present? && location.id == workshop.event_location_id - return :other_space - end - - return :other_host - end - - if location.present? - needs = JSON.parse(workshop.needs || '[]').map &:to_sym - amenities = JSON.parse(location.amenities || '[]').map &:to_sym - - if (needs & amenities).length < needs.length - return :bad_match - end - end - - (((((@schedule[@day] || {})[:times] || {})[@time] || {})[:item] || {})[:workshops] || {}).each do | l, w | - if w[:workshop].id != workshop.id - f_a = w[:workshop].active_facilitators.map { | f | f.id } - f_b = workshop.active_facilitators.map { | f | f.id } - if (f_a & f_b).present? - return :bad_match - end - end - end - - # housing_data = guest.housing_data || [] - - # if housing_data['host'].present? - # if housing_data['host'] == host.id - # return space == housing_data['space'] ? :selected_space : :other_space - # end - - # return :other_host - # end - - # if space_matches?(space, guest.housing) && available_dates_match?(host, guest) - # return :good_match - # end - - # return :bad_match - return :good_match - end - - def space_matches?(host_space, guest_space) - return false unless host_space.present? && guest_space.present? - - if host_space.to_s == 'bed_space' || host_space.to_s == 'floor_space' - return guest_space.to_s == 'house' - end - - return host_space.to_s == 'tent_space' && guest_space.to_s == 'tent' - end - - def available_dates_match?(host, guest) - return false unless host.housing_data['availability'].present? && host.housing_data['availability'][1].present? - if host.housing_data['availability'][0] <= guest.arrival && - host.housing_data['availability'][1] >= guest.departure - return true - end - - return false - end - - def host_guests_widget(registration) - html = '' - classes = ['host'] - - id = registration.id - @housing_data[id][:guests].each do | area, guests | - max_space = @housing_data[id][:space][area] || 0 - area_name = (_"forms.labels.generic.#{area}") - status_html = '' - if @housing_data[id][:warnings].present? && @housing_data[id][:warnings][:space].present? && @housing_data[id][:warnings][:space][area].present? - @housing_data[id][:warnings][:space][area].each do | w | - status_html += content_tag(:div, _("warnings.housing.space.#{w.to_s}"), class: 'warning') - end - end - space_html = content_tag(:h5, area_name + _!(" (#{guests.size.to_s}/#{max_space.to_s})") + status_html.html_safe) - guest_items = '' - guests.each do | guest_id, guest | - guest_items += content_tag(:li, guest[:guest].user.name, id: "hosted-guest-#{guest_id}") - end - space_html += content_tag(:ul, guest_items.html_safe) - space_html += button_tag :place_guest, type: :button, value: "#{area}:#{id}", class: [:small, 'place-guest', 'on-top-only', guests.size >= max_space ? (guests.size > max_space ? :overbooked : :booked) : nil, max_space > 0 ? nil : :unwanted] - html += content_tag(:div, space_html, class: [:space, area, max_space > 0 || guests.size > 0 ? nil : 'on-top-only']) - end - - classes << 'status-warning' if @housing_data[id][:warnings].present? - classes << 'status-error' if @housing_data[id][:errors].present? - - return { html: html.html_safe, class: classes.join(' ') } - end - - def signin_link - @login_dlg ||= true - link_to (_'forms.actions.generic.login'), settings_path, data: { 'sign-in': true } - end - - def link_with_confirmation(link_text, confirmation_text, path, args = {}) - @confirmation_dlg ||= true - args[:data] ||= {} - args[:data][:confirmation] = true - link_to path, args do - (link_text.to_s + content_tag(:template, confirmation_text, class: 'message')).html_safe - end - end - - def link_info_dlg(link_text, info_text, info_title, args = {}) - @info_dlg ||= true - args[:data] ||= {} - args[:data]['info-title'] = info_title - args[:data]['info-text'] = true - content_tag(:a, args) do - (link_text.to_s + content_tag(:template, info_text, class: 'message')).html_safe - end - end - - def button_with_confirmation(button_name, confirmation_text = nil, args = {}) - if confirmation_text.is_a? Hash - args = confirmation_text - confirmation_text = nil - end - - confirmation_text ||= (_"forms.confirmations.#{button_name.to_s}", :p) - @confirmation_dlg ||= true - args[:data] ||= {} - args[:data][:confirmation] = true - button_tag button_name, args do - ((_"forms.actions.generic.#{button_name.to_s}") + content_tag(:template, confirmation_text, class: 'message')).html_safe - end - end - - def richtext(text, reduce_headings = 2) - return '' unless text.present? - return _!(text). - gsub(/<(\/?)h4>/, '<\1h' + (reduce_headings + 4).to_s + '>'). - gsub(/<(\/?)h3>/, '<\1h' + (reduce_headings + 3).to_s + '>'). - gsub(/<(\/?)h2>/, '<\1h' + (reduce_headings + 2).to_s + '>'). - gsub(/<(\/?)h1>/, '<\1h' + (reduce_headings + 1).to_s + '>'). - html_safe - end - - def truncate(text) - strip_tags(text.gsub('>', '> ')).gsub(/^(.{40,60})\s.*$/m, '\1…').html_safe - end - - def translate_fields(object, field_options = {}, options = {}) - html = '' - nav = '' - - # set the selected locale - selected_locale = (options[:locale] || object.locale || I18n.locale).to_sym - - I18n.backend.enabled_locales.each do | locale | - # ses if this should b the selected field - class_name = selected_locale == locale.to_sym ? 'selected' : nil - - # add the locale to the nav - nav += content_tag(:li, - content_tag(:a, _("languages.#{locale}"), href: 'javascript:void(0)'), - class: class_name, data: { locale: locale }).html_safe - - fields = '' - field_options.each do | name, __options | - _options = __options.deep_dup - # add the field - value = object.is_a?(Hash) ? object[locale.to_sym] : object.get_column_for_locale!(name, locale, false) - - # use the default value if we need to - if _options[:default].present? && value.blank? - value = _(_options[:default], locale: locale) - end - - _options[:index] = locale - _options[:lang] = locale - _options[:parent_options] = { lang: locale } - type = "#{_options[:type].to_s}" - _options.delete(:type) - - fields += self.send(type, name, value, _options).html_safe - end - - html += content_tag(:li, fields.html_safe, class: class_name, data: { locale: locale }).html_safe - end - - if options[:class].nil? - options[:class] = [] - elsif options[:class].is_a?(String) - options[:class] = [options[:class]] - end - options[:class] += ['translator', 'multi-field-translator'] - - (fieldset(nil, options) do - content_tag(:ul, nav.html_safe, class: 'locale-select').html_safe + - content_tag(:ul, html.html_safe, class: 'text-editors').html_safe - end).html_safe - end - - def translate_textarea(name, object, property = nil, options = {}) - html = '' - nav = '' - - # see if options was passed in as property - if options.blank? && property.is_a?(Hash) - options = property - property = nil - end - - # set the selected locale - selected_locale = (options[:locale] || object.locale || I18n.locale).to_sym - - I18n.backend.enabled_locales.each do | locale | - # ses if this should b the selected field - class_name = selected_locale == locale.to_sym ? 'selected' : nil - - # add the locale to the nav - nav += content_tag(:li, - content_tag(:a, _("languages.#{locale}"), href: 'javascript:void(0)'), - class: class_name, data: { locale: locale }).html_safe - - # add the field - value = object.is_a?(Hash) ? object[locale.to_sym] : object.get_column_for_locale!(name, locale, false) - - # use the default value if we need to - if options[:default].present? && value.blank? - value = _(options[:default], locale: locale) - end - - html += content_tag(:li, textarea(name, value, { - label: false, - edit_on: options[:edit_on], - parent_options: { - lang: locale - }, - index: locale - }).html_safe, class: class_name, data: { locale: locale }).html_safe - end - - if options[:class].nil? - options[:class] = [] - elsif options[:class].is_a?(String) - options[:class] = [options[:class]] - end - options[:class] += ['translator'] - - (fieldset(name, options) do - content_tag(:ul, nav.html_safe, class: 'locale-select').html_safe + - content_tag(:ul, html.html_safe, class: 'text-editors').html_safe - end).html_safe - end - - def textarea(name, value = nil, options = {}) - id = unique_id(name) - label_id = "#{id}-label" - description_id = nil - html = '' - - if options[:heading].present? - label_id = "#{name.to_s}-label" unless options[:label] - html += content_tag(:h3, _(options[:heading], :t, vars: options[:vars] || {}), id: label_id) - end - - if options[:label] == false - label_id = options[:labelledby] - elsif options[:label].present? - html += label_tag([name, id], nil, id: label_id) do - _(options[:label], :t, vars: options[:vars] || {}) - end - else - html += label_tag([name, id], nil, id: label_id) - end - - if options[:help].present? - description_id ||= "#{id}-desc" - html += content_tag(:div, _(options[:help], :s, 2), id: description_id, class: 'input-field-help') - end - - if options[:warning].present? - description_id ||= "#{id}-desc" - html += content_tag(:div, _(options[:warning], :s, 2), id: description_id, class: 'warning-info') - end - - aria = {} - aria[:labelledby] = label_id if label_id.present? - aria[:describedby] = description_id if description_id.present? - css_class = [ - options[:short] === true ? :short : nil - ].compact - - html_name = name.to_s + (options[:index] ? "[#{options[:index]}]" : '') - if options[:plain] - html += (text_area_tag html_name, value, - id: id, - lang: options[:lang], - aria: aria, - class: css_class - ) - else - html += content_tag(:div, value.present? ? value.html_safe : '', - id: id, - data: { name: html_name, 'edit-on': options[:edit_on] || :load }, - lang: options[:lang], - aria: aria, - tabindex: 0, - class: [:textarea] + css_class - ) - - add_stylesheet :editor - add_inline_script :pen - add_inline_script :markdown - add_inline_script :editor - end - - parent_options = options[:parent_options] || {} - if parent_options[:class].nil? - parent_options[:class] = [] - elsif parent_options[:class].is_a?(String) - parent_options[:class] = [parent_options[:class]] - end - - parent_options[:class] += ['text-area-field', 'input-field'] - html = content_tag(:div, html.html_safe, parent_options).html_safe - html += _original_content(options[:original_value], options[:original_lang]) if options[:original_value].present? - - return html.html_safe - end - - def fieldset(name = nil, options = {}, &block) - html = '' - label = '' - description = '' - description_id = nil - errors = '' - - if name.present? - if options[:label] != false - label = content_tag(:legend, - _(( - options[:label].is_a?(String) ? - options[:label] : - "forms.labels.generic.#{name}"), :t, vars: options[:vars] || {})) - end - - if options[:help].present? - description_id = unique_id("#{name.to_s}-desc") - description = content_tag(:div, _(options[:help], :s, 2), class: 'input-field-help', id: description_id) - end - - errors = (show_errors name) - end - - html = label + errors + description + content_tag(:div, class: :fieldgroup, &block) - - aria = description_id.present? ? { describedby: description_id } : nil - (content_tag(:fieldset, html.html_safe, - aria: aria, - class: ((options[:class] || []) + [ - options[:inline] ? :inline : nil, - options[:inline_label] ? 'inline-label' : nil, - errors.present? ? 'has-error' : nil - ]).compact - ) - ).html_safe - end - - def selectfield(name, value, select_options, options = {}) - unless select_options.first.is_a?(Array) - so = select_options - select_options = [] - so.each do | opt | - select_options << [ I18n.t("forms.options.#{name.to_s}.#{opt.to_s}"), opt] - end - end - textfield(name, value, options.merge({type: :select, options: select_options})) - end - - def telephonefield(name, value, options = {}) - textfield(name, value, options.merge({type: :telephone})) - end - - def numberfield(name, value, options = {}) - textfield(name, value, options.merge({type: :number})) - end - - def searchfield(name, value, options = {}) - textfield(name, value, options.merge({type: :search})) - end - - def userfield(name, value, options = {}) - # eventually this will be a dynamic field to find users, for now we'll just use emails - # add_inline_script :userfield - emailfield(name, value, options)# .merge({ - # parent_options: { class: ['user-field'] }, - # after: content_tag(:div, '', class: 'user-name') - # })) - end - - def emailfield(name, value, options = {}) - textfield(name, value, options.merge({type: :email})) - end - - def filefield(name, value, options = {}) - textfield(name, value, options.merge({type: :file})) - end - - def passwordfield(name, value, options = {}) - textfield(name, value, options.merge({type: :password})) - end - - def textfield(name, value, options = {}) - html = '' - id = unique_id(name) - html_name = name.to_s + (options[:index] ? "[#{options[:index]}]" : '') - description_id = nil - - if options[:heading].present? - description_id ||= "#{id.to_s}-desc" - html += content_tag(:h3, _(options[:heading], :t, vars: options[:vars] || {}), id: description_id) - end - - if options[:help].present? - description_id ||= "#{id.to_s}-desc" - html += content_tag(:div, _(options[:help], :s, 2, vars: options[:vars] || {}), class: 'input-field-help', id: description_id) - end - - html += show_errors name, value - - inside_label = '' - - if options[:type] == :file - inside_label = (content_tag(:div, class: 'file-field-selector') do - (options[:preview] ? content_tag(:img, nil, src: value.present? ? value.url : nil).html_safe : '').html_safe + - content_tag(:div, (value.present? ? File.basename(value.url) : (_'forms.labels.generic.no_file_selected')), class: 'file-field-name ' + (value.present? ? 'selected' : 'unselected')).html_safe + - content_tag(:a, (_'forms.actions.generic.select_file'), class: :button) - end) - end - - label_text = nil - if options[:label].present? - label_text = _(options[:label], :t, vars: options[:vars] || {}) - elsif options[:label] != false - label_text = (_"forms.labels.generic.#{name}") - elsif options[:type] == :select || options[:type] == :file - # add an empty label so that the drop down button will still appear - label_text = '' - end - - label_options = {} - # let the label be selected if the input is hidden - label_options[:tabindex] = 0 if options[:type] == :file - - unless label_text.nil? - html += label_tag id, (label_text + inside_label).html_safe - end - - input_options = { - id: id, - required: options[:required], - lang: options[:lang], - min: options[:min], - max: options[:max], - step: options[:step], - aria: description_id ? { describedby: description_id } : nil - } - - case name - when :address - input_options[:autocomplete] = 'address-line1' - when :name - input_options[:autocomplete] = 'name' - when :location - input_options[:autocomplete] = 'address-level2' - when :email - input_options[:autocomplete] = 'email' - when :phone - input_options[:autocomplete] = 'tel' - when :paypal_email_address, :paypal_username, :paypal_password, :paypal_signature - input_options[:autocomplete] = 'off' - end - - case options[:type] - when :select - option_list = options_for_select(options[:options], value) - - # make sure that we have an empty option if the select is required - if options[:required] && options[:options].first.present? && options[:options].first.last.present? - option_list = ('' + option_list).html_safe - end - html += select_tag(html_name, option_list, input_options) - when :file - add_inline_script :filefield - input_options[:tabindex] = '-1' - html += off_screen(file_field_tag html_name, input_options) - else - input_options[:autocomplete] = 'off' if options[:type] == :search - html += send("#{(options[:type] || :text).to_s}_field_tag", html_name, value, input_options) - end - - if options[:after].present? - html += options[:after].html_safe - end - - html = content_tag(:div, html.html_safe, - class: [ - "#{(options[:type] || :text).to_s}-field", - 'input-field', - value.present? ? nil : 'empty', - options[:big] ? 'big' : nil, - options[:small] ? 'small' : nil, - options[:stretch] ? 'stretch-item' : nil, - options[:full] ? 'full' : nil, - options[:inline_label] ? 'inline-label' : nil, - (@errors || {})[name].present? ? 'has-error' : nil - ].compact + (((options[:parent_options] || {})[:class]) || [])) - - html += _original_content(options[:original_value], options[:original_lang]) if options[:original_value].present? - - return html.html_safe - end - - def radiobuttons(name, boxes, value, label_key, options = {}) - checkboxes(name, boxes, [value], label_key, options.merge({radiobuttons: true})) - end - - def checkbox(name, value, label_key, options = {}) - checkboxes(name, [true], value, label_key, options) - end - - def unique_id(id) - id = id.to_s.gsub('[', '_').gsub(']', '') - - @_ids ||= {} - @_ids[id] ||= 0 - - new_id = id - - if @_ids[id] > 0 - new_id += "--#{@_ids[id]}" - end - - @_ids[id] += 1 - - return new_id - end - - def checkboxes(name, boxes, values, label_key, options = {}) - html = '' - - label_id = nil - description_id = nil - - if options[:heading].present? - label_id ||= unique_id("#{name.to_s}-label") - html += content_tag(:h3, _(options[:heading], :t, vars: options[:vars] || {}), id: label_id) - end - - help = nil - - if options[:help].present? - description_id ||= unique_id("#{name.to_s}-desc") - help = content_tag(:div, _(options[:help], :s, 2), class: 'input-field-help', id: description_id) - end - - html += help if help.present? && !options[:right_help] - - boxes_html = '' - - labels = nil - is_single = !values.is_a?(Array) - if boxes.length > 0 - if boxes.first.is_a?(Array) - labels = boxes.map(&:first) - boxes = boxes.map(&:last) - end - elsif !boxes.first.is_a?(Integer) - values = values.present? ? values.map(&:to_s) : [] unless is_single - boxes = boxes.map(&:to_s) - end - - # convert the required value into a pure boolean - required = !!options[:required] - - boxes.each_with_index do | box, i | - checked = (is_single ? values.present? : values.include?(box)) - values -= [box] if checked && !is_single - id = nil - if options[:radiobuttons].present? - id = unique_id("#{name.to_s}_#{box}") - boxes_html += radio_button_tag(name, box, checked, id: id, required: required) - else - _name = (is_single ? name : "#{name.to_s}[#{box}]") - id = unique_id(_name) - boxes_html += check_box_tag(_name, 1, checked, data: { toggles: options[:toggles] }.compact, id: id, required: required) - end - - # we only need the required attribute on one element - required = false - - if labels.present? - label = labels[i] - elsif is_single - label = _(label_key.to_s) - elsif box.is_a?(Integer) - label = I18n.t(label_key.to_s)[box] - else - label = _("#{label_key.to_s}.#{box}") - end - - boxes_html += label_tag(id, label) - end - - if options[:other].present? && !is_single - id = nil - if options[:radiobuttons].present? - id = unique_id("#{name.to_s}_other") - boxes_html += radio_button_tag(name, :other, values.present?, id: id) - else - _name = "#{name.to_s}[other]" - id = unique_id(_name) - boxes_html += check_box_tag(_name, 1, values.present?, id: id) - end - boxes_html += label_tag id, - content_tag(:div, - text_field_tag("other_#{name.to_s}", values.first, placeholder: (_"#{label_key}.other"), required: values.present?), - class: 'other') - end - - html += content_tag(:fieldset, content_tag(:div, boxes_html.html_safe, - class: [ - 'check-box-field', - 'input-field', - options[:vertical] ? 'vertical' : nil, - options[:inline] ? 'inline' : nil, - options[:small] ? 'small' : nil, - options[:big] ? 'big' : nil - ].compact).html_safe, - aria: { - labelledby: label_id, - describedby: description_id - }, - class: [ - options[:centered] ? 'centered' : nil, - options[:right_help] ? 'right-help' : nil - ].compact - ) - - html += help if help.present? && options[:right_help] - - return html.html_safe - end - - def companion(registration) - if registration.housing_data.present? && registration.housing_data['companions'].present? && registration.housing_data['companions'].first.present? - companion_user = User.find_user(registration.housing_data['companions'].first) - - if companion_user.present? - cr = ConferenceRegistration.where(user_id: companion_user.id).order(created_at: :desc).limit(1).first - - if cr.present? && ((cr.steps_completed || []).include? 'questions') - return companion_user - end - end - return :unregistered - end - return nil - end - - def comment(comment) - add_inline_script :time - add_js_translation('datetime.distance_in_words') - - content_tag(:div, class: 'comment-body') do - content_tag(:h4, comment.user.name, class: 'comment-title') + - content_tag(:time, time(comment.created_at, :default), datetime: comment.created_at.to_s) + - content_tag(:div, class: 'comment-text') do - markdown comment.comment - end - end - end - - def html_edit_table(excel_data, options = {}) - attributes = { class: options[:class], id: options[:id] } - attributes[:data] = { 'update-url' => options[:editable] } if options[:editable].present? - - if options[:column_names].is_a? Hash - return content_tag(:table, attributes) do - max_columns = 0 - column_names = {} - (content_tag(:thead) do - headers = '' - options[:column_names].each do | header_name, columns | - column_names[header_name] ||= [] - headers += content_tag(:th, excel_data[:keys][header_name].present? ? _(excel_data[:keys][header_name]) : '', colspan: 2) - row_count = columns.size - columns.each do | column | - column_names[header_name] << column - if (options[:row_spans] || {})[column].present? - row_count += (options[:row_spans][column] - 1) - for i in 1...options[:row_spans][column] - column_names[header_name] << false - end - end - end - max_columns = row_count if row_count > max_columns - end - content_tag(:tr, headers.html_safe) - end) + (content_tag(:tbody) do - rows = '' - - for i in 0...max_columns - columns_html = '' - column_names.each do | header_name, columns | - column = columns[i] - if column.present? - attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } } - if (options[:row_spans] || {})[column].present? - attributes[:rowspan] = options[:row_spans][column] - end - columns_html += content_tag(:th, excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '', rowspan: attributes[:rowspan]) + - edit_column(nil, column, nil, attributes, excel_data, options) - elsif column != false - columns_html += content_tag(:td, ' ', colspan: 2, class: :empty) - end - end - rows += content_tag(:tr, columns_html.html_safe, { class: 'always-edit', data: { key: '' } }) - end - rows.html_safe - end) - end - else - return content_tag(:table, attributes) do - (content_tag(:tbody) do - rows = '' - excel_data[:columns].each do |column| - if (excel_data[:column_types] || {})[column] != :table && ((options[:column_names] || []).include? column) - rows += content_tag(:tr, { class: 'always-edit', data: { key: '' } }) do - attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } } - columns = content_tag(:th, excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '') + - edit_column(nil, column, nil, attributes, excel_data, options) - end - end - end - rows.html_safe - end) - end - end - end - - def html_table(excel_data, options = {}) - options[:html] = true - attributes = { class: options[:class], id: options[:id] } - attributes[:data] = { 'update-url' => options[:editable] } if options[:editable].present? - content_tag(:table, attributes) do - (content_tag(:thead) do - content_tag(:tr, excel_header_columns(excel_data)) - end) + - content_tag(:tbody, excel_rows(excel_data, {}, options)) - end - end - - def excel_table(excel_data) - format_xls 'table' do - workbook use_autowidth: true - format bg_color: '333333' - format 'td', font_name: 'Calibri', fg_color: '333333' - format 'th', font_name: 'Calibri', b: true, bg_color: '333333', fg_color: 'ffffff' - format 'th.sub-table', font_name: 'Calibri', b: true, bg_color: 'DDDDDD', fg_color: '333333' - format 'td.datetime', num_fmt: 22, font_name: 'Courier New', sz: 10, fg_color: '333333' - format 'td.date.day', num_fmt: 14, font_name: 'Courier New', sz: 10, fg_color: '333333' - format 'td.money', num_fmt: 2, font_name: 'Courier New', sz: 10, fg_color: '333333' - format 'td.number', font_name: 'Courier New', sz: 10, fg_color: '333333' - format 'td.bold', font_name: 'Calibri', fg_color: '333333', b: true - end - - content_tag(:table) do - (content_tag(:thead) do - content_tag(:tr, excel_header_columns(excel_data)) - end) + - content_tag(:tbody, excel_rows(excel_data)) - end - end - - def excel_header_columns(data, padding = {}, class_name = nil) - columns = '' - - data[:columns].each do |column| - unless data[:column_types].present? && data[:column_types][column] == :table - columns += content_tag(:th, data[:keys][column].present? ? _(data[:keys][column]) : '', class: class_name) - end - end - - pad_columns(columns, padding, :th) - end - - def excel_empty_row(data, padding = {}) - columns = '' - - data[:columns].each do |column| - unless data[:column_types].present? && data[:column_types][column] == :table - columns += content_tag(:td) - end - end - - content_tag(:tr, pad_columns(columns, padding)) - end - - def pad_columns(columns, padding, column_type = :td) - left = '' - - for i in 1..(padding['left'] || 0) - left += content_tag(:td) - end - - right = '' - for i in 1..(padding['right'] || 0) - right += content_tag(:td) - end - - (left + columns + right).html_safe - end - - def excel_columns(row, data, padding = {}, options = {}) - columns = '' - - data[:columns].each do |column| - value = row[column].present? ? (_!row[column].to_s) : '' - class_name = nil - is_sub_table = false - - if data[:column_types].present? && data[:column_types][column].present? - if data[:column_types][column] == :table - is_sub_table = true - else - class_name = data[:column_types][column] - end - end - - unless is_sub_table - attributes = { class: [class_name] } - if options[:html] && row[:html_values].present? && row[:html_values][column].present? - value = row[:html_values][column] - end - - if options[:editable] - attributes[:data] = { 'column-id' => column } - end - - if (options[:column_names] || []).include? column - attributes[:tabindex] = 0 - end - - columns += content_tag(:td, value, attributes) - end - end - - pad_columns(columns, padding) - end - - def editor_columns(row, data, padding = {}, options = {}) - columns = '' - - data[:columns].each do |column| - value = row[column].present? ? (_!row[column].to_s) : '' - class_name = nil - is_sub_table = false - - if data[:column_types].present? && data[:column_types][column].present? - if data[:column_types][column] == :table - is_sub_table = true - else - class_name = data[:column_types][column] - end - end - - unless is_sub_table - attributes = { class: [class_name] } - - if options[:editable] - attributes[:data] = { 'column-id' => column } - end - - if (options[:column_names] || []).include? column - columns += edit_column(row, column, value, attributes, data, options) - else - columns += content_tag(:td, value, attributes) - end - - end - end - - pad_columns(columns, padding) - end - - def edit_column(row, column, value, attributes, data, options) - attributes[:class] << 'has-editor' - raw_value = row.present? ? (row[:raw_values][column] || value) : nil - - if row.present? && options[:html] && row[:html_values].present? && row[:html_values][column].present? - value = row[:html_values][column] - end - - editor_attributes = { class: 'cell-editor', data: { value: raw_value.to_s } } - - # create the control but add the original value to set the width and height - editor_value = content_tag(:div, value, class: 'value') - if (options[:column_options] || {})[column].present? - value = (editor_value.html_safe + select_tag(column, options_for_select([['', '']] + options[:column_options][column], raw_value), editor_attributes)).html_safe - elsif data[:column_types][column] == :text - editor_attributes[:name] = column - value = (editor_value.html_safe + content_tag(:textarea, raw_value, editor_attributes)).html_safe - else - editor_attributes[:name] = column - editor_attributes[:value] = raw_value - editor_attributes[:required] = :required if (options[:required_columns] || []).include? column - type = data[:column_types][column] || :unknown - editor_attributes[:type] = { money: :number, number: :number, email: :email }[type] || :text - value = (editor_value.html_safe + content_tag(:input, nil, editor_attributes)).html_safe - end - - return content_tag(:td, value, attributes) - end - - def excel_sub_tables(row, data, padding = {}, options = {}) - rows = '' - - # shift the table right - new_padding = { - 'left' => (padding['right'] || 0) + 1, - 'right' => (padding['right'] || 0) - 1 - } - - data[:columns].each do |column| - if data[:column_types].present? && data[:column_types][column] == :table - rows += content_tag(:tr, excel_header_columns(row[column], new_padding, 'sub-table')) - rows += excel_rows(row[column], new_padding) - rows += excel_empty_row(row[column], new_padding) - end - - end - - rows.html_safe - end - - def excel_rows(data, padding = {}, options = {}) - rows = '' - data[:data].each do |row| - attributes = {} - - if options[:primary_key].present? - attributes[:data] = { key: row[options[:primary_key]] } - end - - attributes[:class] = [] - - if options[:editable] - attributes[:class] << :editable - end - - rows += content_tag(:tr, excel_columns(row, data, padding, options), attributes) + - excel_sub_tables(row, data, padding) - rows += content_tag(:tr, editor_columns(row, data, padding, options), class: :editor) if options[:editable] - end - rows.html_safe - end - - def registrations_edit_table_options - { - id: 'create-table', - class: ['registrations', 'admin-edit', 'always-editing'], - primary_key: :id, - column_names: { - contact_info: [ - :name, - :email, - :is_subscribed, - :city, - :preferred_language - ] + User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym }, - questions: [ - :registration_fees_paid, - :is_attending, - :arrival, - :departure, - :housing, - :bike, - :food, - :companion_email, - :allergies, - :other - ], - hosting: [ - :can_provide_housing, - :address, - :phone, - :first_day, - :last_day - ] + ConferenceRegistration.all_spaces + - ConferenceRegistration.all_considerations + [ - :notes - ] - }, - row_spans: { - allergies: 3, - other: 2 - }, - required_columns: [:name, :email], - editable: administration_update_path(@this_conference.slug, @admin_step), - column_options: @column_options - } - end - - def registrations_table_options - { - id: 'search-table', - class: ['registrations', 'admin-edit'], - primary_key: :id, - column_names: [ - :registration_fees_paid, - :is_attending, - :is_subscribed, - :city, - :preferred_language, - :arrival, - :departure, - :housing, - :bike, - :food, - :companion_email, - :allergies, - :other, - :can_provide_housing, - :address, - :phone, - :first_day, - :last_day, - :notes - ] + - User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym } + - ConferenceRegistration.all_spaces + - ConferenceRegistration.all_considerations, - editable: administration_update_path(@this_conference.slug, @admin_step), - column_options: @column_options - } - end - - def potential_provider(registration) - return false unless registration.present? && registration.city.present? && registration.conference.present? - conditions = registration.conference.provider_conditions || - Conference.default_provider_conditions - return city_distance_less_than(registration.conference.city, registration.city, - (conditions['distance']['number'] || '0').to_i, - conditions['distance']['unit']) - end - - def city_distance_less_than(city1, city2, max_distance, unit) - return false if city1.nil? || city2.nil? - return true if city1.id == city2.id - return false if max_distance < 1 - return Geocoder::Calculations.distance_between( - [city1.latitude, city1.longitude], [city2.latitude, city2.longitude], - units: unit.to_sym) < max_distance - end - - private - def _original_content(value, lang) - content_tag(:div, ( - content_tag(:h4, _('translate.content.Translation_of')) + - content_tag(:div, value, class: 'value', lang: lang) - ).html_safe, class: 'original-text') - end - - def _form_field(type, name, value, options) - if type == 'check_box' - self.send(type + '_tag', name, "1", value, options) - else - self.send(type + '_tag', name, value, options) - end - end end diff --git a/app/helpers/bike_bike_form_helper.rb b/app/helpers/bike_bike_form_helper.rb deleted file mode 100644 index 818c048..0000000 --- a/app/helpers/bike_bike_form_helper.rb +++ /dev/null @@ -1,407 +0,0 @@ - -module BikeBikeFormHelper - include ActionView::Helpers::FormTagHelper - - TEMPLATE_DIR = 'layouts/fields' - - def check_box_tag(name, value = "1", checked = false, options = {}) - render_field(name, options = get_options(name, options), super(name, value, checked, options), value) - end - - def color_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def date_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def datetime_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def datetime_local_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def email_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def file_field_tag(name, options = {}) - render_field(name, options = get_options(name, options), super(name, options)) - end - - def hidden_field_tag(name, value = nil, options = {}) - super(name, value, options) - end - - def month_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def number_field_tag(name, value = nil, options = {}) - options[:_no_wrapper] = true - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def password_field_tag(name = "password", value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def phone_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def radio_button_tag(name, value, checked = false, options = {}) - render_field(name, options = get_options(name, options), super(name, value, checked, options), value) - end - - def range_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def search_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def select_tag(name, option_tags = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, option_tags, options)) - end - - def telephone_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def text_area_tag(name, content = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, content, options), content) - end - - def text_field_tag(name, value = nil, options = {}) - if options[:_no_wrapper] - options.delete(:_no_wrapper) - options[:no_wrapper] = true - end - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def time_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def url_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def week_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def country_select_tag(name, value, options={}) - #options[:no_wrapper] = true - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def subregion_select_tag(name, value, parent_region_or_code, options = {}, html_options = {}) - render_field(name, options = get_options(name, options), super(name, value, parent_region_or_code, options), value) - end - - #def button_tag - #def field_set_tag - #def form_tag - #def image_submit_tag - #def label_tag - #def submit_tag - #def utf8_enforcer_tag - - # FormHelper methods - - def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0") - render_field(method, options = get_options(method, options), super(object_name, method, options, checked_value, unchecked_value)) - end - - def color_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options)) - end - - def date_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options)) - end - - def datetime_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options)) - end - - def datetime_local_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options)) - end - - def email_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options)) - end - - def file_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options)) - end - - def hidden_field(object_name, method, options = {}) - super(object_name, method, options) - end - - def month_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def number_field(object_name, method, options = {}) - options[:_no_wrapper] = true - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def password_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def phone_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def radio_button(object_name, method, tag_value, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, tag_value, options), get_value(method, options)) - end - - def range_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def search_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def telephone_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def text_area(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def text_field(object_name, method, options = {}) - if options[:_no_wrapper] - options.delete(:_no_wrapper) - options[:no_wrapper] = true - end - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def time_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def url_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def week_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def form_for(*args, &block) - @record = args.first - - template = 'errors_' + @record.class.name.underscore - template = 'errors_default' unless lookup_context.exists?(template, [TEMPLATE_DIR], true) - - ( render (TEMPLATE_DIR + '/' + template) ) + super(*args, &block) - end - - def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block) - render_field(method, options = get_options(method, options), super(object, method, collection, value_method, text_method, options, html_options, &block), get_value(method, options)) - end - - def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block) - render_field(method, options = get_options(method, options), super(object, method, collection, value_method, text_method, options, html_options, &block), get_value(method, options)) - end - - def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {}) - render_field(method, options = get_options(method, options), super(object, method, collection, value_method, text_method, options, html_options), get_value(method, options)) - end - - def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) - render_field(method, options = get_options(method, options), super(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options), get_value(method, options)) - end - - def select(object, method, choices = nil, options = {}, html_options = {}, &block) - render_field(method, options = get_options(method, options), super(object, method, choices, options, html_options, &block), get_value(method, options)) - end - - def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {}) - render_field(method, options = get_options(method, options), super(object, method, priority_zones, options, html_options), get_value(method, options)) - end - - def country_select(object, method, priorities_or_options = {}, options_or_html_options = {}, html_options = {}) - if priorities_or_options.is_a? Array - options = options_or_html_options = get_options(method, priorities_or_options) - else - options = priorities_or_options = get_options(method, priorities_or_options) - end - render_field(method, options, super(object, method, priorities_or_options, options_or_html_options, html_options), get_value(method, options)) - end - - def subregion_select(object, method, parent_region_or_code, options = {}, html_options = {}) - render_field(method, options = get_options(method, options), super(object, method, parent_region_or_code, options, html_options), get_value(method, options)) - end - - # Custom fields - - def image_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), BikeBikeFormHelper.image_field_tag(name, value, options), value) - end - - def organization_select_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), BikeBikeFormHelper.organization_select_field_tag(name, value, options), value) - end - - def user_select_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), BikeBikeFormHelper.user_select_field_tag(name, value, options), value) - end - - #def grouped_options_for_select - #def option_groups_from_collection_for_select - #def options_for_select - #def options_from_collection_for_select - #def time_zone_options_for_select - - def form_actions(actions = []) - BikeBikeFormHelper.form_actions(actions) - end - - class << self - - def form_actions(actions = []) - render(:actions, {:actions => actions.is_a?(Array) ? actions : [actions]}) - end - - def image_field_tag(name, value, options, form = nil) - render(:field_image_field, {:name => name, :value => value, :options => options, :form => form}) - end - - def organization_select_field_tag(name, value, options, form = nil) - render(:field_organization_select_field, {:name => name, :value => value, :options => options, :form => form}) - end - - def user_select_field_tag(name, value, options, form = nil) - render(:field_user_select_field, {:name => name, :value => value, :options => options, :form => form}) - end - - def get_options(name, options, type) - if options[:placeholder] === false - options.delete(:placeholder) - elsif (['email_field', 'number_field', 'phone_field', 'search_field', 'telephone_field', 'text_area', 'text_field', 'url_field'].include? type) - options[:placeholder] ||= I18n.translate('form.placeholder.Enter_your_' + name.to_s) - end - return options - end - - def render_field(type, name, options, html, value = nil) - options.symbolize_keys! - if (options.has_key?(:no_wrapper) && options[:no_wrapper]) || /country/.match(name.to_s) && /^subregion_select/.match(type.to_s) || options[:type] == 'hidden' - return html - end - - params = Hash.new - params[:name] = name.to_sym - params[:options] = options - params[:html] = html - params[:type] = type - params[:value] = value - - template = template_exists?(type) ? type : 'default' - params[:label_template] = options[:label] === false ? nil : get_label_template(type, options) - params[:label_position] = options[:label] === false ? :none : label_position(type, options) - - render(template, params) - end - - def get_label_template(type, options) - if !options[:label] && /select(_field)?$/.match(type.to_s) - return nil - end - template_exists?('label_' + type) ? type : 'default' - end - - def label_position(type, options) - # one of: :before, :after, :inside, or :none - case type - when 'image_field' - return :inside - when 'organization_select_field' - return :none - #when 'select_field' - # return :before - end - return :before - end - - private - def render (template, params) - view = ActionView::Base.new(ActionController::Base.view_paths, params) - view.extend ApplicationHelper - view.render (TEMPLATE_DIR + '/' + template.to_s) - end - - def template_exists? (template) - view = ActionView::Base.new(ActionController::Base.view_paths, {}) - view.extend ApplicationHelper - view.lookup_context.exists?(template, [TEMPLATE_DIR], true) - end - end - - private - def get_type() - caller[1][/`.*'/][1..-2].gsub(/^(.*?)(_tag)?$/, '\1') - end - - def get_value(method, options) - options && options[:object] ? options[:object][method] : nil - end - - def get_options(name, options) - options[:_controller] = params[:controller] - BikeBikeFormHelper.get_options(name, options, get_type()) - end - - def render_field(name, options, html, value = nil) - BikeBikeFormHelper.render_field(get_type(), name, options, html, value) - end - - class BikeBikeFormBuilder < ActionView::Helpers::FormBuilder - ActionView::Base.default_form_builder = BikeBikeFormHelper::BikeBikeFormBuilder - - def image_field(method, value, options = {}) - custom_field(method, value, options, 'image_field') - end - - def organization_select_field(method, value, options = {}) - custom_field(method, value, options, 'organization_select_field') - end - - def user_select_field(method, value, options = {}) - custom_field(method, value, options, 'user_select_field') - end - - def actions(actions = []) - BikeBikeFormHelper.form_actions(actions) - end - - private - def custom_field(method, value, options, type) - if defined? params - options[:_controller] = params[:controller] - end - options[:_record] = object - options = BikeBikeFormHelper.get_options(method, options, type) - html = BikeBikeFormHelper.send(type + '_tag', method, value, options, self) - BikeBikeFormHelper.render_field(type, method, options, html, value) - end - end -end diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb new file mode 100644 index 0000000..5e1e815 --- /dev/null +++ b/app/helpers/form_helper.rb @@ -0,0 +1,729 @@ + +module FormHelper + def off_screen(text, id = nil) + content_tag(:span, text.html_safe, id: id, class: 'screen-reader-text') + end + + def translate_fields(object, field_options = {}, options = {}) + html = '' + nav = '' + + # set the selected locale + selected_locale = (options[:locale] || object.locale || I18n.locale).to_sym + + I18n.backend.enabled_locales.each do | locale | + # ses if this should b the selected field + class_name = selected_locale == locale.to_sym ? 'selected' : nil + + # add the locale to the nav + nav += content_tag(:li, + content_tag(:a, _("languages.#{locale}"), href: 'javascript:void(0)'), + class: class_name, data: { locale: locale }).html_safe + + fields = '' + field_options.each do | name, __options | + _options = __options.deep_dup + # add the field + value = object.is_a?(Hash) ? object[locale.to_sym] : object.get_column_for_locale!(name, locale, false) + + # use the default value if we need to + if _options[:default].present? && value.blank? + value = _(_options[:default], locale: locale) + end + + _options[:index] = locale + _options[:lang] = locale + _options[:parent_options] = { lang: locale } + type = "#{_options[:type].to_s}" + _options.delete(:type) + + fields += self.send(type, name, value, _options).html_safe + end + + html += content_tag(:li, fields.html_safe, class: class_name, data: { locale: locale }).html_safe + end + + if options[:class].nil? + options[:class] = [] + elsif options[:class].is_a?(String) + options[:class] = [options[:class]] + end + options[:class] += ['translator', 'multi-field-translator'] + + (fieldset(nil, options) do + content_tag(:ul, nav.html_safe, class: 'locale-select').html_safe + + content_tag(:ul, html.html_safe, class: 'text-editors').html_safe + end).html_safe + end + + def translate_textarea(name, object, property = nil, options = {}) + html = '' + nav = '' + + # see if options was passed in as property + if options.blank? && property.is_a?(Hash) + options = property + property = nil + end + + # set the selected locale + selected_locale = (options[:locale] || object.locale || I18n.locale).to_sym + + I18n.backend.enabled_locales.each do | locale | + # ses if this should b the selected field + class_name = selected_locale == locale.to_sym ? 'selected' : nil + + # add the locale to the nav + nav += content_tag(:li, + content_tag(:a, _("languages.#{locale}"), href: 'javascript:void(0)'), + class: class_name, data: { locale: locale }).html_safe + + # add the field + value = object.is_a?(Hash) ? object[locale.to_sym] : object.get_column_for_locale!(name, locale, false) + + # use the default value if we need to + if options[:default].present? && value.blank? + value = _(options[:default], locale: locale) + end + + html += content_tag(:li, textarea(name, value, { + label: false, + edit_on: options[:edit_on], + parent_options: { + lang: locale + }, + index: locale + }).html_safe, class: class_name, data: { locale: locale }).html_safe + end + + if options[:class].nil? + options[:class] = [] + elsif options[:class].is_a?(String) + options[:class] = [options[:class]] + end + options[:class] += ['translator'] + + (fieldset(name, options) do + content_tag(:ul, nav.html_safe, class: 'locale-select').html_safe + + content_tag(:ul, html.html_safe, class: 'text-editors').html_safe + end).html_safe + end + + def textarea(name, value = nil, options = {}) + id = unique_id(name) + label_id = "#{id}-label" + description_id = nil + html = '' + + if options[:heading].present? + label_id = "#{name.to_s}-label" unless options[:label] + html += content_tag(:h3, _(options[:heading], :t, vars: options[:vars] || {}), id: label_id) + end + + if options[:label] == false + label_id = options[:labelledby] + elsif options[:label].present? + html += label_tag([name, id], nil, id: label_id) do + _(options[:label], :t, vars: options[:vars] || {}) + end + else + html += label_tag([name, id], nil, id: label_id) + end + + if options[:help].present? + description_id ||= "#{id}-desc" + html += content_tag(:div, _(options[:help], :s, 2), id: description_id, class: 'input-field-help') + end + + if options[:warning].present? + description_id ||= "#{id}-desc" + html += content_tag(:div, _(options[:warning], :s, 2), id: description_id, class: 'warning-info') + end + + aria = {} + aria[:labelledby] = label_id if label_id.present? + aria[:describedby] = description_id if description_id.present? + css_class = [ + options[:short] === true ? :short : nil + ].compact + + html_name = name.to_s + (options[:index] ? "[#{options[:index]}]" : '') + if options[:plain] + html += (text_area_tag html_name, value, + id: id, + lang: options[:lang], + aria: aria, + class: css_class + ) + else + html += content_tag(:div, value.present? ? value.html_safe : '', + id: id, + data: { name: html_name, 'edit-on': options[:edit_on] || :load }, + lang: options[:lang], + aria: aria, + tabindex: 0, + class: [:textarea] + css_class + ) + + add_stylesheet :editor + add_inline_script :pen + add_inline_script :markdown + add_inline_script :editor + end + + parent_options = options[:parent_options] || {} + if parent_options[:class].nil? + parent_options[:class] = [] + elsif parent_options[:class].is_a?(String) + parent_options[:class] = [parent_options[:class]] + end + + parent_options[:class] += ['text-area-field', 'input-field'] + html = content_tag(:div, html.html_safe, parent_options).html_safe + html += _original_content(options[:original_value], options[:original_lang]) if options[:original_value].present? + + return html.html_safe + end + + def fieldset(name = nil, options = {}, &block) + html = '' + label = '' + description = '' + description_id = nil + errors = '' + + if name.present? + if options[:label] != false + label = content_tag(:legend, + _(( + options[:label].is_a?(String) ? + options[:label] : + "forms.labels.generic.#{name}"), :t, vars: options[:vars] || {})) + end + + if options[:help].present? + description_id = unique_id("#{name.to_s}-desc") + description = content_tag(:div, _(options[:help], :s, 2), class: 'input-field-help', id: description_id) + end + + errors = (show_errors name) + end + + html = label + errors + description + content_tag(:div, class: :fieldgroup, &block) + + aria = description_id.present? ? { describedby: description_id } : nil + (content_tag(:fieldset, html.html_safe, + aria: aria, + class: ((options[:class] || []) + [ + options[:inline] ? :inline : nil, + options[:inline_label] ? 'inline-label' : nil, + errors.present? ? 'has-error' : nil + ]).compact + ) + ).html_safe + end + + def selectfield(name, value, select_options, options = {}) + unless select_options.first.is_a?(Array) + so = select_options + select_options = [] + so.each do | opt | + select_options << [ I18n.t("forms.options.#{name.to_s}.#{opt.to_s}"), opt] + end + end + textfield(name, value, options.merge({type: :select, options: select_options})) + end + + def telephonefield(name, value, options = {}) + textfield(name, value, options.merge({type: :telephone})) + end + + def numberfield(name, value, options = {}) + textfield(name, value, options.merge({type: :number})) + end + + def searchfield(name, value, options = {}) + textfield(name, value, options.merge({type: :search})) + end + + def userfield(name, value, options = {}) + # eventually this will be a dynamic field to find users, for now we'll just use emails + # add_inline_script :userfield + emailfield(name, value, options)# .merge({ + # parent_options: { class: ['user-field'] }, + # after: content_tag(:div, '', class: 'user-name') + # })) + end + + def emailfield(name, value, options = {}) + textfield(name, value, options.merge({type: :email})) + end + + def filefield(name, value, options = {}) + textfield(name, value, options.merge({type: :file})) + end + + def passwordfield(name, value, options = {}) + textfield(name, value, options.merge({type: :password})) + end + + def textfield(name, value, options = {}) + html = '' + id = unique_id(name) + html_name = name.to_s + (options[:index] ? "[#{options[:index]}]" : '') + description_id = nil + + if options[:heading].present? + description_id ||= "#{id.to_s}-desc" + html += content_tag(:h3, _(options[:heading], :t, vars: options[:vars] || {}), id: description_id) + end + + if options[:help].present? + description_id ||= "#{id.to_s}-desc" + html += content_tag(:div, _(options[:help], :s, 2, vars: options[:vars] || {}), class: 'input-field-help', id: description_id) + end + + html += show_errors name, value + + inside_label = '' + + if options[:type] == :file + inside_label = (content_tag(:div, class: 'file-field-selector') do + (options[:preview] ? content_tag(:img, nil, src: value.present? ? value.url : nil).html_safe : '').html_safe + + content_tag(:div, (value.present? ? File.basename(value.url) : (_'forms.labels.generic.no_file_selected')), class: 'file-field-name ' + (value.present? ? 'selected' : 'unselected')).html_safe + + content_tag(:a, (_'forms.actions.generic.select_file'), class: :button) + end) + end + + label_text = nil + if options[:label].present? + label_text = _(options[:label], :t, vars: options[:vars] || {}) + elsif options[:label] != false + label_text = (_"forms.labels.generic.#{name}") + elsif options[:type] == :select || options[:type] == :file + # add an empty label so that the drop down button will still appear + label_text = '' + end + + label_options = {} + # let the label be selected if the input is hidden + label_options[:tabindex] = 0 if options[:type] == :file + + unless label_text.nil? + html += label_tag id, (label_text + inside_label).html_safe + end + + input_options = { + id: id, + required: options[:required], + lang: options[:lang], + min: options[:min], + max: options[:max], + step: options[:step], + aria: description_id ? { describedby: description_id } : nil + } + + case name + when :address + input_options[:autocomplete] = 'address-line1' + when :name + input_options[:autocomplete] = 'name' + when :location + input_options[:autocomplete] = 'address-level2' + when :email + input_options[:autocomplete] = 'email' + when :phone + input_options[:autocomplete] = 'tel' + when :paypal_email_address, :paypal_username, :paypal_password, :paypal_signature + input_options[:autocomplete] = 'off' + end + + case options[:type] + when :select + option_list = options_for_select(options[:options], value) + + # make sure that we have an empty option if the select is required + if options[:required] && options[:options].first.present? && options[:options].first.last.present? + option_list = ('' + option_list).html_safe + end + html += select_tag(html_name, option_list, input_options) + when :file + add_inline_script :filefield + input_options[:tabindex] = '-1' + html += off_screen(file_field_tag html_name, input_options) + else + input_options[:autocomplete] = 'off' if options[:type] == :search + html += send("#{(options[:type] || :text).to_s}_field_tag", html_name, value, input_options) + end + + if options[:after].present? + html += options[:after].html_safe + end + + html = content_tag(:div, html.html_safe, + class: [ + "#{(options[:type] || :text).to_s}-field", + 'input-field', + value.present? ? nil : 'empty', + options[:big] ? 'big' : nil, + options[:small] ? 'small' : nil, + options[:stretch] ? 'stretch-item' : nil, + options[:full] ? 'full' : nil, + options[:inline_label] ? 'inline-label' : nil, + (@errors || {})[name].present? ? 'has-error' : nil + ].compact + (((options[:parent_options] || {})[:class]) || [])) + + html += _original_content(options[:original_value], options[:original_lang]) if options[:original_value].present? + + return html.html_safe + end + + def radiobuttons(name, boxes, value, label_key, options = {}) + checkboxes(name, boxes, [value], label_key, options.merge({radiobuttons: true})) + end + + def checkbox(name, value, label_key, options = {}) + checkboxes(name, [true], value, label_key, options) + end + + def unique_id(id) + id = id.to_s.gsub('[', '_').gsub(']', '') + + @_ids ||= {} + @_ids[id] ||= 0 + + new_id = id + + if @_ids[id] > 0 + new_id += "--#{@_ids[id]}" + end + + @_ids[id] += 1 + + return new_id + end + + def checkboxes(name, boxes, values, label_key, options = {}) + html = '' + boxes.map! { |box| box.is_a?(String) ? box.to_sym : box } + values.map! { |value| value.is_a?(String) ? value.to_sym : value } if values.is_a?(Array) + + label_id = nil + description_id = nil + + if options[:heading].present? + label_id ||= unique_id("#{name.to_s}-label") + html += content_tag(:h3, _(options[:heading], :t, vars: options[:vars] || {}), id: label_id) + end + + help = nil + + if options[:help].present? + description_id ||= unique_id("#{name.to_s}-desc") + help = content_tag(:div, _(options[:help], :s, 2), class: 'input-field-help', id: description_id) + end + + html += help if help.present? && !options[:right_help] + + boxes_html = '' + + labels = nil + is_single = !values.is_a?(Array) + if boxes.length > 0 + if boxes.first.is_a?(Array) + labels = boxes.map(&:first) + boxes = boxes.map(&:last) + end + elsif !boxes.first.is_a?(Integer) + values = values.present? ? values.map(&:to_s) : [] unless is_single + boxes = boxes.map(&:to_s) + end + + # convert the required value into a pure boolean + required = !!options[:required] + + boxes.each_with_index do |box, i| + checked = (is_single ? values.present? : values.include?(box)) + values -= [box] if checked && !is_single + id = nil + if options[:radiobuttons].present? + id = unique_id("#{name.to_s}_#{box}") + boxes_html += radio_button_tag(name, box, checked, id: id, required: required) + else + _name = (is_single ? name : "#{name.to_s}[#{box}]") + id = unique_id(_name) + boxes_html += check_box_tag(_name, 1, checked, data: { toggles: options[:toggles] }.compact, id: id, required: required) + end + + # we only need the required attribute on one element + required = false + + if labels.present? + label = labels[i] + elsif is_single + label = _(label_key.to_s) + elsif box.is_a?(Integer) + label = I18n.t(label_key.to_s)[box] + else + label = _("#{label_key.to_s}.#{box}") + end + + boxes_html += label_tag(id, label) + end + + if options[:other].present? && !is_single + id = nil + if options[:radiobuttons].present? + id = unique_id("#{name.to_s}_other") + boxes_html += radio_button_tag(name, :other, values.present?, id: id) + else + _name = "#{name.to_s}[other]" + id = unique_id(_name) + boxes_html += check_box_tag(_name, 1, values.present?, id: id) + end + boxes_html += label_tag id, + content_tag(:div, + text_field_tag("other_#{name.to_s}", values.first, placeholder: (_"#{label_key}.other"), required: values.present?), + class: 'other') + end + + html += content_tag(:fieldset, content_tag(:div, boxes_html.html_safe, + class: [ + 'check-box-field', + 'input-field', + options[:vertical] ? 'vertical' : nil, + options[:inline] ? 'inline' : nil, + options[:small] ? 'small' : nil, + options[:big] ? 'big' : nil + ].compact).html_safe, + aria: { + labelledby: label_id, + describedby: description_id + }, + class: [ + options[:centered] ? 'centered' : nil, + options[:right_help] ? 'right-help' : nil + ].compact + ) + + html += help if help.present? && options[:right_help] + + return html.html_safe + end + + def button(value = nil, options = {}, &block) + if !block_given? && (value.nil? || value.is_a?(Symbol)) + return button_tag(I18n.t("forms.actions.generic.#{(value || :button)}"), options) + end + + button_tag(value, options, &block) + end + + def conference_days_options(conference = nil) + conference ||= @this_conference || @conference + return [] unless conference + + dates = [] + day = conference.start_date - 7.days + last_day = conference.end_date + 7.days + + while day <= last_day + dates << day + day += 1.day + end + + return dates + end + + def conference_days_options_list(period, conference = nil, format = nil) + conference ||= @this_conference || @conference + return [] unless conference + + days = [] + + conference_days_options(conference).each do |day| + belongs_to_periods = [] + belongs_to_periods << :before if day <= conference.start_date + belongs_to_periods << :after if day >= conference.end_date + belongs_to_periods << :before_plus_one if day <= (conference.start_date + 1.day) + belongs_to_periods << :after_minus_one if day >= (conference.end_date - 1.day) + belongs_to_periods << :during if day >= conference.start_date && day <= conference.end_date + days << [date(day.to_date, format || :span_same_year_date_1), day.to_date] if belongs_to_periods.include?(period) + end + return days + end + + def registration_status_options_list(conference = nil) + conference ||= @this_conference || @conference + return [] unless conference + + options = Array.new + [:closed, :pre, :open].each do | opt | + options << [(_"forms.labels.generic.registration_statuses.#{opt}"), opt] + end + + return options + end + + def month_select(value = nil, args = {}) + options = (1..12).to_a.map { |month| [ (I18n.t "date.#{args[:format] || 'month_names'}")[month], month ] } + selectfield args[:name] || :month, value, options, args + end + + def month_day_select(value = nil, args = {}) + options = (1..31).to_a.map { |day| [ day, day ] } + selectfield args[:name] || :month_day, value, options, args + end + + def day_select(value = nil, args = {}) + selectfield :day, value, conference_days_options_list(:during, nil, args[:format]), args + end + + def hour_select(value = nil, args = {}, start_time = 8, end_time = 23.5, step = 0.5) + time = start_time + times = [] + while time <= end_time + times << [time(DateTime.now.midnight + time.hours), time] + time += step + end + selectfield :time, value, times, args + end + + def length_select(value = nil, args = {}, min_length = 0.5, max_length = 6, step = 0.5) + length = min_length + lengths = [] + while length <= max_length + lengths << [time_length(length), length] + length += step + end + selectfield :time_span, value, lengths, args + end + + def contact_reason_select + reasons = [] + [:website, :conference].each do | reason | + reasons << [ _("forms.labels.generic.reasons.#{reason.to_s}"), reason ] + end + [['Something about the website', :website]] + selectfield :reason, nil, reasons, required: true, heading: 'articles.contact.headings.reason', label: false, full: true + end + + def block_select(value = nil, args = {}) + blocks = {} + @workshop_blocks.each_with_index do | info, block | + info['days'].each do | day | + blocks[(day.to_i * 10) + block] = [ "#{(I18n.t 'date.day_names')[day.to_i]} Block #{block + 1}", "#{day}:#{block}" ] + end + end + selectfield :workshop_block, value, blocks.sort.to_h.values, args + end + + def location_select(value = nil, args = {}) + locations = [] + if @this_conference.event_locations.present? + @this_conference.event_locations.each do | location | + locations << [ location.title, location.id ] unless ((args[:invalid_locations] || []).include? location.id) + end + end + selectfield :event_location, value, locations, args + end + + def location_name(id) + begin + location = EventLocation.find(id) + rescue + return '' + end + return '' unless location.present? + return location.title + end + + def host_options_list(hosts) + options = [[nil, nil]] + hosts.each do | id, registration | + options << [registration.user.name, id] + end + return options + end + + def registration_step_menu + steps = current_registration_steps(@registration) + return '' unless steps.present? && steps.length > 1 + + pre_registration_steps = '' + post_registration_steps = '' + post_registration = false + + steps.each do | step | + text = _"articles.conference_registration.headings.#{step[:name].to_s}" + + if step[:name] == :workshops + post_registration = true + end + + h = content_tag :li, class: [step[:enabled] ? :enabled : nil, @register_template == step[:name] ? :current : nil, post_registration ? :post : :pre].compact do + if step[:enabled] + content_tag :div, (link_to text, register_step_path(@this_conference.slug, step[:name])).html_safe, class: :step + else + content_tag :div, text, class: :step + end + end + + if post_registration + post_registration_steps += h.html_safe + else + pre_registration_steps += h.html_safe + end + end + + html = ( + row class: 'flow-steps' do + columns do + (content_tag :ul, id: 'registration-steps' do + pre_registration_steps.html_safe + + post_registration_steps.html_safe + end).html_safe + end + end + ) + + return html.html_safe + end + + def broadcast_options(conference = nil) + conference ||= @this_conference || @conference + + options = [ + :registered, + :pre_registered, + :workshop_facilitators, + :unregistered, + :housing_providers, + :guests, + :all + ] + + if conference.registration_status != :open + options -= [:registered, :guests] + options -= [:pre_registered] unless conference.registration_status != :pre + end + + return options + end + + def show_errors(field, value = nil) + return '' unless @errors && @errors[field].present? + + error_txt = _"errors.messages.fields.#{field.to_s}.#{@errors[field]}", :s, vars: { value: value } + + content_tag(:div, error_txt, class: 'field-error').html_safe + end + + private + def _original_content(value, lang) + content_tag(:div, ( + content_tag(:h4, _('translate.content.Translation_of')) + + content_tag(:div, value, class: 'value', lang: lang) + ).html_safe, class: 'original-text') + end +end diff --git a/app/helpers/geocoder_helper.rb b/app/helpers/geocoder_helper.rb new file mode 100644 index 0000000..6f5d29a --- /dev/null +++ b/app/helpers/geocoder_helper.rb @@ -0,0 +1,100 @@ +module GeocoderHelper + def lookup_ip + if request.remote_ip == '127.0.0.1' || request.remote_ip == '::1' + session['remote_ip'] || (session['remote_ip'] = open("http://checkip.dyndns.org").first.gsub(/^.*\s([\d\.]+).*$/s, '\1').gsub(/[^\.\d]/, '')) + else + request.remote_ip + end + end + + def get_remote_location + Geocoder.search(session['remote_ip'] || (session['remote_ip'] = open("http://checkip.dyndns.org").first.gsub(/^.*\s([\d\.]+).*$/s, '\1').gsub(/[^\.\d]/, '')), language: 'en').first + end + + def lookup_ip_location + begin + if is_test? && ApplicationController::get_location.present? + Geocoder.search(ApplicationController::get_location, language: 'en').first + elsif request.remote_ip == '127.0.0.1' || request.remote_ip == '::1' + get_remote_location + else + request.location || get_remote_location + end + rescue + nil + end + end + + def potential_provider(registration) + return false unless registration.present? && registration.city.present? && registration.conference.present? + conditions = registration.conference.provider_conditions || + Conference.default_provider_conditions + return city_distance_less_than(registration.conference.city, registration.city, + conditions['distance']['number'], conditions['distance']['unit']) + end + + def city_distance_less_than(city1, city2, max_distance, unit) + return false if city1.nil? || city2.nil? + return true if city1.id == city2.id + return false if max_distance < 1 + return Geocoder::Calculations.distance_between( + [city1.latitude, city1.longitude], [city2.latitude, city2.longitude], + units: unit.to_sym) < max_distance + end + + def location(location, locale = I18n.locale) + return nil if location.blank? + + city = nil + region = nil + country = nil + if location.is_a?(Location) || location.is_a?(City) + country = location.country + region = location.territory + city = location.city + elsif location.data.present? && location.data['address_components'].present? + component_map = { + 'locality' => :city, + 'administrative_area_level_1' => :region, + 'country' => :country + } + location.data['address_components'].each do | component | + types = component['types'] + country = component['short_name'] if types.include? 'country' + region = component['short_name'] if types.include? 'administrative_area_level_1' + city = component['long_name'] if types.include? 'locality' + end + else + country = location.data['country_code'] + region = location.data['region_code'] + city = location.data['city'] + end + + # we need cities for our logic, don't let this continue if we don't have one + return nil unless city.present? + + hash = Hash.new + region_translation = region.present? && country.present? ? _("geography.subregions.#{country}.#{region}", locale: locale) : '' + country_translation = country.present? ? _("geography.countries.#{country}", locale: locale) : '' + hash[:city] = _!(city) if city.present? + hash[:region] = region_translation if region_translation.present? + hash[:country] = country_translation if country_translation.present? + + # return the formatted location or the first value if we only have one value + return hash.length > 1 ? _("geography.formats.#{hash.keys.join('_')}", locale: locale, vars: hash) : hash.values.first + end + + def location_link(location) + return '' unless location.present? && location.address.present? + content_tag(:a, (_!location.address), href: "http://www.google.com/maps/place/#{location.latitude},#{location.longitude}") + end + + def same_city?(location1, location2) + return false unless location1.present? && location2.present? + + location1 = location(location1) unless location1.is_a?(String) + location2 = location(location2) unless location2.is_a?(String) + + location1.eql? location2 + end +end diff --git a/app/helpers/i18n_helper.rb b/app/helpers/i18n_helper.rb new file mode 100644 index 0000000..1b463dd --- /dev/null +++ b/app/helpers/i18n_helper.rb @@ -0,0 +1,74 @@ + +module I18nHelper + def url_for_locale(locale, url = nil) + return url unless locale.present? + + unless url.present? + new_params = params.merge({action: (params[:_original_action] || params[:action])}) + new_params.delete(:_original_action) + + if Rails.env.development? || Rails.env.test? + return url_for(new_params.merge({lang: locale.to_s})) + end + + subdomain = Rails.env.preview? ? "preview-#{locale.to_s}" : locale.to_s + return url_for(new_params.merge(host: "#{subdomain}.bikebike.org")) + end + + return url if Rails.env.development? || Rails.env.test? + return "https://preview-#{locale.to_s}.bikebike.org#{url}" if Rails.env.preview? + "https://#{locale.to_s}.bikebike.org#{url}" + end + + def date(date, format = :long) + I18n.l(date.is_a?(String) ? Date.parse(date) : date, :format => format) + end + + def time(time, format = :short) + if time.is_a?(String) + time = Date.parse(time) + elsif time.is_a?(Float) || time.is_a?(Integer) + time = DateTime.now.midnight + time.hours + end + + I18n.l(time, format: format) + end + + def date_span(date1, date2) + key = 'same_month' + if date1.year != date2.year + key = 'different_year' + elsif date1.month != date2.month + key = 'same_year' + end + d1 = I18n.l(date1.to_date, format: "span_#{key}_date_1".to_sym) + d2 = I18n.l(date2.to_date, format: "span_#{key}_date_2".to_sym) + _('date.date_span', vars: {:date_1 => d1, :date_2 => d2}) + end + + def time_length(length) + hours = length.to_i + minutes = ((length - hours) * 60).to_i + hours = hours > 0 ? (I18n.t 'datetime.distance_in_words.x_hours', count: hours) : nil + minutes = minutes > 0 ? (I18n.t 'datetime.distance_in_words.x_minutes', count: minutes) : nil + return hours.present? ? (minutes.present? ? (I18n.t 'datetime.distance_in_words.x_and_y', x: hours, y: minutes) : hours) : minutes + end + + def hour_span(time1, time2) + (time2 - time1) / 3600 + end + + def hours(time1, time2) + time_length hour_span(time1, time2) + end + + def money(amount) + return _!('$0.00') if amount == 0 + _!((amount * 100).to_i.to_s.gsub(/^(.*)(\d\d)$/, '$\1.\2')) + end + + def percent(p) + return _!('0.00%') if p == 0 + _!((p * 10000).to_i.to_s.gsub(/^(.*)(\d\d)$/, '\1.\2%')) + end +end diff --git a/app/helpers/page_helper.rb b/app/helpers/page_helper.rb new file mode 100644 index 0000000..a31ebe4 --- /dev/null +++ b/app/helpers/page_helper.rb @@ -0,0 +1,88 @@ + +module PageHelper + @@no_banner = true + @@banner_image = nil + @@has_content = true + @@body_class = nil + + def title(page_title) + content_for(:title) { page_title.to_s } + end + + def description(page_description) + content_for(:description) { page_description.to_s } + end + + def banner_image(banner_image, name: nil, id: nil, user_id: nil, src: nil) + @@no_banner = false + @@banner_image = banner_image + content_for(:banner_image) { banner_image.to_s } + end + + def has_content? + @@has_content + end + + def add_stylesheet(sheet) + @stylesheets ||= [] + @stylesheets << sheet unless @stylesheets.include?(sheet) + end + + def stylesheets + html = '' + Rack::MiniProfiler.step('inject_css') do + html += inject_css! + end + (@stylesheets || []).each do |css| + Rack::MiniProfiler.step("inject_css #{css}") do + html += inject_css! css.to_s + end + end + html += stylesheet_link_tag 'i18n-debug' if request.params['i18nDebug'] + return html.html_safe + end + + def add_inline_script(script) + @_inline_scripts ||= [] + script = Rails.application.assets.find_asset("#{script.to_s}.js").to_s + @_inline_scripts << script unless @_inline_scripts.include?(script) + end + + def inline_scripts + return '' unless @_inline_scripts.present? + "".html_safe + end + + def dom_ready(&block) + content_for(:dom_ready, &block) + end + + def body_class(c) + @@body_class ||= Array.new + @@body_class << (c.is_a?(Array) ? c.join(' ') : c) + end + + def page_style + classes = Array.new + + classes << 'no-content' unless @@has_content + classes << 'has-banner-image' if @@banner_image + classes << @@body_class.join(' ') if @@body_class + + if params[:controller] + classes << params[:action] + unless params[:controller] == 'application' + classes << params[:controller] + + if params[:action] + classes << "#{params[:controller]}-#{params[:action]}" + end + end + end + return classes + end + + def yield_or_default(section, default = '') + content_for?(section) ? content_for(section) : default + end +end diff --git a/app/helpers/registration_helper.rb b/app/helpers/registration_helper.rb index c5ec3fa..0c81ba4 100644 --- a/app/helpers/registration_helper.rb +++ b/app/helpers/registration_helper.rb @@ -1,4 +1,16 @@ module RegistrationHelper + def registration_steps(conference = @conference) + { + pre: [:policy, :contact_info, :workshops], + open: [:policy, :contact_info, :questions, :hosting, :payment, :workshops] + }[@this_conference.registration_status] + end + + def registration_status(registration) + return :unregistered if registration.nil? + return registration.status + end + def current_registration_steps(registration = @registration) return nil unless registration.present? @@ -6,32 +18,36 @@ module RegistrationHelper current_steps = [] disable_steps = false completed_steps = registration.steps_completed || [] - # registration_complete = registration_complete?(registration) if potential_provider(registration) steps -= [:questions] else steps -= [:hosting] end + + steps -= [:payment] unless registration.conference.paypal_email_address.present? && registration.conference.paypal_username.present? && registration.conference.paypal_password.present? && registration.conference.paypal_signature.present? + steps -= [:payment, :workshops] if registration.is_attending == 'n' - steps.each do | step | + steps.each do |step| # disable the step if we've already found an incomplete step - enabled = !disable_steps# || registration_complete + # enabled = !disable_steps# || registration_complete # record whether or not we've found an incomplete step - disable_steps ||= !completed_steps.include?(step.to_s) && ![:payment, :workshops].include?(step) current_steps << { name: step, - enabled: enabled + enabled: !disable_steps } + disable_steps ||= !completed_steps.include?(step.to_s)# && ![:payment, :workshops].include?(step) end + return current_steps end def current_step(registration = @registration) completed_steps = registration.steps_completed || [] last_step = nil - (current_registration_steps(registration) || []).each do | step | + steps = current_registration_steps(registration) || [] + steps.each do | step | # return the last enabled step if this one is disabled return last_step unless step[:enabled] @@ -43,6 +59,6 @@ module RegistrationHelper end # if all else fails, return the first step - return registration_steps(registration.conference).last + return steps.last[:name] end end diff --git a/app/helpers/table_helper.rb b/app/helpers/table_helper.rb new file mode 100644 index 0000000..4b20d3c --- /dev/null +++ b/app/helpers/table_helper.rb @@ -0,0 +1,366 @@ +module TableHelper + def html_edit_table(excel_data, options = {}) + attributes = { class: options[:class], id: options[:id] } + attributes[:data] = { 'update-url' => options[:editable] } if options[:editable].present? + + if options[:column_names].is_a? Hash + return content_tag(:table, attributes) do + max_columns = 0 + column_names = {} + (content_tag(:thead) do + headers = '' + options[:column_names].each do | header_name, columns | + column_names[header_name] ||= [] + headers += content_tag(:th, excel_data[:keys][header_name].present? ? _(excel_data[:keys][header_name]) : '', colspan: 2) + row_count = columns.size + columns.each do | column | + column_names[header_name] << column + if (options[:row_spans] || {})[column].present? + row_count += (options[:row_spans][column] - 1) + for i in 1...options[:row_spans][column] + column_names[header_name] << false + end + end + end + max_columns = row_count if row_count > max_columns + end + content_tag(:tr, headers.html_safe) + end) + (content_tag(:tbody) do + rows = '' + + for i in 0...max_columns + columns_html = '' + column_names.each do | header_name, columns | + column = columns[i] + if column.present? + attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } } + if (options[:row_spans] || {})[column].present? + attributes[:rowspan] = options[:row_spans][column] + end + columns_html += content_tag(:th, excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '', rowspan: attributes[:rowspan]) + + edit_column(nil, column, nil, attributes, excel_data, options) + elsif column != false + columns_html += content_tag(:td, ' ', colspan: 2, class: :empty) + end + end + rows += content_tag(:tr, columns_html.html_safe, { class: 'always-edit', data: { key: '' } }) + end + rows.html_safe + end) + end + else + return content_tag(:table, attributes) do + (content_tag(:tbody) do + rows = '' + excel_data[:columns].each do |column| + if (excel_data[:column_types] || {})[column] != :table && ((options[:column_names] || []).include? column) + rows += content_tag(:tr, { class: 'always-edit', data: { key: '' } }) do + attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } } + columns = content_tag(:th, excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '') + + edit_column(nil, column, nil, attributes, excel_data, options) + end + end + end + rows.html_safe + end) + end + end + end + + def html_table(excel_data, options = {}) + options[:html] = true + attributes = { class: options[:class], id: options[:id] } + attributes[:data] = { 'update-url' => options[:editable] } if options[:editable].present? + content_tag(:table, attributes) do + (content_tag(:thead) do + content_tag(:tr, excel_header_columns(excel_data)) + end) + + content_tag(:tbody, excel_rows(excel_data, {}, options)) + end + end + + def excel_table(excel_data) + format_xls 'table' do + workbook use_autowidth: true + format bg_color: '333333' + format 'td', font_name: 'Calibri', fg_color: '333333' + format 'th', font_name: 'Calibri', b: true, bg_color: '333333', fg_color: 'ffffff' + format 'th.sub-table', font_name: 'Calibri', b: true, bg_color: 'DDDDDD', fg_color: '333333' + format 'td.datetime', num_fmt: 22, font_name: 'Courier New', sz: 10, fg_color: '333333' + format 'td.date.day', num_fmt: 14, font_name: 'Courier New', sz: 10, fg_color: '333333' + format 'td.money', num_fmt: 2, font_name: 'Courier New', sz: 10, fg_color: '333333' + format 'td.number', font_name: 'Courier New', sz: 10, fg_color: '333333' + format 'td.bold', font_name: 'Calibri', fg_color: '333333', b: true + end + + content_tag(:table) do + (content_tag(:thead) do + content_tag(:tr, excel_header_columns(excel_data)) + end) + + content_tag(:tbody, excel_rows(excel_data)) + end + end + + def excel_header_columns(data, padding = {}, class_name = nil) + columns = '' + + data[:columns].each do |column| + unless data[:column_types].present? && data[:column_types][column] == :table + columns += content_tag(:th, data[:keys][column].present? ? _(data[:keys][column]) : '', class: class_name) + end + end + + pad_columns(columns, padding, :th) + end + + def excel_empty_row(data, padding = {}) + columns = '' + + data[:columns].each do |column| + unless data[:column_types].present? && data[:column_types][column] == :table + columns += content_tag(:td) + end + end + + content_tag(:tr, pad_columns(columns, padding)) + end + + def pad_columns(columns, padding, column_type = :td) + left = '' + + for i in 1..(padding['left'] || 0) + left += content_tag(:td) + end + + right = '' + for i in 1..(padding['right'] || 0) + right += content_tag(:td) + end + + (left + columns + right).html_safe + end + + def excel_columns(row, data, padding = {}, options = {}) + columns = '' + + data[:columns].each do |column| + value = row[column].present? ? (_!row[column].to_s) : '' + class_name = nil + is_sub_table = false + + if data[:column_types].present? && data[:column_types][column].present? + if data[:column_types][column] == :table + is_sub_table = true + else + class_name = data[:column_types][column] + end + end + + unless is_sub_table + attributes = { class: [class_name] } + if options[:html] && row[:html_values].present? && row[:html_values][column].present? + value = row[:html_values][column] + end + + if options[:editable] + attributes[:data] = { 'column-id' => column } + end + + if (options[:column_names] || []).include? column + attributes[:tabindex] = 0 + end + + columns += content_tag(:td, value, attributes) + end + end + + pad_columns(columns, padding) + end + + def editor_columns(row, data, padding = {}, options = {}) + columns = '' + + data[:columns].each do |column| + value = row[column].present? ? (_!row[column].to_s) : '' + class_name = nil + is_sub_table = false + + if data[:column_types].present? && data[:column_types][column].present? + if data[:column_types][column] == :table + is_sub_table = true + else + class_name = data[:column_types][column] + end + end + + unless is_sub_table + attributes = { class: [class_name] } + + if options[:editable] + attributes[:data] = { 'column-id' => column } + end + + if (options[:column_names] || []).include? column + columns += edit_column(row, column, value, attributes, data, options) + else + columns += content_tag(:td, value, attributes) + end + + end + end + + pad_columns(columns, padding) + end + + def edit_column(row, column, value, attributes, data, options) + attributes[:class] << 'has-editor' + raw_value = row.present? ? (row[:raw_values][column] || value) : nil + + if row.present? && options[:html] && row[:html_values].present? && row[:html_values][column].present? + value = row[:html_values][column] + end + + editor_attributes = { class: 'cell-editor', data: { value: raw_value.to_s } } + + # create the control but add the original value to set the width and height + editor_value = content_tag(:div, value, class: 'value') + if (options[:column_options] || {})[column].present? + value = (editor_value.html_safe + select_tag(column, options_for_select([['', '']] + options[:column_options][column], raw_value), editor_attributes)).html_safe + elsif data[:column_types][column] == :text + editor_attributes[:name] = column + value = (editor_value.html_safe + content_tag(:textarea, raw_value, editor_attributes)).html_safe + else + editor_attributes[:name] = column + editor_attributes[:value] = raw_value + editor_attributes[:required] = :required if (options[:required_columns] || []).include? column + type = data[:column_types][column] || :unknown + editor_attributes[:type] = { money: :number, number: :number, email: :email }[type] || :text + value = (editor_value.html_safe + content_tag(:input, nil, editor_attributes)).html_safe + end + + return content_tag(:td, value, attributes) + end + + def excel_sub_tables(row, data, padding = {}, options = {}) + rows = '' + + # shift the table right + new_padding = { + 'left' => (padding['right'] || 0) + 1, + 'right' => (padding['right'] || 0) - 1 + } + + data[:columns].each do |column| + if data[:column_types].present? && data[:column_types][column] == :table + rows += content_tag(:tr, excel_header_columns(row[column], new_padding, 'sub-table')) + rows += excel_rows(row[column], new_padding) + rows += excel_empty_row(row[column], new_padding) + end + + end + + rows.html_safe + end + + def excel_rows(data, padding = {}, options = {}) + rows = '' + data[:data].each do |row| + attributes = {} + + if options[:primary_key].present? + attributes[:data] = { key: row[options[:primary_key]] } + end + + attributes[:class] = [] + + if options[:editable] + attributes[:class] << :editable + end + + rows += content_tag(:tr, excel_columns(row, data, padding, options), attributes) + + excel_sub_tables(row, data, padding) + rows += content_tag(:tr, editor_columns(row, data, padding, options), class: :editor) if options[:editable] + end + rows.html_safe + end + + def registrations_edit_table_options + { + id: 'create-table', + class: ['registrations', 'admin-edit', 'always-editing'], + primary_key: :id, + column_names: { + contact_info: [ + :name, + :email, + :is_subscribed, + :city, + :preferred_language + ] + User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym }, + questions: [ + :registration_fees_paid, + :is_attending, + :arrival, + :departure, + :housing, + :bike, + :food, + :companion_email, + :allergies, + :other + ], + hosting: [ + :can_provide_housing, + :address, + :phone, + :first_day, + :last_day + ] + ConferenceRegistration.all_spaces + + ConferenceRegistration.all_considerations + [ + :notes + ] + }, + row_spans: { + allergies: 3, + other: 2 + }, + required_columns: [:name, :email], + editable: administration_update_path(@this_conference.slug, @admin_step), + column_options: @column_options + } + end + + def registrations_table_options + { + id: 'search-table', + class: ['registrations', 'admin-edit'], + primary_key: :id, + column_names: [ + :registration_fees_paid, + :is_attending, + :is_subscribed, + :city, + :preferred_language, + :arrival, + :departure, + :housing, + :bike, + :food, + :companion_email, + :allergies, + :other, + :can_provide_housing, + :address, + :phone, + :first_day, + :last_day, + :notes + ] + + User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym } + + ConferenceRegistration.all_spaces + + ConferenceRegistration.all_considerations, + editable: administration_update_path(@this_conference.slug, @admin_step), + column_options: @column_options + } + end +end diff --git a/app/helpers/widgets_helper.rb b/app/helpers/widgets_helper.rb new file mode 100644 index 0000000..9f7bb35 --- /dev/null +++ b/app/helpers/widgets_helper.rb @@ -0,0 +1,288 @@ +require 'redcarpet' + +module WidgetsHelper + + def m(*args) + _(*args) { |t| + markdown(t) + } + end + + def markdown(object, attribute = nil) + return '' unless object + content = attribute ? object.send(attribute.to_s) : object + @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML.new({ + filter_html: true, + hard_wrap: true, + space_after_headers: true, + fenced_code_blocks: true, + link_attributes: { target: "_blank" } + }), { + autolink: true, + disable_indented_code_blocks: true, + superscript: true + }) + @markdown.render(content).html_safe + end + + def paragraph(object, attribute = nil) + return '' unless object + content = attribute ? object.send(attribute.to_s) : object + result = '' + if content =~ /<(p|span|h\d|div)[^>]*>/ + result = content.gsub(/\s*(style|class|id|width|height|font)=\".*?\"/, '') + .gsub(/ /, ' ') + .gsub(/<(\/)?\s*h\d\s*>/, '<\1h3>') + .gsub(/

    (.*?)\s*()+/, '

    \1

    ') + .gsub(/]*>\s*(.*?)\s*<\/span>/, '\1') + .gsub(/

    \s*<\/p>/, '') + .gsub(/<(\/)?div>/, '<\1p>') + if !(result =~ /]*>/) + result = '

    ' + result + '

    ' + end + else + result = markdown(object, attribute) + end + result.html_safe + end + + def nav_link(link, title = nil, class_name = nil) + if title.nil? && link.is_a?(Symbol) + title = link + link = send("#{link.to_s}_path") + end + if class_name.nil? && title.is_a?(Symbol) + class_name = title + end + title = _"page_titles.#{title.to_s.titlecase.gsub(/\s/, '_')}" + classes = [] + classes << class_name if class_name.present? + classes << "strlen-#{strip_tags(title).length}" + classes << 'current' if request.fullpath.start_with?(link.gsub(/^(.*?)\/$/, '\1')) + link_to "#{title}".html_safe, link, :class => classes + end + + def data_set(header_type, header_key, attributes = {}, &block) + raw_data_set(header_type, _(header_key), attributes, &block) + end + + def raw_data_set(header_type, header, attributes = {}, &block) + attributes[:class] = attributes[:class].split(' ') if attributes[:class].is_a?(String) + attributes[:class] = [attributes[:class].to_s] if attributes[:class].is_a?(Symbol) + attributes[:class] ||= [] + attributes[:class] << 'data-set' + content_tag(:div, attributes) do + content_tag(header_type, header, class: 'data-set-key') + + content_tag(:div, class: 'data-set-value', &block) + end + end + + def admin_update_form(options = {}, &block) + form_tag(administration_update_path(@this_conference.slug, @admin_step), options, &block) + end + + def interest_button(workshop) + interested = workshop.interested?(current_user) ? :remove_interest : :show_interest + id = "#{interested.to_s.gsub('_', '-')}-#{workshop.id}" + return (off_screen (_"forms.actions.aria.#{interested.to_s}"), id) + + (button interested, :value => :toggle_interest, :class => (workshop.interested?(current_user) ? :delete : :add), aria: { labelledby: id }) + end + + def interest_text(workshop) + if workshop.interested?(current_user) + return _'articles.workshops.info.you_are_interested_count', :vars => {:count => (workshop.interested_count - 1)} + end + + return _'articles.workshops.info.interested_count', :vars => {:count => workshop.interested_count} + end + + def host_guests_table(registration) + id = registration.id + html = '' + + @housing_data[id][:guests].each do | area, guests | + guest_rows = '' + guests.each do | guest_id, guest | + status_html = '' + + @housing_data[id][:guest_data][guest_id][:errors].each do | error, value | + if value.is_a?(Array) + value.each do | v | + status_html += content_tag(:li, _("errors.messages.housing.space.#{error.to_s}", vars: v)) + end + else + status_html += content_tag(:li, _("errors.messages.housing.space.#{error.to_s}", vars: value)) + end + end + + @housing_data[id][:guest_data][guest_id][:warnings].each do | error, value | + if value.is_a?(Array) + value.each do | v | + status_html += content_tag(:li, _("warnings.messages.housing.space.#{error.to_s}", v)) + end + else + status_html += content_tag(:li, _("warnings.messages.housing.space.#{error.to_s}", vars: value)) + end + end + + if status_html.present? + status_html = content_tag(:ul, status_html.html_safe) + end + + guest_rows += content_tag :tr, id: "hosted-guest-#{guest_id}" do + (content_tag :td, guest[:guest].user.name) + + (content_tag :td do + (guest[:guest].city + + (content_tag :a, (_'actions.workshops.Remove'), href: '#', class: 'remove-guest', data: { guest: guest_id })).html_safe + end) + + (content_tag :td, status_html.html_safe, class: [:state, status_html.present? ? :unhappy : :happy]) + end + end + + space_size = (@housing_data[id][:space][area] || 0) + + # add empty rows to represent empty guest spots + for i in guests.size...space_size + guest_rows += content_tag :tr, class: 'empty-space' do + (content_tag :td, ' '.html_safe, colspan: 2) + + (content_tag :td) + end + end + + status_html = '' + if @housing_data[id][:warnings].present? && @housing_data[id][:warnings][:space].present? && @housing_data[id][:warnings][:space][area].present? + @housing_data[id][:warnings][:space][area].each do | w | + status_html += content_tag(:li, _("warnings.messages.housing.space.#{w.to_s}")) + end + end + if status_html.present? + status_html = content_tag(:ul, status_html.html_safe) + end + + html += content_tag :tr do + (content_tag :th, (_"forms.labels.generic.#{area}"), colspan: 2) + + (content_tag :th, status_html.html_safe, class: [:state, status_html.present? ? :unhappy : :happy]) + end + html += guest_rows + html += content_tag :tr, class: 'place-guest' do + content_tag :td, class: guests.size >= space_size ? 'full' : nil, colspan: 3 do + content_tag :a, (_'forms.actions.generic.place_guest'), class: 'select-guest', href: '#', data: { host: id, space: area } + end + end + end + + content_tag :table, html.html_safe, class: 'host-table' + end + + def host_guests_widget(registration) + html = '' + classes = ['host'] + + id = registration.id + @housing_data[id][:guests].each do | area, guests | + max_space = @housing_data[id][:space][area] || 0 + area_name = (_"forms.labels.generic.#{area}") + status_html = '' + if @housing_data[id][:warnings].present? && @housing_data[id][:warnings][:space].present? && @housing_data[id][:warnings][:space][area].present? + @housing_data[id][:warnings][:space][area].each do | w | + status_html += content_tag(:div, _("warnings.housing.space.#{w.to_s}"), class: 'warning') + end + end + space_html = content_tag(:h5, area_name + _!(" (#{guests.size.to_s}/#{max_space.to_s})") + status_html.html_safe) + guest_items = '' + guests.each do | guest_id, guest | + guest_items += content_tag(:li, guest[:guest].user.name, id: "hosted-guest-#{guest_id}") + end + space_html += content_tag(:ul, guest_items.html_safe) + space_html += button :place_guest, type: :button, value: "#{area}:#{id}", class: [:small, 'place-guest', 'on-top-only', guests.size >= max_space ? (guests.size > max_space ? :overbooked : :booked) : nil, max_space > 0 ? nil : :unwanted] + html += content_tag(:div, space_html, class: [:space, area, max_space > 0 || guests.size > 0 ? nil : 'on-top-only']) + end + + classes << 'status-warning' if @housing_data[id][:warnings].present? + classes << 'status-error' if @housing_data[id][:errors].present? + + return { html: html.html_safe, class: classes.join(' ') } + end + + def signin_link + @login_dlg ||= true + link_to (_'forms.actions.generic.login'), settings_path, data: { 'sign-in': true } + end + + def link_with_confirmation(link_text, confirmation_text, path, args = {}) + @confirmation_dlg ||= true + args[:data] ||= {} + args[:data][:confirmation] = true + link_to path, args do + (link_text.to_s + content_tag(:template, confirmation_text, class: 'message')).html_safe + end + end + + def link_info_dlg(link_text, info_text, info_title, args = {}) + @info_dlg ||= true + args[:data] ||= {} + args[:data]['info-title'] = info_title + args[:data]['info-text'] = true + content_tag(:a, args) do + (link_text.to_s + content_tag(:template, info_text, class: 'message')).html_safe + end + end + + def button_with_confirmation(button_name, confirmation_text = nil, args = {}) + if confirmation_text.is_a? Hash + args = confirmation_text + confirmation_text = nil + end + + confirmation_text ||= (_"forms.confirmations.#{button_name.to_s}", :p) + @confirmation_dlg ||= true + args[:data] ||= {} + args[:data][:confirmation] = true + button button_name, args do + ((_"forms.actions.generic.#{button_name.to_s}") + content_tag(:template, confirmation_text, class: 'message')).html_safe + end + end + + def richtext(text, reduce_headings = 2) + return '' unless text.present? + return _!(text). + gsub(/<(\/?)h4>/, '<\1h' + (reduce_headings + 4).to_s + '>'). + gsub(/<(\/?)h3>/, '<\1h' + (reduce_headings + 3).to_s + '>'). + gsub(/<(\/?)h2>/, '<\1h' + (reduce_headings + 2).to_s + '>'). + gsub(/<(\/?)h1>/, '<\1h' + (reduce_headings + 1).to_s + '>'). + html_safe + end + + def truncate(text) + strip_tags(text.gsub('>', '> ')).gsub(/^(.{40,60})\s.*$/m, '\1…').html_safe + end + + def companion(registration) + if registration.housing_data.present? && registration.housing_data['companions'].present? && registration.housing_data['companions'].first.present? + companion_user = User.find_user(registration.housing_data['companions'].first) + + if companion_user.present? + cr = ConferenceRegistration.where(user_id: companion_user.id).order(created_at: :desc).limit(1).first + + if cr.present? && ((cr.steps_completed || []).include? 'questions') + return companion_user + end + end + return :unregistered + end + return nil + end + + def comment(comment) + add_inline_script :time + add_js_translation('datetime.distance_in_words') + + content_tag(:div, class: 'comment-body') do + content_tag(:h4, _!(comment.user.name), class: 'comment-title') + + content_tag(:time, time(comment.created_at, :default), datetime: comment.created_at.to_s) + + content_tag(:div, class: 'comment-text') do + _!(markdown comment.comment) + end + end + end +end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index d01221a..bdd9ea2 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -1,12 +1,12 @@ require 'diffy' class UserMailer < ActionMailer::Base - add_template_helper(ApplicationHelper) include LinguaFrancaHelper + add_template_helper(ApplicationHelper) before_filter :set_host - default from: "Bike!Bike! " + default from: "Bike!Bike! " def email_confirmation(confirmation) @confirmation = EmailConfirmation.find(confirmation) if confirmation.present? diff --git a/app/models/authentication.rb b/app/models/authentication.rb deleted file mode 100644 index 69a2df1..0000000 --- a/app/models/authentication.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Authentication < ActiveRecord::Base - belongs_to :user -end diff --git a/app/models/city.rb b/app/models/city.rb deleted file mode 100644 index ed82ef8..0000000 --- a/app/models/city.rb +++ /dev/null @@ -1,166 +0,0 @@ -require 'geocoder' -require 'geocoder/railtie' -require 'geocoder/calculations' - -Geocoder::Railtie.insert - -class City < ActiveRecord::Base - geocoded_by :address - translates :city - - reverse_geocoded_by :latitude, :longitude, :address => :full_address - after_validation :geocode, if: ->(obj){ obj.country_changed? or obj.territory_changed? or obj.city_changed? or obj.latitude.blank? or obj.longitude.blank? } - - def address - ([city!, territory, country] - [nil, '']).join(', ') - end - - def get_translation(locale) - location = Geocoder.search(address, language: locale.to_s).first - - # if the service lets us down, return nil - return nil unless location.present? - - searched_component = false - location.data['address_components'].each do | component | - # city is usually labeled a 'locality' but sometimes this is missing and only 'colloquial_area' is present - if component['types'].first == 'locality' - return component['short_name'] - end - - if component['types'] == location.data['types'] - searched_component = component['short_name'] - end - end - - # return the type we searched for but it's still possible that it will be false - searched_component - end - - # this method will get called automatically if a translation is asked for but not found - def translate_city(locale) - translation = get_translation(locale) - - # if we found it, set it - if translation.present? - set_column_for_locale(:city, locale, translation) - save! - end - - return translation - end - - def to_s - ([ - city, - territory.present? && country.present? ? I18n.t("geography.subregions.#{country}.#{territory}") : '', - country.present? ? I18n.t("geography.countries.#{country}") : '' - ] - ['', nil]).join(', ') - end - - def self.search(str) - cache = CityCache.search(str) - - # return the city if this search is in our cache - return cache.city if cache.present? - - # look up the city in the geocoder - location = Geocoder.search(str, language: 'en').first - - # return nil to indicate that the service is down - return nil unless location.present? - # see if the city is already present in our database - city = City.find_by_place_id(location.data['place_id']) - - # if we didn't find a match by place id, collect the city, territory, and country from the result - unless city.present? - # google names things differently than we do, we'll look for these items - component_alises = { - 'locality' => :city, - 'administrative_area_level_1' => :territory, - 'country' => :country - } - - # and populate this map to eventually create the city if we need to - city_data = { - locale: :en, - latitude: location.data['geometry']['location']['lat'], - longitude: location.data['geometry']['location']['lng'], - place_id: location.data['place_id'] - } - - # these things are definitely not cities, make sure we don't think they're one - not_a_city = [ - 'administrative_area_level_1', - 'country', - 'street_address', - 'street_number', - 'postal_code', - 'postal_code_prefix', - 'route', - 'intersection', - 'premise', - 'subpremise', - 'natural_feature', - 'airport', - 'park', - 'point_of_interest', - 'bus_station', - 'train_station', - 'transit_station', - 'room', - 'post_box', - 'parking', - 'establishment', - 'floor' - ] - - searched_component = nil - location.data['address_components'].each do | component | - property = component_alises[component['types'].first] - city_data[property] = component['short_name'] if property.present? - - # ideally we will find the component that is labeled a locality but - # if that fails we will select what was searched for, hopefully they searched for a city - # and not an address or country - # some places are not labeled 'locality', search for 'Halifax NS' for example and you will - # get 'administrative_area_level_2' since Halifax is a municipality - if component['types'] == location.data['types'] && !not_a_city.include?(component['types'].first) - searched_component = component['short_name'] - end - end - - # fall back to the searched component - city_data[:city] ||= searched_component - - # we need to have the city and country at least - return false unless city_data[:city].present? && city_data[:country].present? - - # one last attempt to make sure we don't already have a record of this city - city = City.where(city: city_data[:city], territory: city_data[:territory], country: city_data[:country]).first - - # only if we still can't find the city, then save it as a new one - unless city.present? - city = City.new(city_data) - # if we found exactly what we were looking for, keep these location details - # otherwise we may have searched for 'The Bronx' and set the sity the 'New York' but these details will be about The Bronx - # so if we try to show New York on a map it will always point to The Bronx, not very fair to those from Staten Island - unless city_data[:city] == searched_component - new_location = Geocoder.search(str, language: 'en').first - city.latitude = new_location.data['geometry']['location']['lat'] - city.longitude = new_location.data['geometry']['location']['lng'] - city.place_id = new_location.data['place_id'] - end - - # and create the new city - city.save! - end - end - - # save this to our cache - CityCache.cache(str, city.id) - - # and return it - return city - end -end diff --git a/app/models/city_cache.rb b/app/models/city_cache.rb deleted file mode 100644 index 1e72f8d..0000000 --- a/app/models/city_cache.rb +++ /dev/null @@ -1,21 +0,0 @@ -class CityCache < ActiveRecord::Base - self.table_name = :city_cache - - belongs_to :city - - # look for a term to see if its already been searched for - def self.search(str) - CityCache.find_by_search(normalize_string(str)) - end - - # cache this search term - def self.cache(str, city_id) - CityCache.create(city_id: city_id, search: normalize_string(str)) - end - - private - def self.normalize_string(str) - # remove accents, unnecessary whitespace, punctuation, and lowcase tje string - I18n.transliterate(str).gsub(/[^\w\s]/, '').gsub(/\s\s+/, ' ').strip.downcase - end -end diff --git a/app/models/comment.rb b/app/models/comment.rb deleted file mode 100644 index a82e08c..0000000 --- a/app/models/comment.rb +++ /dev/null @@ -1,37 +0,0 @@ -class Comment < ActiveRecord::Base - belongs_to :user - - def comment_object - model_type.classify.constantize.find(model_id) - end - - def set_model(model) - model_type = model.class.name.tableize - model_id = model.id - end - - def self.for(model) - where(model_type: model.class.name.tableize, model_id: model.id).order(created_at: :asc) - end - - def self.create_for(model, user, comment) - create( - model_type: model.class.name.tableize, - model_id: model.id, - user_id: user.id, - comment: comment - ) - end - - def add_comment(user, comment) - Comment.create_for(self, user, comment) - end - - def comments - Comment.for(self) - end - - def reply? - model_type == 'comments' - end -end diff --git a/app/models/conference.rb b/app/models/conference.rb deleted file mode 100644 index 5bb10bc..0000000 --- a/app/models/conference.rb +++ /dev/null @@ -1,160 +0,0 @@ -class Conference < ActiveRecord::Base - translates :info, :title, :payment_message - - mount_uploader :cover, CoverUploader - mount_uploader :poster, PosterUploader - - belongs_to :conference_type - belongs_to :city - - has_many :conference_host_organizations, dependent: :destroy - has_many :organizations, through: :conference_host_organizations - has_many :conference_administrators, dependent: :destroy - has_many :administrators, through: :conference_administrators, source: :user - has_many :event_locations - - has_many :workshops - - accepts_nested_attributes_for :conference_host_organizations, reject_if: proc {|u| u[:organization_id].blank?}, allow_destroy: true - - before_create :make_slug, :make_title - - def to_param - slug - end - - def host_organization?(org) - return false unless org.present? - org_id = org.is_a?(Organization) ? org.id : org - - organizations.each do |o| - return true if o.id = org_id - end - - return false - end - - def host?(user) - if user.present? - return true if user.administrator? - - conference_administrators.each do |u| - return true if user.id == u.id - end - - organizations.each do |o| - return true if o.host?(user) - end - end - return false - end - - def url(action = :show) - path(action) - end - - def path(action = :show) - action = action.to_sym - '/conferences/' + conference_type.slug + '/' + slug + (action == :show ? '' : '/' + action.to_s) - end - - def location - return nil unless organizations.present? - organizations.first.location - end - - def registered?(user) - registration = ConferenceRegistration.find_by(:user_id => user.id, :conference_id => id) - return registration ? registration.is_attending : false - end - - def registration_exists?(user) - ConferenceRegistration.find_by(:user_id => user.id, :conference_id => id).present? - end - - def registration_open - registration_status == :open - end - - def can_register? - registration_status == :open || registration_status == :pre - end - - def registration_status - s = read_attribute(:registration_status) - s.present? ? s.to_sym : nil - end - - def registration_status=(new_registration_status) - write_attribute :registration_status, new_registration_status.to_s - end - - def make_slug(reset = false) - if reset - self.slug = nil - end - - self.slug ||= Conference.generate_slug( - conferencetype || :annual, - conference_year, - city_name.gsub(/\s/, '') - ) - end - - def make_title(reset = false) - if reset - self.title = nil - end - - self.title ||= Conference.generate_title( - conferencetype || :annual, - conference_year, - city_name.gsub(/\s/, '') - ) - end - - def city_name - return city.city if city.present? - return location.present? ? location.city : nil - end - - def conference_year - self.year || (end_date.present? ? end_date.year : nil) - end - - def over? - return false unless end_date.present? - return end_date < DateTime.now - end - - def self.default_payment_amounts - [25, 50, 100] - end - - def self.conference_types - { - annual: { slug: '%{city}%{year}', title: 'Bike!Bike! %{year}'}, - n: { slug: 'North%{year}', title: 'Bike!Bike! North %{year}'}, - s: { slug: 'South%{year}', title: 'Bike!Bike! South %{year}'}, - e: { slug: 'East%{year}', title: 'Bike!Bike! East %{year}'}, - w: { slug: 'West%{year}', title: 'Bike!Bike! West %{year}'}, - ne: { slug: 'Northeast%{year}', title: 'Bike!Bike! Northeast %{year}'}, - nw: { slug: 'Northwest%{year}', title: 'Bike!Bike! Northwest %{year}'}, - se: { slug: 'Southeast%{year}', title: 'Bike!Bike! Southeast %{year}'}, - sw: { slug: 'Southwest%{year}', title: 'Bike!Bike! Southwest %{year}'} - } - end - - def self.generate_slug(type, year, city) - Conference.conference_types[(type || :annual).to_sym][:slug].gsub('%{city}', city).gsub('%{year}', year.to_s) - end - - def self.generate_title(type, year, city) - Conference.conference_types[(type || :annual).to_sym][:title].gsub('%{city}', city).gsub('%{year}', year.to_s) - end - - def self.default_provider_conditions - { 'distance' => { 'number' => 0, 'unit' => 'mi' }} - end - -end diff --git a/app/models/conference_admin.rb b/app/models/conference_admin.rb deleted file mode 100644 index 0762f7d..0000000 --- a/app/models/conference_admin.rb +++ /dev/null @@ -1,2 +0,0 @@ -class ConferenceAdmin < ActiveRecord::Base -end diff --git a/app/models/conference_administrator.rb b/app/models/conference_administrator.rb deleted file mode 100644 index 2fd7823..0000000 --- a/app/models/conference_administrator.rb +++ /dev/null @@ -1,4 +0,0 @@ -class ConferenceAdministrator < ActiveRecord::Base - belongs_to :user - belongs_to :conference -end diff --git a/app/models/conference_host_organization.rb b/app/models/conference_host_organization.rb deleted file mode 100644 index f9ab1a8..0000000 --- a/app/models/conference_host_organization.rb +++ /dev/null @@ -1,4 +0,0 @@ -class ConferenceHostOrganization < ActiveRecord::Base - belongs_to :conference - belongs_to :organization -end diff --git a/app/models/conference_registration.rb b/app/models/conference_registration.rb deleted file mode 100644 index 8fbdb1c..0000000 --- a/app/models/conference_registration.rb +++ /dev/null @@ -1,74 +0,0 @@ -class ConferenceRegistration < ActiveRecord::Base - belongs_to :conference - belongs_to :user - has_many :conference_registration_responses - - AttendingOptions = [:yes, :no] - - def languages - user.languages - end - - def self.all_housing_options - [:none, :tent, :house] - end - - def self.all_spaces - [:bed_space, :floor_space, :tent_space] - end - - def self.all_bike_options - [:yes, :no] - end - - def self.all_food_options - [:meat, :vegetarian, :vegan] - end - - def self.all_considerations - [:vegan, :smoking, :pets, :quiet] - end - - def city - city_id.present? ? City.find(city_id) : nil - end - - def status(was = false) - # our user hasn't registered if their user doesn't exist or they haven't entered a city - return :unregistered if user.nil? || check(:city, was).blank? - - # registration completes once a guest has entered a housing preference or - # a housing provider has opted in or out of providing housing - return :preregistered unless - check(:housing, was).present? || !check(:can_provide_housing, was).nil? - - # they must be registered - return :registered - end - - around_update :check_status - - def check_status - yield - - old_status = status(true) - new_status = status - - if old_status.present? && old_status != new_status - if (conference.registration_status == :pre && new_status == :preregistered) || - (conference.registration_status == :open && new_status == :registered) - - UserMailer.send_mail :registration_confirmation do - { - :args => self - } - end - end - end - end - -private - def check(field, was) - send("#{field}#{was ? '_was' : ''}") - end -end diff --git a/app/models/conference_registration_form_field.rb b/app/models/conference_registration_form_field.rb deleted file mode 100644 index 2e400f5..0000000 --- a/app/models/conference_registration_form_field.rb +++ /dev/null @@ -1,4 +0,0 @@ -class ConferenceRegistrationFormField < ActiveRecord::Base - belongs_to :conference - belongs_to :registration_form_field -end diff --git a/app/models/conference_registration_response.rb b/app/models/conference_registration_response.rb deleted file mode 100644 index d9dc8a0..0000000 --- a/app/models/conference_registration_response.rb +++ /dev/null @@ -1,5 +0,0 @@ -class ConferenceRegistrationResponse < ActiveRecord::Base - belongs_to :conference_registration - belongs_to :user - #belongs_to :conference, :through => :conference_registration -end diff --git a/app/models/conference_type.rb b/app/models/conference_type.rb deleted file mode 100644 index f8cfca0..0000000 --- a/app/models/conference_type.rb +++ /dev/null @@ -1,7 +0,0 @@ -class ConferenceType < ActiveRecord::Base - #belongs_to :conference - - def to_param - slug - end -end diff --git a/app/models/email_confirmation.rb b/app/models/email_confirmation.rb deleted file mode 100644 index 6ddb699..0000000 --- a/app/models/email_confirmation.rb +++ /dev/null @@ -1,40 +0,0 @@ -class EmailConfirmation < ActiveRecord::Base - belongs_to :user - before_create :prepare - - def prepare - # clean up any expired records - EmailConfirmation.delete_all(['expiry < ?', Time.now]) - - # fill in defaults - self.expiry ||= Time.now + 1.day - - while self.token.nil? do - # create a token based on the user id and current time - self.token = generate_token - - # conflicts should be extremely rare, but let's just be sure - if EmailConfirmation.exists?(:token => self.token) - self.token = nil # keep the loop going - # because we generate the token based on the time, just make sure - # some time has passed - sleep 0.1 - end - end - end - - def valid_for_user?(user) - user.id == user_id && !expired? - end - - def expired? - expiry >= Time.now - end - - protected - - def generate_token - Digest::SHA256.hexdigest(user_id.to_s + (Time.now.to_f * 1000000).to_i.to_s) - end - -end diff --git a/app/models/event.rb b/app/models/event.rb deleted file mode 100644 index cf4677f..0000000 --- a/app/models/event.rb +++ /dev/null @@ -1,20 +0,0 @@ -class Event < ActiveRecord::Base - translates :info, :title - - belongs_to :conference - belongs_to :event_location - - def conference_day - return nil unless start_time.present? && end_time.present? - - start_day = conference.start_date.change(hour: 0, minute: 0, second: 0) - w_start_day = start_time.change(hour: 0, minute: 0, second: 0) - return (((w_start_day - start_day) / 86400) + 1).to_i - end - - def duration - return nil unless start_time.present? && end_time.present? - ((end_time - start_time) / 60).to_i - end - -end diff --git a/app/models/event_location.rb b/app/models/event_location.rb deleted file mode 100644 index a179995..0000000 --- a/app/models/event_location.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'geocoder' -require 'geocoder/railtie' - -Geocoder::Railtie.insert - -class EventLocation < ActiveRecord::Base - belongs_to :conference - geocoded_by :full_address - - reverse_geocoded_by :latitude, :longitude, :address => :full_address - after_validation :geocode, if: ->(obj){ obj.address_changed? } - - def full_address - [address, conference.city.city, conference.city.territory, conference.city.country].join(', ') - end - - def self.all_spaces - Workshop.all_spaces + [:event_space] - end - - def self.all_amenities - Workshop.all_needs - end -end diff --git a/app/models/event_type.rb b/app/models/event_type.rb deleted file mode 100644 index 3c7329a..0000000 --- a/app/models/event_type.rb +++ /dev/null @@ -1,2 +0,0 @@ -class EventType < ActiveRecord::Base -end diff --git a/app/models/location.rb b/app/models/location.rb deleted file mode 100644 index 737fb50..0000000 --- a/app/models/location.rb +++ /dev/null @@ -1,21 +0,0 @@ -class Location < ActiveRecord::Base - #attr_accessible :title, :country, :territory, :city, :street, :postal_code, :latitude, :longitude - has_many :locations_organization - has_many :organizations, :through => :locations_organization - - geocoded_by :full_address - - reverse_geocoded_by :latitude, :longitude, :address => :full_address - after_validation :geocode, if: ->(obj){ obj.country_changed? or obj.territory_changed? or obj.city_changed? or obj.street_changed? or obj.postal_code_changed? or obj.latitude.blank? or obj.longitude.blank? } - - def full_address - addr = title - addr = (addr ? ', ' : '') + (street || '') - addr = (addr ? ', ' : '') + (city || '') - addr = (addr ? ', ' : '') + (territory || '') - addr = (addr ? ' ' : '') + (country || '') - addr = (addr ? ' ' : '') + (postal_code || '') - addr - end - -end diff --git a/app/models/locations_organization.rb b/app/models/locations_organization.rb deleted file mode 100644 index 60a2d25..0000000 --- a/app/models/locations_organization.rb +++ /dev/null @@ -1,6 +0,0 @@ -class LocationsOrganization < ActiveRecord::Base - belongs_to :location - belongs_to :organization - - self.primary_key = :location_id -end diff --git a/app/models/organization.rb b/app/models/organization.rb deleted file mode 100644 index 3b46249..0000000 --- a/app/models/organization.rb +++ /dev/null @@ -1,77 +0,0 @@ -class Organization < ActiveRecord::Base - mount_uploader :logo, LogoUploader - mount_uploader :avatar, AvatarUploader - mount_uploader :cover, CoverUploader - - has_many :locations_organization - has_many :locations, through: :locations_organization - - has_many :user_organization_relationships, dependent: :destroy - has_many :users, through: :user_organization_relationships - - accepts_nested_attributes_for :locations, :reject_if => proc {|l| l[id].blank?} - accepts_nested_attributes_for :user_organization_relationships, :reject_if => proc {|u| u[:user_id].blank?}, :allow_destroy => true - before_create :make_slug - - def location - locations.first - end - - def longitude - location.longitude - end - - def latitude - location.latitude - end - - def to_param - slug - end - - def host?(user) - return false unless user.present? - return true if user.administrator? - - users.each do |u| - return true if u.id == user.id - end - return false - end - - def generate_slug(name, location = nil) - s = name.gsub(/[^a-z1-9]+/i, '-').chomp('-').gsub(/\-([A-Z])/, '\1') - if Organization.find_by(:slug => s).present? && !location.nil? - if location.city.present? - s += '-' + location.city - end - if Organization.find_by(:slug => s).present? && location.territory.present? - s += '-' + location.territory - end - if Organization.find_by(:slug => s).present? - s += '-' + location.country - end - end - attempt = 1 - ss = s - - while Organization.find_by(:slug => s) - attempt += 1 - s = ss + '-' + attempt.to_s - end - s - end - - def self.find_by_city(city) - Organization.joins(:locations).where(locations: { - city_id: city.is_a?(City) ? city.id : city - }) - end - - private - def make_slug - if !self.slug - self.slug = generate_slug(self.name, self.locations && self.locations[0]) - end - end -end diff --git a/app/models/organization_status.rb b/app/models/organization_status.rb deleted file mode 100644 index 309f828..0000000 --- a/app/models/organization_status.rb +++ /dev/null @@ -1,2 +0,0 @@ -class OrganizationStatus < ActiveRecord::Base -end diff --git a/app/models/registration_form_field.rb b/app/models/registration_form_field.rb deleted file mode 100644 index c02288d..0000000 --- a/app/models/registration_form_field.rb +++ /dev/null @@ -1,75 +0,0 @@ -class RegistrationFormField < ActiveRecord::Base - Types = { - :single => [:title, :required, :input_type, :help], - :multiple => [:title, :required, :selection_type, :options, :other, :help] - } - - Fields = { - :title => {:control => 'text_field'}, - :input_type => {:control => 'select', :options => [[:text_field, :text_area, :number_field, :date_field, :time_field, :phone_field, :checkbox]], :option => true}, - :selection_type => {:control => 'select', :options => [[:check_box, :radio_button, :select]], :option => true}, - :options => {:control => 'text_area', :option => true}, - :help => {:control => 'text_area'}, - :other => {:control => 'check_box', :option => true}, - :required => {:control => 'check_box'} - } - - def self.TypesForField(field) - types = [] - Types.each do |k, t| - if t.include?(field) - types << k - end - end - types - end - - def input_type - get_from_options 'input_type' - end - - def selection_type - get_from_options 'selection_type' - end - - def other - get_from_options 'other' - end - - def self.GetOptions(type, values) - o = {} - Fields.each do |k, f| - if f[:option] && Types[type.to_sym].include?(k) - o[k] = values[k] - end - end - o - end - - def self.GetNonOptionKeys(type, values) - o = [] - Fields.each do |k, f| - if !f[:option] && Types[type.to_sym].include?(k) - o << k - end - end - o - end - - def repeats?() - field_type.to_s == 'multiple' && selection_type.to_s != 'select' - end - - def is_array?() - field_type.to_s == 'multiple' && selection_type.to_s != 'radio_button' - end - - private - def get_from_options(key) - if options - _options = ActiveSupport::JSON.decode(options) - return _options[key] - end - nil - end -end diff --git a/app/models/user.rb b/app/models/user.rb deleted file mode 100644 index c04d2b6..0000000 --- a/app/models/user.rb +++ /dev/null @@ -1,68 +0,0 @@ -class User < ActiveRecord::Base - authenticates_with_sorcery! do |config| - config.authentications_class = Authentication - end - - validates :email, uniqueness: true - - mount_uploader :avatar, AvatarUploader - - has_many :user_organization_relationships - has_many :organizations, through: :user_organization_relationships - has_many :conferences, through: :conference_administrators - has_many :authentications, :dependent => :destroy - accepts_nested_attributes_for :authentications - - before_update do |user| - user.locale ||= I18n.locale - user.email.downcase! - end - - before_save do |user| - user.locale ||= I18n.locale - user.email.downcase! - end - - def can_translate?(to_locale = nil, from_locale = nil) - is_translator unless to_locale.present? - - from_locale = I18n.locale unless from_locale.present? - return languages.present? && - to_locale.to_s != from_locale.to_s && - languages.include?(to_locale.to_s) && - languages.include?(from_locale.to_s) - end - - def name - firstname || username || email - end - - def named_email - name = firstname || username - return email unless name - return "#{name} <#{email}>" - end - - def administrator? - role == 'administrator' - end - - def self.AVAILABLE_LANGUAGES - [:en, :es, :fr, :ar] - end - - def self.get(email) - user = find_user(email) - - unless user - user = create(email: email, locale: I18n.locale) - end - - return user - end - - def self.find_user(email) - User.where('lower(email) = ?', email.downcase).first - end - -end diff --git a/app/models/user_organization_relationship.rb b/app/models/user_organization_relationship.rb deleted file mode 100644 index 789182a..0000000 --- a/app/models/user_organization_relationship.rb +++ /dev/null @@ -1,11 +0,0 @@ -class UserOrganizationRelationship < ActiveRecord::Base - belongs_to :user - belongs_to :organization - - Administrator = 'administrator' - Member = 'member' - - DefaultRelationship = Member - - AllRelationships = [Administrator, Member] -end diff --git a/app/models/version.rb b/app/models/version.rb deleted file mode 100644 index 6fd7588..0000000 --- a/app/models/version.rb +++ /dev/null @@ -1,2 +0,0 @@ -class Version < ActiveRecord::Base -end diff --git a/app/models/workshop.rb b/app/models/workshop.rb deleted file mode 100644 index bab1994..0000000 --- a/app/models/workshop.rb +++ /dev/null @@ -1,180 +0,0 @@ -class Workshop < ActiveRecord::Base - translates :info, :title - - belongs_to :conference - belongs_to :event_location - - has_many :workshop_facilitators, :dependent => :destroy - has_many :users, :through => :workshop_facilitators - - accepts_nested_attributes_for :workshop_facilitators, :reject_if => proc {|u| u[:user_id].blank?}, :allow_destroy => true - - before_create :make_slug - - def to_param - slug - end - - def role(user) - return nil unless user - workshop_facilitators.each do |u| - if u.user_id == user.id - return conference.registration_exists?(user) ? u.role.to_sym : :unregistered - end - end - return nil - end - - def facilitator?(user) - !!role(user) - end - - def active_facilitators - users = [] - workshop_facilitators.each do |u| - users << User.find(u.user_id) unless u.role.to_sym == :requested || u.user_id.nil? - end - return users - end - - def active_facilitator?(user) - facilitator?(user) && !requested_collaborator?(user) - end - - def public_facilitator?(user) - return false if !active_facilitator?(user) - return true if creator?(user) - conference.registered?(user) - end - - def creator?(user) - role(user) == :creator - end - - def collaborator?(user) - role(user) == :collaborator - end - - def requested_collaborator?(user) - role(user) == :requested - end - - def can_edit?(user) - creator?(user) || collaborator?(user) || conference.host?(user) - end - - def can_remove?(owner, facilitator) - # creators cannot be removed - return false if creator?(facilitator) - - # creator can remove anyone, facilitators can remove themselves - return creator?(owner) || owner.id == facilitator.id - end - - def can_delete?(user) - creator?(user) || conference.host?(user) - end - - def can_show_interest?(user) - user.present? && !active_facilitator?(user) - end - - def interested?(user) - user.present? && !active_facilitator?(user) && WorkshopInterest.find_by(workshop_id: id, user_id: user.id) - end - - def interested_count - interested.size - end - - def interested - return [] unless id - return @interested if @interested.present? - - collaborators = [] - workshop_facilitators.each do |f| - collaborators << f.user_id unless f.role.to_sym == :requested || f.user_id.nil? - end - return 10 unless collaborators.present? - @interested = WorkshopInterest.where("workshop_id=#{id} AND user_id NOT IN (#{collaborators.join ','})") || [] - end - - def can_translate?(user, lang) - return false unless user.present? - user.can_translate?(lang, locale) || (can_edit?(user) && lang.to_s != locale.to_s) - end - - def conference_day - return nil unless start_time.present? && end_time.present? - - start_day = conference.start_date.change(hour: 0, minute: 0, second: 0) - w_start_day = start_time.change(hour: 0, minute: 0, second: 0) - return (((w_start_day - start_day) / 86400) + 1).to_i - end - - def duration - return nil unless start_time.present? && end_time.present? - ((end_time - start_time) / 60).to_i - end - - def self.all_themes - [:race_gender, :mechanics, :funding, :organization, :community] - end - - def self.all_spaces - [:meeting_room, :workshop, :outdoor_meeting] - end - - def self.all_needs - [:sound, :projector, :tools] - end - - def get_translators(data, loc = nil) - notify_list = {} - active_facilitators.each do |facilitator| - notify_list[facilitator.id] = facilitator - end - - data.each do | column, value | - ( - loc.present? ? - get_translators_for_column_and_locale(column, loc) : - get_translators_for_column(column) - ).each do |id| - notify_list[id] = User.find(id) - end - end - return notify_list - end - - def comments - Comment.for(self) - end - - def add_comment(user, comment) - Comment.create_for(self, user, comment) - end - - private - def make_slug - if !self.slug - s = self.title.gsub(/[^a-z1-9]+/i, '-').chomp('-').gsub(/\-([A-Z])/, '\1') - if Organization.find_by(:slug => s) && self.locations && self.locations[0] - s += '-' + self.locations[0].city - if Organization.find_by(:slug => s) && locations[0].territory - s += '-' + self.locations[0].territory - end - if Organization.find_by(:slug => s) - s += '-' + self.locations[0].country - end - end - attempt = 1 - ss = s - while Organization.find_by(:slug => s) - attempt += 1 - s = ss + '-' + attempt - end - self.slug = s - end - end -end diff --git a/app/models/workshop_facilitator.rb b/app/models/workshop_facilitator.rb deleted file mode 100644 index cbd4419..0000000 --- a/app/models/workshop_facilitator.rb +++ /dev/null @@ -1,2 +0,0 @@ -class WorkshopFacilitator < ActiveRecord::Base -end diff --git a/app/models/workshop_interest.rb b/app/models/workshop_interest.rb deleted file mode 100644 index d35704d..0000000 --- a/app/models/workshop_interest.rb +++ /dev/null @@ -1,4 +0,0 @@ -class WorkshopInterest < ActiveRecord::Base - belongs_to :workshop - has_one :user -end diff --git a/app/models/workshop_presentation_style.rb b/app/models/workshop_presentation_style.rb deleted file mode 100644 index cc6ad25..0000000 --- a/app/models/workshop_presentation_style.rb +++ /dev/null @@ -1,2 +0,0 @@ -class WorkshopPresentationStyle < ActiveRecord::Base -end diff --git a/app/models/workshop_requested_resource.rb b/app/models/workshop_requested_resource.rb deleted file mode 100644 index 317eea9..0000000 --- a/app/models/workshop_requested_resource.rb +++ /dev/null @@ -1,2 +0,0 @@ -class WorkshopRequestedResource < ActiveRecord::Base -end diff --git a/app/models/workshop_resource.rb b/app/models/workshop_resource.rb deleted file mode 100644 index ea63a8a..0000000 --- a/app/models/workshop_resource.rb +++ /dev/null @@ -1,2 +0,0 @@ -class WorkshopResource < ActiveRecord::Base -end diff --git a/app/models/workshop_stream.rb b/app/models/workshop_stream.rb deleted file mode 100644 index cf7d691..0000000 --- a/app/models/workshop_stream.rb +++ /dev/null @@ -1,2 +0,0 @@ -class WorkshopStream < ActiveRecord::Base -end diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb deleted file mode 100644 index 4e4ad8e..0000000 --- a/app/uploaders/avatar_uploader.rb +++ /dev/null @@ -1,104 +0,0 @@ -# encoding: utf-8 -require 'carrierwave/processing/mini_magick' - -class AvatarUploader < CarrierWave::Uploader::Base - - include CarrierWave::ImageOptimizer - include CarrierWave::MiniMagick - - # Include RMagick or MiniMagick support: - # include CarrierWave::RMagick - # include CarrierWave::MiniMagick - - # Choose what kind of storage to use for this uploader: - - storage :file - process :optimize - - @@sizes = {:thumb => [120, 120], :icon => [48, 48], :preview => [360, 120], :normal => [512, 512]} - # 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 - "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" - 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('_') - # "http://placehold.it/" + (@@sizes[version_name] || [300, 300]).join('x') - #end - - # Process files as they are uploaded: - # process :scale => [200, 300] - # - #def scale(width, height) - #end - - # Create different versions of your uploaded files: - version :thumb do - process :resize_to_fill => @@sizes[:thumb] - end - - version :icon do - process :resize_to_fill => @@sizes[:icon] - end - - version :preview do - process :resize_to_fit => @@sizes[:preview] - end - - version :normal do - process :resize_to_fit => @@sizes[:normal] - 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 - # "something.jpg" if original_filename - # end - - def image - @image ||= MiniMagick::Image.open(file.path) - end - - def is_landscape? - image['width'] > (image['height'] * 1.25) - end - - #def recreate_versions!(*versions) - # if !current_path.nil? - # current_path = "'" + (current_path || '') + "'" - # end - # super(*versions) - #end - -# def manipulate! -# cache_stored_file! if !cached? -# image = ::MiniMagick::Image.open(current_path) -# -# begin -# image.format(@format.to_s.downcase) if @format -# image = yield(image) -# image.write(current_path) -# image.run_command("identify", '"' + current_path + '"') -# ensure -# image.destroy! -# end -# rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e -# default = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :locale => :en) -# message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :default => default) -# raise CarrierWave::ProcessingError, message -# end - -end diff --git a/app/uploaders/cover_uploader.rb b/app/uploaders/cover_uploader.rb deleted file mode 100644 index c40dbcb..0000000 --- a/app/uploaders/cover_uploader.rb +++ /dev/null @@ -1,54 +0,0 @@ -# encoding: utf-8 -require 'carrierwave/processing/mini_magick' - -class CoverUploader < CarrierWave::Uploader::Base - - include CarrierWave::ImageOptimizer - include CarrierWave::MiniMagick - - storage :file - process :optimize - - def store_dir - "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" - end - - version :preview do - process :resize_to_fit => [480, 240] - end - - version :full do - process :resize_to_fit => [1200, 800] - end - - def image - @image ||= MiniMagick::Image.open(file.path) - end - - def is_landscape? - image['width'] > image['height'] - end - - def manipulate! - cache_stored_file! if !cached? - image = ::MiniMagick::Image.open(current_path) - - begin - image.format(@format.to_s.downcase) if @format - image = yield(image) - image.write(current_path) - begin - image.run_command("identify", current_path) - rescue - image.run_command("identify", '"' + current_path + '"') - end - ensure - image.destroy! - end - rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e - default = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :locale => :en) - message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :default => default) - raise CarrierWave::ProcessingError, message - end - -end diff --git a/app/uploaders/logo_uploader.rb b/app/uploaders/logo_uploader.rb deleted file mode 100644 index b9f7127..0000000 --- a/app/uploaders/logo_uploader.rb +++ /dev/null @@ -1,57 +0,0 @@ -# encoding: utf-8 - -class LogoUploader < CarrierWave::Uploader::Base - - # Include RMagick or MiniMagick support: - # include CarrierWave::RMagick - include CarrierWave::ImageOptimizer - include CarrierWave::MiniMagick - - # Include RMagick or MiniMagick support: - # include CarrierWave::RMagick - # include CarrierWave::MiniMagick - - # Choose what kind of storage to use for this uploader: - - storage :file - process :optimize - - # 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 - "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" - 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 :scale => [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(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 - # "something.jpg" if original_filename - # end - -end diff --git a/app/uploaders/poster_uploader.rb b/app/uploaders/poster_uploader.rb deleted file mode 100644 index b6e2220..0000000 --- a/app/uploaders/poster_uploader.rb +++ /dev/null @@ -1,69 +0,0 @@ -# encoding: utf-8 -require 'carrierwave/processing/mini_magick' - -class PosterUploader < CarrierWave::Uploader::Base - - include CarrierWave::ImageOptimizer - include CarrierWave::MiniMagick - - storage :file - process :optimize - - @@sizes = { - :thumb => [120, 120], - :icon => [48, 48], - :preview => [512, 512], - :full => [1024, 1024] - } - - def store_dir - "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" - end - - version :thumb do - process :resize_to_fill => @@sizes[:thumb] - end - - version :icon do - process :resize_to_fill => @@sizes[:icon] - end - - version :preview do - process :resize_to_fit => @@sizes[:preview] - end - - version :full do - process :resize_to_fit => @@sizes[:full] - end - - def image - @image ||= MiniMagick::Image.open(file.path) - end - - def is_landscape? - image['width'] > image['height'] - end - - def manipulate! - cache_stored_file! if !cached? - image = ::MiniMagick::Image.open(current_path) - - begin - image.format(@format.to_s.downcase) if @format - image = yield(image) - image.write(current_path) - begin - image.run_command("identify", current_path) - rescue - image.run_command("identify", '"' + current_path + '"') - end - ensure - image.destroy! - end - rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e - default = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :locale => :en) - message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :default => default) - raise CarrierWave::ProcessingError, message - end - -end diff --git a/app/views/admin/new.html.haml b/app/views/admin/new.html.haml index 633a7bb..2429552 100644 --- a/app/views/admin/new.html.haml +++ b/app/views/admin/new.html.haml @@ -19,6 +19,6 @@ = checkbox :is_featured, @this_conference.id.present? && @this_conference.is_featured != false, 'forms.labels.generic.is_featured' = columns(medium: 12) do .actions.next-prev - = button_tag :save, value: :save + = button :save, value: :save - if @this_conference.id.present? = button_with_confirmation :delete, value: :delete, class: 'delete' diff --git a/app/views/application/500.html.haml b/app/views/application/500.html.haml index 30fa659..f86f5fc 100644 --- a/app/views/application/500.html.haml +++ b/app/views/application/500.html.haml @@ -1,6 +1,5 @@ -= render :partial => 'application/header', :locals => {:image_file => 'parts.jpg'} += render partial: 'application/header', locals: {image_file: 'parts.jpg'} = row do = columns(medium: 12) do %p= _'error.500.description', :p = render 'contact', cancel_btn: false, contact_reason: :website - \ No newline at end of file diff --git a/app/views/application/_contact.html.haml b/app/views/application/_contact.html.haml index 3475c48..b944096 100644 --- a/app/views/application/_contact.html.haml +++ b/app/views/application/_contact.html.haml @@ -9,6 +9,6 @@ = textfield :subject, nil, required: true, big: true = textarea :message, nil, required: true, plain: true .actions - = button_tag :send, value: :send + = button :send, value: :send - if cancel_btn %button.close.subdued=_'forms.actions.generic.cancel' diff --git a/app/views/application/_login.html.haml b/app/views/application/_login.html.haml index 65a41e6..591e622 100644 --- a/app/views/application/_login.html.haml +++ b/app/views/application/_login.html.haml @@ -2,5 +2,5 @@ = form_tag do_confirm_path, class: 'flex-form' do = hidden_field_tag :dest, settings_path if dest.present? = emailfield :email, nil, big: true - = button_tag :continue, :value => :confirm_email + = button :continue, value: :confirm_email = link_to (_'forms.actions.generic.facebook_sign_in','Facebook Sign In'), auth_at_provider_path(provider: :facebook, dest: dest), class: [:button, :facebook] diff --git a/app/views/application/_login_confirm.html.haml b/app/views/application/_login_confirm.html.haml index 20245e1..f02c011 100644 --- a/app/views/application/_login_confirm.html.haml +++ b/app/views/application/_login_confirm.html.haml @@ -4,4 +4,4 @@ = form_tag :do_confirm, :authenticity_token => false, class: 'flex-form' do = emailfield :email, nil, required: true, big: true = hidden_field_tag :token, @confirmation.token - = button_tag :login + = button :login diff --git a/app/views/application/_login_confirmation_sent.html.haml b/app/views/application/_login_confirmation_sent.html.haml index 782fefc..5338ec0 100644 --- a/app/views/application/_login_confirmation_sent.html.haml +++ b/app/views/application/_login_confirmation_sent.html.haml @@ -1,4 +1,4 @@ = row do - = columns(medium: 12) do - %h2=_'articles.permission_denied.headings.confirmation_sent' - %p=_'articles.conference_registration.paragraphs.email_confirm', :p + = columns(medium: 12) do + %h2=_'articles.permission_denied.headings.confirmation_sent' + %p=_'articles.conference_registration.paragraphs.email_confirm', :p diff --git a/app/views/application/_not_a_translator.html.haml b/app/views/application/_not_a_translator.html.haml index df40379..9cf5108 100644 --- a/app/views/application/_not_a_translator.html.haml +++ b/app/views/application/_not_a_translator.html.haml @@ -6,4 +6,4 @@ = columns(medium: 6) do = form_tag :translator_request do = text_area_tag :comment, nil, placeholder: true, required: true - = button_tag :translator_request + = button :translator_request diff --git a/app/views/application/_translator_login.html.haml b/app/views/application/_translator_login.html.haml index e2ec862..f49dced 100644 --- a/app/views/application/_translator_login.html.haml +++ b/app/views/application/_translator_login.html.haml @@ -8,4 +8,4 @@ .email-field.input-field = email_field_tag :email, nil, required: true = label_tag :email - = button_tag :login + = button :login diff --git a/app/views/application/contact.html.haml b/app/views/application/contact.html.haml index 513d449..5df755a 100644 --- a/app/views/application/contact.html.haml +++ b/app/views/application/contact.html.haml @@ -1,10 +1,10 @@ = render :partial => 'application/header', :locals => {:image_file => @banner_image || 'grafitti.jpg'} %article - = row do - = columns do - - if @sent - %h2=_'articles.contact.headings.sent', :t - %p=_'articles.contact.paragraphs.sent', :p - - else - %h2=_'articles.contact.headings.contact' - = render 'contact', cancel_btn: false \ No newline at end of file + = row do + = columns do + - if @sent + %h2=_'articles.contact.headings.sent', :t + %p=_'articles.contact.paragraphs.sent', :p + - else + %h2=_'articles.contact.headings.contact' + = render 'contact', cancel_btn: false \ No newline at end of file diff --git a/app/views/application/home.html.haml b/app/views/application/home.html.haml index 4d3d522..cb268b3 100644 --- a/app/views/application/home.html.haml +++ b/app/views/application/home.html.haml @@ -1,5 +1,6 @@ - content_for :og_image do - = (@conference.present? ? @conference.poster.full.url : nil) || image_path('default_poster.jpg') -- if @conferences.present? + - if @conference.present? + = @conference.poster.full.url || image_path('default_poster.jpg') +- if @conferences - @conferences.each do | conference | - = render 'conferences/conference', conference: conference, links: [ :read_more, :register ] + = render 'conferences/conference', conference: conference, links: [ :read_more, :register ], sections: @conference.front_page_details diff --git a/app/views/application/permission_denied.html.haml b/app/views/application/permission_denied.html.haml index dfbc3fa..8fdf1d1 100644 --- a/app/views/application/permission_denied.html.haml +++ b/app/views/application/permission_denied.html.haml @@ -1,10 +1,10 @@ = render :partial => 'application/header', :locals => {:image_file => @banner_image || '403.jpg'} %article - - if @template.present? - =render @template - - else - = row do - = columns do - %h2=_'error.403.title','Sorry, you currently don\'t have access to this page' - %p=_'error.403.description', :p - = render 'contact', cancel_btn: false, contact_reason: :website \ No newline at end of file + - if @template.present? + =render @template + - else + = row do + = columns do + %h2=_'error.403.title','Sorry, you currently don\'t have access to this page' + %p=_'error.403.description', :p + = render 'contact', cancel_btn: false, contact_reason: :website \ No newline at end of file diff --git a/app/views/application/update_user.html.haml b/app/views/application/update_user.html.haml index d107d68..fdf9c89 100644 --- a/app/views/application/update_user.html.haml +++ b/app/views/application/update_user.html.haml @@ -6,4 +6,4 @@ %p=_'articles.conference_registration.paragraphs.provide_email', :p = form_tag oauth_save_path, class: 'flex-form' do = emailfield :email, nil, required: true, big: true - = button_tag :save, value: :save + = button :save, value: :save diff --git a/app/views/application/user_settings.html.haml b/app/views/application/user_settings.html.haml index d34111f..3271df8 100644 --- a/app/views/application/user_settings.html.haml +++ b/app/views/application/user_settings.html.haml @@ -19,7 +19,7 @@ = radiobuttons :preferred_language, I18n.backend.enabled_locales, current_user.locale || I18n.locale, 'languages', heading: 'articles.conference_registration.headings.preferred_language' = checkbox :email_subscribe, current_user.is_subscribed != false, 'articles.user_settings.email_subscribe', heading: 'articles.user_settings.headings.email_subscribe', help: 'articles.user_settings.paragraphs.email_subscribe', inline: true, right_help: true .actions - = button_tag :save, value: :save + = button :save, value: :save - else %h2=_'forms.actions.generic.login' = render 'login' \ No newline at end of file diff --git a/app/views/conference_administration/_administrators.html.haml b/app/views/conference_administration/_administrators.html.haml index b8d65e5..36a2b04 100644 --- a/app/views/conference_administration/_administrators.html.haml +++ b/app/views/conference_administration/_administrators.html.haml @@ -4,7 +4,7 @@ = admin_update_form do = checkboxes :organizations, (@organizations.map { |org| [org.name, org.id] }), @this_conference.organizations.map(&:id), 'test.test', vertical: true, big: true .actions.right.small - = button_tag :save, value: :set_organizations + = button :save, value: :set_organizations - @this_conference.organizations.each do | organization | %h4=organization.name - if organization.users.present? @@ -16,11 +16,11 @@ = admin_update_form class: [:inline, :right] do = hidden_field_tag :user_id, user.id = hidden_field_tag :org_id, organization.id - = button_tag :remove_member, value: :remove_org_member, class: [:small, :delete] + = button :remove_member, value: :remove_org_member, class: [:small, :delete] = admin_update_form class: 'mini-flex-form' do = hidden_field_tag :org_id, organization.id = emailfield :email, nil, required: true - = button_tag :add_member, value: :add_org_member, class: :small + = button :add_member, value: :add_org_member, class: :small %h3=_'articles.admin.info.headings.External_Administrators' %p=_'articles.admin.info.descriptions.External_Administrators' @@ -32,10 +32,10 @@ - unless user.id == current_user.id && !current_user.administrator? = admin_update_form class: [:inline, :right] do = hidden_field_tag :user_id, user.id - = button_tag :remove_member, value: :remove_administrator, class: [:small, :delete] + = button :remove_member, value: :remove_administrator, class: [:small, :delete] = admin_update_form class: 'mini-flex-form' do = userfield :email, nil, required: true -#= emailfield :email, nil, required: true - = button_tag :add_member, value: :add_administrator, class: :small + = button :add_member, value: :add_administrator, class: :small = columns(large: 2) do   diff --git a/app/views/conference_administration/_broadcast.html.haml b/app/views/conference_administration/_broadcast.html.haml index ff1ae50..946f54c 100644 --- a/app/views/conference_administration/_broadcast.html.haml +++ b/app/views/conference_administration/_broadcast.html.haml @@ -20,8 +20,25 @@ = button_with_confirmation :send, (_'modals.admin.broadcast.confirm', vars: { number: "#{(@send_to_count || 0)}".html_safe }), value: :send, class: :delete if @broadcast_step == :test = button_tag :edit, value: :edit - else +<<<<<<< HEAD = selectfield :send_to, nil, broadcast_options, full: true = textfield :subject, @subject, required: true, big: true = textarea :body, @body, lang: @this_conference.locale, edit_on: :focus .actions.right = button_tag :preview, value: :preview +======= + .warning-info.make-room= _'articles.conference_registration.paragraphs.admin.broadcast.preview', vars: { send_to_count: "#{(@send_to_count || 0)}".html_safe } + .test-preview + %h3=@subject + = richtext @body, 4 + .actions.right + = button :test, value: :test, class: :secondary if @broadcast_step == :preview + = button_with_confirmation :send, (_'modals.admin.broadcast.confirm', vars: { number: "#{(@send_to_count || 0)}".html_safe }), value: :send, class: :delete if @broadcast_step == :test + = button :edit, value: :edit + - else + = selectfield :send_to, nil, broadcast_options, full: true + = textfield :subject, @subject, required: true, big: true + = textarea :body, @body, lang: @this_conference.locale, edit_on: :focus + .actions.right + = button :preview, value: :preview +>>>>>>> d1db46f... 2017 refactor diff --git a/app/views/conference_administration/_dates.html.haml b/app/views/conference_administration/_dates.html.haml index b7345b5..aef2097 100644 --- a/app/views/conference_administration/_dates.html.haml +++ b/app/views/conference_administration/_dates.html.haml @@ -14,4 +14,4 @@ = row do = columns(medium: 6, push: { medium: 1 }) do .actions - = button_tag :save, value: :save + = button :save, value: :save diff --git a/app/views/conference_administration/_description.html.haml b/app/views/conference_administration/_description.html.haml index 399ba00..a1a3303 100644 --- a/app/views/conference_administration/_description.html.haml +++ b/app/views/conference_administration/_description.html.haml @@ -2,4 +2,4 @@ = admin_update_form do = translate_textarea :info, @this_conference, label: 'articles.conference_registration.headings.admin.edit.info', help: 'articles.conference_registration.paragraphs.admin.edit.info', edit_on: :focus .actions.right - = button_tag :save, value: :save + = button :save, value: :save diff --git a/app/views/conference_administration/_events.html.haml b/app/views/conference_administration/_events.html.haml index bc4363e..3f77d38 100644 --- a/app/views/conference_administration/_events.html.haml +++ b/app/views/conference_administration/_events.html.haml @@ -37,5 +37,5 @@ = length_select @length, small: true = translate_fields @event, { title: { type: :textfield, big: true, label: 'forms.labels.generic.title' }, info: { type: :textarea, label: 'forms.labels.generic.info', edit_on: :focus } } .actions.next-prev - = button_tag :save, value: :save - = button_tag :cancel, value: :cancel, class: :subdued, formnovalidate: true if @event.id.present? + = button :save, value: :save + = button :cancel, value: :cancel, class: :subdued, formnovalidate: true if @event.id.present? diff --git a/app/views/conference_administration/_locations.html.haml b/app/views/conference_administration/_locations.html.haml index 2594798..492b04f 100644 --- a/app/views/conference_administration/_locations.html.haml +++ b/app/views/conference_administration/_locations.html.haml @@ -36,7 +36,7 @@ = columns(medium: 12) do .actions.next-prev - if @location.present? - = button_tag :save, value: :save - = button_tag :cancel, value: :cancel, class: :subdued, formnovalidate: true + = button :save, value: :save + = button :cancel, value: :cancel, class: :subdued, formnovalidate: true - else - = button_tag :create, value: :create \ No newline at end of file + = button :create, value: :create \ No newline at end of file diff --git a/app/views/conference_administration/_meals.html.haml b/app/views/conference_administration/_meals.html.haml index 78eb924..242a5fa 100644 --- a/app/views/conference_administration/_meals.html.haml +++ b/app/views/conference_administration/_meals.html.haml @@ -20,7 +20,7 @@ %td.form = admin_update_form do = hidden_field_tag :meal, time - = button_tag :delete, value: :delete, class: [:small, :delete] + = button :delete, value: :delete, class: [:small, :delete] = admin_update_form do %h3=_'articles.admin.locations.headings.add_meal', :t .flex-inputs @@ -30,6 +30,6 @@ = textfield :title, nil, required: true, big: true, help: 'articles.admin.locations.paragraphs.meal_title' = textfield :info, nil, help: 'articles.admin.locations.paragraphs.meal_info' .actions.next-prev - = button_tag :add_meal, value: :add_meal + = button :add_meal, value: :add_meal - else .warning-info=_'articles.admin.meals.no_locations_warning' \ No newline at end of file diff --git a/app/views/conference_administration/_payment_message.html.haml b/app/views/conference_administration/_payment_message.html.haml index c902889..2605416 100644 --- a/app/views/conference_administration/_payment_message.html.haml +++ b/app/views/conference_administration/_payment_message.html.haml @@ -3,4 +3,4 @@ = translate_textarea :payment_message, @this_conference, default: 'articles.conference_registration.paragraphs.Payment', help: 'articles.conference_registration.paragraphs.admin.payment.message', edit_on: :focus, short: true .actions.right - = button_tag :save, value: :save + = button :save, value: :save diff --git a/app/views/conference_administration/_paypal.html.haml b/app/views/conference_administration/_paypal.html.haml index 96ea5b4..4affb26 100644 --- a/app/views/conference_administration/_paypal.html.haml +++ b/app/views/conference_administration/_paypal.html.haml @@ -5,4 +5,4 @@ = passwordfield :paypal_password, @this_conference.paypal_password = textfield :paypal_signature, @this_conference.paypal_signature .actions.right - = button_tag :save, value: :save + = button :save, value: :save diff --git a/app/views/conference_administration/_poster.html.haml b/app/views/conference_administration/_poster.html.haml index 97f254b..51ffea7 100644 --- a/app/views/conference_administration/_poster.html.haml +++ b/app/views/conference_administration/_poster.html.haml @@ -2,4 +2,4 @@ = form_tag administration_update_path(@this_conference.slug, :poster), multipart: true do = filefield :poster, @this_conference.poster, required: true, label: false, preview: true .actions.left - = button_tag :upload, value: :upload, id: 'upload-file' + = button :upload, value: :upload, id: 'upload-file' diff --git a/app/views/conference_administration/_providers.html.haml b/app/views/conference_administration/_providers.html.haml index 35e14fd..5d3e32b 100644 --- a/app/views/conference_administration/_providers.html.haml +++ b/app/views/conference_administration/_providers.html.haml @@ -7,4 +7,4 @@ = selectfield :distance_unit, @conditions['distance']['unit'], [:km, :mi], label: false, inline: true = columns(medium: 12) do .actions.right - = button_tag :save, value: :save_distance + = button :save, value: :save_distance diff --git a/app/views/conference_administration/_publish_schedule.html.haml b/app/views/conference_administration/_publish_schedule.html.haml index c2ed962..762ad70 100644 --- a/app/views/conference_administration/_publish_schedule.html.haml +++ b/app/views/conference_administration/_publish_schedule.html.haml @@ -2,7 +2,7 @@ = form_tag administration_update_path(@this_conference.slug, @admin_step) do - if @this_conference.workshop_schedule_published %p=_'articles.conference_registration.paragraphs.admin.schedule.published', :p - .actions= button_tag :un_publish, value: :publish, class: :delete + .actions= button :un_publish, value: :publish, class: :delete - else %p=_'articles.conference_registration.paragraphs.admin.schedule.un_published', :p - .actions= button_tag :publish, value: :publish + .actions= button :publish, value: :publish diff --git a/app/views/conference_administration/_registration_status.html.haml b/app/views/conference_administration/_registration_status.html.haml index 7fef8de..fffc7eb 100644 --- a/app/views/conference_administration/_registration_status.html.haml +++ b/app/views/conference_administration/_registration_status.html.haml @@ -1,9 +1,5 @@ -- if @warning_message - = columns(medium: 12) do - .warning-info=_"articles.admin.registrations.#{@warning_message}" -- else - = columns(medium: 12) do - = form_tag administration_update_path(@this_conference.slug, @admin_step) do - = selectfield :registration_status, @this_conference.registration_status || 'closed', registration_status_options_list, inline_label: true - .actions.left - = button_tag :save, value: :save += columns(medium: 12) do + = form_tag administration_update_path(@this_conference.slug, @admin_step) do + = selectfield :registration_status, @this_conference.registration_status || 'closed', registration_status_options_list, inline_label: true + .actions.left + = button :save, value: :save diff --git a/app/views/conference_administration/_registrations.html.haml b/app/views/conference_administration/_registrations.html.haml index c6a24fa..3d97266 100644 --- a/app/views/conference_administration/_registrations.html.haml +++ b/app/views/conference_administration/_registrations.html.haml @@ -1,21 +1,17 @@ -- if @warning_message - = columns(medium: 12) do - .warning-info=_"articles.admin.registrations.#{@warning_message}" -- else - - add_inline_script :registrations - = columns(medium: 12) do - .goes-fullscreen#registrations-table - .flex-column - = searchfield :search, nil, big: true, stretch: true - %a.button{data: { expands: 'registrations-table' }}='expand' - %a.button.delete{data: { contracts: 'registrations-table' }}='close' - %a.button.modify{data: { 'opens-modal': 'new-registration' }}='+' - .table-scroller - = html_table @excel_data, registrations_table_options - = admin_update_form id: 'new-registration', class: 'modal-edit' do - .modal-edit-overlay{data: { 'closes-modal': 'new-registration' }} - .modal-edit-content - = html_edit_table @excel_data, registrations_edit_table_options - .actions.right - %a.button.subdued{data: { 'closes-modal': 'new-registration' }}='Cancel' - = button_tag :save, value: :save, class: :modify +- add_inline_script :registrations += columns(medium: 12) do + .goes-fullscreen#registrations-table + .flex-column + = searchfield :search, nil, big: true, stretch: true + %a.button{data: { expands: 'registrations-table' }}='expand' + %a.button.delete{data: { contracts: 'registrations-table' }}='close' + %a.button.modify{data: { 'opens-modal': 'new-registration' }}='+' + .table-scroller + = html_table @excel_data, registrations_table_options + = admin_update_form id: 'new-registration', class: 'modal-edit' do + .modal-edit-overlay{data: { 'closes-modal': 'new-registration' }} + .modal-edit-content + = html_edit_table @excel_data, registrations_edit_table_options + .actions.right + %a.button.subdued{data: { 'closes-modal': 'new-registration' }}='Cancel' + = button :save, value: :save, class: :modify diff --git a/app/views/conference_administration/_schedule.html.haml b/app/views/conference_administration/_schedule.html.haml index 8a05083..1426370 100644 --- a/app/views/conference_administration/_schedule.html.haml +++ b/app/views/conference_administration/_schedule.html.haml @@ -6,22 +6,22 @@ - else - add_inline_script :schedule if @entire_page #schedule-preview - - @schedule.each do | day, data | - %h4=date(day, :weekday) + - @schedule.each do |day, data| + %h4=date(day, :weekday).html_safe %table.schedule{class: [data[:locations].present? ? 'has-locations' : 'no-locations', "locations-#{data[:num_locations]}"]} - if data[:locations].present? && data[:locations].values.first != :add %thead %tr %th.corner - - data[:locations].each do | id, location | - %th=location.is_a?(Symbol) ? '' : location.title + - data[:locations].each do |id, location| + %th=location.is_a?(Symbol) ? '' : _!(location.title) %tbody - - data[:times].each do | time, time_data | + - data[:times].each do |time, time_data| %tr - rowspan = (time_data[:length] * 2).to_i - %th=time(time) + %th=time(time).html_safe - if time_data[:type] == :workshop - - data[:locations].each do | id, location | + - data[:locations].each do |id, location| - if time_data[:item][:workshops][id].present? - workshop = time_data[:item][:workshops][id][:workshop] - status = time_data[:item][:workshops][id][:status] @@ -31,11 +31,11 @@ - if workshop.present? && workshop.event_location.present? = link_to view_workshop_path(@conference.slug, workshop.id), class: 'event-detail-link' do .details - .title=workshop.title + .title=_!workshop.title %template.event-details{data: { href: view_workshop_path(@conference.slug, workshop.id) }} - %h1.title=workshop.title + %h1.title=_!workshop.title %p.address - = workshop.event_location.title + _!(': ') + = _!("#{workshop.event_location.title}:") = location_link workshop.event_location .workshop-description= richtext workshop.info, 1 - if @can_edit @@ -46,10 +46,10 @@ %span.value="#{status[:conflict_score]} / #{workshop.interested.size}" - if status[:errors].present? .errors - - status[:errors].each do | error | + - status[:errors].each do |error| .error=_"errors.messages.schedule.#{error[:name].to_s}", vars: error[:i18nVars] = hidden_field_tag :id, workshop.id - = button_tag :deschedule, value: :deschedule_workshop, class: [:delete, :small] + = button :deschedule, value: :deschedule_workshop, class: [:delete, :small] - elsif @can_edit .title="Block #{time_data[:item][:block] + 1}" - elsif time_data[:type] != :nil @@ -60,23 +60,23 @@ - if location.present? %a.event-detail-link .details - .title= time_data[:item]['title'] - .location= location.title + .title=_!(time_data[:item]['title']) + .location=_!location.title %template.event-details - %h1.title=time_data[:item]['title'] + %h1.title=_!(time_data[:item]['title']) %p.address - = location.title + _!(': ') + = _!("#{location.title}:") = location_link location - when :event - if time_data[:item].event_location.present? %a.event-detail-link .details - .title= time_data[:item][:title] - .location= time_data[:item].event_location.title + .title=_!(time_data[:item][:title]) if time_data[:item][:title] + .location=_!(time_data[:item].event_location.title) %template.event-details - %h1.title=time_data[:item][:title] + %h1.title=_!(time_data[:item][:title]) if time_data[:item][:title] %p.address - = time_data[:item].event_location.title + _!(': ') + = _!("#{time_data[:item].event_location.title}:") = location_link time_data[:item].event_location = richtext time_data[:item][:info], 1 - if @entire_page diff --git a/app/views/conference_administration/_suggested_amounts.html.haml b/app/views/conference_administration/_suggested_amounts.html.haml index 7cd7f5d..1af4ca0 100644 --- a/app/views/conference_administration/_suggested_amounts.html.haml +++ b/app/views/conference_administration/_suggested_amounts.html.haml @@ -5,4 +5,4 @@ - for i in 1..5 do = numberfield "payment_amounts[#{i - 1}]", payment_amounts[i - 1], step: 0.01, min: 0.00, label: false .actions.right - = button_tag :save, value: :save + = button :save, value: :save diff --git a/app/views/conference_administration/_workshop_times.html.haml b/app/views/conference_administration/_workshop_times.html.haml index c175b73..d0e0c71 100644 --- a/app/views/conference_administration/_workshop_times.html.haml +++ b/app/views/conference_administration/_workshop_times.html.haml @@ -15,5 +15,5 @@ .table-td=checkboxes :days, @block_days, info['days'].map(&:to_i), 'date.day_names', vertical: true, small: true .table-td.form = hidden_field_tag :workshop_block, block - = button_tag :delete_block, value: :delete_block, class: [:small, :delete] if block == @workshop_blocks.length - 2 - = button_tag (is_new ? :add_block : :update_block), value: :save_block, class: [:small, :add] + = button :delete_block, value: :delete_block, class: [:small, :delete] if block == @workshop_blocks.length - 2 + = button (is_new ? :add_block : :update_block), value: :save_block, class: [:small, :add] diff --git a/app/views/conferences/_administration.html.haml b/app/views/conferences/_administration.html.haml deleted file mode 100644 index 6cb87d9..0000000 --- a/app/views/conferences/_administration.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -= columns(medium: 3, large: 2) do - = admin_menu -= columns(medium: 9, large: 10) do - %h3.subtitle=_("menu.submenu.admin.#{@admin_step.titlecase.gsub(/\s/, '_')}") - %p=(_"articles.admin.#{@admin_step.gsub(/\s/, '_')}.description", :p) unless @hide_description === true - %div{id: "admin-#{@admin_step}"}= render "conferences/admin/#{@admin_step}" diff --git a/app/views/conferences/_conference.html.haml b/app/views/conferences/_conference.html.haml index ec0b923..f1378fb 100644 --- a/app/views/conferences/_conference.html.haml +++ b/app/views/conferences/_conference.html.haml @@ -1,18 +1,17 @@ - links ||= [ :register ] - sections ||= [ :info ] += row(tag: :header) do + = columns(class: 'conference-banner') do + .title + %h1=_!conference.title + .details + %h2.primary=location(conference.city || conference.location) if conference.city_name.present? + - if conference.start_date.present? && conference.end_date.present? + .secondary + = date_span(conference.start_date.to_date, conference.end_date.to_date) + - if conference.poster.present? + %img{src: conference.poster.full.url, role: :presentation, alt: (_'images.conference.poster', vars: { conference_title: conference.title })} %article - = row(tag: :header) do - = columns(class: 'conference-banner') do - .title - %h1=_!conference.title - .details - %h2.primary=location(conference.city || conference.location) if conference.city_name.present? - - if conference.start_date.present? && conference.end_date.present? - .secondary - = date_span(conference.start_date.to_date, conference.end_date.to_date) - - if conference.poster.present? - %img{src: conference.poster.full.url || image_path('default_poster.jpg'), role: :presentation, alt: (_'images.conference.poster', vars: { conference_title: conference.title })} - = row(class: 'conference-details') do = columns(medium: 10, push: {medium: 1}) do %h2=_!conference.title if conference.poster.present? @@ -25,9 +24,9 @@ - else %h3=_'articles.workshops.headings.Proposed_Workshops' %p=_'articles.workshops.paragraphs.Proposed_Workshops' - = render 'workshops/workshop_previews', :workshops => (conference.workshops.sort { |a, b| a.title.downcase <=> b.title.downcase }) + = render 'workshops/workshop_previews', workshops: (conference.workshops.sort { |a, b| a.title.downcase <=> b.title.downcase }) .links - = (link_to (_'forms.actions.generic.register'), register_path(conference.slug), class: [:button, :register]) if links.include?(:register) && conference.registration_status == :open + = (link_to (_'forms.actions.generic.register'), register_path(conference.slug), class: [:button, :register]) if links.include?(:register) && conference.can_register? = (link_to (_'articles.workshops.info.read_more'), conference_path(conference.slug), class: :button) if links.include?(:read_more) = (link_to (_'forms.actions.generic.administrate'), administrate_conference_path(conference.slug), class: [:button]) if links.include?(:administrate) = (link_to (_'forms.actions.generic.edit'), edit_conference_path(conference.slug), class: [:button, :subdued]) if links.include?(:edit) diff --git a/app/views/conferences/_confirm_email.html.haml b/app/views/conferences/_confirm_email.html.haml index f8270b2..6ada132 100644 --- a/app/views/conferences/_confirm_email.html.haml +++ b/app/views/conferences/_confirm_email.html.haml @@ -1,13 +1,13 @@ = columns(medium: 12) do - %p=_"articles.conference_registration.paragraphs.#{@this_conference.registration_status == :open ? '': 'Pre_'}Registration_Details" - %h3=_'articles.conference_registration.headings.Verify_Account' - %p=_'articles.conference_registration.paragraphs.Verify_Account' + %p=_"articles.conference_registration.paragraphs.#{@this_conference.registration_status == :open ? '': 'Pre_'}Registration_Details" + %h3=_'articles.conference_registration.headings.Verify_Account' + %p=_'articles.conference_registration.paragraphs.Verify_Account' - = form_tag register_path(@this_conference.slug), class: 'flex-form' do - .email-field.input-field.big - = email_field_tag :email, nil, required: true - = label_tag :email - = button_tag :continue, :value => :confirm_email + = form_tag register_path(@this_conference.slug), class: 'flex-form' do + .email-field.input-field.big + = email_field_tag :email, nil, required: true + = label_tag :email + = button :continue, value: :confirm_email = columns(medium: 12, class: 'flex-column') do - %p.stretch-item=_'articles.conference_registration.paragraphs.facebook_sign_in' - = link_to (_'forms.actions.generic.facebook_sign_in','Facebook Sign In'), auth_at_provider_path(:provider => :facebook), class: [:button, :facebook] + %p.stretch-item=_'articles.conference_registration.paragraphs.facebook_sign_in' + = link_to (_'forms.actions.generic.facebook_sign_in','Facebook Sign In'), auth_at_provider_path(:provider => :facebook), class: [:button, :facebook] diff --git a/app/views/conferences/_contact_info.html.haml b/app/views/conferences/_contact_info.html.haml index d8a69f8..405137f 100644 --- a/app/views/conferences/_contact_info.html.haml +++ b/app/views/conferences/_contact_info.html.haml @@ -5,5 +5,5 @@ = textfield :location, (params[:location] || location(@registration.city ||lookup_ip_location)), required: true, heading: 'articles.conference_registration.headings.location' = checkboxes :languages, User.AVAILABLE_LANGUAGES, (current_user.languages || [I18n.locale]).map(&:to_sym), 'languages', heading: 'articles.conference_registration.headings.languages' .actions.next-prev - = button_tag (params[:step] == :save ? :save : :next), value: :contact_info - = button_tag :previous, value: :prev_contact_info, class: :subdued, formnovalidate: true + = button (params[:step] == :save ? :save : :next), value: :contact_info + = button :previous, value: :prev_contact_info, class: :subdued, formnovalidate: true diff --git a/app/views/conferences/_done.html.haml b/app/views/conferences/_done.html.haml index 0bfc565..3cc74d1 100644 --- a/app/views/conferences/_done.html.haml +++ b/app/views/conferences/_done.html.haml @@ -48,4 +48,4 @@ - if @conference.registration_open = form_tag register_path(@this_conference.slug) do .actions - = button_tag :edit_registration, :value => :register + = button :edit_registration, :value => :register diff --git a/app/views/conferences/_email_confirm.html.haml b/app/views/conferences/_email_confirm.html.haml index 89b384b..d0256f0 100644 --- a/app/views/conferences/_email_confirm.html.haml +++ b/app/views/conferences/_email_confirm.html.haml @@ -1,6 +1,6 @@ %article - = row do - = columns(medium: 12) do - %h2=_'articles.conference_registration.headings.email_confirm','Please confirm your email address' - = columns(medium: 12) do - %p=_'articles.conference_registration.paragraphs.email_confirm', :p + = row do + = columns(medium: 12) do + %h2=_'articles.conference_registration.headings.email_confirm','Please confirm your email address' + = columns(medium: 12) do + %p=_'articles.conference_registration.paragraphs.email_confirm', :p diff --git a/app/views/conferences/_hosting.html.haml b/app/views/conferences/_hosting.html.haml index fbf596c..98c4004 100644 --- a/app/views/conferences/_hosting.html.haml +++ b/app/views/conferences/_hosting.html.haml @@ -16,5 +16,5 @@ = checkboxes :considerations, ConferenceRegistration.all_considerations, @hosting_data['considerations'], 'articles.conference_registration.host.considerations', heading: 'articles.conference_registration.headings.host.considerations', help: 'articles.conference_registration.paragraphs.host.considerations', vertical: true = textarea :notes, @hosting_data['notes'], help: 'articles.conference_registration.paragraphs.host.notes', edit_on: :focus .actions.next-prev - = button_tag (params[:step] == :save ? :save : :next), value: :hosting - = button_tag :previous, value: :prev_contact_info, class: :subdued, formnovalidate: true + = button (params[:step] == :save ? :save : :next), value: :hosting + = button :previous, value: :prev_contact_info, class: :subdued, formnovalidate: true diff --git a/app/views/conferences/_payment.html.haml b/app/views/conferences/_payment.html.haml index 4a5143e..a76633d 100644 --- a/app/views/conferences/_payment.html.haml +++ b/app/views/conferences/_payment.html.haml @@ -9,21 +9,22 @@ - else %p=@this_conference.payment_message || (_'articles.conference_registration.paragraphs.Payment', :p) = columns(large: 9, push: 1) do + = show_errors :payment = form_tag register_path(@this_conference.slug), class: :payment do = hidden_field_tag :button, :payment - payment_amounts = @this_conference.payment_amounts.present? ? @this_conference.payment_amounts : Conference.default_payment_amounts .graded-options{class: "option-count-#{payment_amounts.size}"} - payment_amounts.each_with_index do | amount, i | - = button_tag :amount_25, name: :amount, value: amount.to_f.to_s, class: "option-#{i + 1}" do + = button :amount_25, name: :amount, value: amount.to_f.to_s, class: "option-#{i + 1}" do =_! (number_to_currency amount, unit: '$') = form_tag register_path(@this_conference.slug), class: ['custom-payment', :centered] do - %span.currency=_!'$' + %span.currency=_!'$' # TODO: this needs to be localized = numberfield :amount, nil, required: true, step: 0.01, min: 0.00, label: false - = button_tag :custom_amount, value: :payment + = button :custom_amount, value: :payment %p=_'articles.conference_registration.paragraphs.currency','(amounts are in $USD)' = form_tag register_path(@this_conference.slug), class: :payment do = hidden_field_tag :button, :payment .actions.skip - = button_tag :skip, name: :amount, value: '0.0' + = button :skip, name: :amount, value: '0.0' = columns(large: 2) diff --git a/app/views/conferences/_paypal_confirm.html.haml b/app/views/conferences/_paypal_confirm.html.haml index aa44e40..d3f5646 100644 --- a/app/views/conferences/_paypal_confirm.html.haml +++ b/app/views/conferences/_paypal_confirm.html.haml @@ -1,5 +1,5 @@ = columns(medium: 12) do - %h3=_'articles.conference_registration.headings.payment_confirm' - %p=_'articles.conference_registration.paragraphs.payment_confirm', vars: {:amount => @amount} - = form_tag register_path(@this_conference.slug), :class => :payment do - .actions= button_tag :confirm_amount, :value => :paypal_confirmed + %h3=_'articles.conference_registration.headings.payment_confirm' + %p=_'articles.conference_registration.paragraphs.payment_confirm', vars: {:amount => @amount} + = form_tag register_path(@this_conference.slug), :class => :payment do + .actions= button :confirm_amount, :value => :paypal_confirmed diff --git a/app/views/conferences/_policy.html.haml b/app/views/conferences/_policy.html.haml index 8a67c29..f614a5e 100644 --- a/app/views/conferences/_policy.html.haml +++ b/app/views/conferences/_policy.html.haml @@ -1,8 +1,8 @@ = columns(medium: 12) do - %p=_'articles.conference_registration.paragraphs.Policy_Agreement', :s, 2 + %p=_'articles.conference_registration.paragraphs.Policy_Agreement', :s, 2 = columns(medium: 10, push: 1) do - = render 'application/policy' + = render 'application/policy' = columns(medium: 12) do - %p=_'articles.conference_registration.paragraphs.Confirm_Agreement', :p - = form_tag register_path(@this_conference.slug) do - .actions= button_tag :agree, :value => :policy + %p=_'articles.conference_registration.paragraphs.Confirm_Agreement', :p + = form_tag register_path(@this_conference.slug) do + .actions= button :agree, value: :policy diff --git a/app/views/conferences/_questions.html.haml b/app/views/conferences/_questions.html.haml index dbce807..c019a55 100644 --- a/app/views/conferences/_questions.html.haml +++ b/app/views/conferences/_questions.html.haml @@ -11,4 +11,4 @@ = emailfield :companion, (@registration.housing_data['companions'] || [nil]).first, heading: 'articles.conference_registration.headings.companion', help: 'articles.conference_registration.paragraphs.companion', big: true = textfield :allergies, @registration.allergies, heading: 'articles.conference_registration.headings.allergies' = textarea :other, @registration.other, plain: true, heading: 'articles.conference_registration.headings.other' - = button_tag :register, :value => :questions + = button :register, value: :questions diff --git a/app/views/conferences/_register.html.haml b/app/views/conferences/_register.html.haml index ff3125b..e058daf 100644 --- a/app/views/conferences/_register.html.haml +++ b/app/views/conferences/_register.html.haml @@ -8,4 +8,4 @@ .email-field.input-field = email_field_tag :email, nil, required: true = label_tag :email - = button_tag :register, :value => :register + = button :register, value: :register diff --git a/app/views/conferences/_registration_register.html.haml b/app/views/conferences/_registration_register.html.haml deleted file mode 100644 index 3f60166..0000000 --- a/app/views/conferences/_registration_register.html.haml +++ /dev/null @@ -1,45 +0,0 @@ -.columns{:class => @host_privledges ? 'medium-8' : 'medium-12'} - = f.fields_for @user do |u| - .columns#step-1.registration-step= u.email_field :email - %button#submit-email.next=_'form.Next' - = f.fields_for @user do |u| - .columns#step-2.registration-step= u.text_field :username - -# .columns.medium-6 - = select_tag :is_attending, options_for_select(ConferenceRegistration::AttendingOptions, @conference_registration.try(:is_attending)), :label => true - %ol - - @conference.registration_form_fields.each do |ff| - %li - - response = @conference_registration ? ConferenceRegistrationResponse.find_by(conference_registration_id: @conference_registration.id, registration_form_field_id: ff.id) : nil - = form_field ff, response -= f.actions :register - -- content_for :scripts do - :plain - function updateForm(data, step) { - $('.registration-step').each(function() { - var $this = $(this); - var this_step = parseInt($this.attr('id').replace(/^step\-(\d+)$/, '$1')); - if (this_step > step) { - $this.find('input,select').each(function(){ - var $input = $(this); - var params = $input.attr('name').match(/\[(.*?)\]/g); - var val = data.conference; - for (key in params) { - var k = params[key].replace(/\[(.*)\]/, '$1'); - val = val[params[key].replace(/\[(.*)\]/, '$1')]; - } - $input.val(val) - }); - } - }); - } - -- dom_ready do - :plain - $('#submit-email').click(function(e){ - e.preventDefault(); - $.post('register/step/1', $('form.edit_conference').serialize(), function(data) { - //console.log(data.conference.user.username); - updateForm(data, 1); - }); - }); diff --git a/app/views/conferences/_workshops.html.haml b/app/views/conferences/_workshops.html.haml index a40b2fd..997f57d 100644 --- a/app/views/conferences/_workshops.html.haml +++ b/app/views/conferences/_workshops.html.haml @@ -1,22 +1,22 @@ = columns(medium: 12, class: 'major-group') do - - if @my_workshops.present? - %h3=_'articles.conference_registration.headings.Your_Workshops' - %p=_'articles.conference_registration.paragraphs.Your_Workshops', :p - = render 'workshops/workshop_previews', :workshops => @my_workshops - - else - %p=_'articles.conference_registration.paragraphs.Create_Workshop', :p - = link_to (_'articles.conference_registration.headings.Add_Workshop'), create_workshop_path(@this_conference.slug), class: :button + - if @my_workshops.present? + %h3=_'articles.conference_registration.headings.Your_Workshops' + %p=_'articles.conference_registration.paragraphs.Your_Workshops', :p + = render 'workshops/workshop_previews', :workshops => @my_workshops + - else + %p=_'articles.conference_registration.paragraphs.Create_Workshop', :p + = link_to (_'articles.conference_registration.headings.Add_Workshop'), create_workshop_path(@this_conference.slug), class: :button - if @requested_workshops.present? - = columns(medium: 12, class: 'major-group') do - %h3=_'articles.conference_registration.headings.Workshops_You_Have_Requested' - %p=_'articles.conference_registration.paragraphs.Workshops_You_Have_Requested', :p - = render 'workshops/workshop_previews', :workshops => @requested_workshops + = columns(medium: 12, class: 'major-group') do + %h3=_'articles.conference_registration.headings.Workshops_You_Have_Requested' + %p=_'articles.conference_registration.paragraphs.Workshops_You_Have_Requested', :p + = render 'workshops/workshop_previews', :workshops => @requested_workshops - if @workshops_in_need.present? - = columns(medium: 12, class: 'major-group') do - %h3=_'articles.conference_registration.headings.Workshops_Looking_For_Facilitators' - %p=_'articles.conference_registration.paragraphs.Workshops_Looking_For_Facilitators', :p - = render 'workshops/workshop_previews', :workshops => @workshops_in_need + = columns(medium: 12, class: 'major-group') do + %h3=_'articles.conference_registration.headings.Workshops_Looking_For_Facilitators' + %p=_'articles.conference_registration.paragraphs.Workshops_Looking_For_Facilitators', :p + = render 'workshops/workshop_previews', :workshops => @workshops_in_need - if @workshops.present? - = columns(medium: 12, class: 'major-group') do - %h3=_'articles.conference_registration.headings.All_Workshops' - = render 'workshops/workshop_previews', :workshops => @workshops + = columns(medium: 12, class: 'major-group') do + %h3=_'articles.conference_registration.headings.All_Workshops' + = render 'workshops/workshop_previews', :workshops => @workshops diff --git a/app/views/conferences/broadcast.html.haml b/app/views/conferences/broadcast.html.haml index e409886..b1adf42 100644 --- a/app/views/conferences/broadcast.html.haml +++ b/app/views/conferences/broadcast.html.haml @@ -19,17 +19,17 @@ - else .text-field.input-field.big = label_tag :subject - = text_field_tag :subject, @subject, :required => true + = text_field_tag :subject, @subject, required: true .select-field.input-field = label_tag :content - = text_area_tag :content, @content, :required => true + = text_area_tag :content, @content, required: true .actions.right - if @email_sent == :test - .note=_('articles.conference_registration.notes.Test_Email_Sent',"An email was sent to #{current_user.email}", vars: {:email_address => current_user.email}) + .note=_('articles.conference_registration.notes.Test_Email_Sent',"An email was sent to #{current_user.email}", vars: {email_address: current_user.email}) - if @email_sent == :preview # let the user send the - = button_tag :edit, :value => :edit - = button_tag :send, :value => :send + = button :edit, value: :edit + = button :send, value: :send - else - = button_tag :preview, :value => :preview - = button_tag :test, :value => :test + = button :preview, value: :preview + = button :test, value: :test diff --git a/app/views/conferences/edit.html.haml b/app/views/conferences/edit.html.haml index 7b1e2bd..910ed13 100644 --- a/app/views/conferences/edit.html.haml +++ b/app/views/conferences/edit.html.haml @@ -1,30 +1,30 @@ -= render 'page_header', :page_key => 'Edit' += render 'page_header', page_key: 'Edit' %article - = row do - = columns(medium: 12) do - = form_tag save_conference_path(@this_conference.slug), class: 'composition' do - .text-editor.input-field - = label_tag :info - = text_area_tag :info, @this_conference.info, :required => true - .actions.right - .left - - I18n.backend.enabled_locales.each do |locale| - = (link_to (_'actions.conference.Translate', "Edit #{language_name(locale)} version", :vars => {:language => language_name(locale)}), edit_conference_path(@this_conference.slug, url_params(locale)), :class => 'translate') if locale.to_sym != I18n.locale.to_sym + = row do + = columns(medium: 12) do + = form_tag save_conference_path(@this_conference.slug), class: 'composition' do + .text-editor.input-field + = label_tag :info + = text_area_tag :info, @this_conference.info, :required => true + .actions.right + .left + - I18n.backend.enabled_locales.each do |locale| + = (link_to (_'actions.conference.Translate', "Edit #{language_name(locale)} version", vars: {:language => language_name(locale)}), edit_conference_path(@this_conference.slug, url_params(locale)), class: 'translate') if locale.to_sym != I18n.locale.to_sym - = button_tag :save, :value => :save + = button :save, value: :save / :javascript -/ window.jQuery || document.write('".html_safe + javascript_tag @_inline_scripts.to_a.join("\n").html_safe end def dom_ready(&block) diff --git a/app/views/conference_administration/administration.html.haml b/app/views/conference_administration/administration.html.haml index e42acf3..4bd44fb 100644 --- a/app/views/conference_administration/administration.html.haml +++ b/app/views/conference_administration/administration.html.haml @@ -1,4 +1,5 @@ - body_class 'banner-bottom' unless @this_conference.poster.present? +- add_stylesheet :admin - content_for :banner do = render :partial => 'application/header', :locals => { page_group: :administration, page_key: 'Administration', image_file: @this_conference.poster_url || 'admin.jpg'} diff --git a/app/views/conference_administration/administration_step.html.haml b/app/views/conference_administration/administration_step.html.haml index b45ea15..569bb89 100644 --- a/app/views/conference_administration/administration_step.html.haml +++ b/app/views/conference_administration/administration_step.html.haml @@ -1,4 +1,5 @@ - body_class 'banner-bottom' unless @this_conference.poster.present? +- add_stylesheet :admin - content_for :banner do = render :partial => 'application/header', :locals => { page_group: :administration, page_key: 'Administration', image_file: @this_conference.poster_url || 'admin.jpg'} diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 451d755..f9f7556 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -12,6 +12,7 @@ %link{ href: asset_path(@favicon), rel: 'icon', type: 'image/x-icon' } - @alt_lang_urls.each do |locale, url| %link{ rel: :alternate, hreflang: locale, href: url } + %link{ href: canonical_url, rel: :canonical } - if content_for?(:og_image) - og_image = yield :og_image - og_image = request.base_url + og_image @@ -80,5 +81,6 @@ %a.more-details.button{href: '#'}=_'articles.workshops.info.read_more' %button.close-btn.subdued=_'forms.actions.generic.close' = yield :footer_scripts if content_for?(:footer_scripts) + = javascripts = inline_scripts = emit_js_translations diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index deb050f..2659406 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -8,4 +8,4 @@ Rails.application.config.assets.version = '1.0' # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. -Rails.application.config.assets.precompile += %w( topojson.js pen.js *.js user-mailer.css favicon.ico ) +Rails.application.config.assets.precompile += %w( quill.js quill.css topojson.js pen.js *.js user-mailer.css favicon.ico ) diff --git a/features/contact_us.feature b/features/contact_us.feature index da16ae8..de6ec6b 100644 --- a/features/contact_us.feature +++ b/features/contact_us.feature @@ -13,7 +13,7 @@ Feature: Contact Us And select 'Something about the website' And enter a subject as 'My Contact Subject' And enter a message as 'My contact message' - And press 'Send' + And press send Then I should be on the contact_sent page And I should see 'Thank you for contacting us' @@ -31,7 +31,7 @@ Feature: Contact Us And select 'Something about the website' And enter a subject as 'My Contact Subject' And enter a message as 'My contact message' - And press 'Send' + And press send Then I should be on the contact_sent page And I should see 'Thank you for contacting us' diff --git a/features/step_definitions/interface_steps.rb b/features/step_definitions/interface_steps.rb index 18f1991..86b86cb 100644 --- a/features/step_definitions/interface_steps.rb +++ b/features/step_definitions/interface_steps.rb @@ -88,6 +88,7 @@ Then /^(?:I )?enter (?:my |an? |some |the )?(.+?)(?: as '(.+)')?$/i do |field, v sel = selector_for(field) element = first(sel, visible: true) || first(sel, visible: false) + element = element.first('[contenteditable]') if element.tag_name.to_s.downcase == 'div' unless value.present? value = case field diff --git a/features/support/env.rb b/features/support/env.rb index 39e1252..8265f30 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -11,7 +11,7 @@ DatabaseCleaner.strategy = :truncation, { except: %w[cities city_cache] } Capybara.register_driver :bb_poltergeist do |app| if ENV['CSS_TEST'] Marmara.options = { - ignore: [/paypal\./], + ignore: [/paypal(?:objects)?/, 'assets.css'], rewrite: { from: /^.*\/(.*?)\/.*?\.css$/, to: '\1.css' diff --git a/features/support/location_cache.yml b/features/support/location_cache.yml index d6e65a6..9badcd9 100644 --- a/features/support/location_cache.yml +++ b/features/support/location_cache.yml @@ -49,3 +49,455 @@ Brooklyn NY: !ruby/object:Geocoder::Result::Google - sublocality - sublocality_level_1 cache_hit: +Yellowknife: !ruby/object:Geocoder::Result::Google + data: + address_components: + - long_name: Yellowknife + short_name: Yellowknife + types: + - locality + - political + - long_name: Fort Smith, Unorganized + short_name: Fort Smith, Unorganized + types: + - administrative_area_level_3 + - political + - long_name: Fort Smith Region + short_name: Fort Smith Region + types: + - administrative_area_level_2 + - political + - long_name: Northwest Territories + short_name: NT + types: + - administrative_area_level_1 + - political + - long_name: Canada + short_name: CA + types: + - country + - political + - long_name: X0E + short_name: X0E + types: + - postal_code + - postal_code_prefix + formatted_address: Yellowknife, NT X0E, Canada + geometry: + bounds: + northeast: + lat: 62.5412576 + lng: -114.299678 + southwest: + lat: 62.4084661 + lng: -114.518312 + location: + lat: 62.4539717 + lng: -114.3717886 + location_type: APPROXIMATE + viewport: + northeast: + lat: 62.5412577 + lng: -114.299678 + southwest: + lat: 62.4084945 + lng: -114.518312 + place_id: ChIJyYJGoyzx0VMRge9xQyQ3wbQ + types: + - locality + - political + cache_hit: +New Orleans: !ruby/object:Geocoder::Result::Google + data: + address_components: + - long_name: New Orleans + short_name: New Orleans + types: + - locality + - political + - long_name: Orleans Parish + short_name: Orleans Parish + types: + - administrative_area_level_2 + - political + - long_name: Louisiana + short_name: LA + types: + - administrative_area_level_1 + - political + - long_name: United States + short_name: US + types: + - country + - political + formatted_address: New Orleans, LA, USA + geometry: + bounds: + northeast: + lat: 30.199332 + lng: -89.625053 + southwest: + lat: 29.8666609 + lng: -90.14007389999999 + location: + lat: 29.95106579999999 + lng: -90.0715323 + location_type: APPROXIMATE + viewport: + northeast: + lat: 30.1748625 + lng: -89.6269311 + southwest: + lat: 29.86842459999999 + lng: -90.1380099 + place_id: ChIJZYIRslSkIIYRtNMiXuhbBts + types: + - locality + - political + cache_hit: +Drumheller AB: !ruby/object:Geocoder::Result::Google + data: + address_components: + - long_name: Drumheller + short_name: Drumheller + types: + - locality + - political + - long_name: Division No. 5 + short_name: Division No. 5 + types: + - administrative_area_level_2 + - political + - long_name: Alberta + short_name: AB + types: + - administrative_area_level_1 + - political + - long_name: Canada + short_name: CA + types: + - country + - political + formatted_address: Drumheller, AB, Canada + geometry: + bounds: + northeast: + lat: 51.488701 + lng: -112.4530051 + southwest: + lat: 51.3208389 + lng: -112.806076 + location: + lat: 51.4651847 + lng: -112.7105343 + location_type: APPROXIMATE + viewport: + northeast: + lat: 51.488701 + lng: -112.4530051 + southwest: + lat: 51.3208389 + lng: -112.806076 + place_id: ChIJHx_0B4ANc1MRWZCRwItZUUw + types: + - locality + - political + cache_hit: +Portland OR: !ruby/object:Geocoder::Result::Google + data: + address_components: + - long_name: Portland + short_name: Portland + types: + - locality + - political + - long_name: Multnomah County + short_name: Multnomah County + types: + - administrative_area_level_2 + - political + - long_name: Oregon + short_name: OR + types: + - administrative_area_level_1 + - political + - long_name: United States + short_name: US + types: + - country + - political + formatted_address: Portland, OR, USA + geometry: + bounds: + northeast: + lat: 45.654424 + lng: -122.4718489 + southwest: + lat: 45.432393 + lng: -122.8369952 + location: + lat: 45.5230622 + lng: -122.6764816 + location_type: APPROXIMATE + viewport: + northeast: + lat: 45.6524799 + lng: -122.4718489 + southwest: + lat: 45.432393 + lng: -122.8369952 + place_id: ChIJJ3SpfQsLlVQRkYXR9ua5Nhw + types: + - locality + - political + cache_hit: +Prince Rupert BC: !ruby/object:Geocoder::Result::Google + data: + address_components: + - long_name: Prince Rupert + short_name: Prince Rupert + types: + - locality + - political + - long_name: Skeena-Queen Charlotte + short_name: Skeena-Queen Charlotte + types: + - administrative_area_level_2 + - political + - long_name: British Columbia + short_name: BC + types: + - administrative_area_level_1 + - political + - long_name: Canada + short_name: CA + types: + - country + - political + formatted_address: Prince Rupert, BC, Canada + geometry: + bounds: + northeast: + lat: 54.338083 + lng: -130.2437961 + southwest: + lat: 54.19392 + lng: -130.3634291 + location: + lat: 54.3150367 + lng: -130.3208187 + location_type: APPROXIMATE + viewport: + northeast: + lat: 54.3343706 + lng: -130.2478032 + southwest: + lat: 54.202669 + lng: -130.3608029 + place_id: ChIJaUV_axPVclQRElbZTQ_jB3E + types: + - locality + - political + cache_hit: +Regina, SK: !ruby/object:Geocoder::Result::Google + data: + address_components: + - long_name: Regina + short_name: Regina + types: + - locality + - political + - long_name: Sherwood No. 159 + short_name: Sherwood No. 159 + types: + - administrative_area_level_3 + - political + - long_name: Division No. 6 + short_name: Division No. 6 + types: + - administrative_area_level_2 + - political + - long_name: Saskatchewan + short_name: SK + types: + - administrative_area_level_1 + - political + - long_name: Canada + short_name: CA + types: + - country + - political + formatted_address: Regina, SK, Canada + geometry: + bounds: + northeast: + lat: 50.5207396 + lng: -104.4924259 + southwest: + lat: 50.3964489 + lng: -104.7783923 + location: + lat: 50.4452112 + lng: -104.6188944 + location_type: APPROXIMATE + viewport: + northeast: + lat: 50.5207396 + lng: -104.4924259 + southwest: + lat: 50.3964489 + lng: -104.7783923 + place_id: ChIJ6z2l-0AeHFMRsVR7t5YySjU + types: + - locality + - political + cache_hit: +Edmundston, NB: !ruby/object:Geocoder::Result::Google + data: + address_components: + - long_name: Edmundston + short_name: Edmundston + types: + - locality + - political + - long_name: Madawaska County + short_name: Madawaska County + types: + - administrative_area_level_2 + - political + - long_name: New Brunswick + short_name: NB + types: + - administrative_area_level_1 + - political + - long_name: Canada + short_name: CA + types: + - country + - political + formatted_address: Edmundston, NB, Canada + geometry: + bounds: + northeast: + lat: 47.456634 + lng: -68.14554509999999 + southwest: + lat: 47.3177789 + lng: -68.44122399999999 + location: + lat: 47.3690127 + lng: -68.32667409999999 + location_type: APPROXIMATE + viewport: + northeast: + lat: 47.456634 + lng: -68.14554509999999 + southwest: + lat: 47.3183866 + lng: -68.44122399999999 + place_id: ChIJuQPKxpipvUwRtNjzmeech34 + types: + - locality + - political + cache_hit: +Souris, MB: !ruby/object:Geocoder::Result::Google + data: + address_components: + - long_name: Souris + short_name: Souris + types: + - locality + - political + - long_name: Glenwood + short_name: Glenwood + types: + - administrative_area_level_3 + - political + - long_name: Division No. 7 + short_name: Division No. 7 + types: + - administrative_area_level_2 + - political + - long_name: Manitoba + short_name: MB + types: + - administrative_area_level_1 + - political + - long_name: Canada + short_name: CA + types: + - country + - political + - long_name: R0K + short_name: R0K + types: + - postal_code + - postal_code_prefix + formatted_address: Souris, MB R0K, Canada + geometry: + bounds: + northeast: + lat: 49.6272052 + lng: -100.2464522 + southwest: + lat: 49.6061908 + lng: -100.2774639 + location: + lat: 49.6207985 + lng: -100.2583026 + location_type: APPROXIMATE + viewport: + northeast: + lat: 49.6272052 + lng: -100.2464522 + southwest: + lat: 49.6061908 + lng: -100.2774639 + place_id: ChIJjVrTVVh851IRLuGKzGdiUj8 + types: + - locality + - political + cache_hit: +Eldorado, MX: !ruby/object:Geocoder::Result::Google + data: + address_components: + - long_name: Eldorado + short_name: Eldorado + types: + - locality + - political + - long_name: Sinaloa + short_name: Sin. + types: + - administrative_area_level_1 + - political + - long_name: Mexico + short_name: MX + types: + - country + - political + formatted_address: Eldorado, Sin., Mexico + geometry: + bounds: + northeast: + lat: 24.3379838 + lng: -107.3476352 + southwest: + lat: 24.3108689 + lng: -107.3831387 + location: + lat: 24.3240714 + lng: -107.3584174 + location_type: APPROXIMATE + viewport: + northeast: + lat: 24.3379838 + lng: -107.3476352 + southwest: + lat: 24.3108689 + lng: -107.3831387 + place_id: ChIJv33Pqm0ho4YRUQ45wKAluZ4 + types: + - locality + - political + cache_hit: diff --git a/features/workshops.feature b/features/workshops.feature index 0a79960..79c91ef 100644 --- a/features/workshops.feature +++ b/features/workshops.feature @@ -320,4 +320,4 @@ Feature: Workshops And click the 'Reply' button Then I should see 'If we can find a Canadian facilitator' - And 'Geronimo' should get a 'replied' email \ No newline at end of file + And 'Geronimo' should get a 'replied' email diff --git a/vendor/assets/javascripts/quill.js b/vendor/assets/javascripts/quill.js new file mode 100644 index 0000000..8d2f87e --- /dev/null +++ b/vendor/assets/javascripts/quill.js @@ -0,0 +1,10576 @@ +/*! + * Quill Editor v1.1.9 + * https://quilljs.com/ + * Copyright (c) 2014, Jason Chen + * Copyright (c) 2013, salesforce.com + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["Quill"] = factory(); + else + root["Quill"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + module.exports = __webpack_require__(53); + + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _quill = __webpack_require__(18); + + var _quill2 = _interopRequireDefault(_quill); + + var _block = __webpack_require__(29); + + var _block2 = _interopRequireDefault(_block); + + var _break = __webpack_require__(30); + + var _break2 = _interopRequireDefault(_break); + + var _container = __webpack_require__(42); + + var _container2 = _interopRequireDefault(_container); + + var _cursor = __webpack_require__(34); + + var _cursor2 = _interopRequireDefault(_cursor); + + var _embed = __webpack_require__(31); + + var _embed2 = _interopRequireDefault(_embed); + + var _inline = __webpack_require__(32); + + var _inline2 = _interopRequireDefault(_inline); + + var _scroll = __webpack_require__(43); + + var _scroll2 = _interopRequireDefault(_scroll); + + var _text = __webpack_require__(33); + + var _text2 = _interopRequireDefault(_text); + + var _clipboard = __webpack_require__(44); + + var _clipboard2 = _interopRequireDefault(_clipboard); + + var _history = __webpack_require__(51); + + var _history2 = _interopRequireDefault(_history); + + var _keyboard = __webpack_require__(52); + + var _keyboard2 = _interopRequireDefault(_keyboard); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + _quill2.default.register({ + 'blots/block': _block2.default, + 'blots/block/embed': _block.BlockEmbed, + 'blots/break': _break2.default, + 'blots/container': _container2.default, + 'blots/cursor': _cursor2.default, + 'blots/embed': _embed2.default, + 'blots/inline': _inline2.default, + 'blots/scroll': _scroll2.default, + 'blots/text': _text2.default, + + 'modules/clipboard': _clipboard2.default, + 'modules/history': _history2.default, + 'modules/keyboard': _keyboard2.default + }); + + _parchment2.default.register(_block2.default, _break2.default, _cursor2.default, _inline2.default, _scroll2.default, _text2.default); + + module.exports = _quill2.default; + +/***/ }, +/* 2 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var container_1 = __webpack_require__(3); + var format_1 = __webpack_require__(7); + var leaf_1 = __webpack_require__(12); + var scroll_1 = __webpack_require__(13); + var inline_1 = __webpack_require__(14); + var block_1 = __webpack_require__(15); + var embed_1 = __webpack_require__(16); + var text_1 = __webpack_require__(17); + var attributor_1 = __webpack_require__(8); + var class_1 = __webpack_require__(10); + var style_1 = __webpack_require__(11); + var store_1 = __webpack_require__(9); + var Registry = __webpack_require__(6); + var Parchment = { + Scope: Registry.Scope, + create: Registry.create, + find: Registry.find, + query: Registry.query, + register: Registry.register, + Container: container_1.default, + Format: format_1.default, + Leaf: leaf_1.default, + Embed: embed_1.default, + Scroll: scroll_1.default, + Block: block_1.default, + Inline: inline_1.default, + Text: text_1.default, + Attributor: { + Attribute: attributor_1.default, + Class: class_1.default, + Style: style_1.default, + Store: store_1.default + } + }; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = Parchment; + + +/***/ }, +/* 3 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + var linked_list_1 = __webpack_require__(4); + var shadow_1 = __webpack_require__(5); + var Registry = __webpack_require__(6); + var ContainerBlot = (function (_super) { + __extends(ContainerBlot, _super); + function ContainerBlot() { + return _super.apply(this, arguments) || this; + } + ContainerBlot.prototype.appendChild = function (other) { + this.insertBefore(other); + }; + ContainerBlot.prototype.attach = function () { + var _this = this; + _super.prototype.attach.call(this); + this.children = new linked_list_1.default(); + // Need to be reversed for if DOM nodes already in order + [].slice.call(this.domNode.childNodes).reverse().forEach(function (node) { + try { + var child = makeBlot(node); + _this.insertBefore(child, _this.children.head); + } + catch (err) { + if (err instanceof Registry.ParchmentError) + return; + else + throw err; + } + }); + }; + ContainerBlot.prototype.deleteAt = function (index, length) { + if (index === 0 && length === this.length()) { + return this.remove(); + } + this.children.forEachAt(index, length, function (child, offset, length) { + child.deleteAt(offset, length); + }); + }; + ContainerBlot.prototype.descendant = function (criteria, index) { + var _a = this.children.find(index), child = _a[0], offset = _a[1]; + if ((criteria.blotName == null && criteria(child)) || + (criteria.blotName != null && child instanceof criteria)) { + return [child, offset]; + } + else if (child instanceof ContainerBlot) { + return child.descendant(criteria, offset); + } + else { + return [null, -1]; + } + }; + ContainerBlot.prototype.descendants = function (criteria, index, length) { + if (index === void 0) { index = 0; } + if (length === void 0) { length = Number.MAX_VALUE; } + var descendants = [], lengthLeft = length; + this.children.forEachAt(index, length, function (child, index, length) { + if ((criteria.blotName == null && criteria(child)) || + (criteria.blotName != null && child instanceof criteria)) { + descendants.push(child); + } + if (child instanceof ContainerBlot) { + descendants = descendants.concat(child.descendants(criteria, index, lengthLeft)); + } + lengthLeft -= length; + }); + return descendants; + }; + ContainerBlot.prototype.detach = function () { + this.children.forEach(function (child) { + child.detach(); + }); + _super.prototype.detach.call(this); + }; + ContainerBlot.prototype.formatAt = function (index, length, name, value) { + this.children.forEachAt(index, length, function (child, offset, length) { + child.formatAt(offset, length, name, value); + }); + }; + ContainerBlot.prototype.insertAt = function (index, value, def) { + var _a = this.children.find(index), child = _a[0], offset = _a[1]; + if (child) { + child.insertAt(offset, value, def); + } + else { + var blot = (def == null) ? Registry.create('text', value) : Registry.create(value, def); + this.appendChild(blot); + } + }; + ContainerBlot.prototype.insertBefore = function (childBlot, refBlot) { + if (this.statics.allowedChildren != null && !this.statics.allowedChildren.some(function (child) { + return childBlot instanceof child; + })) { + throw new Registry.ParchmentError("Cannot insert " + childBlot.statics.blotName + " into " + this.statics.blotName); + } + childBlot.insertInto(this, refBlot); + }; + ContainerBlot.prototype.length = function () { + return this.children.reduce(function (memo, child) { + return memo + child.length(); + }, 0); + }; + ContainerBlot.prototype.moveChildren = function (targetParent, refNode) { + this.children.forEach(function (child) { + targetParent.insertBefore(child, refNode); + }); + }; + ContainerBlot.prototype.optimize = function () { + _super.prototype.optimize.call(this); + if (this.children.length === 0) { + if (this.statics.defaultChild != null) { + var child = Registry.create(this.statics.defaultChild); + this.appendChild(child); + child.optimize(); + } + else { + this.remove(); + } + } + }; + ContainerBlot.prototype.path = function (index, inclusive) { + if (inclusive === void 0) { inclusive = false; } + var _a = this.children.find(index, inclusive), child = _a[0], offset = _a[1]; + var position = [[this, index]]; + if (child instanceof ContainerBlot) { + return position.concat(child.path(offset, inclusive)); + } + else if (child != null) { + position.push([child, offset]); + } + return position; + }; + ContainerBlot.prototype.removeChild = function (child) { + this.children.remove(child); + }; + ContainerBlot.prototype.replace = function (target) { + if (target instanceof ContainerBlot) { + target.moveChildren(this); + } + _super.prototype.replace.call(this, target); + }; + ContainerBlot.prototype.split = function (index, force) { + if (force === void 0) { force = false; } + if (!force) { + if (index === 0) + return this; + if (index === this.length()) + return this.next; + } + var after = this.clone(); + this.parent.insertBefore(after, this.next); + this.children.forEachAt(index, this.length(), function (child, offset, length) { + child = child.split(offset, force); + after.appendChild(child); + }); + return after; + }; + ContainerBlot.prototype.unwrap = function () { + this.moveChildren(this.parent, this.next); + this.remove(); + }; + ContainerBlot.prototype.update = function (mutations) { + var _this = this; + var addedNodes = [], removedNodes = []; + mutations.forEach(function (mutation) { + if (mutation.target === _this.domNode && mutation.type === 'childList') { + addedNodes.push.apply(addedNodes, mutation.addedNodes); + removedNodes.push.apply(removedNodes, mutation.removedNodes); + } + }); + removedNodes.forEach(function (node) { + // Check node has actually been removed + // One exception is Chrome does not immediately remove IFRAMEs + // from DOM but MutationRecord is correct in its reported removal + if (node.parentNode != null && node.tagName !== 'IFRAME' && + (document.body.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_CONTAINED_BY)) { + return; + } + var blot = Registry.find(node); + if (blot == null) + return; + if (blot.domNode.parentNode == null || blot.domNode.parentNode === _this.domNode) { + blot.detach(); + } + }); + addedNodes.filter(function (node) { + return node.parentNode == _this.domNode; + }).sort(function (a, b) { + if (a === b) + return 0; + if (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING) { + return 1; + } + return -1; + }).forEach(function (node) { + var refBlot = null; + if (node.nextSibling != null) { + refBlot = Registry.find(node.nextSibling); + } + var blot = makeBlot(node); + if (blot.next != refBlot || blot.next == null) { + if (blot.parent != null) { + blot.parent.removeChild(_this); + } + _this.insertBefore(blot, refBlot); + } + }); + }; + return ContainerBlot; + }(shadow_1.default)); + function makeBlot(node) { + var blot = Registry.find(node); + if (blot == null) { + try { + blot = Registry.create(node); + } + catch (e) { + blot = Registry.create(Registry.Scope.INLINE); + [].slice.call(node.childNodes).forEach(function (child) { + blot.domNode.appendChild(child); + }); + node.parentNode.replaceChild(blot.domNode, node); + blot.attach(); + } + } + return blot; + } + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = ContainerBlot; + + +/***/ }, +/* 4 */ +/***/ function(module, exports) { + + "use strict"; + var LinkedList = (function () { + function LinkedList() { + this.head = this.tail = undefined; + this.length = 0; + } + LinkedList.prototype.append = function () { + var nodes = []; + for (var _i = 0; _i < arguments.length; _i++) { + nodes[_i] = arguments[_i]; + } + this.insertBefore(nodes[0], undefined); + if (nodes.length > 1) { + this.append.apply(this, nodes.slice(1)); + } + }; + LinkedList.prototype.contains = function (node) { + var cur, next = this.iterator(); + while (cur = next()) { + if (cur === node) + return true; + } + return false; + }; + LinkedList.prototype.insertBefore = function (node, refNode) { + node.next = refNode; + if (refNode != null) { + node.prev = refNode.prev; + if (refNode.prev != null) { + refNode.prev.next = node; + } + refNode.prev = node; + if (refNode === this.head) { + this.head = node; + } + } + else if (this.tail != null) { + this.tail.next = node; + node.prev = this.tail; + this.tail = node; + } + else { + node.prev = undefined; + this.head = this.tail = node; + } + this.length += 1; + }; + LinkedList.prototype.offset = function (target) { + var index = 0, cur = this.head; + while (cur != null) { + if (cur === target) + return index; + index += cur.length(); + cur = cur.next; + } + return -1; + }; + LinkedList.prototype.remove = function (node) { + if (!this.contains(node)) + return; + if (node.prev != null) + node.prev.next = node.next; + if (node.next != null) + node.next.prev = node.prev; + if (node === this.head) + this.head = node.next; + if (node === this.tail) + this.tail = node.prev; + this.length -= 1; + }; + LinkedList.prototype.iterator = function (curNode) { + if (curNode === void 0) { curNode = this.head; } + // TODO use yield when we can + return function () { + var ret = curNode; + if (curNode != null) + curNode = curNode.next; + return ret; + }; + }; + LinkedList.prototype.find = function (index, inclusive) { + if (inclusive === void 0) { inclusive = false; } + var cur, next = this.iterator(); + while (cur = next()) { + var length_1 = cur.length(); + if (index < length_1 || (inclusive && index === length_1 && (cur.next == null || cur.next.length() !== 0))) { + return [cur, index]; + } + index -= length_1; + } + return [null, 0]; + }; + LinkedList.prototype.forEach = function (callback) { + var cur, next = this.iterator(); + while (cur = next()) { + callback(cur); + } + }; + LinkedList.prototype.forEachAt = function (index, length, callback) { + if (length <= 0) + return; + var _a = this.find(index), startNode = _a[0], offset = _a[1]; + var cur, curIndex = index - offset, next = this.iterator(startNode); + while ((cur = next()) && curIndex < index + length) { + var curLength = cur.length(); + if (index > curIndex) { + callback(cur, index - curIndex, Math.min(length, curIndex + curLength - index)); + } + else { + callback(cur, 0, Math.min(curLength, index + length - curIndex)); + } + curIndex += curLength; + } + }; + LinkedList.prototype.map = function (callback) { + return this.reduce(function (memo, cur) { + memo.push(callback(cur)); + return memo; + }, []); + }; + LinkedList.prototype.reduce = function (callback, memo) { + var cur, next = this.iterator(); + while (cur = next()) { + memo = callback(memo, cur); + } + return memo; + }; + return LinkedList; + }()); + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = LinkedList; + + +/***/ }, +/* 5 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var Registry = __webpack_require__(6); + var ShadowBlot = (function () { + function ShadowBlot(domNode) { + this.domNode = domNode; + this.attach(); + } + Object.defineProperty(ShadowBlot.prototype, "statics", { + // Hack for accessing inherited static methods + get: function () { + return this.constructor; + }, + enumerable: true, + configurable: true + }); + ShadowBlot.create = function (value) { + if (this.tagName == null) { + throw new Registry.ParchmentError('Blot definition missing tagName'); + } + var node; + if (Array.isArray(this.tagName)) { + if (typeof value === 'string') { + value = value.toUpperCase(); + if (parseInt(value).toString() === value) { + value = parseInt(value); + } + } + if (typeof value === 'number') { + node = document.createElement(this.tagName[value - 1]); + } + else if (this.tagName.indexOf(value) > -1) { + node = document.createElement(value); + } + else { + node = document.createElement(this.tagName[0]); + } + } + else { + node = document.createElement(this.tagName); + } + if (this.className) { + node.classList.add(this.className); + } + return node; + }; + ShadowBlot.prototype.attach = function () { + this.domNode[Registry.DATA_KEY] = { blot: this }; + }; + ShadowBlot.prototype.clone = function () { + var domNode = this.domNode.cloneNode(); + return Registry.create(domNode); + }; + ShadowBlot.prototype.detach = function () { + if (this.parent != null) + this.parent.removeChild(this); + delete this.domNode[Registry.DATA_KEY]; + }; + ShadowBlot.prototype.deleteAt = function (index, length) { + var blot = this.isolate(index, length); + blot.remove(); + }; + ShadowBlot.prototype.formatAt = function (index, length, name, value) { + var blot = this.isolate(index, length); + if (Registry.query(name, Registry.Scope.BLOT) != null && value) { + blot.wrap(name, value); + } + else if (Registry.query(name, Registry.Scope.ATTRIBUTE) != null) { + var parent_1 = Registry.create(this.statics.scope); + blot.wrap(parent_1); + parent_1.format(name, value); + } + }; + ShadowBlot.prototype.insertAt = function (index, value, def) { + var blot = (def == null) ? Registry.create('text', value) : Registry.create(value, def); + var ref = this.split(index); + this.parent.insertBefore(blot, ref); + }; + ShadowBlot.prototype.insertInto = function (parentBlot, refBlot) { + if (this.parent != null) { + this.parent.children.remove(this); + } + parentBlot.children.insertBefore(this, refBlot); + if (refBlot != null) { + var refDomNode = refBlot.domNode; + } + if (this.next == null || this.domNode.nextSibling != refDomNode) { + parentBlot.domNode.insertBefore(this.domNode, (typeof refDomNode !== 'undefined') ? refDomNode : null); + } + this.parent = parentBlot; + }; + ShadowBlot.prototype.isolate = function (index, length) { + var target = this.split(index); + target.split(length); + return target; + }; + ShadowBlot.prototype.length = function () { + return 1; + }; + ; + ShadowBlot.prototype.offset = function (root) { + if (root === void 0) { root = this.parent; } + if (this.parent == null || this == root) + return 0; + return this.parent.children.offset(this) + this.parent.offset(root); + }; + ShadowBlot.prototype.optimize = function () { + // TODO clean up once we use WeakMap + if (this.domNode[Registry.DATA_KEY] != null) { + delete this.domNode[Registry.DATA_KEY].mutations; + } + }; + ShadowBlot.prototype.remove = function () { + if (this.domNode.parentNode != null) { + this.domNode.parentNode.removeChild(this.domNode); + } + this.detach(); + }; + ShadowBlot.prototype.replace = function (target) { + if (target.parent == null) + return; + target.parent.insertBefore(this, target.next); + target.remove(); + }; + ShadowBlot.prototype.replaceWith = function (name, value) { + var replacement = typeof name === 'string' ? Registry.create(name, value) : name; + replacement.replace(this); + return replacement; + }; + ShadowBlot.prototype.split = function (index, force) { + return index === 0 ? this : this.next; + }; + ShadowBlot.prototype.update = function (mutations) { + if (mutations === void 0) { mutations = []; } + // Nothing to do by default + }; + ShadowBlot.prototype.wrap = function (name, value) { + var wrapper = typeof name === 'string' ? Registry.create(name, value) : name; + if (this.parent != null) { + this.parent.insertBefore(wrapper, this.next); + } + wrapper.appendChild(this); + return wrapper; + }; + return ShadowBlot; + }()); + ShadowBlot.blotName = 'abstract'; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = ShadowBlot; + + +/***/ }, +/* 6 */ +/***/ function(module, exports) { + + "use strict"; + var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + var ParchmentError = (function (_super) { + __extends(ParchmentError, _super); + function ParchmentError(message) { + var _this; + message = '[Parchment] ' + message; + _this = _super.call(this, message) || this; + _this.message = message; + _this.name = _this.constructor.name; + return _this; + } + return ParchmentError; + }(Error)); + exports.ParchmentError = ParchmentError; + var attributes = {}; + var classes = {}; + var tags = {}; + var types = {}; + exports.DATA_KEY = '__blot'; + var Scope; + (function (Scope) { + Scope[Scope["TYPE"] = 3] = "TYPE"; + Scope[Scope["LEVEL"] = 12] = "LEVEL"; + Scope[Scope["ATTRIBUTE"] = 13] = "ATTRIBUTE"; + Scope[Scope["BLOT"] = 14] = "BLOT"; + Scope[Scope["INLINE"] = 7] = "INLINE"; + Scope[Scope["BLOCK"] = 11] = "BLOCK"; + Scope[Scope["BLOCK_BLOT"] = 10] = "BLOCK_BLOT"; + Scope[Scope["INLINE_BLOT"] = 6] = "INLINE_BLOT"; + Scope[Scope["BLOCK_ATTRIBUTE"] = 9] = "BLOCK_ATTRIBUTE"; + Scope[Scope["INLINE_ATTRIBUTE"] = 5] = "INLINE_ATTRIBUTE"; + Scope[Scope["ANY"] = 15] = "ANY"; + })(Scope = exports.Scope || (exports.Scope = {})); + ; + function create(input, value) { + var match = query(input); + if (match == null) { + throw new ParchmentError("Unable to create " + input + " blot"); + } + var BlotClass = match; + var node = input instanceof Node ? input : BlotClass.create(value); + return new BlotClass(node, value); + } + exports.create = create; + function find(node, bubble) { + if (bubble === void 0) { bubble = false; } + if (node == null) + return null; + if (node[exports.DATA_KEY] != null) + return node[exports.DATA_KEY].blot; + if (bubble) + return find(node.parentNode, bubble); + return null; + } + exports.find = find; + function query(query, scope) { + if (scope === void 0) { scope = Scope.ANY; } + var match; + if (typeof query === 'string') { + match = types[query] || attributes[query]; + } + else if (query instanceof Text) { + match = types['text']; + } + else if (typeof query === 'number') { + if (query & Scope.LEVEL & Scope.BLOCK) { + match = types['block']; + } + else if (query & Scope.LEVEL & Scope.INLINE) { + match = types['inline']; + } + } + else if (query instanceof HTMLElement) { + var names = (query.getAttribute('class') || '').split(/\s+/); + for (var i in names) { + match = classes[names[i]]; + if (match) + break; + } + match = match || tags[query.tagName]; + } + if (match == null) + return null; + if ((scope & Scope.LEVEL & match.scope) && (scope & Scope.TYPE & match.scope)) + return match; + return null; + } + exports.query = query; + function register() { + var Definitions = []; + for (var _i = 0; _i < arguments.length; _i++) { + Definitions[_i] = arguments[_i]; + } + if (Definitions.length > 1) { + return Definitions.map(function (d) { + return register(d); + }); + } + var Definition = Definitions[0]; + if (typeof Definition.blotName !== 'string' && typeof Definition.attrName !== 'string') { + throw new ParchmentError('Invalid definition'); + } + else if (Definition.blotName === 'abstract') { + throw new ParchmentError('Cannot register abstract class'); + } + types[Definition.blotName || Definition.attrName] = Definition; + if (typeof Definition.keyName === 'string') { + attributes[Definition.keyName] = Definition; + } + else { + if (Definition.className != null) { + classes[Definition.className] = Definition; + } + if (Definition.tagName != null) { + if (Array.isArray(Definition.tagName)) { + Definition.tagName = Definition.tagName.map(function (tagName) { + return tagName.toUpperCase(); + }); + } + else { + Definition.tagName = Definition.tagName.toUpperCase(); + } + var tagNames = Array.isArray(Definition.tagName) ? Definition.tagName : [Definition.tagName]; + tagNames.forEach(function (tag) { + if (tags[tag] == null || Definition.className == null) { + tags[tag] = Definition; + } + }); + } + } + return Definition; + } + exports.register = register; + + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + var attributor_1 = __webpack_require__(8); + var store_1 = __webpack_require__(9); + var container_1 = __webpack_require__(3); + var Registry = __webpack_require__(6); + var FormatBlot = (function (_super) { + __extends(FormatBlot, _super); + function FormatBlot() { + return _super.apply(this, arguments) || this; + } + FormatBlot.formats = function (domNode) { + if (typeof this.tagName === 'string') { + return true; + } + else if (Array.isArray(this.tagName)) { + return domNode.tagName.toLowerCase(); + } + return undefined; + }; + FormatBlot.prototype.attach = function () { + _super.prototype.attach.call(this); + this.attributes = new store_1.default(this.domNode); + }; + FormatBlot.prototype.format = function (name, value) { + var format = Registry.query(name); + if (format instanceof attributor_1.default) { + this.attributes.attribute(format, value); + } + else if (value) { + if (format != null && (name !== this.statics.blotName || this.formats()[name] !== value)) { + this.replaceWith(name, value); + } + } + }; + FormatBlot.prototype.formats = function () { + var formats = this.attributes.values(); + var format = this.statics.formats(this.domNode); + if (format != null) { + formats[this.statics.blotName] = format; + } + return formats; + }; + FormatBlot.prototype.replaceWith = function (name, value) { + var replacement = _super.prototype.replaceWith.call(this, name, value); + this.attributes.copy(replacement); + return replacement; + }; + FormatBlot.prototype.update = function (mutations) { + var _this = this; + _super.prototype.update.call(this, mutations); + if (mutations.some(function (mutation) { + return mutation.target === _this.domNode && mutation.type === 'attributes'; + })) { + this.attributes.build(); + } + }; + FormatBlot.prototype.wrap = function (name, value) { + var wrapper = _super.prototype.wrap.call(this, name, value); + if (wrapper instanceof FormatBlot && wrapper.statics.scope === this.statics.scope) { + this.attributes.move(wrapper); + } + return wrapper; + }; + return FormatBlot; + }(container_1.default)); + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = FormatBlot; + + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var Registry = __webpack_require__(6); + var Attributor = (function () { + function Attributor(attrName, keyName, options) { + if (options === void 0) { options = {}; } + this.attrName = attrName; + this.keyName = keyName; + var attributeBit = Registry.Scope.TYPE & Registry.Scope.ATTRIBUTE; + if (options.scope != null) { + // Ignore type bits, force attribute bit + this.scope = (options.scope & Registry.Scope.LEVEL) | attributeBit; + } + else { + this.scope = Registry.Scope.ATTRIBUTE; + } + if (options.whitelist != null) + this.whitelist = options.whitelist; + } + Attributor.keys = function (node) { + return [].map.call(node.attributes, function (item) { + return item.name; + }); + }; + Attributor.prototype.add = function (node, value) { + if (!this.canAdd(node, value)) + return false; + node.setAttribute(this.keyName, value); + return true; + }; + Attributor.prototype.canAdd = function (node, value) { + var match = Registry.query(node, Registry.Scope.BLOT & (this.scope | Registry.Scope.TYPE)); + if (match != null && (this.whitelist == null || this.whitelist.indexOf(value) > -1)) { + return true; + } + return false; + }; + Attributor.prototype.remove = function (node) { + node.removeAttribute(this.keyName); + }; + Attributor.prototype.value = function (node) { + var value = node.getAttribute(this.keyName); + return this.canAdd(node, value) ? value : ''; + }; + return Attributor; + }()); + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = Attributor; + + +/***/ }, +/* 9 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var attributor_1 = __webpack_require__(8); + var class_1 = __webpack_require__(10); + var style_1 = __webpack_require__(11); + var Registry = __webpack_require__(6); + var AttributorStore = (function () { + function AttributorStore(domNode) { + this.attributes = {}; + this.domNode = domNode; + this.build(); + } + AttributorStore.prototype.attribute = function (attribute, value) { + if (value) { + if (attribute.add(this.domNode, value)) { + if (attribute.value(this.domNode) != null) { + this.attributes[attribute.attrName] = attribute; + } + else { + delete this.attributes[attribute.attrName]; + } + } + } + else { + attribute.remove(this.domNode); + delete this.attributes[attribute.attrName]; + } + }; + AttributorStore.prototype.build = function () { + var _this = this; + this.attributes = {}; + var attributes = attributor_1.default.keys(this.domNode); + var classes = class_1.default.keys(this.domNode); + var styles = style_1.default.keys(this.domNode); + attributes.concat(classes).concat(styles).forEach(function (name) { + var attr = Registry.query(name, Registry.Scope.ATTRIBUTE); + if (attr instanceof attributor_1.default) { + _this.attributes[attr.attrName] = attr; + } + }); + }; + AttributorStore.prototype.copy = function (target) { + var _this = this; + Object.keys(this.attributes).forEach(function (key) { + var value = _this.attributes[key].value(_this.domNode); + target.format(key, value); + }); + }; + AttributorStore.prototype.move = function (target) { + var _this = this; + this.copy(target); + Object.keys(this.attributes).forEach(function (key) { + _this.attributes[key].remove(_this.domNode); + }); + this.attributes = {}; + }; + AttributorStore.prototype.values = function () { + var _this = this; + return Object.keys(this.attributes).reduce(function (attributes, name) { + attributes[name] = _this.attributes[name].value(_this.domNode); + return attributes; + }, {}); + }; + return AttributorStore; + }()); + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = AttributorStore; + + +/***/ }, +/* 10 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + var attributor_1 = __webpack_require__(8); + function match(node, prefix) { + var className = node.getAttribute('class') || ''; + return className.split(/\s+/).filter(function (name) { + return name.indexOf(prefix + "-") === 0; + }); + } + var ClassAttributor = (function (_super) { + __extends(ClassAttributor, _super); + function ClassAttributor() { + return _super.apply(this, arguments) || this; + } + ClassAttributor.keys = function (node) { + return (node.getAttribute('class') || '').split(/\s+/).map(function (name) { + return name.split('-').slice(0, -1).join('-'); + }); + }; + ClassAttributor.prototype.add = function (node, value) { + if (!this.canAdd(node, value)) + return false; + this.remove(node); + node.classList.add(this.keyName + "-" + value); + return true; + }; + ClassAttributor.prototype.remove = function (node) { + var matches = match(node, this.keyName); + matches.forEach(function (name) { + node.classList.remove(name); + }); + if (node.classList.length === 0) { + node.removeAttribute('class'); + } + }; + ClassAttributor.prototype.value = function (node) { + var result = match(node, this.keyName)[0] || ''; + var value = result.slice(this.keyName.length + 1); // +1 for hyphen + return this.canAdd(node, value) ? value : ''; + }; + return ClassAttributor; + }(attributor_1.default)); + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = ClassAttributor; + + +/***/ }, +/* 11 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + var attributor_1 = __webpack_require__(8); + function camelize(name) { + var parts = name.split('-'); + var rest = parts.slice(1).map(function (part) { + return part[0].toUpperCase() + part.slice(1); + }).join(''); + return parts[0] + rest; + } + var StyleAttributor = (function (_super) { + __extends(StyleAttributor, _super); + function StyleAttributor() { + return _super.apply(this, arguments) || this; + } + StyleAttributor.keys = function (node) { + return (node.getAttribute('style') || '').split(';').map(function (value) { + var arr = value.split(':'); + return arr[0].trim(); + }); + }; + StyleAttributor.prototype.add = function (node, value) { + if (!this.canAdd(node, value)) + return false; + node.style[camelize(this.keyName)] = value; + return true; + }; + StyleAttributor.prototype.remove = function (node) { + node.style[camelize(this.keyName)] = ''; + if (!node.getAttribute('style')) { + node.removeAttribute('style'); + } + }; + StyleAttributor.prototype.value = function (node) { + var value = node.style[camelize(this.keyName)]; + return this.canAdd(node, value) ? value : ''; + }; + return StyleAttributor; + }(attributor_1.default)); + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = StyleAttributor; + + +/***/ }, +/* 12 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + var shadow_1 = __webpack_require__(5); + var Registry = __webpack_require__(6); + var LeafBlot = (function (_super) { + __extends(LeafBlot, _super); + function LeafBlot() { + return _super.apply(this, arguments) || this; + } + LeafBlot.value = function (domNode) { + return true; + }; + LeafBlot.prototype.index = function (node, offset) { + if (node !== this.domNode) + return -1; + return Math.min(offset, 1); + }; + LeafBlot.prototype.position = function (index, inclusive) { + var offset = [].indexOf.call(this.parent.domNode.childNodes, this.domNode); + if (index > 0) + offset += 1; + return [this.parent.domNode, offset]; + }; + LeafBlot.prototype.value = function () { + return _a = {}, _a[this.statics.blotName] = this.statics.value(this.domNode) || true, _a; + var _a; + }; + return LeafBlot; + }(shadow_1.default)); + LeafBlot.scope = Registry.Scope.INLINE_BLOT; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = LeafBlot; + + +/***/ }, +/* 13 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + var container_1 = __webpack_require__(3); + var Registry = __webpack_require__(6); + var OBSERVER_CONFIG = { + attributes: true, + characterData: true, + characterDataOldValue: true, + childList: true, + subtree: true + }; + var MAX_OPTIMIZE_ITERATIONS = 100; + var ScrollBlot = (function (_super) { + __extends(ScrollBlot, _super); + function ScrollBlot(node) { + var _this = _super.call(this, node) || this; + _this.parent = null; + _this.observer = new MutationObserver(function (mutations) { + _this.update(mutations); + }); + _this.observer.observe(_this.domNode, OBSERVER_CONFIG); + return _this; + } + ScrollBlot.prototype.detach = function () { + _super.prototype.detach.call(this); + this.observer.disconnect(); + }; + ScrollBlot.prototype.deleteAt = function (index, length) { + this.update(); + if (index === 0 && length === this.length()) { + this.children.forEach(function (child) { + child.remove(); + }); + } + else { + _super.prototype.deleteAt.call(this, index, length); + } + }; + ScrollBlot.prototype.formatAt = function (index, length, name, value) { + this.update(); + _super.prototype.formatAt.call(this, index, length, name, value); + }; + ScrollBlot.prototype.insertAt = function (index, value, def) { + this.update(); + _super.prototype.insertAt.call(this, index, value, def); + }; + ScrollBlot.prototype.optimize = function (mutations) { + var _this = this; + if (mutations === void 0) { mutations = []; } + _super.prototype.optimize.call(this); + // We must modify mutations directly, cannot make copy and then modify + var records = [].slice.call(this.observer.takeRecords()); + // Array.push currently seems to be implemented by a non-tail recursive function + // so we cannot just mutations.push.apply(mutations, this.observer.takeRecords()); + while (records.length > 0) + mutations.push(records.pop()); + // TODO use WeakMap + var mark = function (blot, markParent) { + if (markParent === void 0) { markParent = true; } + if (blot == null || blot === _this) + return; + if (blot.domNode.parentNode == null) + return; + if (blot.domNode[Registry.DATA_KEY].mutations == null) { + blot.domNode[Registry.DATA_KEY].mutations = []; + } + if (markParent) + mark(blot.parent); + }; + var optimize = function (blot) { + if (blot.domNode[Registry.DATA_KEY] == null || blot.domNode[Registry.DATA_KEY].mutations == null) { + return; + } + if (blot instanceof container_1.default) { + blot.children.forEach(optimize); + } + blot.optimize(); + }; + var remaining = mutations; + for (var i = 0; remaining.length > 0; i += 1) { + if (i >= MAX_OPTIMIZE_ITERATIONS) { + throw new Error('[Parchment] Maximum optimize iterations reached'); + } + remaining.forEach(function (mutation) { + var blot = Registry.find(mutation.target, true); + if (blot == null) + return; + if (blot.domNode === mutation.target) { + if (mutation.type === 'childList') { + mark(Registry.find(mutation.previousSibling, false)); + [].forEach.call(mutation.addedNodes, function (node) { + var child = Registry.find(node, false); + mark(child, false); + if (child instanceof container_1.default) { + child.children.forEach(function (grandChild) { + mark(grandChild, false); + }); + } + }); + } + else if (mutation.type === 'attributes') { + mark(blot.prev); + } + } + mark(blot); + }); + this.children.forEach(optimize); + remaining = [].slice.call(this.observer.takeRecords()); + records = remaining.slice(); + while (records.length > 0) + mutations.push(records.pop()); + } + }; + ScrollBlot.prototype.update = function (mutations) { + var _this = this; + mutations = mutations || this.observer.takeRecords(); + // TODO use WeakMap + mutations.map(function (mutation) { + var blot = Registry.find(mutation.target, true); + if (blot == null) + return; + if (blot.domNode[Registry.DATA_KEY].mutations == null) { + blot.domNode[Registry.DATA_KEY].mutations = [mutation]; + return blot; + } + else { + blot.domNode[Registry.DATA_KEY].mutations.push(mutation); + return null; + } + }).forEach(function (blot) { + if (blot == null || blot === _this || blot.domNode[Registry.DATA_KEY] == null) + return; + blot.update(blot.domNode[Registry.DATA_KEY].mutations || []); + }); + if (this.domNode[Registry.DATA_KEY].mutations != null) { + _super.prototype.update.call(this, this.domNode[Registry.DATA_KEY].mutations); + } + this.optimize(mutations); + }; + return ScrollBlot; + }(container_1.default)); + ScrollBlot.blotName = 'scroll'; + ScrollBlot.defaultChild = 'block'; + ScrollBlot.scope = Registry.Scope.BLOCK_BLOT; + ScrollBlot.tagName = 'DIV'; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = ScrollBlot; + + +/***/ }, +/* 14 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + var format_1 = __webpack_require__(7); + var Registry = __webpack_require__(6); + // Shallow object comparison + function isEqual(obj1, obj2) { + if (Object.keys(obj1).length !== Object.keys(obj2).length) + return false; + for (var prop in obj1) { + if (obj1[prop] !== obj2[prop]) + return false; + } + return true; + } + var InlineBlot = (function (_super) { + __extends(InlineBlot, _super); + function InlineBlot() { + return _super.apply(this, arguments) || this; + } + InlineBlot.formats = function (domNode) { + if (domNode.tagName === InlineBlot.tagName) + return undefined; + return _super.formats.call(this, domNode); + }; + InlineBlot.prototype.format = function (name, value) { + var _this = this; + if (name === this.statics.blotName && !value) { + this.children.forEach(function (child) { + if (!(child instanceof format_1.default)) { + child = child.wrap(InlineBlot.blotName, true); + } + _this.attributes.copy(child); + }); + this.unwrap(); + } + else { + _super.prototype.format.call(this, name, value); + } + }; + InlineBlot.prototype.formatAt = function (index, length, name, value) { + if (this.formats()[name] != null || Registry.query(name, Registry.Scope.ATTRIBUTE)) { + var blot = this.isolate(index, length); + blot.format(name, value); + } + else { + _super.prototype.formatAt.call(this, index, length, name, value); + } + }; + InlineBlot.prototype.optimize = function () { + _super.prototype.optimize.call(this); + var formats = this.formats(); + if (Object.keys(formats).length === 0) { + return this.unwrap(); // unformatted span + } + var next = this.next; + if (next instanceof InlineBlot && next.prev === this && isEqual(formats, next.formats())) { + next.moveChildren(this); + next.remove(); + } + }; + return InlineBlot; + }(format_1.default)); + InlineBlot.blotName = 'inline'; + InlineBlot.scope = Registry.Scope.INLINE_BLOT; + InlineBlot.tagName = 'SPAN'; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = InlineBlot; + + +/***/ }, +/* 15 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + var format_1 = __webpack_require__(7); + var Registry = __webpack_require__(6); + var BlockBlot = (function (_super) { + __extends(BlockBlot, _super); + function BlockBlot() { + return _super.apply(this, arguments) || this; + } + BlockBlot.formats = function (domNode) { + var tagName = Registry.query(BlockBlot.blotName).tagName; + if (domNode.tagName === tagName) + return undefined; + return _super.formats.call(this, domNode); + }; + BlockBlot.prototype.format = function (name, value) { + if (Registry.query(name, Registry.Scope.BLOCK) == null) { + return; + } + else if (name === this.statics.blotName && !value) { + this.replaceWith(BlockBlot.blotName); + } + else { + _super.prototype.format.call(this, name, value); + } + }; + BlockBlot.prototype.formatAt = function (index, length, name, value) { + if (Registry.query(name, Registry.Scope.BLOCK) != null) { + this.format(name, value); + } + else { + _super.prototype.formatAt.call(this, index, length, name, value); + } + }; + BlockBlot.prototype.insertAt = function (index, value, def) { + if (def == null || Registry.query(value, Registry.Scope.INLINE) != null) { + // Insert text or inline + _super.prototype.insertAt.call(this, index, value, def); + } + else { + var after = this.split(index); + var blot = Registry.create(value, def); + after.parent.insertBefore(blot, after); + } + }; + BlockBlot.prototype.update = function (mutations) { + if (navigator.userAgent.match(/Trident/)) { + this.attach(); + } + else { + _super.prototype.update.call(this, mutations); + } + }; + return BlockBlot; + }(format_1.default)); + BlockBlot.blotName = 'block'; + BlockBlot.scope = Registry.Scope.BLOCK_BLOT; + BlockBlot.tagName = 'P'; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = BlockBlot; + + +/***/ }, +/* 16 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + var leaf_1 = __webpack_require__(12); + var EmbedBlot = (function (_super) { + __extends(EmbedBlot, _super); + function EmbedBlot() { + return _super.apply(this, arguments) || this; + } + EmbedBlot.formats = function (domNode) { + return undefined; + }; + EmbedBlot.prototype.format = function (name, value) { + // super.formatAt wraps, which is what we want in general, + // but this allows subclasses to overwrite for formats + // that just apply to particular embeds + _super.prototype.formatAt.call(this, 0, this.length(), name, value); + }; + EmbedBlot.prototype.formatAt = function (index, length, name, value) { + if (index === 0 && length === this.length()) { + this.format(name, value); + } + else { + _super.prototype.formatAt.call(this, index, length, name, value); + } + }; + EmbedBlot.prototype.formats = function () { + return this.statics.formats(this.domNode); + }; + return EmbedBlot; + }(leaf_1.default)); + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = EmbedBlot; + + +/***/ }, +/* 17 */ +/***/ function(module, exports, __webpack_require__) { + + "use strict"; + var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + var leaf_1 = __webpack_require__(12); + var Registry = __webpack_require__(6); + var TextBlot = (function (_super) { + __extends(TextBlot, _super); + function TextBlot(node) { + var _this = _super.call(this, node) || this; + _this.text = _this.statics.value(_this.domNode); + return _this; + } + TextBlot.create = function (value) { + return document.createTextNode(value); + }; + TextBlot.value = function (domNode) { + return domNode.data; + }; + TextBlot.prototype.deleteAt = function (index, length) { + this.domNode.data = this.text = this.text.slice(0, index) + this.text.slice(index + length); + }; + TextBlot.prototype.index = function (node, offset) { + if (this.domNode === node) { + return offset; + } + return -1; + }; + TextBlot.prototype.insertAt = function (index, value, def) { + if (def == null) { + this.text = this.text.slice(0, index) + value + this.text.slice(index); + this.domNode.data = this.text; + } + else { + _super.prototype.insertAt.call(this, index, value, def); + } + }; + TextBlot.prototype.length = function () { + return this.text.length; + }; + TextBlot.prototype.optimize = function () { + _super.prototype.optimize.call(this); + this.text = this.statics.value(this.domNode); + if (this.text.length === 0) { + this.remove(); + } + else if (this.next instanceof TextBlot && this.next.prev === this) { + this.insertAt(this.length(), this.next.value()); + this.next.remove(); + } + }; + TextBlot.prototype.position = function (index, inclusive) { + if (inclusive === void 0) { inclusive = false; } + return [this.domNode, index]; + }; + TextBlot.prototype.split = function (index, force) { + if (force === void 0) { force = false; } + if (!force) { + if (index === 0) + return this; + if (index === this.length()) + return this.next; + } + var after = Registry.create(this.domNode.splitText(index)); + this.parent.insertBefore(after, this.next); + this.text = this.statics.value(this.domNode); + return after; + }; + TextBlot.prototype.update = function (mutations) { + var _this = this; + if (mutations.some(function (mutation) { + return mutation.type === 'characterData' && mutation.target === _this.domNode; + })) { + this.text = this.statics.value(this.domNode); + } + }; + TextBlot.prototype.value = function () { + return this.text; + }; + return TextBlot; + }(leaf_1.default)); + TextBlot.blotName = 'text'; + TextBlot.scope = Registry.Scope.INLINE_BLOT; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = TextBlot; + + +/***/ }, +/* 18 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.default = exports.overload = exports.expandConfig = undefined; + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + __webpack_require__(19); + + var _quillDelta = __webpack_require__(20); + + var _quillDelta2 = _interopRequireDefault(_quillDelta); + + var _editor = __webpack_require__(27); + + var _editor2 = _interopRequireDefault(_editor); + + var _emitter3 = __webpack_require__(35); + + var _emitter4 = _interopRequireDefault(_emitter3); + + var _module = __webpack_require__(39); + + var _module2 = _interopRequireDefault(_module); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _selection = __webpack_require__(40); + + var _selection2 = _interopRequireDefault(_selection); + + var _extend = __webpack_require__(25); + + var _extend2 = _interopRequireDefault(_extend); + + var _logger = __webpack_require__(37); + + var _logger2 = _interopRequireDefault(_logger); + + var _theme = __webpack_require__(41); + + var _theme2 = _interopRequireDefault(_theme); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var debug = (0, _logger2.default)('quill'); + + var Quill = function () { + _createClass(Quill, null, [{ + key: 'debug', + value: function debug(limit) { + if (limit === true) { + limit = 'log'; + } + _logger2.default.level(limit); + } + }, { + key: 'import', + value: function _import(name) { + if (this.imports[name] == null) { + debug.error('Cannot import ' + name + '. Are you sure it was registered?'); + } + return this.imports[name]; + } + }, { + key: 'register', + value: function register(path, target) { + var _this = this; + + var overwrite = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + + if (typeof path !== 'string') { + var name = path.attrName || path.blotName; + if (typeof name === 'string') { + // register(Blot | Attributor, overwrite) + this.register('formats/' + name, path, target); + } else { + Object.keys(path).forEach(function (key) { + _this.register(key, path[key], target); + }); + } + } else { + if (this.imports[path] != null && !overwrite) { + debug.warn('Overwriting ' + path + ' with', target); + } + this.imports[path] = target; + if ((path.startsWith('blots/') || path.startsWith('formats/')) && target.blotName !== 'abstract') { + _parchment2.default.register(target); + } + } + } + }]); + + function Quill(container) { + var _this2 = this; + + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, Quill); + + this.options = expandConfig(container, options); + this.container = this.options.container; + this.scrollingContainer = this.options.scrollingContainer || document.body; + if (this.container == null) { + return debug.error('Invalid Quill container', container); + } + if (this.options.debug) { + Quill.debug(this.options.debug); + } + var html = this.container.innerHTML.trim(); + this.container.classList.add('ql-container'); + this.container.innerHTML = ''; + this.root = this.addContainer('ql-editor'); + this.root.classList.add('ql-blank'); + this.emitter = new _emitter4.default(); + this.scroll = _parchment2.default.create(this.root, { + emitter: this.emitter, + whitelist: this.options.formats + }); + this.editor = new _editor2.default(this.scroll); + this.selection = new _selection2.default(this.scroll, this.emitter); + this.theme = new this.options.theme(this, this.options); + this.keyboard = this.theme.addModule('keyboard'); + this.clipboard = this.theme.addModule('clipboard'); + this.history = this.theme.addModule('history'); + this.theme.init(); + this.emitter.on(_emitter4.default.events.EDITOR_CHANGE, function (type) { + if (type === _emitter4.default.events.TEXT_CHANGE) { + _this2.root.classList.toggle('ql-blank', _this2.editor.isBlank()); + } + }); + this.emitter.on(_emitter4.default.events.SCROLL_UPDATE, function (source, mutations) { + var range = _this2.selection.lastRange; + var index = range && range.length === 0 ? range.index : undefined; + modify.call(_this2, function () { + return _this2.editor.update(null, mutations, index); + }, source); + }); + var contents = this.clipboard.convert('
    ' + html + '


    '); + this.setContents(contents); + this.history.clear(); + if (this.options.placeholder) { + this.root.setAttribute('data-placeholder', this.options.placeholder); + } + if (this.options.readOnly) { + this.disable(); + } + } + + _createClass(Quill, [{ + key: 'addContainer', + value: function addContainer(container) { + var refNode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + + if (typeof container === 'string') { + var className = container; + container = document.createElement('div'); + container.classList.add(className); + } + this.container.insertBefore(container, refNode); + return container; + } + }, { + key: 'blur', + value: function blur() { + this.selection.setRange(null); + } + }, { + key: 'deleteText', + value: function deleteText(index, length, source) { + var _this3 = this; + + var _overload = overload(index, length, source); + + var _overload2 = _slicedToArray(_overload, 4); + + index = _overload2[0]; + length = _overload2[1]; + source = _overload2[3]; + + return modify.call(this, function () { + return _this3.editor.deleteText(index, length); + }, source, index, -1 * length); + } + }, { + key: 'disable', + value: function disable() { + this.enable(false); + } + }, { + key: 'enable', + value: function enable() { + var enabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + + this.scroll.enable(enabled); + this.container.classList.toggle('ql-disabled', !enabled); + if (!enabled) { + this.blur(); + } + } + }, { + key: 'focus', + value: function focus() { + var scrollTop = this.scrollingContainer.scrollTop; + this.selection.focus(); + this.scrollingContainer.scrollTop = scrollTop; + this.selection.scrollIntoView(); + } + }, { + key: 'format', + value: function format(name, value) { + var _this4 = this; + + var source = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _emitter4.default.sources.API; + + return modify.call(this, function () { + var range = _this4.getSelection(true); + var change = new _quillDelta2.default(); + if (range == null) { + return change; + } else if (_parchment2.default.query(name, _parchment2.default.Scope.BLOCK)) { + change = _this4.editor.formatLine(range.index, range.length, _defineProperty({}, name, value)); + } else if (range.length === 0) { + _this4.selection.format(name, value); + return change; + } else { + change = _this4.editor.formatText(range.index, range.length, _defineProperty({}, name, value)); + } + _this4.setSelection(range, _emitter4.default.sources.SILENT); + return change; + }, source); + } + }, { + key: 'formatLine', + value: function formatLine(index, length, name, value, source) { + var _this5 = this; + + var formats = void 0; + + var _overload3 = overload(index, length, name, value, source); + + var _overload4 = _slicedToArray(_overload3, 4); + + index = _overload4[0]; + length = _overload4[1]; + formats = _overload4[2]; + source = _overload4[3]; + + return modify.call(this, function () { + return _this5.editor.formatLine(index, length, formats); + }, source, index, 0); + } + }, { + key: 'formatText', + value: function formatText(index, length, name, value, source) { + var _this6 = this; + + var formats = void 0; + + var _overload5 = overload(index, length, name, value, source); + + var _overload6 = _slicedToArray(_overload5, 4); + + index = _overload6[0]; + length = _overload6[1]; + formats = _overload6[2]; + source = _overload6[3]; + + return modify.call(this, function () { + return _this6.editor.formatText(index, length, formats); + }, source, index, 0); + } + }, { + key: 'getBounds', + value: function getBounds(index) { + var length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + + if (typeof index === 'number') { + return this.selection.getBounds(index, length); + } else { + return this.selection.getBounds(index.index, index.length); + } + } + }, { + key: 'getContents', + value: function getContents() { + var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + var length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.getLength() - index; + + var _overload7 = overload(index, length); + + var _overload8 = _slicedToArray(_overload7, 2); + + index = _overload8[0]; + length = _overload8[1]; + + return this.editor.getContents(index, length); + } + }, { + key: 'getFormat', + value: function getFormat() { + var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.getSelection(); + var length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + + if (typeof index === 'number') { + return this.editor.getFormat(index, length); + } else { + return this.editor.getFormat(index.index, index.length); + } + } + }, { + key: 'getLength', + value: function getLength() { + return this.scroll.length(); + } + }, { + key: 'getModule', + value: function getModule(name) { + return this.theme.modules[name]; + } + }, { + key: 'getSelection', + value: function getSelection() { + var focus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + + if (focus) this.focus(); + this.update(); // Make sure we access getRange with editor in consistent state + return this.selection.getRange()[0]; + } + }, { + key: 'getText', + value: function getText() { + var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + var length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.getLength() - index; + + var _overload9 = overload(index, length); + + var _overload10 = _slicedToArray(_overload9, 2); + + index = _overload10[0]; + length = _overload10[1]; + + return this.editor.getText(index, length); + } + }, { + key: 'hasFocus', + value: function hasFocus() { + return this.selection.hasFocus(); + } + }, { + key: 'insertEmbed', + value: function insertEmbed(index, embed, value) { + var _this7 = this; + + var source = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Quill.sources.API; + + return modify.call(this, function () { + return _this7.editor.insertEmbed(index, embed, value); + }, source, index); + } + }, { + key: 'insertText', + value: function insertText(index, text, name, value, source) { + var _this8 = this; + + var formats = void 0; + + var _overload11 = overload(index, 0, name, value, source); + + var _overload12 = _slicedToArray(_overload11, 4); + + index = _overload12[0]; + formats = _overload12[2]; + source = _overload12[3]; + + return modify.call(this, function () { + return _this8.editor.insertText(index, text, formats); + }, source, index, text.length); + } + }, { + key: 'isEnabled', + value: function isEnabled() { + return !this.container.classList.contains('ql-disabled'); + } + }, { + key: 'off', + value: function off() { + return this.emitter.off.apply(this.emitter, arguments); + } + }, { + key: 'on', + value: function on() { + return this.emitter.on.apply(this.emitter, arguments); + } + }, { + key: 'once', + value: function once() { + return this.emitter.once.apply(this.emitter, arguments); + } + }, { + key: 'pasteHTML', + value: function pasteHTML(index, html, source) { + this.clipboard.dangerouslyPasteHTML(index, html, source); + } + }, { + key: 'removeFormat', + value: function removeFormat(index, length, source) { + var _this9 = this; + + var _overload13 = overload(index, length, source); + + var _overload14 = _slicedToArray(_overload13, 4); + + index = _overload14[0]; + length = _overload14[1]; + source = _overload14[3]; + + return modify.call(this, function () { + return _this9.editor.removeFormat(index, length); + }, source, index); + } + }, { + key: 'setContents', + value: function setContents(delta) { + var _this10 = this; + + var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _emitter4.default.sources.API; + + return modify.call(this, function () { + delta = new _quillDelta2.default(delta); + var length = _this10.getLength(); + var deleted = _this10.editor.deleteText(0, length); + var applied = _this10.editor.applyDelta(delta); + var lastOp = applied.ops[applied.ops.length - 1]; + if (lastOp != null && typeof lastOp.insert === 'string' && lastOp.insert[lastOp.insert.length - 1] === '\n') { + _this10.editor.deleteText(_this10.getLength() - 1, 1); + applied.delete(1); + } + var ret = deleted.compose(applied); + return ret; + }, source); + } + }, { + key: 'setSelection', + value: function setSelection(index, length, source) { + if (index == null) { + this.selection.setRange(null, length || Quill.sources.API); + } else { + var _overload15 = overload(index, length, source); + + var _overload16 = _slicedToArray(_overload15, 4); + + index = _overload16[0]; + length = _overload16[1]; + source = _overload16[3]; + + this.selection.setRange(new _selection.Range(index, length), source); + } + this.selection.scrollIntoView(); + } + }, { + key: 'setText', + value: function setText(text) { + var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _emitter4.default.sources.API; + + var delta = new _quillDelta2.default().insert(text); + return this.setContents(delta, source); + } + }, { + key: 'update', + value: function update() { + var source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _emitter4.default.sources.USER; + + var change = this.scroll.update(source); // Will update selection before selection.update() does if text changes + this.selection.update(source); + return change; + } + }, { + key: 'updateContents', + value: function updateContents(delta) { + var _this11 = this; + + var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _emitter4.default.sources.API; + + return modify.call(this, function () { + delta = new _quillDelta2.default(delta); + return _this11.editor.applyDelta(delta, source); + }, source, true); + } + }]); + + return Quill; + }(); + + Quill.DEFAULTS = { + bounds: null, + formats: null, + modules: {}, + placeholder: '', + readOnly: false, + scrollingContainer: null, + strict: true, + theme: 'default' + }; + Quill.events = _emitter4.default.events; + Quill.sources = _emitter4.default.sources; + // eslint-disable-next-line no-undef + Quill.version = false ? 'dev' : ("1.1.9"); + + Quill.imports = { + 'delta': _quillDelta2.default, + 'parchment': _parchment2.default, + 'core/module': _module2.default, + 'core/theme': _theme2.default + }; + + function expandConfig(container, userConfig) { + userConfig = (0, _extend2.default)(true, { + container: container, + modules: { + clipboard: true, + keyboard: true, + history: true + } + }, userConfig); + if (!userConfig.theme || userConfig.theme === Quill.DEFAULTS.theme) { + userConfig.theme = _theme2.default; + } else { + userConfig.theme = Quill.import('themes/' + userConfig.theme); + if (userConfig.theme == null) { + throw new Error('Invalid theme ' + userConfig.theme + '. Did you register it?'); + } + } + var themeConfig = (0, _extend2.default)(true, {}, userConfig.theme.DEFAULTS); + [themeConfig, userConfig].forEach(function (config) { + config.modules = config.modules || {}; + Object.keys(config.modules).forEach(function (module) { + if (config.modules[module] === true) { + config.modules[module] = {}; + } + }); + }); + var moduleNames = Object.keys(themeConfig.modules).concat(Object.keys(userConfig.modules)); + var moduleConfig = moduleNames.reduce(function (config, name) { + var moduleClass = Quill.import('modules/' + name); + if (moduleClass == null) { + debug.error('Cannot load ' + name + ' module. Are you sure you registered it?'); + } else { + config[name] = moduleClass.DEFAULTS || {}; + } + return config; + }, {}); + // Special case toolbar shorthand + if (userConfig.modules != null && userConfig.modules.toolbar && userConfig.modules.toolbar.constructor !== Object) { + userConfig.modules.toolbar = { + container: userConfig.modules.toolbar + }; + } + userConfig = (0, _extend2.default)(true, {}, Quill.DEFAULTS, { modules: moduleConfig }, themeConfig, userConfig); + ['bounds', 'container', 'scrollingContainer'].forEach(function (key) { + if (typeof userConfig[key] === 'string') { + userConfig[key] = document.querySelector(userConfig[key]); + } + }); + userConfig.modules = Object.keys(userConfig.modules).reduce(function (config, name) { + if (userConfig.modules[name]) { + config[name] = userConfig.modules[name]; + } + return config; + }, {}); + return userConfig; + } + + // Handle selection preservation and TEXT_CHANGE emission + // common to modification APIs + function modify(modifier, source, index, shift) { + if (this.options.strict && !this.isEnabled() && source === _emitter4.default.sources.USER) { + return new _quillDelta2.default(); + } + var range = index == null ? null : this.getSelection(); + var oldDelta = this.editor.delta; + var change = modifier(); + if (range != null && source === _emitter4.default.sources.USER) { + if (index === true) index = range.index; + if (shift == null) { + range = shiftRange(range, change, source); + } else if (shift !== 0) { + range = shiftRange(range, index, shift, source); + } + this.setSelection(range, _emitter4.default.sources.SILENT); + } + if (change.length() > 0) { + var _emitter; + + var args = [_emitter4.default.events.TEXT_CHANGE, change, oldDelta, source]; + (_emitter = this.emitter).emit.apply(_emitter, [_emitter4.default.events.EDITOR_CHANGE].concat(args)); + if (source !== _emitter4.default.sources.SILENT) { + var _emitter2; + + (_emitter2 = this.emitter).emit.apply(_emitter2, args); + } + } + return change; + } + + function overload(index, length, name, value, source) { + var formats = {}; + if (typeof index.index === 'number' && typeof index.length === 'number') { + // Allow for throwaway end (used by insertText/insertEmbed) + if (typeof length !== 'number') { + source = value, value = name, name = length, length = index.length, index = index.index; + } else { + length = index.length, index = index.index; + } + } else if (typeof length !== 'number') { + source = value, value = name, name = length, length = 0; + } + // Handle format being object, two format name/value strings or excluded + if ((typeof name === 'undefined' ? 'undefined' : _typeof(name)) === 'object') { + formats = name; + source = value; + } else if (typeof name === 'string') { + if (value != null) { + formats[name] = value; + } else { + source = name; + } + } + // Handle optional source + source = source || _emitter4.default.sources.API; + return [index, length, formats, source]; + } + + function shiftRange(range, index, length, source) { + if (range == null) return null; + var start = void 0, + end = void 0; + if (index instanceof _quillDelta2.default) { + var _map = [range.index, range.index + range.length].map(function (pos) { + return index.transformPosition(pos, source === _emitter4.default.sources.USER); + }); + + var _map2 = _slicedToArray(_map, 2); + + start = _map2[0]; + end = _map2[1]; + } else { + var _map3 = [range.index, range.index + range.length].map(function (pos) { + if (pos < index || pos === index && source !== _emitter4.default.sources.USER) return pos; + if (length >= 0) { + return pos + length; + } else { + return Math.max(index, pos + length); + } + }); + + var _map4 = _slicedToArray(_map3, 2); + + start = _map4[0]; + end = _map4[1]; + } + return new _selection.Range(start, end - start); + } + + exports.expandConfig = expandConfig; + exports.overload = overload; + exports.default = Quill; + +/***/ }, +/* 19 */ +/***/ function(module, exports) { + + 'use strict'; + + var elem = document.createElement('div'); + elem.classList.toggle('test-class', false); + if (elem.classList.contains('test-class')) { + (function () { + var _toggle = DOMTokenList.prototype.toggle; + DOMTokenList.prototype.toggle = function (token, force) { + if (arguments.length > 1 && !this.contains(token) === !force) { + return force; + } else { + return _toggle.call(this, token); + } + }; + })(); + } + + if (!String.prototype.startsWith) { + String.prototype.startsWith = function (searchString, position) { + position = position || 0; + return this.substr(position, searchString.length) === searchString; + }; + } + + if (!String.prototype.endsWith) { + String.prototype.endsWith = function (searchString, position) { + var subjectString = this.toString(); + if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { + position = subjectString.length; + } + position -= searchString.length; + var lastIndex = subjectString.indexOf(searchString, position); + return lastIndex !== -1 && lastIndex === position; + }; + } + + if (!Array.prototype.find) { + Object.defineProperty(Array.prototype, "find", { + value: function value(predicate) { + if (this === null) { + throw new TypeError('Array.prototype.find called on null or undefined'); + } + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } + var list = Object(this); + var length = list.length >>> 0; + var thisArg = arguments[1]; + var value; + + for (var i = 0; i < length; i++) { + value = list[i]; + if (predicate.call(thisArg, value, i, list)) { + return value; + } + } + return undefined; + } + }); + } + + // Disable resizing in Firefox + document.addEventListener("DOMContentLoaded", function () { + document.execCommand("enableObjectResizing", false, false); + }); + +/***/ }, +/* 20 */ +/***/ function(module, exports, __webpack_require__) { + + var diff = __webpack_require__(21); + var equal = __webpack_require__(22); + var extend = __webpack_require__(25); + var op = __webpack_require__(26); + + + var NULL_CHARACTER = String.fromCharCode(0); // Placeholder char for embed in diff() + + + var Delta = function (ops) { + // Assume we are given a well formed ops + if (Array.isArray(ops)) { + this.ops = ops; + } else if (ops != null && Array.isArray(ops.ops)) { + this.ops = ops.ops; + } else { + this.ops = []; + } + }; + + + Delta.prototype.insert = function (text, attributes) { + var newOp = {}; + if (text.length === 0) return this; + newOp.insert = text; + if (attributes != null && typeof attributes === 'object' && Object.keys(attributes).length > 0) { + newOp.attributes = attributes; + } + return this.push(newOp); + }; + + Delta.prototype['delete'] = function (length) { + if (length <= 0) return this; + return this.push({ 'delete': length }); + }; + + Delta.prototype.retain = function (length, attributes) { + if (length <= 0) return this; + var newOp = { retain: length }; + if (attributes != null && typeof attributes === 'object' && Object.keys(attributes).length > 0) { + newOp.attributes = attributes; + } + return this.push(newOp); + }; + + Delta.prototype.push = function (newOp) { + var index = this.ops.length; + var lastOp = this.ops[index - 1]; + newOp = extend(true, {}, newOp); + if (typeof lastOp === 'object') { + if (typeof newOp['delete'] === 'number' && typeof lastOp['delete'] === 'number') { + this.ops[index - 1] = { 'delete': lastOp['delete'] + newOp['delete'] }; + return this; + } + // Since it does not matter if we insert before or after deleting at the same index, + // always prefer to insert first + if (typeof lastOp['delete'] === 'number' && newOp.insert != null) { + index -= 1; + lastOp = this.ops[index - 1]; + if (typeof lastOp !== 'object') { + this.ops.unshift(newOp); + return this; + } + } + if (equal(newOp.attributes, lastOp.attributes)) { + if (typeof newOp.insert === 'string' && typeof lastOp.insert === 'string') { + this.ops[index - 1] = { insert: lastOp.insert + newOp.insert }; + if (typeof newOp.attributes === 'object') this.ops[index - 1].attributes = newOp.attributes + return this; + } else if (typeof newOp.retain === 'number' && typeof lastOp.retain === 'number') { + this.ops[index - 1] = { retain: lastOp.retain + newOp.retain }; + if (typeof newOp.attributes === 'object') this.ops[index - 1].attributes = newOp.attributes + return this; + } + } + } + if (index === this.ops.length) { + this.ops.push(newOp); + } else { + this.ops.splice(index, 0, newOp); + } + return this; + }; + + Delta.prototype.filter = function (predicate) { + return this.ops.filter(predicate); + }; + + Delta.prototype.forEach = function (predicate) { + this.ops.forEach(predicate); + }; + + Delta.prototype.map = function (predicate) { + return this.ops.map(predicate); + }; + + Delta.prototype.partition = function (predicate) { + var passed = [], failed = []; + this.forEach(function(op) { + var target = predicate(op) ? passed : failed; + target.push(op); + }); + return [passed, failed]; + }; + + Delta.prototype.reduce = function (predicate, initial) { + return this.ops.reduce(predicate, initial); + }; + + Delta.prototype.chop = function () { + var lastOp = this.ops[this.ops.length - 1]; + if (lastOp && lastOp.retain && !lastOp.attributes) { + this.ops.pop(); + } + return this; + }; + + Delta.prototype.length = function () { + return this.reduce(function (length, elem) { + return length + op.length(elem); + }, 0); + }; + + Delta.prototype.slice = function (start, end) { + start = start || 0; + if (typeof end !== 'number') end = Infinity; + var ops = []; + var iter = op.iterator(this.ops); + var index = 0; + while (index < end && iter.hasNext()) { + var nextOp; + if (index < start) { + nextOp = iter.next(start - index); + } else { + nextOp = iter.next(end - index); + ops.push(nextOp); + } + index += op.length(nextOp); + } + return new Delta(ops); + }; + + + Delta.prototype.compose = function (other) { + var thisIter = op.iterator(this.ops); + var otherIter = op.iterator(other.ops); + var delta = new Delta(); + while (thisIter.hasNext() || otherIter.hasNext()) { + if (otherIter.peekType() === 'insert') { + delta.push(otherIter.next()); + } else if (thisIter.peekType() === 'delete') { + delta.push(thisIter.next()); + } else { + var length = Math.min(thisIter.peekLength(), otherIter.peekLength()); + var thisOp = thisIter.next(length); + var otherOp = otherIter.next(length); + if (typeof otherOp.retain === 'number') { + var newOp = {}; + if (typeof thisOp.retain === 'number') { + newOp.retain = length; + } else { + newOp.insert = thisOp.insert; + } + // Preserve null when composing with a retain, otherwise remove it for inserts + var attributes = op.attributes.compose(thisOp.attributes, otherOp.attributes, typeof thisOp.retain === 'number'); + if (attributes) newOp.attributes = attributes; + delta.push(newOp); + // Other op should be delete, we could be an insert or retain + // Insert + delete cancels out + } else if (typeof otherOp['delete'] === 'number' && typeof thisOp.retain === 'number') { + delta.push(otherOp); + } + } + } + return delta.chop(); + }; + + Delta.prototype.concat = function (other) { + var delta = new Delta(this.ops.slice()); + if (other.ops.length > 0) { + delta.push(other.ops[0]); + delta.ops = delta.ops.concat(other.ops.slice(1)); + } + return delta; + }; + + Delta.prototype.diff = function (other, index) { + if (this.ops === other.ops) { + return new Delta(); + } + var strings = [this, other].map(function (delta) { + return delta.map(function (op) { + if (op.insert != null) { + return typeof op.insert === 'string' ? op.insert : NULL_CHARACTER; + } + var prep = (ops === other.ops) ? 'on' : 'with'; + throw new Error('diff() called ' + prep + ' non-document'); + }).join(''); + }); + var delta = new Delta(); + var diffResult = diff(strings[0], strings[1], index); + var thisIter = op.iterator(this.ops); + var otherIter = op.iterator(other.ops); + diffResult.forEach(function (component) { + var length = component[1].length; + while (length > 0) { + var opLength = 0; + switch (component[0]) { + case diff.INSERT: + opLength = Math.min(otherIter.peekLength(), length); + delta.push(otherIter.next(opLength)); + break; + case diff.DELETE: + opLength = Math.min(length, thisIter.peekLength()); + thisIter.next(opLength); + delta['delete'](opLength); + break; + case diff.EQUAL: + opLength = Math.min(thisIter.peekLength(), otherIter.peekLength(), length); + var thisOp = thisIter.next(opLength); + var otherOp = otherIter.next(opLength); + if (equal(thisOp.insert, otherOp.insert)) { + delta.retain(opLength, op.attributes.diff(thisOp.attributes, otherOp.attributes)); + } else { + delta.push(otherOp)['delete'](opLength); + } + break; + } + length -= opLength; + } + }); + return delta.chop(); + }; + + Delta.prototype.eachLine = function (predicate, newline) { + newline = newline || '\n'; + var iter = op.iterator(this.ops); + var line = new Delta(); + while (iter.hasNext()) { + if (iter.peekType() !== 'insert') return; + var thisOp = iter.peek(); + var start = op.length(thisOp) - iter.peekLength(); + var index = typeof thisOp.insert === 'string' ? + thisOp.insert.indexOf(newline, start) - start : -1; + if (index < 0) { + line.push(iter.next()); + } else if (index > 0) { + line.push(iter.next(index)); + } else { + predicate(line, iter.next(1).attributes || {}); + line = new Delta(); + } + } + if (line.length() > 0) { + predicate(line, {}); + } + }; + + Delta.prototype.transform = function (other, priority) { + priority = !!priority; + if (typeof other === 'number') { + return this.transformPosition(other, priority); + } + var thisIter = op.iterator(this.ops); + var otherIter = op.iterator(other.ops); + var delta = new Delta(); + while (thisIter.hasNext() || otherIter.hasNext()) { + if (thisIter.peekType() === 'insert' && (priority || otherIter.peekType() !== 'insert')) { + delta.retain(op.length(thisIter.next())); + } else if (otherIter.peekType() === 'insert') { + delta.push(otherIter.next()); + } else { + var length = Math.min(thisIter.peekLength(), otherIter.peekLength()); + var thisOp = thisIter.next(length); + var otherOp = otherIter.next(length); + if (thisOp['delete']) { + // Our delete either makes their delete redundant or removes their retain + continue; + } else if (otherOp['delete']) { + delta.push(otherOp); + } else { + // We retain either their retain or insert + delta.retain(length, op.attributes.transform(thisOp.attributes, otherOp.attributes, priority)); + } + } + } + return delta.chop(); + }; + + Delta.prototype.transformPosition = function (index, priority) { + priority = !!priority; + var thisIter = op.iterator(this.ops); + var offset = 0; + while (thisIter.hasNext() && offset <= index) { + var length = thisIter.peekLength(); + var nextType = thisIter.peekType(); + thisIter.next(); + if (nextType === 'delete') { + index -= Math.min(length, index - offset); + continue; + } else if (nextType === 'insert' && (offset < index || !priority)) { + index += length; + } + offset += length; + } + return index; + }; + + + module.exports = Delta; + + +/***/ }, +/* 21 */ +/***/ function(module, exports) { + + /** + * This library modifies the diff-patch-match library by Neil Fraser + * by removing the patch and match functionality and certain advanced + * options in the diff function. The original license is as follows: + * + * === + * + * Diff Match and Patch + * + * Copyright 2006 Google Inc. + * http://code.google.com/p/google-diff-match-patch/ + * + * 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. + */ + + + /** + * The data structure representing a diff is an array of tuples: + * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] + * which means: delete 'Hello', add 'Goodbye' and keep ' world.' + */ + var DIFF_DELETE = -1; + var DIFF_INSERT = 1; + var DIFF_EQUAL = 0; + + + /** + * Find the differences between two texts. Simplifies the problem by stripping + * any common prefix or suffix off the texts before diffing. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {Int} cursor_pos Expected edit position in text1 (optional) + * @return {Array} Array of diff tuples. + */ + function diff_main(text1, text2, cursor_pos) { + // Check for equality (speedup). + if (text1 == text2) { + if (text1) { + return [[DIFF_EQUAL, text1]]; + } + return []; + } + + // Check cursor_pos within bounds + if (cursor_pos < 0 || text1.length < cursor_pos) { + cursor_pos = null; + } + + // Trim off common prefix (speedup). + var commonlength = diff_commonPrefix(text1, text2); + var commonprefix = text1.substring(0, commonlength); + text1 = text1.substring(commonlength); + text2 = text2.substring(commonlength); + + // Trim off common suffix (speedup). + commonlength = diff_commonSuffix(text1, text2); + var commonsuffix = text1.substring(text1.length - commonlength); + text1 = text1.substring(0, text1.length - commonlength); + text2 = text2.substring(0, text2.length - commonlength); + + // Compute the diff on the middle block. + var diffs = diff_compute_(text1, text2); + + // Restore the prefix and suffix. + if (commonprefix) { + diffs.unshift([DIFF_EQUAL, commonprefix]); + } + if (commonsuffix) { + diffs.push([DIFF_EQUAL, commonsuffix]); + } + diff_cleanupMerge(diffs); + if (cursor_pos != null) { + diffs = fix_cursor(diffs, cursor_pos); + } + return diffs; + }; + + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @return {Array} Array of diff tuples. + */ + function diff_compute_(text1, text2) { + var diffs; + + if (!text1) { + // Just add some text (speedup). + return [[DIFF_INSERT, text2]]; + } + + if (!text2) { + // Just delete some text (speedup). + return [[DIFF_DELETE, text1]]; + } + + var longtext = text1.length > text2.length ? text1 : text2; + var shorttext = text1.length > text2.length ? text2 : text1; + var i = longtext.indexOf(shorttext); + if (i != -1) { + // Shorter text is inside the longer text (speedup). + diffs = [[DIFF_INSERT, longtext.substring(0, i)], + [DIFF_EQUAL, shorttext], + [DIFF_INSERT, longtext.substring(i + shorttext.length)]]; + // Swap insertions for deletions if diff is reversed. + if (text1.length > text2.length) { + diffs[0][0] = diffs[2][0] = DIFF_DELETE; + } + return diffs; + } + + if (shorttext.length == 1) { + // Single character string. + // After the previous speedup, the character can't be an equality. + return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; + } + + // Check to see if the problem can be split in two. + var hm = diff_halfMatch_(text1, text2); + if (hm) { + // A half-match was found, sort out the return data. + var text1_a = hm[0]; + var text1_b = hm[1]; + var text2_a = hm[2]; + var text2_b = hm[3]; + var mid_common = hm[4]; + // Send both pairs off for separate processing. + var diffs_a = diff_main(text1_a, text2_a); + var diffs_b = diff_main(text1_b, text2_b); + // Merge the results. + return diffs_a.concat([[DIFF_EQUAL, mid_common]], diffs_b); + } + + return diff_bisect_(text1, text2); + }; + + + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @return {Array} Array of diff tuples. + * @private + */ + function diff_bisect_(text1, text2) { + // Cache the text lengths to prevent multiple calls. + var text1_length = text1.length; + var text2_length = text2.length; + var max_d = Math.ceil((text1_length + text2_length) / 2); + var v_offset = max_d; + var v_length = 2 * max_d; + var v1 = new Array(v_length); + var v2 = new Array(v_length); + // Setting all elements to -1 is faster in Chrome & Firefox than mixing + // integers and undefined. + for (var x = 0; x < v_length; x++) { + v1[x] = -1; + v2[x] = -1; + } + v1[v_offset + 1] = 0; + v2[v_offset + 1] = 0; + var delta = text1_length - text2_length; + // If the total number of characters is odd, then the front path will collide + // with the reverse path. + var front = (delta % 2 != 0); + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + var k1start = 0; + var k1end = 0; + var k2start = 0; + var k2end = 0; + for (var d = 0; d < max_d; d++) { + // Walk the front path one step. + for (var k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { + var k1_offset = v_offset + k1; + var x1; + if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) { + x1 = v1[k1_offset + 1]; + } else { + x1 = v1[k1_offset - 1] + 1; + } + var y1 = x1 - k1; + while (x1 < text1_length && y1 < text2_length && + text1.charAt(x1) == text2.charAt(y1)) { + x1++; + y1++; + } + v1[k1_offset] = x1; + if (x1 > text1_length) { + // Ran off the right of the graph. + k1end += 2; + } else if (y1 > text2_length) { + // Ran off the bottom of the graph. + k1start += 2; + } else if (front) { + var k2_offset = v_offset + delta - k1; + if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) { + // Mirror x2 onto top-left coordinate system. + var x2 = text1_length - v2[k2_offset]; + if (x1 >= x2) { + // Overlap detected. + return diff_bisectSplit_(text1, text2, x1, y1); + } + } + } + } + + // Walk the reverse path one step. + for (var k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { + var k2_offset = v_offset + k2; + var x2; + if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) { + x2 = v2[k2_offset + 1]; + } else { + x2 = v2[k2_offset - 1] + 1; + } + var y2 = x2 - k2; + while (x2 < text1_length && y2 < text2_length && + text1.charAt(text1_length - x2 - 1) == + text2.charAt(text2_length - y2 - 1)) { + x2++; + y2++; + } + v2[k2_offset] = x2; + if (x2 > text1_length) { + // Ran off the left of the graph. + k2end += 2; + } else if (y2 > text2_length) { + // Ran off the top of the graph. + k2start += 2; + } else if (!front) { + var k1_offset = v_offset + delta - k2; + if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) { + var x1 = v1[k1_offset]; + var y1 = v_offset + x1 - k1_offset; + // Mirror x2 onto top-left coordinate system. + x2 = text1_length - x2; + if (x1 >= x2) { + // Overlap detected. + return diff_bisectSplit_(text1, text2, x1, y1); + } + } + } + } + } + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; + }; + + + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} x Index of split point in text1. + * @param {number} y Index of split point in text2. + * @return {Array} Array of diff tuples. + */ + function diff_bisectSplit_(text1, text2, x, y) { + var text1a = text1.substring(0, x); + var text2a = text2.substring(0, y); + var text1b = text1.substring(x); + var text2b = text2.substring(y); + + // Compute both diffs serially. + var diffs = diff_main(text1a, text2a); + var diffsb = diff_main(text1b, text2b); + + return diffs.concat(diffsb); + }; + + + /** + * Determine the common prefix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the start of each + * string. + */ + function diff_commonPrefix(text1, text2) { + // Quick check for common null cases. + if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) { + return 0; + } + // Binary search. + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + var pointermin = 0; + var pointermax = Math.min(text1.length, text2.length); + var pointermid = pointermax; + var pointerstart = 0; + while (pointermin < pointermid) { + if (text1.substring(pointerstart, pointermid) == + text2.substring(pointerstart, pointermid)) { + pointermin = pointermid; + pointerstart = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); + } + return pointermid; + }; + + + /** + * Determine the common suffix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of each string. + */ + function diff_commonSuffix(text1, text2) { + // Quick check for common null cases. + if (!text1 || !text2 || + text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) { + return 0; + } + // Binary search. + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + var pointermin = 0; + var pointermax = Math.min(text1.length, text2.length); + var pointermid = pointermax; + var pointerend = 0; + while (pointermin < pointermid) { + if (text1.substring(text1.length - pointermid, text1.length - pointerend) == + text2.substring(text2.length - pointermid, text2.length - pointerend)) { + pointermin = pointermid; + pointerend = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); + } + return pointermid; + }; + + + /** + * Do the two texts share a substring which is at least half the length of the + * longer text? + * This speedup can produce non-minimal diffs. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {Array.} Five element Array, containing the prefix of + * text1, the suffix of text1, the prefix of text2, the suffix of + * text2 and the common middle. Or null if there was no match. + */ + function diff_halfMatch_(text1, text2) { + var longtext = text1.length > text2.length ? text1 : text2; + var shorttext = text1.length > text2.length ? text2 : text1; + if (longtext.length < 4 || shorttext.length * 2 < longtext.length) { + return null; // Pointless. + } + + /** + * Does a substring of shorttext exist within longtext such that the substring + * is at least half the length of longtext? + * Closure, but does not reference any external variables. + * @param {string} longtext Longer string. + * @param {string} shorttext Shorter string. + * @param {number} i Start index of quarter length substring within longtext. + * @return {Array.} Five element Array, containing the prefix of + * longtext, the suffix of longtext, the prefix of shorttext, the suffix + * of shorttext and the common middle. Or null if there was no match. + * @private + */ + function diff_halfMatchI_(longtext, shorttext, i) { + // Start with a 1/4 length substring at position i as a seed. + var seed = longtext.substring(i, i + Math.floor(longtext.length / 4)); + var j = -1; + var best_common = ''; + var best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b; + while ((j = shorttext.indexOf(seed, j + 1)) != -1) { + var prefixLength = diff_commonPrefix(longtext.substring(i), + shorttext.substring(j)); + var suffixLength = diff_commonSuffix(longtext.substring(0, i), + shorttext.substring(0, j)); + if (best_common.length < suffixLength + prefixLength) { + best_common = shorttext.substring(j - suffixLength, j) + + shorttext.substring(j, j + prefixLength); + best_longtext_a = longtext.substring(0, i - suffixLength); + best_longtext_b = longtext.substring(i + prefixLength); + best_shorttext_a = shorttext.substring(0, j - suffixLength); + best_shorttext_b = shorttext.substring(j + prefixLength); + } + } + if (best_common.length * 2 >= longtext.length) { + return [best_longtext_a, best_longtext_b, + best_shorttext_a, best_shorttext_b, best_common]; + } else { + return null; + } + } + + // First check if the second quarter is the seed for a half-match. + var hm1 = diff_halfMatchI_(longtext, shorttext, + Math.ceil(longtext.length / 4)); + // Check again based on the third quarter. + var hm2 = diff_halfMatchI_(longtext, shorttext, + Math.ceil(longtext.length / 2)); + var hm; + if (!hm1 && !hm2) { + return null; + } else if (!hm2) { + hm = hm1; + } else if (!hm1) { + hm = hm2; + } else { + // Both matched. Select the longest. + hm = hm1[4].length > hm2[4].length ? hm1 : hm2; + } + + // A half-match was found, sort out the return data. + var text1_a, text1_b, text2_a, text2_b; + if (text1.length > text2.length) { + text1_a = hm[0]; + text1_b = hm[1]; + text2_a = hm[2]; + text2_b = hm[3]; + } else { + text2_a = hm[0]; + text2_b = hm[1]; + text1_a = hm[2]; + text1_b = hm[3]; + } + var mid_common = hm[4]; + return [text1_a, text1_b, text2_a, text2_b, mid_common]; + }; + + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param {Array} diffs Array of diff tuples. + */ + function diff_cleanupMerge(diffs) { + diffs.push([DIFF_EQUAL, '']); // Add a dummy entry at the end. + var pointer = 0; + var count_delete = 0; + var count_insert = 0; + var text_delete = ''; + var text_insert = ''; + var commonlength; + while (pointer < diffs.length) { + switch (diffs[pointer][0]) { + case DIFF_INSERT: + count_insert++; + text_insert += diffs[pointer][1]; + pointer++; + break; + case DIFF_DELETE: + count_delete++; + text_delete += diffs[pointer][1]; + pointer++; + break; + case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (count_delete + count_insert > 1) { + if (count_delete !== 0 && count_insert !== 0) { + // Factor out any common prefixies. + commonlength = diff_commonPrefix(text_insert, text_delete); + if (commonlength !== 0) { + if ((pointer - count_delete - count_insert) > 0 && + diffs[pointer - count_delete - count_insert - 1][0] == + DIFF_EQUAL) { + diffs[pointer - count_delete - count_insert - 1][1] += + text_insert.substring(0, commonlength); + } else { + diffs.splice(0, 0, [DIFF_EQUAL, + text_insert.substring(0, commonlength)]); + pointer++; + } + text_insert = text_insert.substring(commonlength); + text_delete = text_delete.substring(commonlength); + } + // Factor out any common suffixies. + commonlength = diff_commonSuffix(text_insert, text_delete); + if (commonlength !== 0) { + diffs[pointer][1] = text_insert.substring(text_insert.length - + commonlength) + diffs[pointer][1]; + text_insert = text_insert.substring(0, text_insert.length - + commonlength); + text_delete = text_delete.substring(0, text_delete.length - + commonlength); + } + } + // Delete the offending records and add the merged ones. + if (count_delete === 0) { + diffs.splice(pointer - count_insert, + count_delete + count_insert, [DIFF_INSERT, text_insert]); + } else if (count_insert === 0) { + diffs.splice(pointer - count_delete, + count_delete + count_insert, [DIFF_DELETE, text_delete]); + } else { + diffs.splice(pointer - count_delete - count_insert, + count_delete + count_insert, [DIFF_DELETE, text_delete], + [DIFF_INSERT, text_insert]); + } + pointer = pointer - count_delete - count_insert + + (count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1; + } else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) { + // Merge this equality with the previous one. + diffs[pointer - 1][1] += diffs[pointer][1]; + diffs.splice(pointer, 1); + } else { + pointer++; + } + count_insert = 0; + count_delete = 0; + text_delete = ''; + text_insert = ''; + break; + } + } + if (diffs[diffs.length - 1][1] === '') { + diffs.pop(); // Remove the dummy entry at the end. + } + + // Second pass: look for single edits surrounded on both sides by equalities + // which can be shifted sideways to eliminate an equality. + // e.g: ABAC -> ABAC + var changes = false; + pointer = 1; + // Intentionally ignore the first and last element (don't need checking). + while (pointer < diffs.length - 1) { + if (diffs[pointer - 1][0] == DIFF_EQUAL && + diffs[pointer + 1][0] == DIFF_EQUAL) { + // This is a single edit surrounded by equalities. + if (diffs[pointer][1].substring(diffs[pointer][1].length - + diffs[pointer - 1][1].length) == diffs[pointer - 1][1]) { + // Shift the edit over the previous equality. + diffs[pointer][1] = diffs[pointer - 1][1] + + diffs[pointer][1].substring(0, diffs[pointer][1].length - + diffs[pointer - 1][1].length); + diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; + diffs.splice(pointer - 1, 1); + changes = true; + } else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) == + diffs[pointer + 1][1]) { + // Shift the edit over the next equality. + diffs[pointer - 1][1] += diffs[pointer + 1][1]; + diffs[pointer][1] = + diffs[pointer][1].substring(diffs[pointer + 1][1].length) + + diffs[pointer + 1][1]; + diffs.splice(pointer + 1, 1); + changes = true; + } + } + pointer++; + } + // If shifts were made, the diff needs reordering and another shift sweep. + if (changes) { + diff_cleanupMerge(diffs); + } + }; + + + var diff = diff_main; + diff.INSERT = DIFF_INSERT; + diff.DELETE = DIFF_DELETE; + diff.EQUAL = DIFF_EQUAL; + + module.exports = diff; + + /* + * Modify a diff such that the cursor position points to the start of a change: + * E.g. + * cursor_normalize_diff([[DIFF_EQUAL, 'abc']], 1) + * => [1, [[DIFF_EQUAL, 'a'], [DIFF_EQUAL, 'bc']]] + * cursor_normalize_diff([[DIFF_INSERT, 'new'], [DIFF_DELETE, 'xyz']], 2) + * => [2, [[DIFF_INSERT, 'new'], [DIFF_DELETE, 'xy'], [DIFF_DELETE, 'z']]] + * + * @param {Array} diffs Array of diff tuples + * @param {Int} cursor_pos Suggested edit position. Must not be out of bounds! + * @return {Array} A tuple [cursor location in the modified diff, modified diff] + */ + function cursor_normalize_diff (diffs, cursor_pos) { + if (cursor_pos === 0) { + return [DIFF_EQUAL, diffs]; + } + for (var current_pos = 0, i = 0; i < diffs.length; i++) { + var d = diffs[i]; + if (d[0] === DIFF_DELETE || d[0] === DIFF_EQUAL) { + var next_pos = current_pos + d[1].length; + if (cursor_pos === next_pos) { + return [i + 1, diffs]; + } else if (cursor_pos < next_pos) { + // copy to prevent side effects + diffs = diffs.slice(); + // split d into two diff changes + var split_pos = cursor_pos - current_pos; + var d_left = [d[0], d[1].slice(0, split_pos)]; + var d_right = [d[0], d[1].slice(split_pos)]; + diffs.splice(i, 1, d_left, d_right); + return [i + 1, diffs]; + } else { + current_pos = next_pos; + } + } + } + throw new Error('cursor_pos is out of bounds!') + } + + /* + * Modify a diff such that the edit position is "shifted" to the proposed edit location (cursor_position). + * + * Case 1) + * Check if a naive shift is possible: + * [0, X], [ 1, Y] -> [ 1, Y], [0, X] (if X + Y === Y + X) + * [0, X], [-1, Y] -> [-1, Y], [0, X] (if X + Y === Y + X) - holds same result + * Case 2) + * Check if the following shifts are possible: + * [0, 'pre'], [ 1, 'prefix'] -> [ 1, 'pre'], [0, 'pre'], [ 1, 'fix'] + * [0, 'pre'], [-1, 'prefix'] -> [-1, 'pre'], [0, 'pre'], [-1, 'fix'] + * ^ ^ + * d d_next + * + * @param {Array} diffs Array of diff tuples + * @param {Int} cursor_pos Suggested edit position. Must not be out of bounds! + * @return {Array} Array of diff tuples + */ + function fix_cursor (diffs, cursor_pos) { + var norm = cursor_normalize_diff(diffs, cursor_pos); + var ndiffs = norm[1]; + var cursor_pointer = norm[0]; + var d = ndiffs[cursor_pointer]; + var d_next = ndiffs[cursor_pointer + 1]; + + if (d == null) { + // Text was deleted from end of original string, + // cursor is now out of bounds in new string + return diffs; + } else if (d[0] !== DIFF_EQUAL) { + // A modification happened at the cursor location. + // This is the expected outcome, so we can return the original diff. + return diffs; + } else { + if (d_next != null && d[1] + d_next[1] === d_next[1] + d[1]) { + // Case 1) + // It is possible to perform a naive shift + ndiffs.splice(cursor_pointer, 2, d_next, d) + return merge_tuples(ndiffs, cursor_pointer, 2) + } else if (d_next != null && d_next[1].indexOf(d[1]) === 0) { + // Case 2) + // d[1] is a prefix of d_next[1] + // We can assume that d_next[0] !== 0, since d[0] === 0 + // Shift edit locations.. + ndiffs.splice(cursor_pointer, 2, [d_next[0], d[1]], [0, d[1]]); + var suffix = d_next[1].slice(d[1].length); + if (suffix.length > 0) { + ndiffs.splice(cursor_pointer + 2, 0, [d_next[0], suffix]); + } + return merge_tuples(ndiffs, cursor_pointer, 3) + } else { + // Not possible to perform any modification + return diffs; + } + } + + } + + /* + * Try to merge tuples with their neigbors in a given range. + * E.g. [0, 'a'], [0, 'b'] -> [0, 'ab'] + * + * @param {Array} diffs Array of diff tuples. + * @param {Int} start Position of the first element to merge (diffs[start] is also merged with diffs[start - 1]). + * @param {Int} length Number of consecutive elements to check. + * @return {Array} Array of merged diff tuples. + */ + function merge_tuples (diffs, start, length) { + // Check from (start-1) to (start+length). + for (var i = start + length - 1; i >= 0 && i >= start - 1; i--) { + if (i + 1 < diffs.length) { + var left_d = diffs[i]; + var right_d = diffs[i+1]; + if (left_d[0] === right_d[1]) { + diffs.splice(i, 2, [left_d[0], left_d[1] + right_d[1]]); + } + } + } + return diffs; + } + + +/***/ }, +/* 22 */ +/***/ function(module, exports, __webpack_require__) { + + var pSlice = Array.prototype.slice; + var objectKeys = __webpack_require__(23); + var isArguments = __webpack_require__(24); + + var deepEqual = module.exports = function (actual, expected, opts) { + if (!opts) opts = {}; + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + } else if (actual instanceof Date && expected instanceof Date) { + return actual.getTime() === expected.getTime(); + + // 7.3. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if (!actual || !expected || typeof actual != 'object' && typeof expected != 'object') { + return opts.strict ? actual === expected : actual == expected; + + // 7.4. 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, opts); + } + } + + function isUndefinedOrNull(value) { + return value === null || value === undefined; + } + + function isBuffer (x) { + if (!x || typeof x !== 'object' || typeof x.length !== 'number') return false; + if (typeof x.copy !== 'function' || typeof x.slice !== 'function') { + return false; + } + if (x.length > 0 && typeof x[0] !== 'number') return false; + return true; + } + + function objEquiv(a, b, opts) { + var i, key; + if (isUndefinedOrNull(a) || isUndefinedOrNull(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, opts); + } + if (isBuffer(a)) { + if (!isBuffer(b)) { + return false; + } + if (a.length !== b.length) return false; + for (i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; + } + try { + var ka = objectKeys(a), + kb = objectKeys(b); + } 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], opts)) return false; + } + return typeof a === typeof b; + } + + +/***/ }, +/* 23 */ +/***/ function(module, exports) { + + exports = module.exports = typeof Object.keys === 'function' + ? Object.keys : shim; + + exports.shim = shim; + function shim (obj) { + var keys = []; + for (var key in obj) keys.push(key); + return keys; + } + + +/***/ }, +/* 24 */ +/***/ function(module, exports) { + + var supportsArgumentsClass = (function(){ + return Object.prototype.toString.call(arguments) + })() == '[object Arguments]'; + + exports = module.exports = supportsArgumentsClass ? supported : unsupported; + + exports.supported = supported; + function supported(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; + }; + + exports.unsupported = unsupported; + function unsupported(object){ + return object && + typeof object == 'object' && + typeof object.length == 'number' && + Object.prototype.hasOwnProperty.call(object, 'callee') && + !Object.prototype.propertyIsEnumerable.call(object, 'callee') || + false; + }; + + +/***/ }, +/* 25 */ +/***/ function(module, exports) { + + 'use strict'; + + var hasOwn = Object.prototype.hasOwnProperty; + var toStr = Object.prototype.toString; + + var isArray = function isArray(arr) { + if (typeof Array.isArray === 'function') { + return Array.isArray(arr); + } + + return toStr.call(arr) === '[object Array]'; + }; + + var isPlainObject = function isPlainObject(obj) { + if (!obj || toStr.call(obj) !== '[object Object]') { + return false; + } + + var hasOwnConstructor = hasOwn.call(obj, 'constructor'); + var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf'); + // Not own constructor property must be Object + if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + var key; + for (key in obj) {/**/} + + return typeof key === 'undefined' || hasOwn.call(obj, key); + }; + + module.exports = function extend() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0], + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if (typeof target === 'boolean') { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } else if ((typeof target !== 'object' && typeof target !== 'function') || target == null) { + target = {}; + } + + for (; i < length; ++i) { + options = arguments[i]; + // Only deal with non-null/undefined values + if (options != null) { + // Extend the base object + for (name in options) { + src = target[name]; + copy = options[name]; + + // Prevent never-ending loop + if (target !== copy) { + // Recurse if we're merging plain objects or arrays + if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) { + if (copyIsArray) { + copyIsArray = false; + clone = src && isArray(src) ? src : []; + } else { + clone = src && isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[name] = extend(deep, clone, copy); + + // Don't bring in undefined values + } else if (typeof copy !== 'undefined') { + target[name] = copy; + } + } + } + } + } + + // Return the modified object + return target; + }; + + + +/***/ }, +/* 26 */ +/***/ function(module, exports, __webpack_require__) { + + var equal = __webpack_require__(22); + var extend = __webpack_require__(25); + + + var lib = { + attributes: { + compose: function (a, b, keepNull) { + if (typeof a !== 'object') a = {}; + if (typeof b !== 'object') b = {}; + var attributes = extend(true, {}, b); + if (!keepNull) { + attributes = Object.keys(attributes).reduce(function (copy, key) { + if (attributes[key] != null) { + copy[key] = attributes[key]; + } + return copy; + }, {}); + } + for (var key in a) { + if (a[key] !== undefined && b[key] === undefined) { + attributes[key] = a[key]; + } + } + return Object.keys(attributes).length > 0 ? attributes : undefined; + }, + + diff: function(a, b) { + if (typeof a !== 'object') a = {}; + if (typeof b !== 'object') b = {}; + var attributes = Object.keys(a).concat(Object.keys(b)).reduce(function (attributes, key) { + if (!equal(a[key], b[key])) { + attributes[key] = b[key] === undefined ? null : b[key]; + } + return attributes; + }, {}); + return Object.keys(attributes).length > 0 ? attributes : undefined; + }, + + transform: function (a, b, priority) { + if (typeof a !== 'object') return b; + if (typeof b !== 'object') return undefined; + if (!priority) return b; // b simply overwrites us without priority + var attributes = Object.keys(b).reduce(function (attributes, key) { + if (a[key] === undefined) attributes[key] = b[key]; // null is a valid value + return attributes; + }, {}); + return Object.keys(attributes).length > 0 ? attributes : undefined; + } + }, + + iterator: function (ops) { + return new Iterator(ops); + }, + + length: function (op) { + if (typeof op['delete'] === 'number') { + return op['delete']; + } else if (typeof op.retain === 'number') { + return op.retain; + } else { + return typeof op.insert === 'string' ? op.insert.length : 1; + } + } + }; + + + function Iterator(ops) { + this.ops = ops; + this.index = 0; + this.offset = 0; + }; + + Iterator.prototype.hasNext = function () { + return this.peekLength() < Infinity; + }; + + Iterator.prototype.next = function (length) { + if (!length) length = Infinity; + var nextOp = this.ops[this.index]; + if (nextOp) { + var offset = this.offset; + var opLength = lib.length(nextOp) + if (length >= opLength - offset) { + length = opLength - offset; + this.index += 1; + this.offset = 0; + } else { + this.offset += length; + } + if (typeof nextOp['delete'] === 'number') { + return { 'delete': length }; + } else { + var retOp = {}; + if (nextOp.attributes) { + retOp.attributes = nextOp.attributes; + } + if (typeof nextOp.retain === 'number') { + retOp.retain = length; + } else if (typeof nextOp.insert === 'string') { + retOp.insert = nextOp.insert.substr(offset, length); + } else { + // offset should === 0, length should === 1 + retOp.insert = nextOp.insert; + } + return retOp; + } + } else { + return { retain: Infinity }; + } + }; + + Iterator.prototype.peek = function () { + return this.ops[this.index]; + }; + + Iterator.prototype.peekLength = function () { + if (this.ops[this.index]) { + // Should never return 0 if our index is being managed correctly + return lib.length(this.ops[this.index]) - this.offset; + } else { + return Infinity; + } + }; + + Iterator.prototype.peekType = function () { + if (this.ops[this.index]) { + if (typeof this.ops[this.index]['delete'] === 'number') { + return 'delete'; + } else if (typeof this.ops[this.index].retain === 'number') { + return 'retain'; + } else { + return 'insert'; + } + } + return 'retain'; + }; + + + module.exports = lib; + + +/***/ }, +/* 27 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _quillDelta = __webpack_require__(20); + + var _quillDelta2 = _interopRequireDefault(_quillDelta); + + var _op = __webpack_require__(26); + + var _op2 = _interopRequireDefault(_op); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _code = __webpack_require__(28); + + var _code2 = _interopRequireDefault(_code); + + var _cursor = __webpack_require__(34); + + var _cursor2 = _interopRequireDefault(_cursor); + + var _block = __webpack_require__(29); + + var _block2 = _interopRequireDefault(_block); + + var _clone = __webpack_require__(38); + + var _clone2 = _interopRequireDefault(_clone); + + var _deepEqual = __webpack_require__(22); + + var _deepEqual2 = _interopRequireDefault(_deepEqual); + + var _extend = __webpack_require__(25); + + var _extend2 = _interopRequireDefault(_extend); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var Editor = function () { + function Editor(scroll) { + _classCallCheck(this, Editor); + + this.scroll = scroll; + this.delta = this.getDelta(); + } + + _createClass(Editor, [{ + key: 'applyDelta', + value: function applyDelta(delta) { + var _this = this; + + var consumeNextNewline = false; + this.scroll.update(); + var scrollLength = this.scroll.length(); + this.scroll.batch = true; + delta = normalizeDelta(delta); + delta.reduce(function (index, op) { + var length = op.retain || op.delete || op.insert.length || 1; + var attributes = op.attributes || {}; + if (op.insert != null) { + if (typeof op.insert === 'string') { + var text = op.insert; + if (text.endsWith('\n') && consumeNextNewline) { + consumeNextNewline = false; + text = text.slice(0, -1); + } + if (index >= scrollLength && !text.endsWith('\n')) { + consumeNextNewline = true; + } + _this.scroll.insertAt(index, text); + + var _scroll$line = _this.scroll.line(index), + _scroll$line2 = _slicedToArray(_scroll$line, 2), + line = _scroll$line2[0], + offset = _scroll$line2[1]; + + var formats = (0, _extend2.default)({}, (0, _block.bubbleFormats)(line)); + if (line instanceof _block2.default) { + var _line$descendant = line.descendant(_parchment2.default.Leaf, offset), + _line$descendant2 = _slicedToArray(_line$descendant, 1), + leaf = _line$descendant2[0]; + + formats = (0, _extend2.default)(formats, (0, _block.bubbleFormats)(leaf)); + } + attributes = _op2.default.attributes.diff(formats, attributes) || {}; + } else if (_typeof(op.insert) === 'object') { + var key = Object.keys(op.insert)[0]; // There should only be one key + if (key == null) return index; + _this.scroll.insertAt(index, key, op.insert[key]); + } + scrollLength += length; + } + Object.keys(attributes).forEach(function (name) { + _this.scroll.formatAt(index, length, name, attributes[name]); + }); + return index + length; + }, 0); + delta.reduce(function (index, op) { + if (typeof op.delete === 'number') { + _this.scroll.deleteAt(index, op.delete); + return index; + } + return index + (op.retain || op.insert.length || 1); + }, 0); + this.scroll.batch = false; + this.scroll.optimize(); + return this.update(delta); + } + }, { + key: 'deleteText', + value: function deleteText(index, length) { + this.scroll.deleteAt(index, length); + return this.update(new _quillDelta2.default().retain(index).delete(length)); + } + }, { + key: 'formatLine', + value: function formatLine(index, length) { + var _this2 = this; + + var formats = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + this.scroll.update(); + Object.keys(formats).forEach(function (format) { + var lines = _this2.scroll.lines(index, Math.max(length, 1)); + var lengthRemaining = length; + lines.forEach(function (line) { + var lineLength = line.length(); + if (!(line instanceof _code2.default)) { + line.format(format, formats[format]); + } else { + var codeIndex = index - line.offset(_this2.scroll); + var codeLength = line.newlineIndex(codeIndex + lengthRemaining) - codeIndex + 1; + line.formatAt(codeIndex, codeLength, format, formats[format]); + } + lengthRemaining -= lineLength; + }); + }); + this.scroll.optimize(); + return this.update(new _quillDelta2.default().retain(index).retain(length, (0, _clone2.default)(formats))); + } + }, { + key: 'formatText', + value: function formatText(index, length) { + var _this3 = this; + + var formats = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + Object.keys(formats).forEach(function (format) { + _this3.scroll.formatAt(index, length, format, formats[format]); + }); + return this.update(new _quillDelta2.default().retain(index).retain(length, (0, _clone2.default)(formats))); + } + }, { + key: 'getContents', + value: function getContents(index, length) { + return this.delta.slice(index, index + length); + } + }, { + key: 'getDelta', + value: function getDelta() { + return this.scroll.lines().reduce(function (delta, line) { + return delta.concat(line.delta()); + }, new _quillDelta2.default()); + } + }, { + key: 'getFormat', + value: function getFormat(index) { + var length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + + var lines = [], + leaves = []; + if (length === 0) { + this.scroll.path(index).forEach(function (path) { + var _path = _slicedToArray(path, 1), + blot = _path[0]; + + if (blot instanceof _block2.default) { + lines.push(blot); + } else if (blot instanceof _parchment2.default.Leaf) { + leaves.push(blot); + } + }); + } else { + lines = this.scroll.lines(index, length); + leaves = this.scroll.descendants(_parchment2.default.Leaf, index, length); + } + var formatsArr = [lines, leaves].map(function (blots) { + if (blots.length === 0) return {}; + var formats = (0, _block.bubbleFormats)(blots.shift()); + while (Object.keys(formats).length > 0) { + var blot = blots.shift(); + if (blot == null) return formats; + formats = combineFormats((0, _block.bubbleFormats)(blot), formats); + } + return formats; + }); + return _extend2.default.apply(_extend2.default, formatsArr); + } + }, { + key: 'getText', + value: function getText(index, length) { + return this.getContents(index, length).filter(function (op) { + return typeof op.insert === 'string'; + }).map(function (op) { + return op.insert; + }).join(''); + } + }, { + key: 'insertEmbed', + value: function insertEmbed(index, embed, value) { + this.scroll.insertAt(index, embed, value); + return this.update(new _quillDelta2.default().retain(index).insert(_defineProperty({}, embed, value))); + } + }, { + key: 'insertText', + value: function insertText(index, text) { + var _this4 = this; + + var formats = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + this.scroll.insertAt(index, text); + Object.keys(formats).forEach(function (format) { + _this4.scroll.formatAt(index, text.length, format, formats[format]); + }); + return this.update(new _quillDelta2.default().retain(index).insert(text, (0, _clone2.default)(formats))); + } + }, { + key: 'isBlank', + value: function isBlank() { + if (this.scroll.children.length == 0) return true; + if (this.scroll.children.length > 1) return false; + var child = this.scroll.children.head; + return child.length() <= 1 && Object.keys(child.formats()).length == 0; + } + }, { + key: 'removeFormat', + value: function removeFormat(index, length) { + var text = this.getText(index, length); + + var _scroll$line3 = this.scroll.line(index + length), + _scroll$line4 = _slicedToArray(_scroll$line3, 2), + line = _scroll$line4[0], + offset = _scroll$line4[1]; + + var suffixLength = 0, + suffix = new _quillDelta2.default(); + if (line != null) { + if (!(line instanceof _code2.default)) { + suffixLength = line.length() - offset; + } else { + suffixLength = line.newlineIndex(offset) - offset + 1; + } + suffix = line.delta().slice(offset, offset + suffixLength - 1).insert('\n'); + } + var contents = this.getContents(index, length + suffixLength); + var diff = contents.diff(new _quillDelta2.default().insert(text).concat(suffix)); + var delta = new _quillDelta2.default().retain(index).concat(diff); + return this.applyDelta(delta); + } + }, { + key: 'update', + value: function update(change) { + var _this5 = this; + + var mutations = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; + var cursorIndex = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined; + + var oldDelta = this.delta; + if (mutations.length === 1 && mutations[0].type === 'characterData' && _parchment2.default.find(mutations[0].target)) { + (function () { + // Optimization for character changes + var textBlot = _parchment2.default.find(mutations[0].target); + var formats = (0, _block.bubbleFormats)(textBlot); + var index = textBlot.offset(_this5.scroll); + var oldValue = mutations[0].oldValue.replace(_cursor2.default.CONTENTS, ''); + var oldText = new _quillDelta2.default().insert(oldValue); + var newText = new _quillDelta2.default().insert(textBlot.value()); + var diffDelta = new _quillDelta2.default().retain(index).concat(oldText.diff(newText, cursorIndex)); + change = diffDelta.reduce(function (delta, op) { + if (op.insert) { + return delta.insert(op.insert, formats); + } else { + return delta.push(op); + } + }, new _quillDelta2.default()); + _this5.delta = oldDelta.compose(change); + })(); + } else { + this.delta = this.getDelta(); + if (!change || !(0, _deepEqual2.default)(oldDelta.compose(change), this.delta)) { + change = oldDelta.diff(this.delta, cursorIndex); + } + } + return change; + } + }]); + + return Editor; + }(); + + function combineFormats(formats, combined) { + return Object.keys(combined).reduce(function (merged, name) { + if (formats[name] == null) return merged; + if (combined[name] === formats[name]) { + merged[name] = combined[name]; + } else if (Array.isArray(combined[name])) { + if (combined[name].indexOf(formats[name]) < 0) { + merged[name] = combined[name].concat([formats[name]]); + } + } else { + merged[name] = [combined[name], formats[name]]; + } + return merged; + }, {}); + } + + function normalizeDelta(delta) { + return delta.reduce(function (delta, op) { + if (op.insert === 1) { + var attributes = (0, _clone2.default)(op.attributes); + delete attributes['image']; + return delta.insert({ image: op.attributes.image }, attributes); + } + if (op.attributes != null && (op.attributes.list === true || op.attributes.bullet === true)) { + op = (0, _clone2.default)(op); + if (op.attributes.list) { + op.attributes.list = 'ordered'; + } else { + op.attributes.list = 'bullet'; + delete op.attributes.bullet; + } + } + if (typeof op.insert === 'string') { + var text = op.insert.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + return delta.insert(text, op.attributes); + } + return delta.push(op); + }, new _quillDelta2.default()); + } + + exports.default = Editor; + +/***/ }, +/* 28 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.default = exports.Code = undefined; + + var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _quillDelta = __webpack_require__(20); + + var _quillDelta2 = _interopRequireDefault(_quillDelta); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _block = __webpack_require__(29); + + var _block2 = _interopRequireDefault(_block); + + var _inline = __webpack_require__(32); + + var _inline2 = _interopRequireDefault(_inline); + + var _text = __webpack_require__(33); + + var _text2 = _interopRequireDefault(_text); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Code = function (_Inline) { + _inherits(Code, _Inline); + + function Code() { + _classCallCheck(this, Code); + + return _possibleConstructorReturn(this, (Code.__proto__ || Object.getPrototypeOf(Code)).apply(this, arguments)); + } + + return Code; + }(_inline2.default); + + Code.blotName = 'code'; + Code.tagName = 'CODE'; + + var CodeBlock = function (_Block) { + _inherits(CodeBlock, _Block); + + function CodeBlock() { + _classCallCheck(this, CodeBlock); + + return _possibleConstructorReturn(this, (CodeBlock.__proto__ || Object.getPrototypeOf(CodeBlock)).apply(this, arguments)); + } + + _createClass(CodeBlock, [{ + key: 'delta', + value: function delta() { + var _this3 = this; + + var text = this.domNode.textContent; + if (text.endsWith('\n')) { + // Should always be true + text = text.slice(0, -1); + } + return text.split('\n').reduce(function (delta, frag) { + return delta.insert(frag).insert('\n', _this3.formats()); + }, new _quillDelta2.default()); + } + }, { + key: 'format', + value: function format(name, value) { + if (name === this.statics.blotName && value) return; + + var _descendant = this.descendant(_text2.default, this.length() - 1), + _descendant2 = _slicedToArray(_descendant, 1), + text = _descendant2[0]; + + if (text != null) { + text.deleteAt(text.length() - 1, 1); + } + _get(CodeBlock.prototype.__proto__ || Object.getPrototypeOf(CodeBlock.prototype), 'format', this).call(this, name, value); + } + }, { + key: 'formatAt', + value: function formatAt(index, length, name, value) { + if (length === 0) return; + if (_parchment2.default.query(name, _parchment2.default.Scope.BLOCK) == null || name === this.statics.blotName && value === this.statics.formats(this.domNode)) { + return; + } + var nextNewline = this.newlineIndex(index); + if (nextNewline < 0 || nextNewline >= index + length) return; + var prevNewline = this.newlineIndex(index, true) + 1; + var isolateLength = nextNewline - prevNewline + 1; + var blot = this.isolate(prevNewline, isolateLength); + var next = blot.next; + blot.format(name, value); + if (next instanceof CodeBlock) { + next.formatAt(0, index - prevNewline + length - isolateLength, name, value); + } + } + }, { + key: 'insertAt', + value: function insertAt(index, value, def) { + if (def != null) return; + + var _descendant3 = this.descendant(_text2.default, index), + _descendant4 = _slicedToArray(_descendant3, 2), + text = _descendant4[0], + offset = _descendant4[1]; + + text.insertAt(offset, value); + } + }, { + key: 'length', + value: function length() { + var length = this.domNode.textContent.length; + if (!this.domNode.textContent.endsWith('\n')) { + return length + 1; + } + return length; + } + }, { + key: 'newlineIndex', + value: function newlineIndex(searchIndex) { + var reverse = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + if (!reverse) { + var offset = this.domNode.textContent.slice(searchIndex).indexOf('\n'); + return offset > -1 ? searchIndex + offset : -1; + } else { + return this.domNode.textContent.slice(0, searchIndex).lastIndexOf('\n'); + } + } + }, { + key: 'optimize', + value: function optimize() { + if (!this.domNode.textContent.endsWith('\n')) { + this.appendChild(_parchment2.default.create('text', '\n')); + } + _get(CodeBlock.prototype.__proto__ || Object.getPrototypeOf(CodeBlock.prototype), 'optimize', this).call(this); + var next = this.next; + if (next != null && next.prev === this && next.statics.blotName === this.statics.blotName && this.statics.formats(this.domNode) === next.statics.formats(next.domNode)) { + next.optimize(); + next.moveChildren(this); + next.remove(); + } + } + }, { + key: 'replace', + value: function replace(target) { + _get(CodeBlock.prototype.__proto__ || Object.getPrototypeOf(CodeBlock.prototype), 'replace', this).call(this, target); + [].slice.call(this.domNode.querySelectorAll('*')).forEach(function (node) { + var blot = _parchment2.default.find(node); + if (blot == null) { + node.parentNode.removeChild(node); + } else if (blot instanceof _parchment2.default.Embed) { + blot.remove(); + } else { + blot.unwrap(); + } + }); + } + }], [{ + key: 'create', + value: function create(value) { + var domNode = _get(CodeBlock.__proto__ || Object.getPrototypeOf(CodeBlock), 'create', this).call(this, value); + domNode.setAttribute('spellcheck', false); + return domNode; + } + }, { + key: 'formats', + value: function formats() { + return true; + } + }]); + + return CodeBlock; + }(_block2.default); + + CodeBlock.blotName = 'code-block'; + CodeBlock.tagName = 'PRE'; + CodeBlock.TAB = ' '; + + exports.Code = Code; + exports.default = CodeBlock; + +/***/ }, +/* 29 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.default = exports.BlockEmbed = exports.bubbleFormats = undefined; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _extend = __webpack_require__(25); + + var _extend2 = _interopRequireDefault(_extend); + + var _quillDelta = __webpack_require__(20); + + var _quillDelta2 = _interopRequireDefault(_quillDelta); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _break = __webpack_require__(30); + + var _break2 = _interopRequireDefault(_break); + + var _embed = __webpack_require__(31); + + var _embed2 = _interopRequireDefault(_embed); + + var _inline = __webpack_require__(32); + + var _inline2 = _interopRequireDefault(_inline); + + var _text = __webpack_require__(33); + + var _text2 = _interopRequireDefault(_text); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var NEWLINE_LENGTH = 1; + + var BlockEmbed = function (_Embed) { + _inherits(BlockEmbed, _Embed); + + function BlockEmbed() { + _classCallCheck(this, BlockEmbed); + + return _possibleConstructorReturn(this, (BlockEmbed.__proto__ || Object.getPrototypeOf(BlockEmbed)).apply(this, arguments)); + } + + _createClass(BlockEmbed, [{ + key: 'attach', + value: function attach() { + _get(BlockEmbed.prototype.__proto__ || Object.getPrototypeOf(BlockEmbed.prototype), 'attach', this).call(this); + this.attributes = new _parchment2.default.Attributor.Store(this.domNode); + } + }, { + key: 'delta', + value: function delta() { + return new _quillDelta2.default().insert(this.value(), (0, _extend2.default)(this.formats(), this.attributes.values())); + } + }, { + key: 'format', + value: function format(name, value) { + var attribute = _parchment2.default.query(name, _parchment2.default.Scope.BLOCK_ATTRIBUTE); + if (attribute != null) { + this.attributes.attribute(attribute, value); + } + } + }, { + key: 'formatAt', + value: function formatAt(index, length, name, value) { + this.format(name, value); + } + }, { + key: 'insertAt', + value: function insertAt(index, value, def) { + if (typeof value === 'string' && value.endsWith('\n')) { + var block = _parchment2.default.create(Block.blotName); + this.parent.insertBefore(block, index === 0 ? this : this.next); + block.insertAt(0, value.slice(0, -1)); + } else { + _get(BlockEmbed.prototype.__proto__ || Object.getPrototypeOf(BlockEmbed.prototype), 'insertAt', this).call(this, index, value, def); + } + } + }]); + + return BlockEmbed; + }(_embed2.default); + + BlockEmbed.scope = _parchment2.default.Scope.BLOCK_BLOT; + // It is important for cursor behavior BlockEmbeds use tags that are block level elements + + + var Block = function (_Parchment$Block) { + _inherits(Block, _Parchment$Block); + + function Block(domNode) { + _classCallCheck(this, Block); + + var _this2 = _possibleConstructorReturn(this, (Block.__proto__ || Object.getPrototypeOf(Block)).call(this, domNode)); + + _this2.cache = {}; + return _this2; + } + + _createClass(Block, [{ + key: 'delta', + value: function delta() { + if (this.cache.delta == null) { + this.cache.delta = this.descendants(_parchment2.default.Leaf).reduce(function (delta, leaf) { + if (leaf.length() === 0) { + return delta; + } else { + return delta.insert(leaf.value(), bubbleFormats(leaf)); + } + }, new _quillDelta2.default()).insert('\n', bubbleFormats(this)); + } + return this.cache.delta; + } + }, { + key: 'deleteAt', + value: function deleteAt(index, length) { + _get(Block.prototype.__proto__ || Object.getPrototypeOf(Block.prototype), 'deleteAt', this).call(this, index, length); + this.cache = {}; + } + }, { + key: 'formatAt', + value: function formatAt(index, length, name, value) { + if (length <= 0) return; + if (_parchment2.default.query(name, _parchment2.default.Scope.BLOCK)) { + if (index + length === this.length()) { + this.format(name, value); + } + } else { + _get(Block.prototype.__proto__ || Object.getPrototypeOf(Block.prototype), 'formatAt', this).call(this, index, Math.min(length, this.length() - index - 1), name, value); + } + this.cache = {}; + } + }, { + key: 'insertAt', + value: function insertAt(index, value, def) { + if (def != null) return _get(Block.prototype.__proto__ || Object.getPrototypeOf(Block.prototype), 'insertAt', this).call(this, index, value, def); + if (value.length === 0) return; + var lines = value.split('\n'); + var text = lines.shift(); + if (text.length > 0) { + if (index < this.length() - 1 || this.children.tail == null) { + _get(Block.prototype.__proto__ || Object.getPrototypeOf(Block.prototype), 'insertAt', this).call(this, Math.min(index, this.length() - 1), text); + } else { + this.children.tail.insertAt(this.children.tail.length(), text); + } + this.cache = {}; + } + var block = this; + lines.reduce(function (index, line) { + block = block.split(index, true); + block.insertAt(0, line); + return line.length; + }, index + text.length); + } + }, { + key: 'insertBefore', + value: function insertBefore(blot, ref) { + var head = this.children.head; + _get(Block.prototype.__proto__ || Object.getPrototypeOf(Block.prototype), 'insertBefore', this).call(this, blot, ref); + if (head instanceof _break2.default) { + head.remove(); + } + this.cache = {}; + } + }, { + key: 'length', + value: function length() { + if (this.cache.length == null) { + this.cache.length = _get(Block.prototype.__proto__ || Object.getPrototypeOf(Block.prototype), 'length', this).call(this) + NEWLINE_LENGTH; + } + return this.cache.length; + } + }, { + key: 'moveChildren', + value: function moveChildren(target, ref) { + _get(Block.prototype.__proto__ || Object.getPrototypeOf(Block.prototype), 'moveChildren', this).call(this, target, ref); + this.cache = {}; + } + }, { + key: 'optimize', + value: function optimize() { + _get(Block.prototype.__proto__ || Object.getPrototypeOf(Block.prototype), 'optimize', this).call(this); + this.cache = {}; + } + }, { + key: 'path', + value: function path(index) { + return _get(Block.prototype.__proto__ || Object.getPrototypeOf(Block.prototype), 'path', this).call(this, index, true); + } + }, { + key: 'removeChild', + value: function removeChild(child) { + _get(Block.prototype.__proto__ || Object.getPrototypeOf(Block.prototype), 'removeChild', this).call(this, child); + this.cache = {}; + } + }, { + key: 'split', + value: function split(index) { + var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + if (force && (index === 0 || index >= this.length() - NEWLINE_LENGTH)) { + var clone = this.clone(); + if (index === 0) { + this.parent.insertBefore(clone, this); + return this; + } else { + this.parent.insertBefore(clone, this.next); + return clone; + } + } else { + var next = _get(Block.prototype.__proto__ || Object.getPrototypeOf(Block.prototype), 'split', this).call(this, index, force); + this.cache = {}; + return next; + } + } + }]); + + return Block; + }(_parchment2.default.Block); + + Block.blotName = 'block'; + Block.tagName = 'P'; + Block.defaultChild = 'break'; + Block.allowedChildren = [_inline2.default, _embed2.default, _text2.default]; + + function bubbleFormats(blot) { + var formats = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + if (blot == null) return formats; + if (typeof blot.formats === 'function') { + formats = (0, _extend2.default)(formats, blot.formats()); + } + if (blot.parent == null || blot.parent.blotName == 'scroll' || blot.parent.statics.scope !== blot.statics.scope) { + return formats; + } + return bubbleFormats(blot.parent, formats); + } + + exports.bubbleFormats = bubbleFormats; + exports.BlockEmbed = BlockEmbed; + exports.default = Block; + +/***/ }, +/* 30 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _embed = __webpack_require__(31); + + var _embed2 = _interopRequireDefault(_embed); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Break = function (_Embed) { + _inherits(Break, _Embed); + + function Break() { + _classCallCheck(this, Break); + + return _possibleConstructorReturn(this, (Break.__proto__ || Object.getPrototypeOf(Break)).apply(this, arguments)); + } + + _createClass(Break, [{ + key: 'insertInto', + value: function insertInto(parent, ref) { + if (parent.children.length === 0) { + _get(Break.prototype.__proto__ || Object.getPrototypeOf(Break.prototype), 'insertInto', this).call(this, parent, ref); + } else { + this.remove(); + } + } + }, { + key: 'length', + value: function length() { + return 0; + } + }, { + key: 'value', + value: function value() { + return ''; + } + }], [{ + key: 'value', + value: function value() { + return undefined; + } + }]); + + return Break; + }(_embed2.default); + + Break.blotName = 'break'; + Break.tagName = 'BR'; + + exports.default = Break; + +/***/ }, +/* 31 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Embed = function (_Parchment$Embed) { + _inherits(Embed, _Parchment$Embed); + + function Embed() { + _classCallCheck(this, Embed); + + return _possibleConstructorReturn(this, (Embed.__proto__ || Object.getPrototypeOf(Embed)).apply(this, arguments)); + } + + return Embed; + }(_parchment2.default.Embed); + + exports.default = Embed; + +/***/ }, +/* 32 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _embed = __webpack_require__(31); + + var _embed2 = _interopRequireDefault(_embed); + + var _text = __webpack_require__(33); + + var _text2 = _interopRequireDefault(_text); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Inline = function (_Parchment$Inline) { + _inherits(Inline, _Parchment$Inline); + + function Inline() { + _classCallCheck(this, Inline); + + return _possibleConstructorReturn(this, (Inline.__proto__ || Object.getPrototypeOf(Inline)).apply(this, arguments)); + } + + _createClass(Inline, [{ + key: 'formatAt', + value: function formatAt(index, length, name, value) { + if (Inline.compare(this.statics.blotName, name) < 0 && _parchment2.default.query(name, _parchment2.default.Scope.BLOT)) { + var blot = this.isolate(index, length); + if (value) { + blot.wrap(name, value); + } + } else { + _get(Inline.prototype.__proto__ || Object.getPrototypeOf(Inline.prototype), 'formatAt', this).call(this, index, length, name, value); + } + } + }, { + key: 'optimize', + value: function optimize() { + _get(Inline.prototype.__proto__ || Object.getPrototypeOf(Inline.prototype), 'optimize', this).call(this); + if (this.parent instanceof Inline && Inline.compare(this.statics.blotName, this.parent.statics.blotName) > 0) { + var parent = this.parent.isolate(this.offset(), this.length()); + this.moveChildren(parent); + parent.wrap(this); + } + } + }], [{ + key: 'compare', + value: function compare(self, other) { + var selfIndex = Inline.order.indexOf(self); + var otherIndex = Inline.order.indexOf(other); + if (selfIndex >= 0 || otherIndex >= 0) { + return selfIndex - otherIndex; + } else if (self === other) { + return 0; + } else if (self < other) { + return -1; + } else { + return 1; + } + } + }]); + + return Inline; + }(_parchment2.default.Inline); + + Inline.allowedChildren = [Inline, _embed2.default, _text2.default]; + // Lower index means deeper in the DOM tree, since not found (-1) is for embeds + Inline.order = ['cursor', 'inline', // Must be lower + 'code', 'underline', 'strike', 'italic', 'bold', 'script', 'link' // Must be higher + ]; + + exports.default = Inline; + +/***/ }, +/* 33 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var TextBlot = function (_Parchment$Text) { + _inherits(TextBlot, _Parchment$Text); + + function TextBlot() { + _classCallCheck(this, TextBlot); + + return _possibleConstructorReturn(this, (TextBlot.__proto__ || Object.getPrototypeOf(TextBlot)).apply(this, arguments)); + } + + return TextBlot; + }(_parchment2.default.Text); + + exports.default = TextBlot; + +/***/ }, +/* 34 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _embed = __webpack_require__(31); + + var _embed2 = _interopRequireDefault(_embed); + + var _text = __webpack_require__(33); + + var _text2 = _interopRequireDefault(_text); + + var _emitter = __webpack_require__(35); + + var _emitter2 = _interopRequireDefault(_emitter); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Cursor = function (_Embed) { + _inherits(Cursor, _Embed); + + _createClass(Cursor, null, [{ + key: 'value', + value: function value() { + return undefined; + } + }]); + + function Cursor(domNode, selection) { + _classCallCheck(this, Cursor); + + var _this = _possibleConstructorReturn(this, (Cursor.__proto__ || Object.getPrototypeOf(Cursor)).call(this, domNode)); + + _this.selection = selection; + _this.textNode = document.createTextNode(Cursor.CONTENTS); + _this.domNode.appendChild(_this.textNode); + _this._length = 0; + return _this; + } + + _createClass(Cursor, [{ + key: 'detach', + value: function detach() { + // super.detach() will also clear domNode.__blot + if (this.parent != null) this.parent.removeChild(this); + } + }, { + key: 'format', + value: function format(name, value) { + if (this._length !== 0) { + return _get(Cursor.prototype.__proto__ || Object.getPrototypeOf(Cursor.prototype), 'format', this).call(this, name, value); + } + var target = this, + index = 0; + while (target != null && target.statics.scope !== _parchment2.default.Scope.BLOCK_BLOT) { + index += target.offset(target.parent); + target = target.parent; + } + if (target != null) { + this._length = Cursor.CONTENTS.length; + target.optimize(); + target.formatAt(index, Cursor.CONTENTS.length, name, value); + this._length = 0; + } + } + }, { + key: 'index', + value: function index(node, offset) { + if (node === this.textNode) return 0; + return _get(Cursor.prototype.__proto__ || Object.getPrototypeOf(Cursor.prototype), 'index', this).call(this, node, offset); + } + }, { + key: 'length', + value: function length() { + return this._length; + } + }, { + key: 'position', + value: function position() { + return [this.textNode, this.textNode.data.length]; + } + }, { + key: 'remove', + value: function remove() { + _get(Cursor.prototype.__proto__ || Object.getPrototypeOf(Cursor.prototype), 'remove', this).call(this); + this.parent = null; + } + }, { + key: 'restore', + value: function restore() { + var _this2 = this; + + if (this.selection.composing) return; + if (this.parent == null) return; + var textNode = this.textNode; + var range = this.selection.getNativeRange(); + var restoreText = void 0, + start = void 0, + end = void 0; + if (range != null && range.start.node === textNode && range.end.node === textNode) { + var _ref = [textNode, range.start.offset, range.end.offset]; + restoreText = _ref[0]; + start = _ref[1]; + end = _ref[2]; + } + // Link format will insert text outside of anchor tag + while (this.domNode.lastChild != null && this.domNode.lastChild !== this.textNode) { + this.domNode.parentNode.insertBefore(this.domNode.lastChild, this.domNode); + } + if (this.textNode.data !== Cursor.CONTENTS) { + var text = this.textNode.data.split(Cursor.CONTENTS).join(''); + if (this.next instanceof _text2.default) { + restoreText = this.next.domNode; + this.next.insertAt(0, text); + this.textNode.data = Cursor.CONTENTS; + } else { + this.textNode.data = text; + this.parent.insertBefore(_parchment2.default.create(this.textNode), this); + this.textNode = document.createTextNode(Cursor.CONTENTS); + this.domNode.appendChild(this.textNode); + } + } + this.remove(); + if (start == null) return; + this.selection.emitter.once(_emitter2.default.events.SCROLL_OPTIMIZE, function () { + var _map = [start, end].map(function (offset) { + return Math.max(0, Math.min(restoreText.data.length, offset - 1)); + }); + + var _map2 = _slicedToArray(_map, 2); + + start = _map2[0]; + end = _map2[1]; + + _this2.selection.setNativeRange(restoreText, start, restoreText, end); + }); + } + }, { + key: 'update', + value: function update(mutations) { + var _this3 = this; + + mutations.forEach(function (mutation) { + if (mutation.type === 'characterData' && mutation.target === _this3.textNode) { + _this3.restore(); + } + }); + } + }, { + key: 'value', + value: function value() { + return ''; + } + }]); + + return Cursor; + }(_embed2.default); + + Cursor.blotName = 'cursor'; + Cursor.className = 'ql-cursor'; + Cursor.tagName = 'span'; + Cursor.CONTENTS = '\uFEFF'; // Zero width no break space + + + exports.default = Cursor; + +/***/ }, +/* 35 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _eventemitter = __webpack_require__(36); + + var _eventemitter2 = _interopRequireDefault(_eventemitter); + + var _logger = __webpack_require__(37); + + var _logger2 = _interopRequireDefault(_logger); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var debug = (0, _logger2.default)('quill:events'); + + var Emitter = function (_EventEmitter) { + _inherits(Emitter, _EventEmitter); + + function Emitter() { + _classCallCheck(this, Emitter); + + var _this = _possibleConstructorReturn(this, (Emitter.__proto__ || Object.getPrototypeOf(Emitter)).call(this)); + + _this.on('error', debug.error); + return _this; + } + + _createClass(Emitter, [{ + key: 'emit', + value: function emit() { + debug.log.apply(debug, arguments); + _get(Emitter.prototype.__proto__ || Object.getPrototypeOf(Emitter.prototype), 'emit', this).apply(this, arguments); + } + }]); + + return Emitter; + }(_eventemitter2.default); + + Emitter.events = { + EDITOR_CHANGE: 'editor-change', + SCROLL_BEFORE_UPDATE: 'scroll-before-update', + SCROLL_OPTIMIZE: 'scroll-optimize', + SCROLL_UPDATE: 'scroll-update', + SELECTION_CHANGE: 'selection-change', + TEXT_CHANGE: 'text-change' + }; + Emitter.sources = { + API: 'api', + SILENT: 'silent', + USER: 'user' + }; + + exports.default = Emitter; + +/***/ }, +/* 36 */ +/***/ function(module, exports) { + + 'use strict'; + + var has = Object.prototype.hasOwnProperty + , prefix = '~'; + + /** + * Constructor to create a storage for our `EE` objects. + * An `Events` instance is a plain object whose properties are event names. + * + * @constructor + * @api private + */ + function Events() {} + + // + // We try to not inherit from `Object.prototype`. In some engines creating an + // instance in this way is faster than calling `Object.create(null)` directly. + // If `Object.create(null)` is not supported we prefix the event names with a + // character to make sure that the built-in object properties are not + // overridden or used as an attack vector. + // + if (Object.create) { + Events.prototype = Object.create(null); + + // + // This hack is needed because the `__proto__` property is still inherited in + // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. + // + if (!new Events().__proto__) prefix = false; + } + + /** + * Representation of a single event listener. + * + * @param {Function} fn The listener function. + * @param {Mixed} context The context to invoke the listener with. + * @param {Boolean} [once=false] Specify if the listener is a one-time listener. + * @constructor + * @api private + */ + function EE(fn, context, once) { + this.fn = fn; + this.context = context; + this.once = once || false; + } + + /** + * Minimal `EventEmitter` interface that is molded against the Node.js + * `EventEmitter` interface. + * + * @constructor + * @api public + */ + function EventEmitter() { + this._events = new Events(); + this._eventsCount = 0; + } + + /** + * Return an array listing the events for which the emitter has registered + * listeners. + * + * @returns {Array} + * @api public + */ + EventEmitter.prototype.eventNames = function eventNames() { + var names = [] + , events + , name; + + if (this._eventsCount === 0) return names; + + for (name in (events = this._events)) { + if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); + } + + if (Object.getOwnPropertySymbols) { + return names.concat(Object.getOwnPropertySymbols(events)); + } + + return names; + }; + + /** + * Return the listeners registered for a given event. + * + * @param {String|Symbol} event The event name. + * @param {Boolean} exists Only check if there are listeners. + * @returns {Array|Boolean} + * @api public + */ + EventEmitter.prototype.listeners = function listeners(event, exists) { + var evt = prefix ? prefix + event : event + , available = this._events[evt]; + + if (exists) return !!available; + if (!available) return []; + if (available.fn) return [available.fn]; + + for (var i = 0, l = available.length, ee = new Array(l); i < l; i++) { + ee[i] = available[i].fn; + } + + return ee; + }; + + /** + * Calls each of the listeners registered for a given event. + * + * @param {String|Symbol} event The event name. + * @returns {Boolean} `true` if the event had listeners, else `false`. + * @api public + */ + EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { + var evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return false; + + var listeners = this._events[evt] + , len = arguments.length + , args + , i; + + if (listeners.fn) { + if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); + + switch (len) { + case 1: return listeners.fn.call(listeners.context), true; + case 2: return listeners.fn.call(listeners.context, a1), true; + case 3: return listeners.fn.call(listeners.context, a1, a2), true; + case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; + case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; + case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; + } + + for (i = 1, args = new Array(len -1); i < len; i++) { + args[i - 1] = arguments[i]; + } + + listeners.fn.apply(listeners.context, args); + } else { + var length = listeners.length + , j; + + for (i = 0; i < length; i++) { + if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); + + switch (len) { + case 1: listeners[i].fn.call(listeners[i].context); break; + case 2: listeners[i].fn.call(listeners[i].context, a1); break; + case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; + case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; + default: + if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { + args[j - 1] = arguments[j]; + } + + listeners[i].fn.apply(listeners[i].context, args); + } + } + } + + return true; + }; + + /** + * Add a listener for a given event. + * + * @param {String|Symbol} event The event name. + * @param {Function} fn The listener function. + * @param {Mixed} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @api public + */ + EventEmitter.prototype.on = function on(event, fn, context) { + var listener = new EE(fn, context || this) + , evt = prefix ? prefix + event : event; + + if (!this._events[evt]) this._events[evt] = listener, this._eventsCount++; + else if (!this._events[evt].fn) this._events[evt].push(listener); + else this._events[evt] = [this._events[evt], listener]; + + return this; + }; + + /** + * Add a one-time listener for a given event. + * + * @param {String|Symbol} event The event name. + * @param {Function} fn The listener function. + * @param {Mixed} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @api public + */ + EventEmitter.prototype.once = function once(event, fn, context) { + var listener = new EE(fn, context || this, true) + , evt = prefix ? prefix + event : event; + + if (!this._events[evt]) this._events[evt] = listener, this._eventsCount++; + else if (!this._events[evt].fn) this._events[evt].push(listener); + else this._events[evt] = [this._events[evt], listener]; + + return this; + }; + + /** + * Remove the listeners of a given event. + * + * @param {String|Symbol} event The event name. + * @param {Function} fn Only remove the listeners that match this function. + * @param {Mixed} context Only remove the listeners that have this context. + * @param {Boolean} once Only remove one-time listeners. + * @returns {EventEmitter} `this`. + * @api public + */ + EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { + var evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return this; + if (!fn) { + if (--this._eventsCount === 0) this._events = new Events(); + else delete this._events[evt]; + return this; + } + + var listeners = this._events[evt]; + + if (listeners.fn) { + if ( + listeners.fn === fn + && (!once || listeners.once) + && (!context || listeners.context === context) + ) { + if (--this._eventsCount === 0) this._events = new Events(); + else delete this._events[evt]; + } + } else { + for (var i = 0, events = [], length = listeners.length; i < length; i++) { + if ( + listeners[i].fn !== fn + || (once && !listeners[i].once) + || (context && listeners[i].context !== context) + ) { + events.push(listeners[i]); + } + } + + // + // Reset the array, or remove it completely if we have no more listeners. + // + if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; + else if (--this._eventsCount === 0) this._events = new Events(); + else delete this._events[evt]; + } + + return this; + }; + + /** + * Remove all listeners, or those of the specified event. + * + * @param {String|Symbol} [event] The event name. + * @returns {EventEmitter} `this`. + * @api public + */ + EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { + var evt; + + if (event) { + evt = prefix ? prefix + event : event; + if (this._events[evt]) { + if (--this._eventsCount === 0) this._events = new Events(); + else delete this._events[evt]; + } + } else { + this._events = new Events(); + this._eventsCount = 0; + } + + return this; + }; + + // + // Alias methods names because people roll like that. + // + EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + EventEmitter.prototype.addListener = EventEmitter.prototype.on; + + // + // This function doesn't apply anymore. + // + EventEmitter.prototype.setMaxListeners = function setMaxListeners() { + return this; + }; + + // + // Expose the prefix. + // + EventEmitter.prefixed = prefix; + + // + // Allow `EventEmitter` to be imported as module namespace. + // + EventEmitter.EventEmitter = EventEmitter; + + // + // Expose the module. + // + if ('undefined' !== typeof module) { + module.exports = EventEmitter; + } + + +/***/ }, +/* 37 */ +/***/ function(module, exports) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + var levels = ['error', 'warn', 'log', 'info']; + var level = 'warn'; + + function debug(method) { + if (levels.indexOf(method) <= levels.indexOf(level)) { + for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + console[method].apply(console, args); // eslint-disable-line no-console + } + } + + function namespace(ns) { + return levels.reduce(function (logger, method) { + logger[method] = debug.bind(console, method, ns); + return logger; + }, {}); + } + + debug.level = namespace.level = function (newLevel) { + level = newLevel; + }; + + exports.default = namespace; + +/***/ }, +/* 38 */ +/***/ function(module, exports) { + + var clone = (function() { + 'use strict'; + + var nativeMap; + try { + nativeMap = Map; + } catch(_) { + // maybe a reference error because no `Map`. Give it a dummy value that no + // value will ever be an instanceof. + nativeMap = function() {}; + } + + var nativeSet; + try { + nativeSet = Set; + } catch(_) { + nativeSet = function() {}; + } + + var nativePromise; + try { + nativePromise = Promise; + } catch(_) { + nativePromise = function() {}; + } + + /** + * Clones (copies) an Object using deep copying. + * + * This function supports circular references by default, but if you are certain + * there are no circular references in your object, you can save some CPU time + * by calling clone(obj, false). + * + * Caution: if `circular` is false and `parent` contains circular references, + * your program may enter an infinite loop and crash. + * + * @param `parent` - the object to be cloned + * @param `circular` - set to true if the object to be cloned may contain + * circular references. (optional - true by default) + * @param `depth` - set to a number if the object is only to be cloned to + * a particular depth. (optional - defaults to Infinity) + * @param `prototype` - sets the prototype to be used when cloning an object. + * (optional - defaults to parent prototype). + * @param `includeNonEnumerable` - set to true if the non-enumerable properties + * should be cloned as well. Non-enumerable properties on the prototype + * chain will be ignored. (optional - false by default) + */ + function clone(parent, circular, depth, prototype, includeNonEnumerable) { + if (typeof circular === 'object') { + depth = circular.depth; + prototype = circular.prototype; + includeNonEnumerable = circular.includeNonEnumerable; + circular = circular.circular; + } + // maintain two arrays for circular references, where corresponding parents + // and children have the same index + var allParents = []; + var allChildren = []; + + var useBuffer = typeof Buffer != 'undefined'; + + if (typeof circular == 'undefined') + circular = true; + + if (typeof depth == 'undefined') + depth = Infinity; + + // recurse this function so we don't reset allParents and allChildren + function _clone(parent, depth) { + // cloning null always returns null + if (parent === null) + return null; + + if (depth === 0) + return parent; + + var child; + var proto; + if (typeof parent != 'object') { + return parent; + } + + if (parent instanceof nativeMap) { + child = new nativeMap(); + } else if (parent instanceof nativeSet) { + child = new nativeSet(); + } else if (parent instanceof nativePromise) { + child = new nativePromise(function (resolve, reject) { + parent.then(function(value) { + resolve(_clone(value, depth - 1)); + }, function(err) { + reject(_clone(err, depth - 1)); + }); + }); + } else if (clone.__isArray(parent)) { + child = []; + } else if (clone.__isRegExp(parent)) { + child = new RegExp(parent.source, __getRegExpFlags(parent)); + if (parent.lastIndex) child.lastIndex = parent.lastIndex; + } else if (clone.__isDate(parent)) { + child = new Date(parent.getTime()); + } else if (useBuffer && Buffer.isBuffer(parent)) { + child = new Buffer(parent.length); + parent.copy(child); + return child; + } else if (parent instanceof Error) { + child = Object.create(parent); + } else { + if (typeof prototype == 'undefined') { + proto = Object.getPrototypeOf(parent); + child = Object.create(proto); + } + else { + child = Object.create(prototype); + proto = prototype; + } + } + + if (circular) { + var index = allParents.indexOf(parent); + + if (index != -1) { + return allChildren[index]; + } + allParents.push(parent); + allChildren.push(child); + } + + if (parent instanceof nativeMap) { + var keyIterator = parent.keys(); + while(true) { + var next = keyIterator.next(); + if (next.done) { + break; + } + var keyChild = _clone(next.value, depth - 1); + var valueChild = _clone(parent.get(next.value), depth - 1); + child.set(keyChild, valueChild); + } + } + if (parent instanceof nativeSet) { + var iterator = parent.keys(); + while(true) { + var next = iterator.next(); + if (next.done) { + break; + } + var entryChild = _clone(next.value, depth - 1); + child.add(entryChild); + } + } + + for (var i in parent) { + var attrs; + if (proto) { + attrs = Object.getOwnPropertyDescriptor(proto, i); + } + + if (attrs && attrs.set == null) { + continue; + } + child[i] = _clone(parent[i], depth - 1); + } + + if (Object.getOwnPropertySymbols) { + var symbols = Object.getOwnPropertySymbols(parent); + for (var i = 0; i < symbols.length; i++) { + // Don't need to worry about cloning a symbol because it is a primitive, + // like a number or string. + var symbol = symbols[i]; + var descriptor = Object.getOwnPropertyDescriptor(parent, symbol); + if (descriptor && !descriptor.enumerable && !includeNonEnumerable) { + continue; + } + child[symbol] = _clone(parent[symbol], depth - 1); + if (!descriptor.enumerable) { + Object.defineProperty(child, symbol, { + enumerable: false + }); + } + } + } + + if (includeNonEnumerable) { + var allPropertyNames = Object.getOwnPropertyNames(parent); + for (var i = 0; i < allPropertyNames.length; i++) { + var propertyName = allPropertyNames[i]; + var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName); + if (descriptor && descriptor.enumerable) { + continue; + } + child[propertyName] = _clone(parent[propertyName], depth - 1); + Object.defineProperty(child, propertyName, { + enumerable: false + }); + } + } + + return child; + } + + return _clone(parent, depth); + } + + /** + * Simple flat clone using prototype, accepts only objects, usefull for property + * override on FLAT configuration object (no nested props). + * + * USE WITH CAUTION! This may not behave as you wish if you do not know how this + * works. + */ + clone.clonePrototype = function clonePrototype(parent) { + if (parent === null) + return null; + + var c = function () {}; + c.prototype = parent; + return new c(); + }; + + // private utility functions + + function __objToStr(o) { + return Object.prototype.toString.call(o); + } + clone.__objToStr = __objToStr; + + function __isDate(o) { + return typeof o === 'object' && __objToStr(o) === '[object Date]'; + } + clone.__isDate = __isDate; + + function __isArray(o) { + return typeof o === 'object' && __objToStr(o) === '[object Array]'; + } + clone.__isArray = __isArray; + + function __isRegExp(o) { + return typeof o === 'object' && __objToStr(o) === '[object RegExp]'; + } + clone.__isRegExp = __isRegExp; + + function __getRegExpFlags(re) { + var flags = ''; + if (re.global) flags += 'g'; + if (re.ignoreCase) flags += 'i'; + if (re.multiline) flags += 'm'; + return flags; + } + clone.__getRegExpFlags = __getRegExpFlags; + + return clone; + })(); + + if (typeof module === 'object' && module.exports) { + module.exports = clone; + } + + +/***/ }, +/* 39 */ +/***/ function(module, exports) { + + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var Module = function Module(quill) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, Module); + + this.quill = quill; + this.options = options; + }; + + Module.DEFAULTS = {}; + + exports.default = Module; + +/***/ }, +/* 40 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.default = exports.Range = undefined; + + var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _clone = __webpack_require__(38); + + var _clone2 = _interopRequireDefault(_clone); + + var _deepEqual = __webpack_require__(22); + + var _deepEqual2 = _interopRequireDefault(_deepEqual); + + var _emitter3 = __webpack_require__(35); + + var _emitter4 = _interopRequireDefault(_emitter3); + + var _logger = __webpack_require__(37); + + var _logger2 = _interopRequireDefault(_logger); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var debug = (0, _logger2.default)('quill:selection'); + + var Range = function Range(index) { + var length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + + _classCallCheck(this, Range); + + this.index = index; + this.length = length; + }; + + var Selection = function () { + function Selection(scroll, emitter) { + var _this = this; + + _classCallCheck(this, Selection); + + this.emitter = emitter; + this.scroll = scroll; + this.composing = false; + this.root = this.scroll.domNode; + this.root.addEventListener('compositionstart', function () { + _this.composing = true; + }); + this.root.addEventListener('compositionend', function () { + _this.composing = false; + }); + this.cursor = _parchment2.default.create('cursor', this); + // savedRange is last non-null range + this.lastRange = this.savedRange = new Range(0, 0); + ['keyup', 'mouseup', 'mouseleave', 'touchend', 'touchleave', 'focus', 'blur'].forEach(function (eventName) { + _this.root.addEventListener(eventName, function () { + // When range used to be a selection and user click within the selection, + // the range now being a cursor has not updated yet without setTimeout + setTimeout(_this.update.bind(_this, _emitter4.default.sources.USER), 100); + }); + }); + this.emitter.on(_emitter4.default.events.EDITOR_CHANGE, function (type, delta) { + if (type === _emitter4.default.events.TEXT_CHANGE && delta.length() > 0) { + _this.update(_emitter4.default.sources.SILENT); + } + }); + this.emitter.on(_emitter4.default.events.SCROLL_BEFORE_UPDATE, function () { + var native = _this.getNativeRange(); + if (native == null) return; + if (native.start.node === _this.cursor.textNode) return; // cursor.restore() will handle + // TODO unclear if this has negative side effects + _this.emitter.once(_emitter4.default.events.SCROLL_UPDATE, function () { + try { + _this.setNativeRange(native.start.node, native.start.offset, native.end.node, native.end.offset); + } catch (ignored) {} + }); + }); + this.update(_emitter4.default.sources.SILENT); + } + + _createClass(Selection, [{ + key: 'focus', + value: function focus() { + if (this.hasFocus()) return; + this.root.focus(); + this.setRange(this.savedRange); + } + }, { + key: 'format', + value: function format(_format, value) { + if (this.scroll.whitelist != null && !this.scroll.whitelist[_format]) return; + this.scroll.update(); + var nativeRange = this.getNativeRange(); + if (nativeRange == null || !nativeRange.native.collapsed || _parchment2.default.query(_format, _parchment2.default.Scope.BLOCK)) return; + if (nativeRange.start.node !== this.cursor.textNode) { + var blot = _parchment2.default.find(nativeRange.start.node, false); + if (blot == null) return; + // TODO Give blot ability to not split + if (blot instanceof _parchment2.default.Leaf) { + var after = blot.split(nativeRange.start.offset); + blot.parent.insertBefore(this.cursor, after); + } else { + blot.insertBefore(this.cursor, nativeRange.start.node); // Should never happen + } + this.cursor.attach(); + } + this.cursor.format(_format, value); + this.scroll.optimize(); + this.setNativeRange(this.cursor.textNode, this.cursor.textNode.data.length); + this.update(); + } + }, { + key: 'getBounds', + value: function getBounds(index) { + var length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + + var scrollLength = this.scroll.length(); + index = Math.min(index, scrollLength - 1); + length = Math.min(index + length, scrollLength - 1) - index; + var bounds = void 0, + node = void 0, + _scroll$leaf = this.scroll.leaf(index), + _scroll$leaf2 = _slicedToArray(_scroll$leaf, 2), + leaf = _scroll$leaf2[0], + offset = _scroll$leaf2[1]; + if (leaf == null) return null; + + var _leaf$position = leaf.position(offset, true); + + var _leaf$position2 = _slicedToArray(_leaf$position, 2); + + node = _leaf$position2[0]; + offset = _leaf$position2[1]; + + var range = document.createRange(); + if (length > 0) { + range.setStart(node, offset); + + var _scroll$leaf3 = this.scroll.leaf(index + length); + + var _scroll$leaf4 = _slicedToArray(_scroll$leaf3, 2); + + leaf = _scroll$leaf4[0]; + offset = _scroll$leaf4[1]; + + if (leaf == null) return null; + + var _leaf$position3 = leaf.position(offset, true); + + var _leaf$position4 = _slicedToArray(_leaf$position3, 2); + + node = _leaf$position4[0]; + offset = _leaf$position4[1]; + + range.setEnd(node, offset); + bounds = range.getBoundingClientRect(); + } else { + var side = 'left'; + var rect = void 0; + if (node instanceof Text) { + if (offset < node.data.length) { + range.setStart(node, offset); + range.setEnd(node, offset + 1); + } else { + range.setStart(node, offset - 1); + range.setEnd(node, offset); + side = 'right'; + } + rect = range.getBoundingClientRect(); + } else { + rect = leaf.domNode.getBoundingClientRect(); + if (offset > 0) side = 'right'; + } + bounds = { + height: rect.height, + left: rect[side], + width: 0, + top: rect.top + }; + } + var containerBounds = this.root.parentNode.getBoundingClientRect(); + return { + left: bounds.left - containerBounds.left, + right: bounds.left + bounds.width - containerBounds.left, + top: bounds.top - containerBounds.top, + bottom: bounds.top + bounds.height - containerBounds.top, + height: bounds.height, + width: bounds.width + }; + } + }, { + key: 'getNativeRange', + value: function getNativeRange() { + var selection = document.getSelection(); + if (selection == null || selection.rangeCount <= 0) return null; + var nativeRange = selection.getRangeAt(0); + if (nativeRange == null) return null; + if (!contains(this.root, nativeRange.startContainer) || !nativeRange.collapsed && !contains(this.root, nativeRange.endContainer)) { + return null; + } + var range = { + start: { node: nativeRange.startContainer, offset: nativeRange.startOffset }, + end: { node: nativeRange.endContainer, offset: nativeRange.endOffset }, + native: nativeRange + }; + [range.start, range.end].forEach(function (position) { + var node = position.node, + offset = position.offset; + while (!(node instanceof Text) && node.childNodes.length > 0) { + if (node.childNodes.length > offset) { + node = node.childNodes[offset]; + offset = 0; + } else if (node.childNodes.length === offset) { + node = node.lastChild; + offset = node instanceof Text ? node.data.length : node.childNodes.length + 1; + } else { + break; + } + } + position.node = node, position.offset = offset; + }); + debug.info('getNativeRange', range); + return range; + } + }, { + key: 'getRange', + value: function getRange() { + var _this2 = this; + + var range = this.getNativeRange(); + if (range == null) return [null, null]; + var positions = [[range.start.node, range.start.offset]]; + if (!range.native.collapsed) { + positions.push([range.end.node, range.end.offset]); + } + var indexes = positions.map(function (position) { + var _position = _slicedToArray(position, 2), + node = _position[0], + offset = _position[1]; + + var blot = _parchment2.default.find(node, true); + var index = blot.offset(_this2.scroll); + if (offset === 0) { + return index; + } else if (blot instanceof _parchment2.default.Container) { + return index + blot.length(); + } else { + return index + blot.index(node, offset); + } + }); + var start = Math.min.apply(Math, _toConsumableArray(indexes)), + end = Math.max.apply(Math, _toConsumableArray(indexes)); + end = Math.min(end, this.scroll.length() - 1); + return [new Range(start, end - start), range]; + } + }, { + key: 'hasFocus', + value: function hasFocus() { + return document.activeElement === this.root; + } + }, { + key: 'scrollIntoView', + value: function scrollIntoView() { + var range = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.lastRange; + + if (range == null) return; + var bounds = this.getBounds(range.index, range.length); + if (bounds == null) return; + if (this.root.offsetHeight < bounds.bottom) { + var _scroll$line = this.scroll.line(Math.min(range.index + range.length, this.scroll.length() - 1)), + _scroll$line2 = _slicedToArray(_scroll$line, 1), + line = _scroll$line2[0]; + + this.root.scrollTop = line.domNode.offsetTop + line.domNode.offsetHeight - this.root.offsetHeight; + } else if (bounds.top < 0) { + var _scroll$line3 = this.scroll.line(Math.min(range.index, this.scroll.length() - 1)), + _scroll$line4 = _slicedToArray(_scroll$line3, 1), + _line = _scroll$line4[0]; + + this.root.scrollTop = _line.domNode.offsetTop; + } + } + }, { + key: 'setNativeRange', + value: function setNativeRange(startNode, startOffset) { + var endNode = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : startNode; + var endOffset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : startOffset; + var force = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; + + debug.info('setNativeRange', startNode, startOffset, endNode, endOffset); + if (startNode != null && (this.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) { + return; + } + var selection = document.getSelection(); + if (selection == null) return; + if (startNode != null) { + if (!this.hasFocus()) this.root.focus(); + var native = (this.getNativeRange() || {}).native; + if (native == null || force || startNode !== native.startContainer || startOffset !== native.startOffset || endNode !== native.endContainer || endOffset !== native.endOffset) { + var range = document.createRange(); + range.setStart(startNode, startOffset); + range.setEnd(endNode, endOffset); + selection.removeAllRanges(); + selection.addRange(range); + } + } else { + selection.removeAllRanges(); + this.root.blur(); + document.body.focus(); // root.blur() not enough on IE11+Travis+SauceLabs (but not local VMs) + } + } + }, { + key: 'setRange', + value: function setRange(range) { + var _this3 = this; + + var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + var source = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _emitter4.default.sources.API; + + if (typeof force === 'string') { + source = force; + force = false; + } + debug.info('setRange', range); + if (range != null) { + (function () { + var indexes = range.collapsed ? [range.index] : [range.index, range.index + range.length]; + var args = []; + var scrollLength = _this3.scroll.length(); + indexes.forEach(function (index, i) { + index = Math.min(scrollLength - 1, index); + var node = void 0, + _scroll$leaf5 = _this3.scroll.leaf(index), + _scroll$leaf6 = _slicedToArray(_scroll$leaf5, 2), + leaf = _scroll$leaf6[0], + offset = _scroll$leaf6[1]; + var _leaf$position5 = leaf.position(offset, i !== 0); + + var _leaf$position6 = _slicedToArray(_leaf$position5, 2); + + node = _leaf$position6[0]; + offset = _leaf$position6[1]; + + args.push(node, offset); + }); + if (args.length < 2) { + args = args.concat(args); + } + _this3.setNativeRange.apply(_this3, _toConsumableArray(args).concat([force])); + })(); + } else { + this.setNativeRange(null); + } + this.update(source); + } + }, { + key: 'update', + value: function update() { + var source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _emitter4.default.sources.USER; + + var nativeRange = void 0, + oldRange = this.lastRange; + + var _getRange = this.getRange(); + + var _getRange2 = _slicedToArray(_getRange, 2); + + this.lastRange = _getRange2[0]; + nativeRange = _getRange2[1]; + + if (this.lastRange != null) { + this.savedRange = this.lastRange; + } + if (!(0, _deepEqual2.default)(oldRange, this.lastRange)) { + var _emitter; + + if (!this.composing && nativeRange != null && nativeRange.native.collapsed && nativeRange.start.node !== this.cursor.textNode) { + this.cursor.restore(); + } + var args = [_emitter4.default.events.SELECTION_CHANGE, (0, _clone2.default)(this.lastRange), (0, _clone2.default)(oldRange), source]; + (_emitter = this.emitter).emit.apply(_emitter, [_emitter4.default.events.EDITOR_CHANGE].concat(args)); + if (source !== _emitter4.default.sources.SILENT) { + var _emitter2; + + (_emitter2 = this.emitter).emit.apply(_emitter2, args); + } + } + } + }]); + + return Selection; + }(); + + function contains(parent, descendant) { + try { + // Firefox inserts inaccessible nodes around video elements + descendant.parentNode; + } catch (e) { + return false; + } + // IE11 has bug with Text nodes + // https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect + if (descendant instanceof Text) { + descendant = descendant.parentNode; + } + return parent.contains(descendant); + } + + exports.Range = Range; + exports.default = Selection; + +/***/ }, +/* 41 */ +/***/ function(module, exports) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var Theme = function () { + function Theme(quill, options) { + _classCallCheck(this, Theme); + + this.quill = quill; + this.options = options; + this.modules = {}; + } + + _createClass(Theme, [{ + key: 'init', + value: function init() { + var _this = this; + + Object.keys(this.options.modules).forEach(function (name) { + if (_this.modules[name] == null) { + _this.addModule(name); + } + }); + } + }, { + key: 'addModule', + value: function addModule(name) { + var moduleClass = this.quill.constructor.import('modules/' + name); + this.modules[name] = new moduleClass(this.quill, this.options.modules[name] || {}); + return this.modules[name]; + } + }]); + + return Theme; + }(); + + Theme.DEFAULTS = { + modules: {} + }; + Theme.themes = { + 'default': Theme + }; + + exports.default = Theme; + +/***/ }, +/* 42 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _block = __webpack_require__(29); + + var _block2 = _interopRequireDefault(_block); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Container = function (_Parchment$Container) { + _inherits(Container, _Parchment$Container); + + function Container() { + _classCallCheck(this, Container); + + return _possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).apply(this, arguments)); + } + + return Container; + }(_parchment2.default.Container); + + Container.allowedChildren = [_block2.default, _block.BlockEmbed, Container]; + + exports.default = Container; + +/***/ }, +/* 43 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _emitter = __webpack_require__(35); + + var _emitter2 = _interopRequireDefault(_emitter); + + var _block = __webpack_require__(29); + + var _block2 = _interopRequireDefault(_block); + + var _break = __webpack_require__(30); + + var _break2 = _interopRequireDefault(_break); + + var _container = __webpack_require__(42); + + var _container2 = _interopRequireDefault(_container); + + var _code = __webpack_require__(28); + + var _code2 = _interopRequireDefault(_code); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + function isLine(blot) { + return blot instanceof _block2.default || blot instanceof _block.BlockEmbed; + } + + var Scroll = function (_Parchment$Scroll) { + _inherits(Scroll, _Parchment$Scroll); + + function Scroll(domNode, config) { + _classCallCheck(this, Scroll); + + var _this = _possibleConstructorReturn(this, (Scroll.__proto__ || Object.getPrototypeOf(Scroll)).call(this, domNode)); + + _this.emitter = config.emitter; + if (Array.isArray(config.whitelist)) { + _this.whitelist = config.whitelist.reduce(function (whitelist, format) { + whitelist[format] = true; + return whitelist; + }, {}); + } + _this.optimize(); + _this.enable(); + return _this; + } + + _createClass(Scroll, [{ + key: 'deleteAt', + value: function deleteAt(index, length) { + var _line = this.line(index), + _line2 = _slicedToArray(_line, 2), + first = _line2[0], + offset = _line2[1]; + + var _line3 = this.line(index + length), + _line4 = _slicedToArray(_line3, 1), + last = _line4[0]; + + _get(Scroll.prototype.__proto__ || Object.getPrototypeOf(Scroll.prototype), 'deleteAt', this).call(this, index, length); + if (last != null && first !== last && offset > 0 && !(first instanceof _block.BlockEmbed) && !(last instanceof _block.BlockEmbed)) { + if (last instanceof _code2.default) { + last.deleteAt(last.length() - 1, 1); + } + var ref = last.children.head instanceof _break2.default ? null : last.children.head; + first.moveChildren(last, ref); + first.remove(); + } + this.optimize(); + } + }, { + key: 'enable', + value: function enable() { + var enabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + + this.domNode.setAttribute('contenteditable', enabled); + } + }, { + key: 'formatAt', + value: function formatAt(index, length, format, value) { + if (this.whitelist != null && !this.whitelist[format]) return; + _get(Scroll.prototype.__proto__ || Object.getPrototypeOf(Scroll.prototype), 'formatAt', this).call(this, index, length, format, value); + this.optimize(); + } + }, { + key: 'insertAt', + value: function insertAt(index, value, def) { + if (def != null && this.whitelist != null && !this.whitelist[value]) return; + if (index >= this.length()) { + if (def == null || _parchment2.default.query(value, _parchment2.default.Scope.BLOCK) == null) { + var blot = _parchment2.default.create(this.statics.defaultChild); + this.appendChild(blot); + if (def == null && value.endsWith('\n')) { + value = value.slice(0, -1); + } + blot.insertAt(0, value, def); + } else { + var embed = _parchment2.default.create(value, def); + this.appendChild(embed); + } + } else { + _get(Scroll.prototype.__proto__ || Object.getPrototypeOf(Scroll.prototype), 'insertAt', this).call(this, index, value, def); + } + this.optimize(); + } + }, { + key: 'insertBefore', + value: function insertBefore(blot, ref) { + if (blot.statics.scope === _parchment2.default.Scope.INLINE_BLOT) { + var wrapper = _parchment2.default.create(this.statics.defaultChild); + wrapper.appendChild(blot); + blot = wrapper; + } + _get(Scroll.prototype.__proto__ || Object.getPrototypeOf(Scroll.prototype), 'insertBefore', this).call(this, blot, ref); + } + }, { + key: 'leaf', + value: function leaf(index) { + return this.path(index).pop() || [null, -1]; + } + }, { + key: 'line', + value: function line(index) { + if (index === this.length()) { + return this.line(index - 1); + } + return this.descendant(isLine, index); + } + }, { + key: 'lines', + value: function lines() { + var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + var length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Number.MAX_VALUE; + + var getLines = function getLines(blot, index, length) { + var lines = [], + lengthLeft = length; + blot.children.forEachAt(index, length, function (child, index, length) { + if (isLine(child)) { + lines.push(child); + } else if (child instanceof _parchment2.default.Container) { + lines = lines.concat(getLines(child, index, lengthLeft)); + } + lengthLeft -= length; + }); + return lines; + }; + return getLines(this, index, length); + } + }, { + key: 'optimize', + value: function optimize() { + var mutations = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + + if (this.batch === true) return; + _get(Scroll.prototype.__proto__ || Object.getPrototypeOf(Scroll.prototype), 'optimize', this).call(this, mutations); + if (mutations.length > 0) { + this.emitter.emit(_emitter2.default.events.SCROLL_OPTIMIZE, mutations); + } + } + }, { + key: 'path', + value: function path(index) { + return _get(Scroll.prototype.__proto__ || Object.getPrototypeOf(Scroll.prototype), 'path', this).call(this, index).slice(1); // Exclude self + } + }, { + key: 'update', + value: function update(mutations) { + if (this.batch === true) return; + var source = _emitter2.default.sources.USER; + if (typeof mutations === 'string') { + source = mutations; + } + if (!Array.isArray(mutations)) { + mutations = this.observer.takeRecords(); + } + if (mutations.length > 0) { + this.emitter.emit(_emitter2.default.events.SCROLL_BEFORE_UPDATE, source, mutations); + } + _get(Scroll.prototype.__proto__ || Object.getPrototypeOf(Scroll.prototype), 'update', this).call(this, mutations.concat([])); // pass copy + if (mutations.length > 0) { + this.emitter.emit(_emitter2.default.events.SCROLL_UPDATE, source, mutations); + } + } + }]); + + return Scroll; + }(_parchment2.default.Scroll); + + Scroll.blotName = 'scroll'; + Scroll.className = 'ql-editor'; + Scroll.tagName = 'DIV'; + Scroll.defaultChild = 'block'; + Scroll.allowedChildren = [_block2.default, _block.BlockEmbed, _container2.default]; + + exports.default = Scroll; + +/***/ }, +/* 44 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.matchText = exports.matchSpacing = exports.matchNewline = exports.matchBlot = exports.matchAttributor = exports.default = undefined; + + var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _quillDelta = __webpack_require__(20); + + var _quillDelta2 = _interopRequireDefault(_quillDelta); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _quill = __webpack_require__(18); + + var _quill2 = _interopRequireDefault(_quill); + + var _logger = __webpack_require__(37); + + var _logger2 = _interopRequireDefault(_logger); + + var _module = __webpack_require__(39); + + var _module2 = _interopRequireDefault(_module); + + var _align = __webpack_require__(45); + + var _background = __webpack_require__(46); + + var _color = __webpack_require__(47); + + var _direction = __webpack_require__(48); + + var _font = __webpack_require__(49); + + var _size = __webpack_require__(50); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + + function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var debug = (0, _logger2.default)('quill:clipboard'); + + var DOM_KEY = '__ql-matcher'; + + var CLIPBOARD_CONFIG = [[Node.TEXT_NODE, matchText], ['br', matchBreak], [Node.ELEMENT_NODE, matchNewline], [Node.ELEMENT_NODE, matchBlot], [Node.ELEMENT_NODE, matchSpacing], [Node.ELEMENT_NODE, matchAttributor], [Node.ELEMENT_NODE, matchStyles], ['b', matchAlias.bind(matchAlias, 'bold')], ['i', matchAlias.bind(matchAlias, 'italic')], ['style', matchIgnore]]; + + var ATTRIBUTE_ATTRIBUTORS = [_align.AlignAttribute, _direction.DirectionAttribute].reduce(function (memo, attr) { + memo[attr.keyName] = attr; + return memo; + }, {}); + + var STYLE_ATTRIBUTORS = [_align.AlignStyle, _background.BackgroundStyle, _color.ColorStyle, _direction.DirectionStyle, _font.FontStyle, _size.SizeStyle].reduce(function (memo, attr) { + memo[attr.keyName] = attr; + return memo; + }, {}); + + var Clipboard = function (_Module) { + _inherits(Clipboard, _Module); + + function Clipboard(quill, options) { + _classCallCheck(this, Clipboard); + + var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this, quill, options)); + + _this.quill.root.addEventListener('paste', _this.onPaste.bind(_this)); + _this.container = _this.quill.addContainer('ql-clipboard'); + _this.container.setAttribute('contenteditable', true); + _this.container.setAttribute('tabindex', -1); + _this.matchers = []; + CLIPBOARD_CONFIG.concat(_this.options.matchers).forEach(function (pair) { + _this.addMatcher.apply(_this, _toConsumableArray(pair)); + }); + return _this; + } + + _createClass(Clipboard, [{ + key: 'addMatcher', + value: function addMatcher(selector, matcher) { + this.matchers.push([selector, matcher]); + } + }, { + key: 'convert', + value: function convert(html) { + if (typeof html === 'string') { + this.container.innerHTML = html; + } + + var _prepareMatching = this.prepareMatching(), + _prepareMatching2 = _slicedToArray(_prepareMatching, 2), + elementMatchers = _prepareMatching2[0], + textMatchers = _prepareMatching2[1]; + + var delta = traverse(this.container, elementMatchers, textMatchers); + // Remove trailing newline + if (deltaEndsWith(delta, '\n') && delta.ops[delta.ops.length - 1].attributes == null) { + delta = delta.compose(new _quillDelta2.default().retain(delta.length() - 1).delete(1)); + } + debug.log('convert', this.container.innerHTML, delta); + this.container.innerHTML = ''; + return delta; + } + }, { + key: 'dangerouslyPasteHTML', + value: function dangerouslyPasteHTML(index, html) { + var source = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _quill2.default.sources.API; + + if (typeof index === 'string') { + return this.quill.setContents(this.convert(index), html); + } else { + var paste = this.convert(html); + return this.quill.updateContents(new _quillDelta2.default().retain(index).concat(paste), source); + } + } + }, { + key: 'onPaste', + value: function onPaste(e) { + var _this2 = this; + + if (e.defaultPrevented || !this.quill.isEnabled()) return; + var range = this.quill.getSelection(); + var delta = new _quillDelta2.default().retain(range.index); + var scrollTop = this.quill.scrollingContainer.scrollTop; + this.container.focus(); + setTimeout(function () { + _this2.quill.selection.update(_quill2.default.sources.SILENT); + delta = delta.concat(_this2.convert()).delete(range.length); + _this2.quill.updateContents(delta, _quill2.default.sources.USER); + // range.length contributes to delta.length() + _this2.quill.setSelection(delta.length() - range.length, _quill2.default.sources.SILENT); + _this2.quill.scrollingContainer.scrollTop = scrollTop; + _this2.quill.selection.scrollIntoView(); + }, 1); + } + }, { + key: 'prepareMatching', + value: function prepareMatching() { + var _this3 = this; + + var elementMatchers = [], + textMatchers = []; + this.matchers.forEach(function (pair) { + var _pair = _slicedToArray(pair, 2), + selector = _pair[0], + matcher = _pair[1]; + + switch (selector) { + case Node.TEXT_NODE: + textMatchers.push(matcher); + break; + case Node.ELEMENT_NODE: + elementMatchers.push(matcher); + break; + default: + [].forEach.call(_this3.container.querySelectorAll(selector), function (node) { + // TODO use weakmap + node[DOM_KEY] = node[DOM_KEY] || []; + node[DOM_KEY].push(matcher); + }); + break; + } + }); + return [elementMatchers, textMatchers]; + } + }]); + + return Clipboard; + }(_module2.default); + + Clipboard.DEFAULTS = { + matchers: [] + }; + + function computeStyle(node) { + if (node.nodeType !== Node.ELEMENT_NODE) return {}; + var DOM_KEY = '__ql-computed-style'; + return node[DOM_KEY] || (node[DOM_KEY] = window.getComputedStyle(node)); + } + + function deltaEndsWith(delta, text) { + var endText = ""; + for (var i = delta.ops.length - 1; i >= 0 && endText.length < text.length; --i) { + var op = delta.ops[i]; + if (typeof op.insert !== 'string') break; + endText = op.insert + endText; + } + return endText.slice(-1 * text.length) === text; + } + + function isLine(node) { + if (node.childNodes.length === 0) return false; // Exclude embed blocks + var style = computeStyle(node); + return ['block', 'list-item'].indexOf(style.display) > -1; + } + + function traverse(node, elementMatchers, textMatchers) { + // Post-order + if (node.nodeType === node.TEXT_NODE) { + return textMatchers.reduce(function (delta, matcher) { + return matcher(node, delta); + }, new _quillDelta2.default()); + } else if (node.nodeType === node.ELEMENT_NODE) { + return [].reduce.call(node.childNodes || [], function (delta, childNode) { + var childrenDelta = traverse(childNode, elementMatchers, textMatchers); + if (childNode.nodeType === node.ELEMENT_NODE) { + childrenDelta = elementMatchers.reduce(function (childrenDelta, matcher) { + return matcher(childNode, childrenDelta); + }, childrenDelta); + childrenDelta = (childNode[DOM_KEY] || []).reduce(function (childrenDelta, matcher) { + return matcher(childNode, childrenDelta); + }, childrenDelta); + } + return delta.concat(childrenDelta); + }, new _quillDelta2.default()); + } else { + return new _quillDelta2.default(); + } + } + + function matchAlias(format, node, delta) { + return delta.compose(new _quillDelta2.default().retain(delta.length(), _defineProperty({}, format, true))); + } + + function matchAttributor(node, delta) { + var attributes = _parchment2.default.Attributor.Attribute.keys(node); + var classes = _parchment2.default.Attributor.Class.keys(node); + var styles = _parchment2.default.Attributor.Style.keys(node); + var formats = {}; + attributes.concat(classes).concat(styles).forEach(function (name) { + var attr = _parchment2.default.query(name, _parchment2.default.Scope.ATTRIBUTE); + if (attr != null) { + formats[attr.attrName] = attr.value(node); + if (formats[attr.attrName]) return; + } + if (ATTRIBUTE_ATTRIBUTORS[name] != null) { + attr = ATTRIBUTE_ATTRIBUTORS[name]; + formats[attr.attrName] = attr.value(node) || undefined; + } + if (STYLE_ATTRIBUTORS[name] != null) { + attr = STYLE_ATTRIBUTORS[name]; + formats[attr.attrName] = attr.value(node) || undefined; + } + }); + if (Object.keys(formats).length > 0) { + delta = delta.compose(new _quillDelta2.default().retain(delta.length(), formats)); + } + return delta; + } + + function matchBlot(node, delta) { + var match = _parchment2.default.query(node); + if (match == null) return delta; + if (match.prototype instanceof _parchment2.default.Embed) { + var embed = {}; + var value = match.value(node); + if (value != null) { + embed[match.blotName] = value; + delta = new _quillDelta2.default().insert(embed, match.formats(node)); + } + } else if (typeof match.formats === 'function') { + var formats = _defineProperty({}, match.blotName, match.formats(node)); + delta = delta.compose(new _quillDelta2.default().retain(delta.length(), formats)); + } + return delta; + } + + function matchBreak(node, delta) { + if (!deltaEndsWith(delta, '\n')) { + delta.insert('\n'); + } + return delta; + } + + function matchIgnore() { + return new _quillDelta2.default(); + } + + function matchNewline(node, delta) { + if (isLine(node) && !deltaEndsWith(delta, '\n')) { + delta.insert('\n'); + } + return delta; + } + + function matchSpacing(node, delta) { + if (isLine(node) && node.nextElementSibling != null && !deltaEndsWith(delta, '\n\n')) { + var nodeHeight = node.offsetHeight + parseFloat(computeStyle(node).marginTop) + parseFloat(computeStyle(node).marginBottom); + if (node.nextElementSibling.offsetTop > node.offsetTop + nodeHeight * 1.5) { + delta.insert('\n'); + } + } + return delta; + } + + function matchStyles(node, delta) { + var formats = {}; + var style = node.style || {}; + if (style.fontStyle && computeStyle(node).fontStyle === 'italic') { + formats.italic = true; + } + if (style.fontWeight && computeStyle(node).fontWeight === 'bold') { + formats.bold = true; + } + if (Object.keys(formats).length > 0) { + delta = delta.compose(new _quillDelta2.default().retain(delta.length(), formats)); + } + if (parseFloat(style.textIndent || 0) > 0) { + // Could be 0.5in + delta = new _quillDelta2.default().insert('\t').concat(delta); + } + return delta; + } + + function matchText(node, delta) { + var text = node.data; + // Word represents empty line with   + if (node.parentNode.tagName === 'O:P') { + return delta.insert(text.trim()); + } + if (!computeStyle(node.parentNode).whiteSpace.startsWith('pre')) { + // eslint-disable-next-line func-style + var replacer = function replacer(collapse, match) { + match = match.replace(/[^\u00a0]/g, ''); // \u00a0 is nbsp; + return match.length < 1 && collapse ? ' ' : match; + }; + text = text.replace(/\r\n/g, ' ').replace(/\n/g, ' '); + text = text.replace(/\s\s+/g, replacer.bind(replacer, true)); // collapse whitespace + if (node.previousSibling == null && isLine(node.parentNode) || node.previousSibling != null && isLine(node.previousSibling)) { + text = text.replace(/^\s+/, replacer.bind(replacer, false)); + } + if (node.nextSibling == null && isLine(node.parentNode) || node.nextSibling != null && isLine(node.nextSibling)) { + text = text.replace(/\s+$/, replacer.bind(replacer, false)); + } + } + return delta.insert(text); + } + + exports.default = Clipboard; + exports.matchAttributor = matchAttributor; + exports.matchBlot = matchBlot; + exports.matchNewline = matchNewline; + exports.matchSpacing = matchSpacing; + exports.matchText = matchText; + +/***/ }, +/* 45 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.AlignStyle = exports.AlignClass = exports.AlignAttribute = undefined; + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + var config = { + scope: _parchment2.default.Scope.BLOCK, + whitelist: ['right', 'center', 'justify'] + }; + + var AlignAttribute = new _parchment2.default.Attributor.Attribute('align', 'align', config); + var AlignClass = new _parchment2.default.Attributor.Class('align', 'ql-align', config); + var AlignStyle = new _parchment2.default.Attributor.Style('align', 'text-align', config); + + exports.AlignAttribute = AlignAttribute; + exports.AlignClass = AlignClass; + exports.AlignStyle = AlignStyle; + +/***/ }, +/* 46 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.BackgroundStyle = exports.BackgroundClass = undefined; + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _color = __webpack_require__(47); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + var BackgroundClass = new _parchment2.default.Attributor.Class('background', 'ql-bg', { + scope: _parchment2.default.Scope.INLINE + }); + var BackgroundStyle = new _color.ColorAttributor('background', 'background-color', { + scope: _parchment2.default.Scope.INLINE + }); + + exports.BackgroundClass = BackgroundClass; + exports.BackgroundStyle = BackgroundStyle; + +/***/ }, +/* 47 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.ColorStyle = exports.ColorClass = exports.ColorAttributor = undefined; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var ColorAttributor = function (_Parchment$Attributor) { + _inherits(ColorAttributor, _Parchment$Attributor); + + function ColorAttributor() { + _classCallCheck(this, ColorAttributor); + + return _possibleConstructorReturn(this, (ColorAttributor.__proto__ || Object.getPrototypeOf(ColorAttributor)).apply(this, arguments)); + } + + _createClass(ColorAttributor, [{ + key: 'value', + value: function value(domNode) { + var value = _get(ColorAttributor.prototype.__proto__ || Object.getPrototypeOf(ColorAttributor.prototype), 'value', this).call(this, domNode); + if (!value.startsWith('rgb(')) return value; + value = value.replace(/^[^\d]+/, '').replace(/[^\d]+$/, ''); + return '#' + value.split(',').map(function (component) { + return ('00' + parseInt(component).toString(16)).slice(-2); + }).join(''); + } + }]); + + return ColorAttributor; + }(_parchment2.default.Attributor.Style); + + var ColorClass = new _parchment2.default.Attributor.Class('color', 'ql-color', { + scope: _parchment2.default.Scope.INLINE + }); + var ColorStyle = new ColorAttributor('color', 'color', { + scope: _parchment2.default.Scope.INLINE + }); + + exports.ColorAttributor = ColorAttributor; + exports.ColorClass = ColorClass; + exports.ColorStyle = ColorStyle; + +/***/ }, +/* 48 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.DirectionStyle = exports.DirectionClass = exports.DirectionAttribute = undefined; + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + var config = { + scope: _parchment2.default.Scope.BLOCK, + whitelist: ['rtl'] + }; + + var DirectionAttribute = new _parchment2.default.Attributor.Attribute('direction', 'dir', config); + var DirectionClass = new _parchment2.default.Attributor.Class('direction', 'ql-direction', config); + var DirectionStyle = new _parchment2.default.Attributor.Style('direction', 'direction', config); + + exports.DirectionAttribute = DirectionAttribute; + exports.DirectionClass = DirectionClass; + exports.DirectionStyle = DirectionStyle; + +/***/ }, +/* 49 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.FontClass = exports.FontStyle = undefined; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var config = { + scope: _parchment2.default.Scope.INLINE, + whitelist: ['serif', 'monospace'] + }; + + var FontClass = new _parchment2.default.Attributor.Class('font', 'ql-font', config); + + var FontStyleAttributor = function (_Parchment$Attributor) { + _inherits(FontStyleAttributor, _Parchment$Attributor); + + function FontStyleAttributor() { + _classCallCheck(this, FontStyleAttributor); + + return _possibleConstructorReturn(this, (FontStyleAttributor.__proto__ || Object.getPrototypeOf(FontStyleAttributor)).apply(this, arguments)); + } + + _createClass(FontStyleAttributor, [{ + key: 'value', + value: function value(node) { + return _get(FontStyleAttributor.prototype.__proto__ || Object.getPrototypeOf(FontStyleAttributor.prototype), 'value', this).call(this, node).replace(/["']/g, ''); + } + }]); + + return FontStyleAttributor; + }(_parchment2.default.Attributor.Style); + + var FontStyle = new FontStyleAttributor('font', 'font-family', config); + + exports.FontStyle = FontStyle; + exports.FontClass = FontClass; + +/***/ }, +/* 50 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.SizeStyle = exports.SizeClass = undefined; + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + var SizeClass = new _parchment2.default.Attributor.Class('size', 'ql-size', { + scope: _parchment2.default.Scope.INLINE, + whitelist: ['small', 'large', 'huge'] + }); + var SizeStyle = new _parchment2.default.Attributor.Style('size', 'font-size', { + scope: _parchment2.default.Scope.INLINE, + whitelist: ['10px', '18px', '32px'] + }); + + exports.SizeClass = SizeClass; + exports.SizeStyle = SizeStyle; + +/***/ }, +/* 51 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.getLastChangeIndex = exports.default = undefined; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _quill = __webpack_require__(18); + + var _quill2 = _interopRequireDefault(_quill); + + var _module = __webpack_require__(39); + + var _module2 = _interopRequireDefault(_module); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var History = function (_Module) { + _inherits(History, _Module); + + function History(quill, options) { + _classCallCheck(this, History); + + var _this = _possibleConstructorReturn(this, (History.__proto__ || Object.getPrototypeOf(History)).call(this, quill, options)); + + _this.lastRecorded = 0; + _this.ignoreChange = false; + _this.clear(); + _this.quill.on(_quill2.default.events.EDITOR_CHANGE, function (eventName, delta, oldDelta, source) { + if (eventName !== _quill2.default.events.TEXT_CHANGE || _this.ignoreChange) return; + if (!_this.options.userOnly || source === _quill2.default.sources.USER) { + _this.record(delta, oldDelta); + } else { + _this.transform(delta); + } + }); + _this.quill.keyboard.addBinding({ key: 'Z', shortKey: true }, _this.undo.bind(_this)); + _this.quill.keyboard.addBinding({ key: 'Z', shortKey: true, shiftKey: true }, _this.redo.bind(_this)); + if (/Win/i.test(navigator.platform)) { + _this.quill.keyboard.addBinding({ key: 'Y', shortKey: true }, _this.redo.bind(_this)); + } + return _this; + } + + _createClass(History, [{ + key: 'change', + value: function change(source, dest) { + if (this.stack[source].length === 0) return; + var delta = this.stack[source].pop(); + this.lastRecorded = 0; + this.ignoreChange = true; + this.quill.updateContents(delta[source], _quill2.default.sources.USER); + this.ignoreChange = false; + var index = getLastChangeIndex(delta[source]); + this.quill.setSelection(index); + this.quill.selection.scrollIntoView(); + this.stack[dest].push(delta); + } + }, { + key: 'clear', + value: function clear() { + this.stack = { undo: [], redo: [] }; + } + }, { + key: 'record', + value: function record(changeDelta, oldDelta) { + if (changeDelta.ops.length === 0) return; + this.stack.redo = []; + var undoDelta = this.quill.getContents().diff(oldDelta); + var timestamp = Date.now(); + if (this.lastRecorded + this.options.delay > timestamp && this.stack.undo.length > 0) { + var delta = this.stack.undo.pop(); + undoDelta = undoDelta.compose(delta.undo); + changeDelta = delta.redo.compose(changeDelta); + } else { + this.lastRecorded = timestamp; + } + this.stack.undo.push({ + redo: changeDelta, + undo: undoDelta + }); + if (this.stack.undo.length > this.options.maxStack) { + this.stack.undo.shift(); + } + } + }, { + key: 'redo', + value: function redo() { + this.change('redo', 'undo'); + } + }, { + key: 'transform', + value: function transform(delta) { + this.stack.undo.forEach(function (change) { + change.undo = delta.transform(change.undo, true); + change.redo = delta.transform(change.redo, true); + }); + this.stack.redo.forEach(function (change) { + change.undo = delta.transform(change.undo, true); + change.redo = delta.transform(change.redo, true); + }); + } + }, { + key: 'undo', + value: function undo() { + this.change('undo', 'redo'); + } + }]); + + return History; + }(_module2.default); + + History.DEFAULTS = { + delay: 1000, + maxStack: 100, + userOnly: false + }; + + function endsWithNewlineChange(delta) { + var lastOp = delta.ops[delta.ops.length - 1]; + if (lastOp == null) return false; + if (lastOp.insert != null) { + return typeof lastOp.insert === 'string' && lastOp.insert.endsWith('\n'); + } + if (lastOp.attributes != null) { + return Object.keys(lastOp.attributes).some(function (attr) { + return _parchment2.default.query(attr, _parchment2.default.Scope.BLOCK) != null; + }); + } + return false; + } + + function getLastChangeIndex(delta) { + var deleteLength = delta.reduce(function (length, op) { + length += op.delete || 0; + return length; + }, 0); + var changeIndex = delta.length() - deleteLength; + if (endsWithNewlineChange(delta)) { + changeIndex -= 1; + } + return changeIndex; + } + + exports.default = History; + exports.getLastChangeIndex = getLastChangeIndex; + +/***/ }, +/* 52 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _clone = __webpack_require__(38); + + var _clone2 = _interopRequireDefault(_clone); + + var _deepEqual = __webpack_require__(22); + + var _deepEqual2 = _interopRequireDefault(_deepEqual); + + var _extend = __webpack_require__(25); + + var _extend2 = _interopRequireDefault(_extend); + + var _op = __webpack_require__(26); + + var _op2 = _interopRequireDefault(_op); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _quill = __webpack_require__(18); + + var _quill2 = _interopRequireDefault(_quill); + + var _logger = __webpack_require__(37); + + var _logger2 = _interopRequireDefault(_logger); + + var _module = __webpack_require__(39); + + var _module2 = _interopRequireDefault(_module); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var debug = (0, _logger2.default)('quill:keyboard'); + + var SHORTKEY = /Mac/i.test(navigator.platform) ? 'metaKey' : 'ctrlKey'; + + var Keyboard = function (_Module) { + _inherits(Keyboard, _Module); + + _createClass(Keyboard, null, [{ + key: 'match', + value: function match(evt, binding) { + binding = normalize(binding); + if (!!binding.shortKey !== evt[SHORTKEY] && binding.shortKey !== null) return false; + if (['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].some(function (key) { + return key != SHORTKEY && !!binding[key] !== evt[key] && binding[key] !== null; + })) { + return false; + } + return binding.key === (evt.which || evt.keyCode); + } + }]); + + function Keyboard(quill, options) { + _classCallCheck(this, Keyboard); + + var _this = _possibleConstructorReturn(this, (Keyboard.__proto__ || Object.getPrototypeOf(Keyboard)).call(this, quill, options)); + + _this.bindings = {}; + Object.keys(_this.options.bindings).forEach(function (name) { + if (_this.options.bindings[name]) { + _this.addBinding(_this.options.bindings[name]); + } + }); + _this.addBinding({ key: Keyboard.keys.ENTER, shiftKey: null }, handleEnter); + _this.addBinding({ key: Keyboard.keys.ENTER, metaKey: null, ctrlKey: null, altKey: null }, function () {}); + if (/Gecko/i.test(navigator.userAgent)) { + // Need to handle delete and backspace for Firefox in the general case #1171 + _this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: true }, handleBackspace); + _this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: true }, handleDelete); + } else { + _this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: true, prefix: /^.?$/ }, handleBackspace); + _this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: true, suffix: /^.?$/ }, handleDelete); + } + _this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: false }, handleDeleteRange); + _this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: false }, handleDeleteRange); + _this.listen(); + return _this; + } + + _createClass(Keyboard, [{ + key: 'addBinding', + value: function addBinding(key) { + var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var handler = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + var binding = normalize(key); + if (binding == null || binding.key == null) { + return debug.warn('Attempted to add invalid keyboard binding', binding); + } + if (typeof context === 'function') { + context = { handler: context }; + } + if (typeof handler === 'function') { + handler = { handler: handler }; + } + binding = (0, _extend2.default)(binding, context, handler); + this.bindings[binding.key] = this.bindings[binding.key] || []; + this.bindings[binding.key].push(binding); + } + }, { + key: 'listen', + value: function listen() { + var _this2 = this; + + this.quill.root.addEventListener('keydown', function (evt) { + if (evt.defaultPrevented) return; + var which = evt.which || evt.keyCode; + var bindings = (_this2.bindings[which] || []).filter(function (binding) { + return Keyboard.match(evt, binding); + }); + if (bindings.length === 0) return; + var range = _this2.quill.getSelection(); + if (range == null || !_this2.quill.hasFocus()) return; + + var _quill$scroll$line = _this2.quill.scroll.line(range.index), + _quill$scroll$line2 = _slicedToArray(_quill$scroll$line, 2), + line = _quill$scroll$line2[0], + offset = _quill$scroll$line2[1]; + + var _quill$scroll$leaf = _this2.quill.scroll.leaf(range.index), + _quill$scroll$leaf2 = _slicedToArray(_quill$scroll$leaf, 2), + leafStart = _quill$scroll$leaf2[0], + offsetStart = _quill$scroll$leaf2[1]; + + var _ref = range.length === 0 ? [leafStart, offsetStart] : _this2.quill.scroll.leaf(range.index + range.length), + _ref2 = _slicedToArray(_ref, 2), + leafEnd = _ref2[0], + offsetEnd = _ref2[1]; + + var prefixText = leafStart instanceof _parchment2.default.Text ? leafStart.value().slice(0, offsetStart) : ''; + var suffixText = leafEnd instanceof _parchment2.default.Text ? leafEnd.value().slice(offsetEnd) : ''; + var curContext = { + collapsed: range.length === 0, + empty: range.length === 0 && line.length() <= 1, + format: _this2.quill.getFormat(range), + offset: offset, + prefix: prefixText, + suffix: suffixText + }; + var prevented = bindings.some(function (binding) { + if (binding.collapsed != null && binding.collapsed !== curContext.collapsed) return false; + if (binding.empty != null && binding.empty !== curContext.empty) return false; + if (binding.offset != null && binding.offset !== curContext.offset) return false; + if (Array.isArray(binding.format)) { + // any format is present + if (binding.format.every(function (name) { + return curContext.format[name] == null; + })) { + return false; + } + } else if (_typeof(binding.format) === 'object') { + // all formats must match + if (!Object.keys(binding.format).every(function (name) { + if (binding.format[name] === true) return curContext.format[name] != null; + if (binding.format[name] === false) return curContext.format[name] == null; + return (0, _deepEqual2.default)(binding.format[name], curContext.format[name]); + })) { + return false; + } + } + if (binding.prefix != null && !binding.prefix.test(curContext.prefix)) return false; + if (binding.suffix != null && !binding.suffix.test(curContext.suffix)) return false; + return binding.handler.call(_this2, range, curContext) !== true; + }); + if (prevented) { + evt.preventDefault(); + } + }); + } + }]); + + return Keyboard; + }(_module2.default); + + Keyboard.keys = { + BACKSPACE: 8, + TAB: 9, + ENTER: 13, + ESCAPE: 27, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + DELETE: 46 + }; + + Keyboard.DEFAULTS = { + bindings: { + 'bold': makeFormatHandler('bold'), + 'italic': makeFormatHandler('italic'), + 'underline': makeFormatHandler('underline'), + 'indent': { + // highlight tab or tab at beginning of list, indent or blockquote + key: Keyboard.keys.TAB, + format: ['blockquote', 'indent', 'list'], + handler: function handler(range, context) { + if (context.collapsed && context.offset !== 0) return true; + this.quill.format('indent', '+1', _quill2.default.sources.USER); + } + }, + 'outdent': { + key: Keyboard.keys.TAB, + shiftKey: true, + format: ['blockquote', 'indent', 'list'], + // highlight tab or tab at beginning of list, indent or blockquote + handler: function handler(range, context) { + if (context.collapsed && context.offset !== 0) return true; + this.quill.format('indent', '-1', _quill2.default.sources.USER); + } + }, + 'outdent backspace': { + key: Keyboard.keys.BACKSPACE, + collapsed: true, + format: ['blockquote', 'indent', 'list'], + offset: 0, + handler: function handler(range, context) { + if (context.format.indent != null) { + this.quill.format('indent', '-1', _quill2.default.sources.USER); + } else if (context.format.blockquote != null) { + this.quill.format('blockquote', false, _quill2.default.sources.USER); + } else if (context.format.list != null) { + this.quill.format('list', false, _quill2.default.sources.USER); + } + } + }, + 'indent code-block': makeCodeBlockHandler(true), + 'outdent code-block': makeCodeBlockHandler(false), + 'remove tab': { + key: Keyboard.keys.TAB, + shiftKey: true, + collapsed: true, + prefix: /\t$/, + handler: function handler(range) { + this.quill.deleteText(range.index - 1, 1, _quill2.default.sources.USER); + } + }, + 'tab': { + key: Keyboard.keys.TAB, + handler: function handler(range, context) { + if (!context.collapsed) { + this.quill.scroll.deleteAt(range.index, range.length); + } + this.quill.insertText(range.index, '\t', _quill2.default.sources.USER); + this.quill.setSelection(range.index + 1, _quill2.default.sources.SILENT); + } + }, + 'list empty enter': { + key: Keyboard.keys.ENTER, + collapsed: true, + format: ['list'], + empty: true, + handler: function handler(range, context) { + this.quill.format('list', false, _quill2.default.sources.USER); + if (context.format.indent) { + this.quill.format('indent', false, _quill2.default.sources.USER); + } + } + }, + 'checklist enter': { + key: Keyboard.keys.ENTER, + collapsed: true, + format: { list: 'checked' }, + handler: function handler(range) { + this.quill.scroll.insertAt(range.index, '\n'); + + var _quill$scroll$line3 = this.quill.scroll.line(range.index + 1), + _quill$scroll$line4 = _slicedToArray(_quill$scroll$line3, 1), + line = _quill$scroll$line4[0]; + + line.format('list', 'unchecked'); + this.quill.update(_quill2.default.sources.USER); + this.quill.setSelection(range.index + 1, _quill2.default.sources.SILENT); + this.quill.selection.scrollIntoView(); + } + }, + 'header enter': { + key: Keyboard.keys.ENTER, + collapsed: true, + format: ['header'], + suffix: /^$/, + handler: function handler(range) { + this.quill.scroll.insertAt(range.index, '\n'); + this.quill.formatText(range.index + 1, 1, 'header', false, _quill2.default.sources.USER); + this.quill.setSelection(range.index + 1, _quill2.default.sources.SILENT); + this.quill.selection.scrollIntoView(); + } + }, + 'list autofill': { + key: ' ', + collapsed: true, + format: { list: false }, + prefix: /^(1\.|-)$/, + handler: function handler(range, context) { + var length = context.prefix.length; + this.quill.scroll.deleteAt(range.index - length, length); + this.quill.formatLine(range.index - length, 1, 'list', length === 1 ? 'bullet' : 'ordered', _quill2.default.sources.USER); + this.quill.setSelection(range.index - length, _quill2.default.sources.SILENT); + } + } + } + }; + + function handleBackspace(range, context) { + if (range.index === 0) return; + + var _quill$scroll$line5 = this.quill.scroll.line(range.index), + _quill$scroll$line6 = _slicedToArray(_quill$scroll$line5, 1), + line = _quill$scroll$line6[0]; + + var formats = {}; + if (context.offset === 0) { + var curFormats = line.formats(); + var prevFormats = this.quill.getFormat(range.index - 1, 1); + formats = _op2.default.attributes.diff(curFormats, prevFormats) || {}; + } + this.quill.deleteText(range.index - 1, 1, _quill2.default.sources.USER); + if (Object.keys(formats).length > 0) { + this.quill.formatLine(range.index - 1, 1, formats, _quill2.default.sources.USER); + } + this.quill.selection.scrollIntoView(); + } + + function handleDelete(range) { + if (range.index >= this.quill.getLength() - 1) return; + this.quill.deleteText(range.index, 1, _quill2.default.sources.USER); + } + + function handleDeleteRange(range) { + this.quill.deleteText(range, _quill2.default.sources.USER); + this.quill.setSelection(range.index, _quill2.default.sources.SILENT); + this.quill.selection.scrollIntoView(); + } + + function handleEnter(range, context) { + var _this3 = this; + + if (range.length > 0) { + this.quill.scroll.deleteAt(range.index, range.length); // So we do not trigger text-change + } + var lineFormats = Object.keys(context.format).reduce(function (lineFormats, format) { + if (_parchment2.default.query(format, _parchment2.default.Scope.BLOCK) && !Array.isArray(context.format[format])) { + lineFormats[format] = context.format[format]; + } + return lineFormats; + }, {}); + this.quill.insertText(range.index, '\n', lineFormats, _quill2.default.sources.USER); + this.quill.selection.scrollIntoView(); + Object.keys(context.format).forEach(function (name) { + if (lineFormats[name] != null) return; + if (Array.isArray(context.format[name])) return; + if (name === 'link') return; + _this3.quill.format(name, context.format[name], _quill2.default.sources.USER); + }); + } + + function makeCodeBlockHandler(indent) { + return { + key: Keyboard.keys.TAB, + shiftKey: !indent, + format: { 'code-block': true }, + handler: function handler(range) { + var CodeBlock = _parchment2.default.query('code-block'); + var index = range.index, + length = range.length; + + var _quill$scroll$descend = this.quill.scroll.descendant(CodeBlock, index), + _quill$scroll$descend2 = _slicedToArray(_quill$scroll$descend, 2), + block = _quill$scroll$descend2[0], + offset = _quill$scroll$descend2[1]; + + if (block == null) return; + var scrollOffset = this.quill.scroll.offset(block); + var start = block.newlineIndex(offset, true) + 1; + var end = block.newlineIndex(scrollOffset + offset + length); + var lines = block.domNode.textContent.slice(start, end).split('\n'); + offset = 0; + lines.forEach(function (line, i) { + if (indent) { + block.insertAt(start + offset, CodeBlock.TAB); + offset += CodeBlock.TAB.length; + if (i === 0) { + index += CodeBlock.TAB.length; + } else { + length += CodeBlock.TAB.length; + } + } else if (line.startsWith(CodeBlock.TAB)) { + block.deleteAt(start + offset, CodeBlock.TAB.length); + offset -= CodeBlock.TAB.length; + if (i === 0) { + index -= CodeBlock.TAB.length; + } else { + length -= CodeBlock.TAB.length; + } + } + offset += line.length + 1; + }); + this.quill.update(_quill2.default.sources.USER); + this.quill.setSelection(index, length, _quill2.default.sources.SILENT); + } + }; + } + + function makeFormatHandler(format) { + return { + key: format[0].toUpperCase(), + shortKey: true, + handler: function handler(range, context) { + this.quill.format(format, !context.format[format], _quill2.default.sources.USER); + } + }; + } + + function normalize(binding) { + if (typeof binding === 'string' || typeof binding === 'number') { + return normalize({ key: binding }); + } + if ((typeof binding === 'undefined' ? 'undefined' : _typeof(binding)) === 'object') { + binding = (0, _clone2.default)(binding, false); + } + if (typeof binding.key === 'string') { + if (Keyboard.keys[binding.key.toUpperCase()] != null) { + binding.key = Keyboard.keys[binding.key.toUpperCase()]; + } else if (binding.key.length === 1) { + binding.key = binding.key.toUpperCase().charCodeAt(0); + } else { + return null; + } + } + return binding; + } + + exports.default = Keyboard; + +/***/ }, +/* 53 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _core = __webpack_require__(1); + + var _core2 = _interopRequireDefault(_core); + + var _align = __webpack_require__(45); + + var _direction = __webpack_require__(48); + + var _indent = __webpack_require__(54); + + var _blockquote = __webpack_require__(55); + + var _blockquote2 = _interopRequireDefault(_blockquote); + + var _header = __webpack_require__(56); + + var _header2 = _interopRequireDefault(_header); + + var _list = __webpack_require__(57); + + var _list2 = _interopRequireDefault(_list); + + var _background = __webpack_require__(46); + + var _color = __webpack_require__(47); + + var _font = __webpack_require__(49); + + var _size = __webpack_require__(50); + + var _bold = __webpack_require__(58); + + var _bold2 = _interopRequireDefault(_bold); + + var _italic = __webpack_require__(59); + + var _italic2 = _interopRequireDefault(_italic); + + var _link = __webpack_require__(60); + + var _link2 = _interopRequireDefault(_link); + + var _script = __webpack_require__(61); + + var _script2 = _interopRequireDefault(_script); + + var _strike = __webpack_require__(62); + + var _strike2 = _interopRequireDefault(_strike); + + var _underline = __webpack_require__(63); + + var _underline2 = _interopRequireDefault(_underline); + + var _image = __webpack_require__(64); + + var _image2 = _interopRequireDefault(_image); + + var _video = __webpack_require__(65); + + var _video2 = _interopRequireDefault(_video); + + var _code = __webpack_require__(28); + + var _code2 = _interopRequireDefault(_code); + + var _formula = __webpack_require__(66); + + var _formula2 = _interopRequireDefault(_formula); + + var _syntax = __webpack_require__(67); + + var _syntax2 = _interopRequireDefault(_syntax); + + var _toolbar = __webpack_require__(68); + + var _toolbar2 = _interopRequireDefault(_toolbar); + + var _icons = __webpack_require__(69); + + var _icons2 = _interopRequireDefault(_icons); + + var _picker = __webpack_require__(102); + + var _picker2 = _interopRequireDefault(_picker); + + var _colorPicker = __webpack_require__(104); + + var _colorPicker2 = _interopRequireDefault(_colorPicker); + + var _iconPicker = __webpack_require__(105); + + var _iconPicker2 = _interopRequireDefault(_iconPicker); + + var _tooltip = __webpack_require__(106); + + var _tooltip2 = _interopRequireDefault(_tooltip); + + var _bubble = __webpack_require__(107); + + var _bubble2 = _interopRequireDefault(_bubble); + + var _snow = __webpack_require__(109); + + var _snow2 = _interopRequireDefault(_snow); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + _core2.default.register({ + 'attributors/attribute/direction': _direction.DirectionAttribute, + + 'attributors/class/align': _align.AlignClass, + 'attributors/class/background': _background.BackgroundClass, + 'attributors/class/color': _color.ColorClass, + 'attributors/class/direction': _direction.DirectionClass, + 'attributors/class/font': _font.FontClass, + 'attributors/class/size': _size.SizeClass, + + 'attributors/style/align': _align.AlignStyle, + 'attributors/style/background': _background.BackgroundStyle, + 'attributors/style/color': _color.ColorStyle, + 'attributors/style/direction': _direction.DirectionStyle, + 'attributors/style/font': _font.FontStyle, + 'attributors/style/size': _size.SizeStyle + }, true); + + _core2.default.register({ + 'formats/align': _align.AlignClass, + 'formats/direction': _direction.DirectionClass, + 'formats/indent': _indent.IndentClass, + + 'formats/background': _background.BackgroundStyle, + 'formats/color': _color.ColorStyle, + 'formats/font': _font.FontClass, + 'formats/size': _size.SizeClass, + + 'formats/blockquote': _blockquote2.default, + 'formats/code-block': _code2.default, + 'formats/header': _header2.default, + 'formats/list': _list2.default, + + 'formats/bold': _bold2.default, + 'formats/code': _code.Code, + 'formats/italic': _italic2.default, + 'formats/link': _link2.default, + 'formats/script': _script2.default, + 'formats/strike': _strike2.default, + 'formats/underline': _underline2.default, + + 'formats/image': _image2.default, + 'formats/video': _video2.default, + + 'formats/list/item': _list.ListItem, + + 'modules/formula': _formula2.default, + 'modules/syntax': _syntax2.default, + 'modules/toolbar': _toolbar2.default, + + 'themes/bubble': _bubble2.default, + 'themes/snow': _snow2.default, + + 'ui/icons': _icons2.default, + 'ui/picker': _picker2.default, + 'ui/icon-picker': _iconPicker2.default, + 'ui/color-picker': _colorPicker2.default, + 'ui/tooltip': _tooltip2.default + }, true); + + module.exports = _core2.default; + +/***/ }, +/* 54 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.IndentClass = undefined; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var IdentAttributor = function (_Parchment$Attributor) { + _inherits(IdentAttributor, _Parchment$Attributor); + + function IdentAttributor() { + _classCallCheck(this, IdentAttributor); + + return _possibleConstructorReturn(this, (IdentAttributor.__proto__ || Object.getPrototypeOf(IdentAttributor)).apply(this, arguments)); + } + + _createClass(IdentAttributor, [{ + key: 'add', + value: function add(node, value) { + if (value === '+1' || value === '-1') { + var indent = this.value(node) || 0; + value = value === '+1' ? indent + 1 : indent - 1; + } + if (value === 0) { + this.remove(node); + return true; + } else { + return _get(IdentAttributor.prototype.__proto__ || Object.getPrototypeOf(IdentAttributor.prototype), 'add', this).call(this, node, value); + } + } + }, { + key: 'canAdd', + value: function canAdd(node, value) { + return _get(IdentAttributor.prototype.__proto__ || Object.getPrototypeOf(IdentAttributor.prototype), 'canAdd', this).call(this, node, value) || _get(IdentAttributor.prototype.__proto__ || Object.getPrototypeOf(IdentAttributor.prototype), 'canAdd', this).call(this, node, parseInt(value)); + } + }, { + key: 'value', + value: function value(node) { + return parseInt(_get(IdentAttributor.prototype.__proto__ || Object.getPrototypeOf(IdentAttributor.prototype), 'value', this).call(this, node)) || undefined; // Don't return NaN + } + }]); + + return IdentAttributor; + }(_parchment2.default.Attributor.Class); + + var IndentClass = new IdentAttributor('indent', 'ql-indent', { + scope: _parchment2.default.Scope.BLOCK, + whitelist: [1, 2, 3, 4, 5, 6, 7, 8] + }); + + exports.IndentClass = IndentClass; + +/***/ }, +/* 55 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _block = __webpack_require__(29); + + var _block2 = _interopRequireDefault(_block); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Blockquote = function (_Block) { + _inherits(Blockquote, _Block); + + function Blockquote() { + _classCallCheck(this, Blockquote); + + return _possibleConstructorReturn(this, (Blockquote.__proto__ || Object.getPrototypeOf(Blockquote)).apply(this, arguments)); + } + + return Blockquote; + }(_block2.default); + + Blockquote.blotName = 'blockquote'; + Blockquote.tagName = 'blockquote'; + + exports.default = Blockquote; + +/***/ }, +/* 56 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _block = __webpack_require__(29); + + var _block2 = _interopRequireDefault(_block); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Header = function (_Block) { + _inherits(Header, _Block); + + function Header() { + _classCallCheck(this, Header); + + return _possibleConstructorReturn(this, (Header.__proto__ || Object.getPrototypeOf(Header)).apply(this, arguments)); + } + + _createClass(Header, null, [{ + key: 'formats', + value: function formats(domNode) { + return this.tagName.indexOf(domNode.tagName) + 1; + } + }]); + + return Header; + }(_block2.default); + + Header.blotName = 'header'; + Header.tagName = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6']; + + exports.default = Header; + +/***/ }, +/* 57 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.default = exports.ListItem = undefined; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _block = __webpack_require__(29); + + var _block2 = _interopRequireDefault(_block); + + var _container = __webpack_require__(42); + + var _container2 = _interopRequireDefault(_container); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var ListItem = function (_Block) { + _inherits(ListItem, _Block); + + function ListItem() { + _classCallCheck(this, ListItem); + + return _possibleConstructorReturn(this, (ListItem.__proto__ || Object.getPrototypeOf(ListItem)).apply(this, arguments)); + } + + _createClass(ListItem, [{ + key: 'format', + value: function format(name, value) { + if (name === List.blotName && !value) { + this.replaceWith(_parchment2.default.create(this.statics.scope)); + } else { + _get(ListItem.prototype.__proto__ || Object.getPrototypeOf(ListItem.prototype), 'format', this).call(this, name, value); + } + } + }, { + key: 'remove', + value: function remove() { + if (this.prev == null && this.next == null) { + this.parent.remove(); + } else { + _get(ListItem.prototype.__proto__ || Object.getPrototypeOf(ListItem.prototype), 'remove', this).call(this); + } + } + }, { + key: 'replaceWith', + value: function replaceWith(name, value) { + this.parent.isolate(this.offset(this.parent), this.length()); + if (name === this.parent.statics.blotName) { + this.parent.replaceWith(name, value); + return this; + } else { + this.parent.unwrap(); + return _get(ListItem.prototype.__proto__ || Object.getPrototypeOf(ListItem.prototype), 'replaceWith', this).call(this, name, value); + } + } + }], [{ + key: 'formats', + value: function formats(domNode) { + return domNode.tagName === this.tagName ? undefined : _get(ListItem.__proto__ || Object.getPrototypeOf(ListItem), 'formats', this).call(this, domNode); + } + }]); + + return ListItem; + }(_block2.default); + + ListItem.blotName = 'list-item'; + ListItem.tagName = 'LI'; + + var List = function (_Container) { + _inherits(List, _Container); + + function List() { + _classCallCheck(this, List); + + return _possibleConstructorReturn(this, (List.__proto__ || Object.getPrototypeOf(List)).apply(this, arguments)); + } + + _createClass(List, [{ + key: 'format', + value: function format(name, value) { + if (this.children.length > 0) { + this.children.tail.format(name, value); + } + } + }, { + key: 'formats', + value: function formats() { + // We don't inherit from FormatBlot + return _defineProperty({}, this.statics.blotName, this.statics.formats(this.domNode)); + } + }, { + key: 'insertBefore', + value: function insertBefore(blot, ref) { + if (blot instanceof ListItem) { + _get(List.prototype.__proto__ || Object.getPrototypeOf(List.prototype), 'insertBefore', this).call(this, blot, ref); + } else { + var index = ref == null ? this.length() : ref.offset(this); + var after = this.split(index); + after.parent.insertBefore(blot, after); + } + } + }, { + key: 'optimize', + value: function optimize() { + _get(List.prototype.__proto__ || Object.getPrototypeOf(List.prototype), 'optimize', this).call(this); + var next = this.next; + if (next != null && next.prev === this && next.statics.blotName === this.statics.blotName && next.domNode.tagName === this.domNode.tagName && next.domNode.getAttribute('data-checked') === this.domNode.getAttribute('data-checked')) { + next.moveChildren(this); + next.remove(); + } + } + }, { + key: 'replace', + value: function replace(target) { + if (target.statics.blotName !== this.statics.blotName) { + var item = _parchment2.default.create(this.statics.defaultChild); + target.moveChildren(item); + this.appendChild(item); + } + _get(List.prototype.__proto__ || Object.getPrototypeOf(List.prototype), 'replace', this).call(this, target); + } + }], [{ + key: 'create', + value: function create(value) { + var tagName = value === 'ordered' ? 'OL' : 'UL'; + var node = _get(List.__proto__ || Object.getPrototypeOf(List), 'create', this).call(this, tagName); + if (value === 'checked' || value === 'unchecked') { + node.setAttribute('data-checked', value === 'checked'); + } + return node; + } + }, { + key: 'formats', + value: function formats(domNode) { + if (domNode.tagName === 'OL') return 'ordered'; + if (domNode.tagName === 'UL') { + if (domNode.hasAttribute('data-checked')) { + return domNode.getAttribute('data-checked') === 'true' ? 'checked' : 'unchecked'; + } else { + return 'bullet'; + } + } + return undefined; + } + }]); + + return List; + }(_container2.default); + + List.blotName = 'list'; + List.scope = _parchment2.default.Scope.BLOCK_BLOT; + List.tagName = ['OL', 'UL']; + List.defaultChild = 'list-item'; + List.allowedChildren = [ListItem]; + + exports.ListItem = ListItem; + exports.default = List; + +/***/ }, +/* 58 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _inline = __webpack_require__(32); + + var _inline2 = _interopRequireDefault(_inline); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Bold = function (_Inline) { + _inherits(Bold, _Inline); + + function Bold() { + _classCallCheck(this, Bold); + + return _possibleConstructorReturn(this, (Bold.__proto__ || Object.getPrototypeOf(Bold)).apply(this, arguments)); + } + + _createClass(Bold, [{ + key: 'optimize', + value: function optimize() { + _get(Bold.prototype.__proto__ || Object.getPrototypeOf(Bold.prototype), 'optimize', this).call(this); + if (this.domNode.tagName !== this.statics.tagName[0]) { + this.replaceWith(this.statics.blotName); + } + } + }], [{ + key: 'create', + value: function create() { + return _get(Bold.__proto__ || Object.getPrototypeOf(Bold), 'create', this).call(this); + } + }, { + key: 'formats', + value: function formats() { + return true; + } + }]); + + return Bold; + }(_inline2.default); + + Bold.blotName = 'bold'; + Bold.tagName = ['STRONG', 'B']; + + exports.default = Bold; + +/***/ }, +/* 59 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _bold = __webpack_require__(58); + + var _bold2 = _interopRequireDefault(_bold); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Italic = function (_Bold) { + _inherits(Italic, _Bold); + + function Italic() { + _classCallCheck(this, Italic); + + return _possibleConstructorReturn(this, (Italic.__proto__ || Object.getPrototypeOf(Italic)).apply(this, arguments)); + } + + return Italic; + }(_bold2.default); + + Italic.blotName = 'italic'; + Italic.tagName = ['EM', 'I']; + + exports.default = Italic; + +/***/ }, +/* 60 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.sanitize = exports.default = undefined; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _inline = __webpack_require__(32); + + var _inline2 = _interopRequireDefault(_inline); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Link = function (_Inline) { + _inherits(Link, _Inline); + + function Link() { + _classCallCheck(this, Link); + + return _possibleConstructorReturn(this, (Link.__proto__ || Object.getPrototypeOf(Link)).apply(this, arguments)); + } + + _createClass(Link, [{ + key: 'format', + value: function format(name, value) { + if (name !== this.statics.blotName || !value) return _get(Link.prototype.__proto__ || Object.getPrototypeOf(Link.prototype), 'format', this).call(this, name, value); + value = this.constructor.sanitize(value); + this.domNode.setAttribute('href', value); + } + }], [{ + key: 'create', + value: function create(value) { + var node = _get(Link.__proto__ || Object.getPrototypeOf(Link), 'create', this).call(this, value); + value = this.sanitize(value); + node.setAttribute('href', value); + node.setAttribute('target', '_blank'); + return node; + } + }, { + key: 'formats', + value: function formats(domNode) { + return domNode.getAttribute('href'); + } + }, { + key: 'sanitize', + value: function sanitize(url) { + return _sanitize(url, ['http', 'https', 'mailto']) ? url : this.SANITIZED_URL; + } + }]); + + return Link; + }(_inline2.default); + + Link.blotName = 'link'; + Link.tagName = 'A'; + Link.SANITIZED_URL = 'about:blank'; + + function _sanitize(url, protocols) { + var anchor = document.createElement('a'); + anchor.href = url; + var protocol = anchor.href.slice(0, anchor.href.indexOf(':')); + return protocols.indexOf(protocol) > -1; + } + + exports.default = Link; + exports.sanitize = _sanitize; + +/***/ }, +/* 61 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _inline = __webpack_require__(32); + + var _inline2 = _interopRequireDefault(_inline); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Script = function (_Inline) { + _inherits(Script, _Inline); + + function Script() { + _classCallCheck(this, Script); + + return _possibleConstructorReturn(this, (Script.__proto__ || Object.getPrototypeOf(Script)).apply(this, arguments)); + } + + _createClass(Script, null, [{ + key: 'create', + value: function create(value) { + if (value === 'super') { + return document.createElement('sup'); + } else if (value === 'sub') { + return document.createElement('sub'); + } else { + return _get(Script.__proto__ || Object.getPrototypeOf(Script), 'create', this).call(this, value); + } + } + }, { + key: 'formats', + value: function formats(domNode) { + if (domNode.tagName === 'SUB') return 'sub'; + if (domNode.tagName === 'SUP') return 'super'; + return undefined; + } + }]); + + return Script; + }(_inline2.default); + + Script.blotName = 'script'; + Script.tagName = ['SUB', 'SUP']; + + exports.default = Script; + +/***/ }, +/* 62 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _inline = __webpack_require__(32); + + var _inline2 = _interopRequireDefault(_inline); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Strike = function (_Inline) { + _inherits(Strike, _Inline); + + function Strike() { + _classCallCheck(this, Strike); + + return _possibleConstructorReturn(this, (Strike.__proto__ || Object.getPrototypeOf(Strike)).apply(this, arguments)); + } + + return Strike; + }(_inline2.default); + + Strike.blotName = 'strike'; + Strike.tagName = 'S'; + + exports.default = Strike; + +/***/ }, +/* 63 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _inline = __webpack_require__(32); + + var _inline2 = _interopRequireDefault(_inline); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var Underline = function (_Inline) { + _inherits(Underline, _Inline); + + function Underline() { + _classCallCheck(this, Underline); + + return _possibleConstructorReturn(this, (Underline.__proto__ || Object.getPrototypeOf(Underline)).apply(this, arguments)); + } + + return Underline; + }(_inline2.default); + + Underline.blotName = 'underline'; + Underline.tagName = 'U'; + + exports.default = Underline; + +/***/ }, +/* 64 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _embed = __webpack_require__(31); + + var _embed2 = _interopRequireDefault(_embed); + + var _link = __webpack_require__(60); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var ATTRIBUTES = ['alt', 'height', 'width']; + + var Image = function (_Embed) { + _inherits(Image, _Embed); + + function Image() { + _classCallCheck(this, Image); + + return _possibleConstructorReturn(this, (Image.__proto__ || Object.getPrototypeOf(Image)).apply(this, arguments)); + } + + _createClass(Image, [{ + key: 'format', + value: function format(name, value) { + if (ATTRIBUTES.indexOf(name) > -1) { + if (value) { + this.domNode.setAttribute(name, value); + } else { + this.domNode.removeAttribute(name); + } + } else { + _get(Image.prototype.__proto__ || Object.getPrototypeOf(Image.prototype), 'format', this).call(this, name, value); + } + } + }], [{ + key: 'create', + value: function create(value) { + var node = _get(Image.__proto__ || Object.getPrototypeOf(Image), 'create', this).call(this, value); + if (typeof value === 'string') { + node.setAttribute('src', this.sanitize(value)); + } + return node; + } + }, { + key: 'formats', + value: function formats(domNode) { + return ATTRIBUTES.reduce(function (formats, attribute) { + if (domNode.hasAttribute(attribute)) { + formats[attribute] = domNode.getAttribute(attribute); + } + return formats; + }, {}); + } + }, { + key: 'match', + value: function match(url) { + return (/\.(jpe?g|gif|png)$/.test(url) || /^data:image\/.+;base64/.test(url) + ); + } + }, { + key: 'sanitize', + value: function sanitize(url) { + return (0, _link.sanitize)(url, ['http', 'https', 'data']) ? url : '//:0'; + } + }, { + key: 'value', + value: function value(domNode) { + return domNode.getAttribute('src'); + } + }]); + + return Image; + }(_embed2.default); + + Image.blotName = 'image'; + Image.tagName = 'IMG'; + + exports.default = Image; + +/***/ }, +/* 65 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _block = __webpack_require__(29); + + var _link = __webpack_require__(60); + + var _link2 = _interopRequireDefault(_link); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var ATTRIBUTES = ['height', 'width']; + + var Video = function (_BlockEmbed) { + _inherits(Video, _BlockEmbed); + + function Video() { + _classCallCheck(this, Video); + + return _possibleConstructorReturn(this, (Video.__proto__ || Object.getPrototypeOf(Video)).apply(this, arguments)); + } + + _createClass(Video, [{ + key: 'format', + value: function format(name, value) { + if (ATTRIBUTES.indexOf(name) > -1) { + if (value) { + this.domNode.setAttribute(name, value); + } else { + this.domNode.removeAttribute(name); + } + } else { + _get(Video.prototype.__proto__ || Object.getPrototypeOf(Video.prototype), 'format', this).call(this, name, value); + } + } + }], [{ + key: 'create', + value: function create(value) { + var node = _get(Video.__proto__ || Object.getPrototypeOf(Video), 'create', this).call(this, value); + node.setAttribute('frameborder', '0'); + node.setAttribute('allowfullscreen', true); + node.setAttribute('src', this.sanitize(value)); + return node; + } + }, { + key: 'formats', + value: function formats(domNode) { + return ATTRIBUTES.reduce(function (formats, attribute) { + if (domNode.hasAttribute(attribute)) { + formats[attribute] = domNode.getAttribute(attribute); + } + return formats; + }, {}); + } + }, { + key: 'sanitize', + value: function sanitize(url) { + return _link2.default.sanitize(url); + } + }, { + key: 'value', + value: function value(domNode) { + return domNode.getAttribute('src'); + } + }]); + + return Video; + }(_block.BlockEmbed); + + Video.blotName = 'video'; + Video.className = 'ql-video'; + Video.tagName = 'IFRAME'; + + exports.default = Video; + +/***/ }, +/* 66 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.default = exports.FormulaBlot = undefined; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _embed = __webpack_require__(31); + + var _embed2 = _interopRequireDefault(_embed); + + var _quill = __webpack_require__(18); + + var _quill2 = _interopRequireDefault(_quill); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var FormulaBlot = function (_Embed) { + _inherits(FormulaBlot, _Embed); + + function FormulaBlot() { + _classCallCheck(this, FormulaBlot); + + return _possibleConstructorReturn(this, (FormulaBlot.__proto__ || Object.getPrototypeOf(FormulaBlot)).apply(this, arguments)); + } + + _createClass(FormulaBlot, [{ + key: 'index', + value: function index() { + return 1; + } + }], [{ + key: 'create', + value: function create(value) { + var node = _get(FormulaBlot.__proto__ || Object.getPrototypeOf(FormulaBlot), 'create', this).call(this, value); + if (typeof value === 'string') { + window.katex.render(value, node); + node.setAttribute('data-value', value); + } + node.setAttribute('contenteditable', false); + return node; + } + }, { + key: 'value', + value: function value(domNode) { + return domNode.getAttribute('data-value'); + } + }]); + + return FormulaBlot; + }(_embed2.default); + + FormulaBlot.blotName = 'formula'; + FormulaBlot.className = 'ql-formula'; + FormulaBlot.tagName = 'SPAN'; + + function Formula() { + if (window.katex == null) { + throw new Error('Formula module requires KaTeX.'); + } + _quill2.default.register(FormulaBlot, true); + } + + exports.FormulaBlot = FormulaBlot; + exports.default = Formula; + +/***/ }, +/* 67 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.default = exports.CodeToken = exports.CodeBlock = undefined; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _quill = __webpack_require__(18); + + var _quill2 = _interopRequireDefault(_quill); + + var _module = __webpack_require__(39); + + var _module2 = _interopRequireDefault(_module); + + var _code = __webpack_require__(28); + + var _code2 = _interopRequireDefault(_code); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var SyntaxCodeBlock = function (_CodeBlock) { + _inherits(SyntaxCodeBlock, _CodeBlock); + + function SyntaxCodeBlock() { + _classCallCheck(this, SyntaxCodeBlock); + + return _possibleConstructorReturn(this, (SyntaxCodeBlock.__proto__ || Object.getPrototypeOf(SyntaxCodeBlock)).apply(this, arguments)); + } + + _createClass(SyntaxCodeBlock, [{ + key: 'replaceWith', + value: function replaceWith(block) { + this.domNode.textContent = this.domNode.textContent; + this.attach(); + _get(SyntaxCodeBlock.prototype.__proto__ || Object.getPrototypeOf(SyntaxCodeBlock.prototype), 'replaceWith', this).call(this, block); + } + }, { + key: 'highlight', + value: function highlight(_highlight) { + if (this.cachedHTML !== this.domNode.innerHTML) { + var text = this.domNode.textContent; + if (text.trim().length > 0 || this.cachedHTML == null) { + this.domNode.innerHTML = _highlight(text); + this.attach(); + } + this.cachedHTML = this.domNode.innerHTML; + } + } + }]); + + return SyntaxCodeBlock; + }(_code2.default); + + SyntaxCodeBlock.className = 'ql-syntax'; + + var CodeToken = new _parchment2.default.Attributor.Class('token', 'hljs', { + scope: _parchment2.default.Scope.INLINE + }); + + var Syntax = function (_Module) { + _inherits(Syntax, _Module); + + function Syntax(quill, options) { + _classCallCheck(this, Syntax); + + var _this2 = _possibleConstructorReturn(this, (Syntax.__proto__ || Object.getPrototypeOf(Syntax)).call(this, quill, options)); + + if (typeof _this2.options.highlight !== 'function') { + throw new Error('Syntax module requires highlight.js. Please include the library on the page before Quill.'); + } + _quill2.default.register(CodeToken, true); + _quill2.default.register(SyntaxCodeBlock, true); + var timer = null; + _this2.quill.on(_quill2.default.events.SCROLL_OPTIMIZE, function () { + if (timer != null) return; + timer = setTimeout(function () { + _this2.highlight(); + timer = null; + }, 100); + }); + _this2.highlight(); + return _this2; + } + + _createClass(Syntax, [{ + key: 'highlight', + value: function highlight() { + var _this3 = this; + + if (this.quill.selection.composing) return; + var range = this.quill.getSelection(); + this.quill.scroll.descendants(SyntaxCodeBlock).forEach(function (code) { + code.highlight(_this3.options.highlight); + }); + this.quill.update(_quill2.default.sources.SILENT); + if (range != null) { + this.quill.setSelection(range, _quill2.default.sources.SILENT); + } + } + }]); + + return Syntax; + }(_module2.default); + + Syntax.DEFAULTS = { + highlight: function () { + if (window.hljs == null) return null; + return function (text) { + var result = window.hljs.highlightAuto(text); + return result.value; + }; + }() + }; + + exports.CodeBlock = SyntaxCodeBlock; + exports.CodeToken = CodeToken; + exports.default = Syntax; + +/***/ }, +/* 68 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.addControls = exports.default = undefined; + + var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _quillDelta = __webpack_require__(20); + + var _quillDelta2 = _interopRequireDefault(_quillDelta); + + var _parchment = __webpack_require__(2); + + var _parchment2 = _interopRequireDefault(_parchment); + + var _quill = __webpack_require__(18); + + var _quill2 = _interopRequireDefault(_quill); + + var _logger = __webpack_require__(37); + + var _logger2 = _interopRequireDefault(_logger); + + var _module = __webpack_require__(39); + + var _module2 = _interopRequireDefault(_module); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var debug = (0, _logger2.default)('quill:toolbar'); + + var Toolbar = function (_Module) { + _inherits(Toolbar, _Module); + + function Toolbar(quill, options) { + _classCallCheck(this, Toolbar); + + var _this = _possibleConstructorReturn(this, (Toolbar.__proto__ || Object.getPrototypeOf(Toolbar)).call(this, quill, options)); + + if (Array.isArray(_this.options.container)) { + var container = document.createElement('div'); + addControls(container, _this.options.container); + quill.container.parentNode.insertBefore(container, quill.container); + _this.container = container; + } else if (typeof _this.options.container === 'string') { + _this.container = document.querySelector(_this.options.container); + } else { + _this.container = _this.options.container; + } + if (!(_this.container instanceof HTMLElement)) { + var _ret; + + return _ret = debug.error('Container required for toolbar', _this.options), _possibleConstructorReturn(_this, _ret); + } + _this.container.classList.add('ql-toolbar'); + _this.controls = []; + _this.handlers = {}; + Object.keys(_this.options.handlers).forEach(function (format) { + _this.addHandler(format, _this.options.handlers[format]); + }); + [].forEach.call(_this.container.querySelectorAll('button, select'), function (input) { + _this.attach(input); + }); + _this.quill.on(_quill2.default.events.EDITOR_CHANGE, function (type, range) { + if (type === _quill2.default.events.SELECTION_CHANGE) { + _this.update(range); + } + }); + _this.quill.on(_quill2.default.events.SCROLL_OPTIMIZE, function () { + var _this$quill$selection = _this.quill.selection.getRange(), + _this$quill$selection2 = _slicedToArray(_this$quill$selection, 1), + range = _this$quill$selection2[0]; // quill.getSelection triggers update + + + _this.update(range); + }); + return _this; + } + + _createClass(Toolbar, [{ + key: 'addHandler', + value: function addHandler(format, handler) { + this.handlers[format] = handler; + } + }, { + key: 'attach', + value: function attach(input) { + var _this2 = this; + + var format = [].find.call(input.classList, function (className) { + return className.indexOf('ql-') === 0; + }); + if (!format) return; + format = format.slice('ql-'.length); + if (input.tagName === 'BUTTON') { + input.setAttribute('type', 'button'); + } + if (this.handlers[format] == null) { + if (this.quill.scroll.whitelist != null && this.quill.scroll.whitelist[format] == null) { + debug.warn('ignoring attaching to disabled format', format, input); + return; + } + if (_parchment2.default.query(format) == null) { + debug.warn('ignoring attaching to nonexistent format', format, input); + return; + } + } + var eventName = input.tagName === 'SELECT' ? 'change' : 'click'; + input.addEventListener(eventName, function (e) { + var value = void 0; + if (input.tagName === 'SELECT') { + if (input.selectedIndex < 0) return; + var selected = input.options[input.selectedIndex]; + if (selected.hasAttribute('selected')) { + value = false; + } else { + value = selected.value || false; + } + } else { + if (input.classList.contains('ql-active')) { + value = false; + } else { + value = input.value || !input.hasAttribute('value'); + } + e.preventDefault(); + } + _this2.quill.focus(); + + var _quill$selection$getR = _this2.quill.selection.getRange(), + _quill$selection$getR2 = _slicedToArray(_quill$selection$getR, 1), + range = _quill$selection$getR2[0]; + + if (_this2.handlers[format] != null) { + _this2.handlers[format].call(_this2, value); + } else if (_parchment2.default.query(format).prototype instanceof _parchment2.default.Embed) { + value = prompt('Enter ' + format); + if (!value) return; + _this2.quill.updateContents(new _quillDelta2.default().retain(range.index).delete(range.length).insert(_defineProperty({}, format, value)), _quill2.default.sources.USER); + } else { + _this2.quill.format(format, value, _quill2.default.sources.USER); + } + _this2.update(range); + }); + // TODO use weakmap + this.controls.push([format, input]); + } + }, { + key: 'update', + value: function update(range) { + var formats = range == null ? {} : this.quill.getFormat(range); + this.controls.forEach(function (pair) { + var _pair = _slicedToArray(pair, 2), + format = _pair[0], + input = _pair[1]; + + if (input.tagName === 'SELECT') { + var option = void 0; + if (range == null) { + option = null; + } else if (formats[format] == null) { + option = input.querySelector('option[selected]'); + } else if (!Array.isArray(formats[format])) { + var value = formats[format]; + if (typeof value === 'string') { + value = value.replace(/\"/g, '\\"'); + } + option = input.querySelector('option[value="' + value + '"]'); + } + if (option == null) { + input.value = ''; // TODO make configurable? + input.selectedIndex = -1; + } else { + option.selected = true; + } + } else { + if (range == null) { + input.classList.remove('ql-active'); + } else if (input.hasAttribute('value')) { + // both being null should match (default values) + // '1' should match with 1 (headers) + var isActive = formats[format] === input.getAttribute('value') || formats[format] != null && formats[format].toString() === input.getAttribute('value') || formats[format] == null && !input.getAttribute('value'); + input.classList.toggle('ql-active', isActive); + } else { + input.classList.toggle('ql-active', formats[format] != null); + } + } + }); + } + }]); + + return Toolbar; + }(_module2.default); + + Toolbar.DEFAULTS = {}; + + function addButton(container, format, value) { + var input = document.createElement('button'); + input.setAttribute('type', 'button'); + input.classList.add('ql-' + format); + if (value != null) { + input.value = value; + } + container.appendChild(input); + } + + function addControls(container, groups) { + if (!Array.isArray(groups[0])) { + groups = [groups]; + } + groups.forEach(function (controls) { + var group = document.createElement('span'); + group.classList.add('ql-formats'); + controls.forEach(function (control) { + if (typeof control === 'string') { + addButton(group, control); + } else { + var format = Object.keys(control)[0]; + var value = control[format]; + if (Array.isArray(value)) { + addSelect(group, format, value); + } else { + addButton(group, format, value); + } + } + }); + container.appendChild(group); + }); + } + + function addSelect(container, format, values) { + var input = document.createElement('select'); + input.classList.add('ql-' + format); + values.forEach(function (value) { + var option = document.createElement('option'); + if (value !== false) { + option.setAttribute('value', value); + } else { + option.setAttribute('selected', 'selected'); + } + input.appendChild(option); + }); + container.appendChild(input); + } + + Toolbar.DEFAULTS = { + container: null, + handlers: { + clean: function clean() { + var _this3 = this; + + var range = this.quill.getSelection(); + if (range == null) return; + if (range.length == 0) { + var formats = this.quill.getFormat(); + Object.keys(formats).forEach(function (name) { + // Clean functionality in existing apps only clean inline formats + if (_parchment2.default.query(name, _parchment2.default.Scope.INLINE) != null) { + _this3.quill.format(name, false); + } + }); + } else { + this.quill.removeFormat(range, _quill2.default.sources.USER); + } + }, + direction: function direction(value) { + var align = this.quill.getFormat()['align']; + if (value === 'rtl' && align == null) { + this.quill.format('align', 'right', _quill2.default.sources.USER); + } else if (!value && align === 'right') { + this.quill.format('align', false, _quill2.default.sources.USER); + } + this.quill.format('direction', value, _quill2.default.sources.USER); + }, + link: function link(value) { + if (value === true) { + value = prompt('Enter link URL:'); + } + this.quill.format('link', value, _quill2.default.sources.USER); + }, + indent: function indent(value) { + var range = this.quill.getSelection(); + var formats = this.quill.getFormat(range); + var indent = parseInt(formats.indent || 0); + if (value === '+1' || value === '-1') { + var modifier = value === '+1' ? 1 : -1; + if (formats.direction === 'rtl') modifier *= -1; + this.quill.format('indent', indent + modifier, _quill2.default.sources.USER); + } + } + } + }; + + exports.default = Toolbar; + exports.addControls = addControls; + +/***/ }, +/* 69 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + module.exports = { + 'align': { + '': __webpack_require__(70), + 'center': __webpack_require__(71), + 'right': __webpack_require__(72), + 'justify': __webpack_require__(73) + }, + 'background': __webpack_require__(74), + 'blockquote': __webpack_require__(75), + 'bold': __webpack_require__(76), + 'clean': __webpack_require__(77), + 'code': __webpack_require__(78), + 'code-block': __webpack_require__(78), + 'color': __webpack_require__(79), + 'direction': { + '': __webpack_require__(80), + 'rtl': __webpack_require__(81) + }, + 'float': { + 'center': __webpack_require__(82), + 'full': __webpack_require__(83), + 'left': __webpack_require__(84), + 'right': __webpack_require__(85) + }, + 'formula': __webpack_require__(86), + 'header': { + '1': __webpack_require__(87), + '2': __webpack_require__(88) + }, + 'italic': __webpack_require__(89), + 'image': __webpack_require__(90), + 'indent': { + '+1': __webpack_require__(91), + '-1': __webpack_require__(92) + }, + 'link': __webpack_require__(93), + 'list': { + 'ordered': __webpack_require__(94), + 'bullet': __webpack_require__(95), + 'unchecked': __webpack_require__(96) + }, + 'script': { + 'sub': __webpack_require__(97), + 'super': __webpack_require__(98) + }, + 'strike': __webpack_require__(99), + 'underline': __webpack_require__(100), + 'video': __webpack_require__(101) + }; + +/***/ }, +/* 70 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 71 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 72 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 73 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 74 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 75 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 76 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 77 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 78 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 79 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 80 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 81 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 82 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 83 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 84 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 85 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 86 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 87 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 88 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 89 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 90 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 91 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 92 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 93 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 94 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 95 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 96 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 97 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 98 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 99 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 100 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 101 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 102 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _dropdown = __webpack_require__(103); + + var _dropdown2 = _interopRequireDefault(_dropdown); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var Picker = function () { + function Picker(select) { + var _this = this; + + _classCallCheck(this, Picker); + + this.select = select; + this.container = document.createElement('span'); + this.buildPicker(); + this.select.style.display = 'none'; + this.select.parentNode.insertBefore(this.container, this.select); + this.label.addEventListener('mousedown', function () { + _this.container.classList.toggle('ql-expanded'); + }); + this.select.addEventListener('change', this.update.bind(this)); + } + + _createClass(Picker, [{ + key: 'buildItem', + value: function buildItem(option) { + var _this2 = this; + + var item = document.createElement('span'); + item.classList.add('ql-picker-item'); + if (option.hasAttribute('value')) { + item.setAttribute('data-value', option.getAttribute('value')); + } + if (option.textContent) { + item.setAttribute('data-label', option.textContent); + } + item.addEventListener('click', function () { + _this2.selectItem(item, true); + }); + return item; + } + }, { + key: 'buildLabel', + value: function buildLabel() { + var label = document.createElement('span'); + label.classList.add('ql-picker-label'); + label.innerHTML = _dropdown2.default; + this.container.appendChild(label); + return label; + } + }, { + key: 'buildOptions', + value: function buildOptions() { + var _this3 = this; + + var options = document.createElement('span'); + options.classList.add('ql-picker-options'); + [].slice.call(this.select.options).forEach(function (option) { + var item = _this3.buildItem(option); + options.appendChild(item); + if (option.hasAttribute('selected')) { + _this3.selectItem(item); + } + }); + this.container.appendChild(options); + } + }, { + key: 'buildPicker', + value: function buildPicker() { + var _this4 = this; + + [].slice.call(this.select.attributes).forEach(function (item) { + _this4.container.setAttribute(item.name, item.value); + }); + this.container.classList.add('ql-picker'); + this.label = this.buildLabel(); + this.buildOptions(); + } + }, { + key: 'close', + value: function close() { + this.container.classList.remove('ql-expanded'); + } + }, { + key: 'selectItem', + value: function selectItem(item) { + var trigger = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var selected = this.container.querySelector('.ql-selected'); + if (item === selected) return; + if (selected != null) { + selected.classList.remove('ql-selected'); + } + if (item == null) return; + item.classList.add('ql-selected'); + this.select.selectedIndex = [].indexOf.call(item.parentNode.children, item); + if (item.hasAttribute('data-value')) { + this.label.setAttribute('data-value', item.getAttribute('data-value')); + } else { + this.label.removeAttribute('data-value'); + } + if (item.hasAttribute('data-label')) { + this.label.setAttribute('data-label', item.getAttribute('data-label')); + } else { + this.label.removeAttribute('data-label'); + } + if (trigger) { + if (typeof Event === 'function') { + this.select.dispatchEvent(new Event('change')); + } else if ((typeof Event === 'undefined' ? 'undefined' : _typeof(Event)) === 'object') { + // IE11 + var event = document.createEvent('Event'); + event.initEvent('change', true, true); + this.select.dispatchEvent(event); + } + this.close(); + } + } + }, { + key: 'update', + value: function update() { + var option = void 0; + if (this.select.selectedIndex > -1) { + var item = this.container.querySelector('.ql-picker-options').children[this.select.selectedIndex]; + option = this.select.options[this.select.selectedIndex]; + this.selectItem(item); + } else { + this.selectItem(null); + } + var isActive = option != null && option !== this.select.querySelector('option[selected]'); + this.label.classList.toggle('ql-active', isActive); + } + }]); + + return Picker; + }(); + + exports.default = Picker; + +/***/ }, +/* 103 */ +/***/ function(module, exports) { + + module.exports = " "; + +/***/ }, +/* 104 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _picker = __webpack_require__(102); + + var _picker2 = _interopRequireDefault(_picker); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var ColorPicker = function (_Picker) { + _inherits(ColorPicker, _Picker); + + function ColorPicker(select, label) { + _classCallCheck(this, ColorPicker); + + var _this = _possibleConstructorReturn(this, (ColorPicker.__proto__ || Object.getPrototypeOf(ColorPicker)).call(this, select)); + + _this.label.innerHTML = label; + _this.container.classList.add('ql-color-picker'); + [].slice.call(_this.container.querySelectorAll('.ql-picker-item'), 0, 7).forEach(function (item) { + item.classList.add('ql-primary'); + }); + return _this; + } + + _createClass(ColorPicker, [{ + key: 'buildItem', + value: function buildItem(option) { + var item = _get(ColorPicker.prototype.__proto__ || Object.getPrototypeOf(ColorPicker.prototype), 'buildItem', this).call(this, option); + item.style.backgroundColor = option.getAttribute('value') || ''; + return item; + } + }, { + key: 'selectItem', + value: function selectItem(item, trigger) { + _get(ColorPicker.prototype.__proto__ || Object.getPrototypeOf(ColorPicker.prototype), 'selectItem', this).call(this, item, trigger); + var colorLabel = this.label.querySelector('.ql-color-label'); + var value = item ? item.getAttribute('data-value') || '' : ''; + if (colorLabel) { + if (colorLabel.tagName === 'line') { + colorLabel.style.stroke = value; + } else { + colorLabel.style.fill = value; + } + } + } + }]); + + return ColorPicker; + }(_picker2.default); + + exports.default = ColorPicker; + +/***/ }, +/* 105 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _picker = __webpack_require__(102); + + var _picker2 = _interopRequireDefault(_picker); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var IconPicker = function (_Picker) { + _inherits(IconPicker, _Picker); + + function IconPicker(select, icons) { + _classCallCheck(this, IconPicker); + + var _this = _possibleConstructorReturn(this, (IconPicker.__proto__ || Object.getPrototypeOf(IconPicker)).call(this, select)); + + _this.container.classList.add('ql-icon-picker'); + [].forEach.call(_this.container.querySelectorAll('.ql-picker-item'), function (item) { + item.innerHTML = icons[item.getAttribute('data-value') || '']; + }); + _this.defaultItem = _this.container.querySelector('.ql-selected'); + _this.selectItem(_this.defaultItem); + return _this; + } + + _createClass(IconPicker, [{ + key: 'selectItem', + value: function selectItem(item, trigger) { + _get(IconPicker.prototype.__proto__ || Object.getPrototypeOf(IconPicker.prototype), 'selectItem', this).call(this, item, trigger); + item = item || this.defaultItem; + this.label.innerHTML = item.innerHTML; + } + }]); + + return IconPicker; + }(_picker2.default); + + exports.default = IconPicker; + +/***/ }, +/* 106 */ +/***/ function(module, exports) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var Tooltip = function () { + function Tooltip(quill, boundsContainer) { + var _this = this; + + _classCallCheck(this, Tooltip); + + this.quill = quill; + this.boundsContainer = boundsContainer || document.body; + this.root = quill.addContainer('ql-tooltip'); + this.root.innerHTML = this.constructor.TEMPLATE; + this.quill.root.addEventListener('scroll', function () { + _this.root.style.marginTop = -1 * _this.quill.root.scrollTop + 'px'; + }); + this.hide(); + } + + _createClass(Tooltip, [{ + key: 'hide', + value: function hide() { + this.root.classList.add('ql-hidden'); + } + }, { + key: 'position', + value: function position(reference) { + var left = reference.left + reference.width / 2 - this.root.offsetWidth / 2; + var top = reference.bottom + this.quill.root.scrollTop; + this.root.style.left = left + 'px'; + this.root.style.top = top + 'px'; + this.root.classList.remove('ql-flip'); + var containerBounds = this.boundsContainer.getBoundingClientRect(); + var rootBounds = this.root.getBoundingClientRect(); + var shift = 0; + if (rootBounds.right > containerBounds.right) { + shift = containerBounds.right - rootBounds.right; + this.root.style.left = left + shift + 'px'; + } + if (rootBounds.left < containerBounds.left) { + shift = containerBounds.left - rootBounds.left; + this.root.style.left = left + shift + 'px'; + } + if (rootBounds.bottom > containerBounds.bottom) { + var height = rootBounds.bottom - rootBounds.top; + var verticalShift = containerBounds.bottom - rootBounds.bottom - height; + this.root.style.top = top + verticalShift + 'px'; + this.root.classList.add('ql-flip'); + } + return shift; + } + }, { + key: 'show', + value: function show() { + this.root.classList.remove('ql-editing'); + this.root.classList.remove('ql-hidden'); + } + }]); + + return Tooltip; + }(); + + exports.default = Tooltip; + +/***/ }, +/* 107 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.default = exports.BubbleTooltip = undefined; + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _extend = __webpack_require__(25); + + var _extend2 = _interopRequireDefault(_extend); + + var _emitter = __webpack_require__(35); + + var _emitter2 = _interopRequireDefault(_emitter); + + var _base = __webpack_require__(108); + + var _base2 = _interopRequireDefault(_base); + + var _selection = __webpack_require__(40); + + var _icons = __webpack_require__(69); + + var _icons2 = _interopRequireDefault(_icons); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var TOOLBAR_CONFIG = [['bold', 'italic', 'link'], [{ header: 1 }, { header: 2 }, 'blockquote']]; + + var BubbleTheme = function (_BaseTheme) { + _inherits(BubbleTheme, _BaseTheme); + + function BubbleTheme(quill, options) { + _classCallCheck(this, BubbleTheme); + + if (options.modules.toolbar != null && options.modules.toolbar.container == null) { + options.modules.toolbar.container = TOOLBAR_CONFIG; + } + + var _this = _possibleConstructorReturn(this, (BubbleTheme.__proto__ || Object.getPrototypeOf(BubbleTheme)).call(this, quill, options)); + + _this.quill.container.classList.add('ql-bubble'); + return _this; + } + + _createClass(BubbleTheme, [{ + key: 'extendToolbar', + value: function extendToolbar(toolbar) { + this.tooltip = new BubbleTooltip(this.quill, this.options.bounds); + this.tooltip.root.appendChild(toolbar.container); + this.buildButtons([].slice.call(toolbar.container.querySelectorAll('button')), _icons2.default); + this.buildPickers([].slice.call(toolbar.container.querySelectorAll('select')), _icons2.default); + } + }]); + + return BubbleTheme; + }(_base2.default); + + BubbleTheme.DEFAULTS = (0, _extend2.default)(true, {}, _base2.default.DEFAULTS, { + modules: { + toolbar: { + handlers: { + link: function link(value) { + if (!value) { + this.quill.format('link', false); + } else { + this.quill.theme.tooltip.edit(); + } + } + } + } + } + }); + + var BubbleTooltip = function (_BaseTooltip) { + _inherits(BubbleTooltip, _BaseTooltip); + + function BubbleTooltip(quill, bounds) { + _classCallCheck(this, BubbleTooltip); + + var _this2 = _possibleConstructorReturn(this, (BubbleTooltip.__proto__ || Object.getPrototypeOf(BubbleTooltip)).call(this, quill, bounds)); + + _this2.quill.on(_emitter2.default.events.EDITOR_CHANGE, function (type, range, oldRange, source) { + if (type !== _emitter2.default.events.SELECTION_CHANGE) return; + if (range != null && range.length > 0 && source === _emitter2.default.sources.USER) { + _this2.show(); + // Lock our width so we will expand beyond our offsetParent boundaries + _this2.root.style.left = '0px'; + _this2.root.style.width = ''; + _this2.root.style.width = _this2.root.offsetWidth + 'px'; + var lines = _this2.quill.scroll.lines(range.index, range.length); + if (lines.length === 1) { + _this2.position(_this2.quill.getBounds(range)); + } else { + var lastLine = lines[lines.length - 1]; + var index = lastLine.offset(_this2.quill.scroll); + var length = Math.min(lastLine.length() - 1, range.index + range.length - index); + var _bounds = _this2.quill.getBounds(new _selection.Range(index, length)); + _this2.position(_bounds); + } + } else if (document.activeElement !== _this2.textbox && _this2.quill.hasFocus()) { + _this2.hide(); + } + }); + return _this2; + } + + _createClass(BubbleTooltip, [{ + key: 'listen', + value: function listen() { + var _this3 = this; + + _get(BubbleTooltip.prototype.__proto__ || Object.getPrototypeOf(BubbleTooltip.prototype), 'listen', this).call(this); + this.root.querySelector('.ql-close').addEventListener('click', function () { + _this3.root.classList.remove('ql-editing'); + }); + this.quill.on(_emitter2.default.events.SCROLL_OPTIMIZE, function () { + // Let selection be restored by toolbar handlers before repositioning + setTimeout(function () { + if (_this3.root.classList.contains('ql-hidden')) return; + var range = _this3.quill.getSelection(); + if (range != null) { + _this3.position(_this3.quill.getBounds(range)); + } + }, 1); + }); + } + }, { + key: 'cancel', + value: function cancel() { + this.show(); + } + }, { + key: 'position', + value: function position(reference) { + var shift = _get(BubbleTooltip.prototype.__proto__ || Object.getPrototypeOf(BubbleTooltip.prototype), 'position', this).call(this, reference); + var arrow = this.root.querySelector('.ql-tooltip-arrow'); + arrow.style.marginLeft = ''; + if (shift === 0) return shift; + arrow.style.marginLeft = -1 * shift - arrow.offsetWidth / 2 + 'px'; + } + }]); + + return BubbleTooltip; + }(_base.BaseTooltip); + + BubbleTooltip.TEMPLATE = ['', '
    ', '', '', '
    '].join(''); + + exports.BubbleTooltip = BubbleTooltip; + exports.default = BubbleTheme; + +/***/ }, +/* 108 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.default = exports.BaseTooltip = undefined; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _extend = __webpack_require__(25); + + var _extend2 = _interopRequireDefault(_extend); + + var _quillDelta = __webpack_require__(20); + + var _quillDelta2 = _interopRequireDefault(_quillDelta); + + var _emitter = __webpack_require__(35); + + var _emitter2 = _interopRequireDefault(_emitter); + + var _keyboard = __webpack_require__(52); + + var _keyboard2 = _interopRequireDefault(_keyboard); + + var _theme = __webpack_require__(41); + + var _theme2 = _interopRequireDefault(_theme); + + var _colorPicker = __webpack_require__(104); + + var _colorPicker2 = _interopRequireDefault(_colorPicker); + + var _iconPicker = __webpack_require__(105); + + var _iconPicker2 = _interopRequireDefault(_iconPicker); + + var _picker = __webpack_require__(102); + + var _picker2 = _interopRequireDefault(_picker); + + var _tooltip = __webpack_require__(106); + + var _tooltip2 = _interopRequireDefault(_tooltip); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var ALIGNS = [false, 'center', 'right', 'justify']; + + var COLORS = ["#000000", "#e60000", "#ff9900", "#ffff00", "#008a00", "#0066cc", "#9933ff", "#ffffff", "#facccc", "#ffebcc", "#ffffcc", "#cce8cc", "#cce0f5", "#ebd6ff", "#bbbbbb", "#f06666", "#ffc266", "#ffff66", "#66b966", "#66a3e0", "#c285ff", "#888888", "#a10000", "#b26b00", "#b2b200", "#006100", "#0047b2", "#6b24b2", "#444444", "#5c0000", "#663d00", "#666600", "#003700", "#002966", "#3d1466"]; + + var FONTS = [false, 'serif', 'monospace']; + + var HEADERS = ['1', '2', '3', false]; + + var SIZES = ['small', false, 'large', 'huge']; + + var BaseTheme = function (_Theme) { + _inherits(BaseTheme, _Theme); + + function BaseTheme(quill, options) { + _classCallCheck(this, BaseTheme); + + var _this = _possibleConstructorReturn(this, (BaseTheme.__proto__ || Object.getPrototypeOf(BaseTheme)).call(this, quill, options)); + + var listener = function listener(e) { + if (!document.body.contains(quill.root)) { + return document.body.removeEventListener('click', listener); + } + if (_this.tooltip != null && !_this.tooltip.root.contains(e.target) && document.activeElement !== _this.tooltip.textbox && !_this.quill.hasFocus()) { + _this.tooltip.hide(); + } + if (_this.pickers != null) { + _this.pickers.forEach(function (picker) { + if (!picker.container.contains(e.target)) { + picker.close(); + } + }); + } + }; + document.body.addEventListener('click', listener); + return _this; + } + + _createClass(BaseTheme, [{ + key: 'addModule', + value: function addModule(name) { + var module = _get(BaseTheme.prototype.__proto__ || Object.getPrototypeOf(BaseTheme.prototype), 'addModule', this).call(this, name); + if (name === 'toolbar') { + this.extendToolbar(module); + } + return module; + } + }, { + key: 'buildButtons', + value: function buildButtons(buttons, icons) { + buttons.forEach(function (button) { + var className = button.getAttribute('class') || ''; + className.split(/\s+/).forEach(function (name) { + if (!name.startsWith('ql-')) return; + name = name.slice('ql-'.length); + if (icons[name] == null) return; + if (name === 'direction') { + button.innerHTML = icons[name][''] + icons[name]['rtl']; + } else if (typeof icons[name] === 'string') { + button.innerHTML = icons[name]; + } else { + var value = button.value || ''; + if (value != null && icons[name][value]) { + button.innerHTML = icons[name][value]; + } + } + }); + }); + } + }, { + key: 'buildPickers', + value: function buildPickers(selects, icons) { + var _this2 = this; + + this.pickers = selects.map(function (select) { + if (select.classList.contains('ql-align')) { + if (select.querySelector('option') == null) { + fillSelect(select, ALIGNS); + } + return new _iconPicker2.default(select, icons.align); + } else if (select.classList.contains('ql-background') || select.classList.contains('ql-color')) { + var format = select.classList.contains('ql-background') ? 'background' : 'color'; + if (select.querySelector('option') == null) { + fillSelect(select, COLORS, format === 'background' ? '#ffffff' : '#000000'); + } + return new _colorPicker2.default(select, icons[format]); + } else { + if (select.querySelector('option') == null) { + if (select.classList.contains('ql-font')) { + fillSelect(select, FONTS); + } else if (select.classList.contains('ql-header')) { + fillSelect(select, HEADERS); + } else if (select.classList.contains('ql-size')) { + fillSelect(select, SIZES); + } + } + return new _picker2.default(select); + } + }); + var update = function update() { + _this2.pickers.forEach(function (picker) { + picker.update(); + }); + }; + this.quill.on(_emitter2.default.events.SELECTION_CHANGE, update).on(_emitter2.default.events.SCROLL_OPTIMIZE, update); + } + }]); + + return BaseTheme; + }(_theme2.default); + + BaseTheme.DEFAULTS = (0, _extend2.default)(true, {}, _theme2.default.DEFAULTS, { + modules: { + toolbar: { + handlers: { + formula: function formula() { + this.quill.theme.tooltip.edit('formula'); + }, + image: function image() { + var _this3 = this; + + var fileInput = this.container.querySelector('input.ql-image[type=file]'); + if (fileInput == null) { + fileInput = document.createElement('input'); + fileInput.setAttribute('type', 'file'); + fileInput.setAttribute('accept', 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon, image/svg+xml'); + fileInput.classList.add('ql-image'); + fileInput.addEventListener('change', function () { + if (fileInput.files != null && fileInput.files[0] != null) { + var reader = new FileReader(); + reader.onload = function (e) { + var range = _this3.quill.getSelection(true); + _this3.quill.updateContents(new _quillDelta2.default().retain(range.index).delete(range.length).insert({ image: e.target.result }), _emitter2.default.sources.USER); + fileInput.value = ""; + }; + reader.readAsDataURL(fileInput.files[0]); + } + }); + this.container.appendChild(fileInput); + } + fileInput.click(); + }, + video: function video() { + this.quill.theme.tooltip.edit('video'); + } + } + } + } + }); + + var BaseTooltip = function (_Tooltip) { + _inherits(BaseTooltip, _Tooltip); + + function BaseTooltip(quill, boundsContainer) { + _classCallCheck(this, BaseTooltip); + + var _this4 = _possibleConstructorReturn(this, (BaseTooltip.__proto__ || Object.getPrototypeOf(BaseTooltip)).call(this, quill, boundsContainer)); + + _this4.textbox = _this4.root.querySelector('input[type="text"]'); + _this4.listen(); + return _this4; + } + + _createClass(BaseTooltip, [{ + key: 'listen', + value: function listen() { + var _this5 = this; + + this.textbox.addEventListener('keydown', function (event) { + if (_keyboard2.default.match(event, 'enter')) { + _this5.save(); + event.preventDefault(); + } else if (_keyboard2.default.match(event, 'escape')) { + _this5.cancel(); + event.preventDefault(); + } + }); + } + }, { + key: 'cancel', + value: function cancel() { + this.hide(); + } + }, { + key: 'edit', + value: function edit() { + var mode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'link'; + var preview = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + + this.root.classList.remove('ql-hidden'); + this.root.classList.add('ql-editing'); + if (preview != null) { + this.textbox.value = preview; + } else if (mode !== this.root.getAttribute('data-mode')) { + this.textbox.value = ''; + } + this.position(this.quill.getBounds(this.quill.selection.savedRange)); + this.textbox.select(); + this.textbox.setAttribute('placeholder', this.textbox.getAttribute('data-' + mode) || ''); + this.root.setAttribute('data-mode', mode); + } + }, { + key: 'restoreFocus', + value: function restoreFocus() { + var scrollTop = this.quill.root.scrollTop; + this.quill.focus(); + this.quill.root.scrollTop = scrollTop; + } + }, { + key: 'save', + value: function save() { + var value = this.textbox.value; + switch (this.root.getAttribute('data-mode')) { + case 'link': + { + var scrollTop = this.quill.root.scrollTop; + if (this.linkRange) { + this.quill.formatText(this.linkRange, 'link', value, _emitter2.default.sources.USER); + delete this.linkRange; + } else { + this.restoreFocus(); + this.quill.format('link', value, _emitter2.default.sources.USER); + } + this.quill.root.scrollTop = scrollTop; + break; + } + case 'video': + { + var match = value.match(/^(https?):\/\/(www\.)?youtube\.com\/watch.*v=([a-zA-Z0-9_-]+)/) || value.match(/^(https?):\/\/(www\.)?youtu\.be\/([a-zA-Z0-9_-]+)/); + if (match) { + value = match[1] + '://www.youtube.com/embed/' + match[3] + '?showinfo=0'; + } else if (match = value.match(/^(https?):\/\/(www\.)?vimeo\.com\/(\d+)/)) { + // eslint-disable-line no-cond-assign + value = match[1] + '://player.vimeo.com/video/' + match[3] + '/'; + } + } // eslint-disable-next-line no-fallthrough + case 'formula': + { + var range = this.quill.getSelection(true); + var index = range.index + range.length; + if (range != null) { + this.quill.insertEmbed(index, this.root.getAttribute('data-mode'), value, _emitter2.default.sources.USER); + if (this.root.getAttribute('data-mode') === 'formula') { + this.quill.insertText(index + 1, ' ', _emitter2.default.sources.USER); + } + this.quill.setSelection(index + 2, _emitter2.default.sources.USER); + } + break; + } + default: + } + this.textbox.value = ''; + this.hide(); + } + }]); + + return BaseTooltip; + }(_tooltip2.default); + + function fillSelect(select, values) { + var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + + values.forEach(function (value) { + var option = document.createElement('option'); + if (value === defaultValue) { + option.setAttribute('selected', 'selected'); + } else { + option.setAttribute('value', value); + } + select.appendChild(option); + }); + } + + exports.BaseTooltip = BaseTooltip; + exports.default = BaseTheme; + +/***/ }, +/* 109 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _extend = __webpack_require__(25); + + var _extend2 = _interopRequireDefault(_extend); + + var _emitter = __webpack_require__(35); + + var _emitter2 = _interopRequireDefault(_emitter); + + var _base = __webpack_require__(108); + + var _base2 = _interopRequireDefault(_base); + + var _link = __webpack_require__(60); + + var _link2 = _interopRequireDefault(_link); + + var _selection = __webpack_require__(40); + + var _icons = __webpack_require__(69); + + var _icons2 = _interopRequireDefault(_icons); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var TOOLBAR_CONFIG = [[{ header: ['1', '2', '3', false] }], ['bold', 'italic', 'underline', 'link'], [{ list: 'ordered' }, { list: 'bullet' }], ['clean']]; + + var SnowTheme = function (_BaseTheme) { + _inherits(SnowTheme, _BaseTheme); + + function SnowTheme(quill, options) { + _classCallCheck(this, SnowTheme); + + if (options.modules.toolbar != null && options.modules.toolbar.container == null) { + options.modules.toolbar.container = TOOLBAR_CONFIG; + } + + var _this = _possibleConstructorReturn(this, (SnowTheme.__proto__ || Object.getPrototypeOf(SnowTheme)).call(this, quill, options)); + + _this.quill.container.classList.add('ql-snow'); + return _this; + } + + _createClass(SnowTheme, [{ + key: 'extendToolbar', + value: function extendToolbar(toolbar) { + toolbar.container.classList.add('ql-snow'); + this.buildButtons([].slice.call(toolbar.container.querySelectorAll('button')), _icons2.default); + this.buildPickers([].slice.call(toolbar.container.querySelectorAll('select')), _icons2.default); + this.tooltip = new SnowTooltip(this.quill, this.options.bounds); + if (toolbar.container.querySelector('.ql-link')) { + this.quill.keyboard.addBinding({ key: 'K', shortKey: true }, function (range, context) { + toolbar.handlers['link'].call(toolbar, !context.format.link); + }); + } + } + }]); + + return SnowTheme; + }(_base2.default); + + SnowTheme.DEFAULTS = (0, _extend2.default)(true, {}, _base2.default.DEFAULTS, { + modules: { + toolbar: { + handlers: { + link: function link(value) { + if (value) { + var range = this.quill.getSelection(); + if (range == null || range.length == 0) return; + var preview = this.quill.getText(range); + if (/^\S+@\S+\.\S+$/.test(preview) && preview.indexOf('mailto:') !== 0) { + preview = 'mailto:' + preview; + } + var tooltip = this.quill.theme.tooltip; + tooltip.edit('link', preview); + } else { + this.quill.format('link', false); + } + } + } + } + } + }); + + var SnowTooltip = function (_BaseTooltip) { + _inherits(SnowTooltip, _BaseTooltip); + + function SnowTooltip(quill, bounds) { + _classCallCheck(this, SnowTooltip); + + var _this2 = _possibleConstructorReturn(this, (SnowTooltip.__proto__ || Object.getPrototypeOf(SnowTooltip)).call(this, quill, bounds)); + + _this2.preview = _this2.root.querySelector('a.ql-preview'); + return _this2; + } + + _createClass(SnowTooltip, [{ + key: 'listen', + value: function listen() { + var _this3 = this; + + _get(SnowTooltip.prototype.__proto__ || Object.getPrototypeOf(SnowTooltip.prototype), 'listen', this).call(this); + this.root.querySelector('a.ql-action').addEventListener('click', function (event) { + if (_this3.root.classList.contains('ql-editing')) { + _this3.save(); + } else { + _this3.edit('link', _this3.preview.textContent); + } + event.preventDefault(); + }); + this.root.querySelector('a.ql-remove').addEventListener('click', function (event) { + if (_this3.linkRange != null) { + _this3.restoreFocus(); + _this3.quill.formatText(_this3.linkRange, 'link', false, _emitter2.default.sources.USER); + delete _this3.linkRange; + } + event.preventDefault(); + _this3.hide(); + }); + this.quill.on(_emitter2.default.events.SELECTION_CHANGE, function (range, oldRange, source) { + if (range == null) return; + if (range.length === 0 && source === _emitter2.default.sources.USER) { + var _quill$scroll$descend = _this3.quill.scroll.descendant(_link2.default, range.index), + _quill$scroll$descend2 = _slicedToArray(_quill$scroll$descend, 2), + link = _quill$scroll$descend2[0], + offset = _quill$scroll$descend2[1]; + + if (link != null) { + _this3.linkRange = new _selection.Range(range.index - offset, link.length()); + var preview = _link2.default.formats(link.domNode); + _this3.preview.textContent = preview; + _this3.preview.setAttribute('href', preview); + _this3.show(); + _this3.position(_this3.quill.getBounds(_this3.linkRange)); + return; + } + } else { + delete _this3.linkRange; + } + _this3.hide(); + }); + } + }, { + key: 'show', + value: function show() { + _get(SnowTooltip.prototype.__proto__ || Object.getPrototypeOf(SnowTooltip.prototype), 'show', this).call(this); + this.root.removeAttribute('data-mode'); + } + }]); + + return SnowTooltip; + }(_base.BaseTooltip); + + SnowTooltip.TEMPLATE = ['', '', '', ''].join(''); + + exports.default = SnowTheme; + +/***/ } +/******/ ]) +}); +; \ No newline at end of file diff --git a/vendor/assets/stylesheets/quill.css b/vendor/assets/stylesheets/quill.css new file mode 100644 index 0000000..70097ab --- /dev/null +++ b/vendor/assets/stylesheets/quill.css @@ -0,0 +1,899 @@ +/*! + * Quill Editor v1.1.9 + * https://quilljs.com/ + * Copyright (c) 2014, Jason Chen + * Copyright (c) 2013, salesforce.com + */ +.ql-container { + box-sizing: border-box; + font-family: Helvetica, Arial, sans-serif; + font-size: 13px; + height: 100%; + margin: 0px; + position: relative; +} +.ql-container.ql-disabled .ql-tooltip { + visibility: hidden; +} +.ql-clipboard { + left: -100000px; + height: 1px; + overflow-y: hidden; + position: absolute; + top: 50%; +} +.ql-clipboard p { + margin: 0; + padding: 0; +} +.ql-editor { + box-sizing: border-box; + cursor: text; + line-height: 1.42; + height: 100%; + outline: none; + overflow-y: auto; + padding: 12px 15px; + tab-size: 4; + -moz-tab-size: 4; + text-align: left; + white-space: pre-wrap; + word-wrap: break-word; +} +.ql-editor p, +.ql-editor ol, +.ql-editor ul, +.ql-editor pre, +.ql-editor blockquote, +.ql-editor h1, +.ql-editor h2, +.ql-editor h3, +.ql-editor h4, +.ql-editor h5, +.ql-editor h6 { + margin: 0; + padding: 0; + counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; +} +.ql-editor ol, +.ql-editor ul { + padding-left: 1.5em; +} +.ql-editor ol > li, +.ql-editor ul > li { + list-style-type: none; +} +.ql-editor ul > li::before { + content: '\25CF'; +} +.ql-editor ul[data-checked=true] > li::before, +.ql-editor ul[data-checked=false] > li::before { + color: #777; + cursor: pointer; +} +.ql-editor ul[data-checked=true] > li::before { + content: '\2611'; +} +.ql-editor ul[data-checked=false] > li::before { + content: '\2610'; +} +.ql-editor li::before { + display: inline-block; + margin-right: 0.3em; + text-align: right; + white-space: nowrap; + width: 1.2em; +} +.ql-editor li:not(.ql-direction-rtl)::before { + margin-left: -1.5em; +} +.ql-editor ol li, +.ql-editor ul li { + padding-left: 1.5em; +} +.ql-editor ol li { + counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; + counter-increment: list-num; +} +.ql-editor ol li:before { + content: counter(list-num, decimal) '. '; +} +.ql-editor ol li.ql-indent-1 { + counter-increment: list-1; +} +.ql-editor ol li.ql-indent-1:before { + content: counter(list-1, lower-alpha) '. '; +} +.ql-editor ol li.ql-indent-1 { + counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; +} +.ql-editor ol li.ql-indent-2 { + counter-increment: list-2; +} +.ql-editor ol li.ql-indent-2:before { + content: counter(list-2, lower-roman) '. '; +} +.ql-editor ol li.ql-indent-2 { + counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; +} +.ql-editor ol li.ql-indent-3 { + counter-increment: list-3; +} +.ql-editor ol li.ql-indent-3:before { + content: counter(list-3, decimal) '. '; +} +.ql-editor ol li.ql-indent-3 { + counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; +} +.ql-editor ol li.ql-indent-4 { + counter-increment: list-4; +} +.ql-editor ol li.ql-indent-4:before { + content: counter(list-4, lower-alpha) '. '; +} +.ql-editor ol li.ql-indent-4 { + counter-reset: list-5 list-6 list-7 list-8 list-9; +} +.ql-editor ol li.ql-indent-5 { + counter-increment: list-5; +} +.ql-editor ol li.ql-indent-5:before { + content: counter(list-5, lower-roman) '. '; +} +.ql-editor ol li.ql-indent-5 { + counter-reset: list-6 list-7 list-8 list-9; +} +.ql-editor ol li.ql-indent-6 { + counter-increment: list-6; +} +.ql-editor ol li.ql-indent-6:before { + content: counter(list-6, decimal) '. '; +} +.ql-editor ol li.ql-indent-6 { + counter-reset: list-7 list-8 list-9; +} +.ql-editor ol li.ql-indent-7 { + counter-increment: list-7; +} +.ql-editor ol li.ql-indent-7:before { + content: counter(list-7, lower-alpha) '. '; +} +.ql-editor ol li.ql-indent-7 { + counter-reset: list-8 list-9; +} +.ql-editor ol li.ql-indent-8 { + counter-increment: list-8; +} +.ql-editor ol li.ql-indent-8:before { + content: counter(list-8, lower-roman) '. '; +} +.ql-editor ol li.ql-indent-8 { + counter-reset: list-9; +} +.ql-editor ol li.ql-indent-9 { + counter-increment: list-9; +} +.ql-editor ol li.ql-indent-9:before { + content: counter(list-9, decimal) '. '; +} +.ql-editor .ql-indent-1:not(.ql-direction-rtl) { + padding-left: 3em; +} +.ql-editor li.ql-indent-1:not(.ql-direction-rtl) { + padding-left: 4.5em; +} +.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right { + padding-right: 3em; +} +.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right { + padding-right: 4.5em; +} +.ql-editor .ql-indent-2:not(.ql-direction-rtl) { + padding-left: 6em; +} +.ql-editor li.ql-indent-2:not(.ql-direction-rtl) { + padding-left: 7.5em; +} +.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right { + padding-right: 6em; +} +.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right { + padding-right: 7.5em; +} +.ql-editor .ql-indent-3:not(.ql-direction-rtl) { + padding-left: 9em; +} +.ql-editor li.ql-indent-3:not(.ql-direction-rtl) { + padding-left: 10.5em; +} +.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right { + padding-right: 9em; +} +.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right { + padding-right: 10.5em; +} +.ql-editor .ql-indent-4:not(.ql-direction-rtl) { + padding-left: 12em; +} +.ql-editor li.ql-indent-4:not(.ql-direction-rtl) { + padding-left: 13.5em; +} +.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right { + padding-right: 12em; +} +.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right { + padding-right: 13.5em; +} +.ql-editor .ql-indent-5:not(.ql-direction-rtl) { + padding-left: 15em; +} +.ql-editor li.ql-indent-5:not(.ql-direction-rtl) { + padding-left: 16.5em; +} +.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right { + padding-right: 15em; +} +.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right { + padding-right: 16.5em; +} +.ql-editor .ql-indent-6:not(.ql-direction-rtl) { + padding-left: 18em; +} +.ql-editor li.ql-indent-6:not(.ql-direction-rtl) { + padding-left: 19.5em; +} +.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right { + padding-right: 18em; +} +.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right { + padding-right: 19.5em; +} +.ql-editor .ql-indent-7:not(.ql-direction-rtl) { + padding-left: 21em; +} +.ql-editor li.ql-indent-7:not(.ql-direction-rtl) { + padding-left: 22.5em; +} +.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right { + padding-right: 21em; +} +.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right { + padding-right: 22.5em; +} +.ql-editor .ql-indent-8:not(.ql-direction-rtl) { + padding-left: 24em; +} +.ql-editor li.ql-indent-8:not(.ql-direction-rtl) { + padding-left: 25.5em; +} +.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right { + padding-right: 24em; +} +.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right { + padding-right: 25.5em; +} +.ql-editor .ql-indent-9:not(.ql-direction-rtl) { + padding-left: 27em; +} +.ql-editor li.ql-indent-9:not(.ql-direction-rtl) { + padding-left: 28.5em; +} +.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right { + padding-right: 27em; +} +.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right { + padding-right: 28.5em; +} +.ql-editor .ql-video { + display: block; + max-width: 100%; +} +.ql-editor .ql-video.ql-align-center { + margin: 0 auto; +} +.ql-editor .ql-video.ql-align-right { + margin: 0 0 0 auto; +} +.ql-editor .ql-bg-black { + background-color: #000; +} +.ql-editor .ql-bg-red { + background-color: #e60000; +} +.ql-editor .ql-bg-orange { + background-color: #f90; +} +.ql-editor .ql-bg-yellow { + background-color: #ff0; +} +.ql-editor .ql-bg-green { + background-color: #008a00; +} +.ql-editor .ql-bg-blue { + background-color: #06c; +} +.ql-editor .ql-bg-purple { + background-color: #93f; +} +.ql-editor .ql-color-white { + color: #fff; +} +.ql-editor .ql-color-red { + color: #e60000; +} +.ql-editor .ql-color-orange { + color: #f90; +} +.ql-editor .ql-color-yellow { + color: #ff0; +} +.ql-editor .ql-color-green { + color: #008a00; +} +.ql-editor .ql-color-blue { + color: #06c; +} +.ql-editor .ql-color-purple { + color: #93f; +} +.ql-editor .ql-font-serif { + font-family: Georgia, Times New Roman, serif; +} +.ql-editor .ql-font-monospace { + font-family: Monaco, Courier New, monospace; +} +.ql-editor .ql-size-small { + font-size: 0.75em; +} +.ql-editor .ql-size-large { + font-size: 1.5em; +} +.ql-editor .ql-size-huge { + font-size: 2.5em; +} +.ql-editor .ql-direction-rtl { + direction: rtl; + text-align: inherit; +} +.ql-editor .ql-align-center { + text-align: center; +} +.ql-editor .ql-align-justify { + text-align: justify; +} +.ql-editor .ql-align-right { + text-align: right; +} +.ql-editor.ql-blank::before { + color: rgba(0,0,0,0.6); + content: attr(data-placeholder); + font-style: italic; + pointer-events: none; + position: absolute; +} +.ql-snow.ql-toolbar:after, +.ql-snow .ql-toolbar:after { + clear: both; + content: ''; + display: table; +} +.ql-snow.ql-toolbar button, +.ql-snow .ql-toolbar button { + background: none; + border: none; + cursor: pointer; + display: inline-block; + float: left; + height: 24px; + padding: 3px 5px; + width: 28px; +} +.ql-snow.ql-toolbar button svg, +.ql-snow .ql-toolbar button svg { + float: left; + height: 100%; +} +.ql-snow.ql-toolbar button:active:hover, +.ql-snow .ql-toolbar button:active:hover { + outline: none; +} +.ql-snow.ql-toolbar input.ql-image[type=file], +.ql-snow .ql-toolbar input.ql-image[type=file] { + display: none; +} +.ql-snow.ql-toolbar button:hover, +.ql-snow .ql-toolbar button:hover, +.ql-snow.ql-toolbar button.ql-active, +.ql-snow .ql-toolbar button.ql-active, +.ql-snow.ql-toolbar .ql-picker-label:hover, +.ql-snow .ql-toolbar .ql-picker-label:hover, +.ql-snow.ql-toolbar .ql-picker-label.ql-active, +.ql-snow .ql-toolbar .ql-picker-label.ql-active, +.ql-snow.ql-toolbar .ql-picker-item:hover, +.ql-snow .ql-toolbar .ql-picker-item:hover, +.ql-snow.ql-toolbar .ql-picker-item.ql-selected, +.ql-snow .ql-toolbar .ql-picker-item.ql-selected { + color: #06c; +} +.ql-snow.ql-toolbar button:hover .ql-fill, +.ql-snow .ql-toolbar button:hover .ql-fill, +.ql-snow.ql-toolbar button.ql-active .ql-fill, +.ql-snow .ql-toolbar button.ql-active .ql-fill, +.ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill, +.ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill, +.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill, +.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill, +.ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill, +.ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill, +.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill, +.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill, +.ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill, +.ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill, +.ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill, +.ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill, +.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, +.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, +.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, +.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, +.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, +.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, +.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill, +.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill { + fill: #06c; +} +.ql-snow.ql-toolbar button:hover .ql-stroke, +.ql-snow .ql-toolbar button:hover .ql-stroke, +.ql-snow.ql-toolbar button.ql-active .ql-stroke, +.ql-snow .ql-toolbar button.ql-active .ql-stroke, +.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke, +.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke, +.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke, +.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke, +.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke, +.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke, +.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke, +.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke, +.ql-snow.ql-toolbar button:hover .ql-stroke-miter, +.ql-snow .ql-toolbar button:hover .ql-stroke-miter, +.ql-snow.ql-toolbar button.ql-active .ql-stroke-miter, +.ql-snow .ql-toolbar button.ql-active .ql-stroke-miter, +.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter, +.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter, +.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, +.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, +.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter, +.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter, +.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter, +.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter { + stroke: #06c; +} +.ql-snow { + box-sizing: border-box; +} +.ql-snow * { + box-sizing: border-box; +} +.ql-snow .ql-hidden { + display: none; +} +.ql-snow .ql-out-bottom, +.ql-snow .ql-out-top { + visibility: hidden; +} +.ql-snow .ql-tooltip { + position: absolute; + transform: translateY(10px); +} +.ql-snow .ql-tooltip a { + cursor: pointer; + text-decoration: none; +} +.ql-snow .ql-tooltip.ql-flip { + transform: translateY(-10px); +} +.ql-snow .ql-formats { + display: inline-block; + vertical-align: middle; +} +.ql-snow .ql-formats:after { + clear: both; + content: ''; + display: table; +} +.ql-snow .ql-stroke { + fill: none; + stroke: #444; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2; +} +.ql-snow .ql-stroke-miter { + fill: none; + stroke: #444; + stroke-miterlimit: 10; + stroke-width: 2; +} +.ql-snow .ql-fill, +.ql-snow .ql-stroke.ql-fill { + fill: #444; +} +.ql-snow .ql-empty { + fill: none; +} +.ql-snow .ql-even { + fill-rule: evenodd; +} +.ql-snow .ql-thin, +.ql-snow .ql-stroke.ql-thin { + stroke-width: 1; +} +.ql-snow .ql-transparent { + opacity: 0.4; +} +.ql-snow .ql-direction svg:last-child { + display: none; +} +.ql-snow .ql-direction.ql-active svg:last-child { + display: inline; +} +.ql-snow .ql-direction.ql-active svg:first-child { + display: none; +} +.ql-snow .ql-editor h1 { + font-size: 2em; +} +.ql-snow .ql-editor h2 { + font-size: 1.5em; +} +.ql-snow .ql-editor h3 { + font-size: 1.17em; +} +.ql-snow .ql-editor h4 { + font-size: 1em; +} +.ql-snow .ql-editor h5 { + font-size: 0.83em; +} +.ql-snow .ql-editor h6 { + font-size: 0.67em; +} +.ql-snow .ql-editor a { + text-decoration: underline; +} +.ql-snow .ql-editor blockquote { + border-left: 4px solid #ccc; + margin-bottom: 5px; + margin-top: 5px; + padding-left: 16px; +} +.ql-snow .ql-editor code, +.ql-snow .ql-editor pre { + background-color: #f0f0f0; + border-radius: 3px; +} +.ql-snow .ql-editor pre { + white-space: pre-wrap; + margin-bottom: 5px; + margin-top: 5px; + padding: 5px 10px; +} +.ql-snow .ql-editor code { + font-size: 85%; + padding-bottom: 2px; + padding-top: 2px; +} +.ql-snow .ql-editor code:before, +.ql-snow .ql-editor code:after { + content: "\A0"; + letter-spacing: -2px; +} +.ql-snow .ql-editor pre.ql-syntax { + background-color: #23241f; + color: #f8f8f2; + overflow: visible; +} +.ql-snow .ql-editor img { + max-width: 100%; +} +.ql-snow .ql-picker { + color: #444; + display: inline-block; + float: left; + font-size: 14px; + font-weight: 500; + height: 24px; + position: relative; + vertical-align: middle; +} +.ql-snow .ql-picker-label { + cursor: pointer; + display: inline-block; + height: 100%; + padding-left: 8px; + padding-right: 2px; + position: relative; + width: 100%; +} +.ql-snow .ql-picker-label::before { + display: inline-block; + line-height: 22px; +} +.ql-snow .ql-picker-options { + background-color: #fff; + display: none; + min-width: 100%; + padding: 4px 8px; + position: absolute; + white-space: nowrap; +} +.ql-snow .ql-picker-options .ql-picker-item { + cursor: pointer; + display: block; + padding-bottom: 5px; + padding-top: 5px; +} +.ql-snow .ql-picker.ql-expanded .ql-picker-label { + color: #ccc; + z-index: 2; +} +.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill { + fill: #ccc; +} +.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke { + stroke: #ccc; +} +.ql-snow .ql-picker.ql-expanded .ql-picker-options { + display: block; + margin-top: -1px; + top: 100%; + z-index: 1; +} +.ql-snow .ql-color-picker, +.ql-snow .ql-icon-picker { + width: 28px; +} +.ql-snow .ql-color-picker .ql-picker-label, +.ql-snow .ql-icon-picker .ql-picker-label { + padding: 2px 4px; +} +.ql-snow .ql-color-picker .ql-picker-label svg, +.ql-snow .ql-icon-picker .ql-picker-label svg { + right: 4px; +} +.ql-snow .ql-icon-picker .ql-picker-options { + padding: 4px 0px; +} +.ql-snow .ql-icon-picker .ql-picker-item { + height: 24px; + width: 24px; + padding: 2px 4px; +} +.ql-snow .ql-color-picker .ql-picker-options { + padding: 3px 5px; + width: 152px; +} +.ql-snow .ql-color-picker .ql-picker-item { + border: 1px solid transparent; + float: left; + height: 16px; + margin: 2px; + padding: 0px; + width: 16px; +} +.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg { + position: absolute; + margin-top: -9px; + right: 0; + top: 50%; + width: 18px; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=''])::before, +.ql-snow .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=''])::before, +.ql-snow .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=''])::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=''])::before, +.ql-snow .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=''])::before, +.ql-snow .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=''])::before { + content: attr(data-label); +} +.ql-snow .ql-picker.ql-header { + width: 98px; +} +.ql-snow .ql-picker.ql-header .ql-picker-label::before, +.ql-snow .ql-picker.ql-header .ql-picker-item::before { + content: 'Normal'; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { + content: 'Heading 1'; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { + content: 'Heading 2'; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { + content: 'Heading 3'; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { + content: 'Heading 4'; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { + content: 'Heading 5'; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { + content: 'Heading 6'; +} +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { + font-size: 2em; +} +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { + font-size: 1.5em; +} +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { + font-size: 1.17em; +} +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { + font-size: 1em; +} +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { + font-size: 0.83em; +} +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { + font-size: 0.67em; +} +.ql-snow .ql-picker.ql-font { + width: 108px; +} +.ql-snow .ql-picker.ql-font .ql-picker-label::before, +.ql-snow .ql-picker.ql-font .ql-picker-item::before { + content: 'Sans Serif'; +} +.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before, +.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before { + content: 'Serif'; +} +.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before, +.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before { + content: 'Monospace'; +} +.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before { + font-family: Georgia, Times New Roman, serif; +} +.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before { + font-family: Monaco, Courier New, monospace; +} +.ql-snow .ql-picker.ql-size { + width: 98px; +} +.ql-snow .ql-picker.ql-size .ql-picker-label::before, +.ql-snow .ql-picker.ql-size .ql-picker-item::before { + content: 'Normal'; +} +.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before, +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before { + content: 'Small'; +} +.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before, +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before { + content: 'Large'; +} +.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before, +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before { + content: 'Huge'; +} +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before { + font-size: 10px; +} +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before { + font-size: 18px; +} +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before { + font-size: 32px; +} +.ql-snow .ql-color-picker.ql-background .ql-picker-item { + background-color: #fff; +} +.ql-snow .ql-color-picker.ql-color .ql-picker-item { + background-color: #000; +} +.ql-toolbar.ql-snow { + border: 1px solid #ccc; + box-sizing: border-box; + font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; + padding: 8px; +} +.ql-toolbar.ql-snow .ql-formats { + margin-right: 15px; +} +.ql-toolbar.ql-snow .ql-picker-label { + border: 1px solid transparent; +} +.ql-toolbar.ql-snow .ql-picker-options { + border: 1px solid transparent; + box-shadow: rgba(0,0,0,0.2) 0 2px 8px; +} +.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label { + border-color: #ccc; +} +.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options { + border-color: #ccc; +} +.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected, +.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover { + border-color: #000; +} +.ql-toolbar.ql-snow + .ql-container.ql-snow { + border-top: 0px; +} +.ql-snow .ql-tooltip { + background-color: #fff; + border: 1px solid #ccc; + box-shadow: 0px 0px 5px #ddd; + color: #444; + padding: 5px 12px; + white-space: nowrap; +} +.ql-snow .ql-tooltip::before { + content: "Visit URL:"; + line-height: 26px; + margin-right: 8px; +} +.ql-snow .ql-tooltip input[type=text] { + display: none; + border: 1px solid #ccc; + font-size: 13px; + height: 26px; + margin: 0px; + padding: 3px 5px; + width: 170px; +} +.ql-snow .ql-tooltip a.ql-preview { + display: inline-block; + max-width: 200px; + overflow-x: hidden; + text-overflow: ellipsis; + vertical-align: top; +} +.ql-snow .ql-tooltip a.ql-action::after { + border-right: 1px solid #ccc; + content: 'Edit'; + margin-left: 16px; + padding-right: 8px; +} +.ql-snow .ql-tooltip a.ql-remove::before { + content: 'Remove'; + margin-left: 8px; +} +.ql-snow .ql-tooltip a { + line-height: 26px; +} +.ql-snow .ql-tooltip.ql-editing a.ql-preview, +.ql-snow .ql-tooltip.ql-editing a.ql-remove { + display: none; +} +.ql-snow .ql-tooltip.ql-editing input[type=text] { + display: inline-block; +} +.ql-snow .ql-tooltip.ql-editing a.ql-action::after { + border-right: 0px; + content: 'Save'; + padding-right: 0px; +} +.ql-snow .ql-tooltip[data-mode=link]::before { + content: "Enter link:"; +} +.ql-snow .ql-tooltip[data-mode=formula]::before { + content: "Enter formula:"; +} +.ql-snow .ql-tooltip[data-mode=video]::before { + content: "Enter video:"; +} +.ql-snow a { + color: #06c; +} +.ql-container.ql-snow { + border: 1px solid #ccc; +} From e9dabe7cd44bfb6d88dc32e5a24161e15f79840b Mon Sep 17 00:00:00 2001 From: Godwin Date: Tue, 18 Apr 2017 21:44:20 -0700 Subject: [PATCH 33/45] Fixed some styling issues --- app/assets/stylesheets/_application.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss index 45289a5..d38248c 100644 --- a/app/assets/stylesheets/_application.scss +++ b/app/assets/stylesheets/_application.scss @@ -1779,6 +1779,11 @@ table.schedule { tbody { border: 0.1rem solid $light-gray; + + th { + white-space: nowrap; + text-align: right; + } } &.locations-1 td.workshop.filled { @@ -1804,6 +1809,7 @@ table.schedule { &.locations-6 td.workshop.filled { width: 16.66667%; } + td { text-align: center; border: 0; From 57fffdf8e840740f543a33d28fceb28a9f206a96 Mon Sep 17 00:00:00 2001 From: Godwin Date: Tue, 18 Apr 2017 21:44:52 -0700 Subject: [PATCH 34/45] Removed Gemfile.lock for now --- Gemfile.lock | 610 --------------------------------------------------- 1 file changed, 610 deletions(-) delete mode 100644 Gemfile.lock diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 930ed95..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,610 +0,0 @@ -GIT - remote: https://github.com/bikebike/bikecollectives_core.git - revision: 3c9c6ac9fb3da839c945fd851f9ab255361ad296 - branch: master - specs: - bikecollectives_core (0.1.0) - activerecord-session_store - carrierwave - carrierwave-imageoptimizer - haml - launchy - letter_opener - mini_magick - pg - premailer-rails - rails (~> 4.2.0) - redcarpet - sass - sass-json-vars - sass-rails - sidekiq - uglifier (>= 1.3.0) - -GIT - remote: https://github.com/bumbleberry/bumbleberry.git - revision: 3b990baa3b5a30dbe378144ed97f0717f910f5f8 - branch: 2017 - specs: - bumbleberry (0.0.1) - blockspring - cairo - railties - rsvg2 - sass-json-vars - sass-rails - -GIT - remote: https://github.com/glebm/to_spreadsheet.git - revision: 4c08455646dd18de51cc1ec05717fbb240c78a68 - specs: - to_spreadsheet (1.0.6) - axlsx - chronic - nokogiri - rails - responders - -GIT - remote: https://github.com/ianfleeton/paypal-express - revision: e40cd3d41d1a5cdf6570332626afd9333fe576f9 - specs: - paypal-express (0.8.1) - activesupport (>= 2.3) - attr_required (>= 0.0.5) - rest-client - -GIT - remote: https://github.com/krzcho/eventmachine - revision: 651a35ee9df9826e048c3b3721e2c6b415c5a328 - branch: master - specs: - eventmachine (1.2.1) - -GIT - remote: https://github.com/lingua-franca/lingua_franca.git - revision: 618d704de71f50cc706c1c073a7f702765c931ac - branch: 2017 - specs: - lingua_franca (0.0.1) - diffy - forgery - http_accept_language - i18n - rails (~> 4.2.0.rc2) - rails-i18n - rubyzip - -GIT - remote: https://github.com/lingua-franca/marmara.git - revision: 51ba7b5ebdd3135b8d127a2bfd065fc20cf5a243 - branch: master - specs: - marmara (1.0.2) - css_parser (>= 1.5.0.pre) - -GIT - remote: https://github.com/tg90nor/sorcery.git - revision: 79b69a87ce168c47fab76921874aa7e8cb727002 - branch: make-facebook-provider-use-json-token-parser - specs: - sorcery (0.10.3) - bcrypt (~> 3.1) - oauth (~> 0.4, >= 0.4.4) - oauth2 (~> 1.0, >= 0.8.0) - -GEM - remote: http://rubygems.org/ - specs: - actionmailer (4.2.0) - actionpack (= 4.2.0) - actionview (= 4.2.0) - activejob (= 4.2.0) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.0) - actionview (= 4.2.0) - activesupport (= 4.2.0) - rack (~> 1.6.0) - rack-test (~> 0.6.2) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.1) - actionview (4.2.0) - activesupport (= 4.2.0) - 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.0) - activesupport (= 4.2.0) - globalid (>= 0.3.0) - activemodel (4.2.0) - activesupport (= 4.2.0) - builder (~> 3.1) - activerecord (4.2.0) - activemodel (= 4.2.0) - activesupport (= 4.2.0) - arel (~> 6.0) - activerecord-session_store (1.0.0) - actionpack (>= 4.0, < 5.1) - activerecord (>= 4.0, < 5.1) - multi_json (~> 1.11, >= 1.11.2) - rack (>= 1.5.2, < 3) - railties (>= 4.0, < 5.1) - activesupport (4.2.0) - i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - addressable (2.5.1) - public_suffix (~> 2.0, >= 2.0.2) - airbrussh (1.2.0) - sshkit (>= 1.6.1, != 1.7.0) - arel (6.0.4) - ast (2.3.0) - attr_required (1.0.1) - axlsx (2.0.1) - htmlentities (~> 4.3.1) - nokogiri (>= 1.4.1) - rubyzip (~> 1.0.0) - bcrypt (3.1.11-x86-mingw32) - better_errors (2.1.1) - coderay (>= 1.0.0) - erubis (>= 2.6.6) - rack (>= 0.9.0) - binding_of_caller (0.7.2) - debug_inspector (>= 0.0.1) - blockspring (0.1.4) - rest-client (> 1.6.7) - builder (3.2.3) - cairo (1.15.5-x86-mingw32) - pkg-config (>= 1.1.5) - callsite (0.0.11) - capistrano (3.8.0) - airbrussh (>= 1.0.0) - i18n - rake (>= 10.0.0) - sshkit (>= 1.9.0) - capistrano-bundler (1.2.0) - capistrano (~> 3.1) - sshkit (~> 1.2) - capistrano-faster-assets (1.0.2) - capistrano (>= 3.1) - capistrano-rails (1.2.3) - capistrano (~> 3.1) - capistrano-bundler (~> 1.1) - capybara (2.13.0) - addressable - mime-types (>= 1.16) - nokogiri (>= 1.3.3) - rack (>= 1.0.0) - rack-test (>= 0.5.4) - xpath (~> 2.0) - capybara-email (2.5.0) - capybara (~> 2.4) - mail - carrierwave (1.0.0) - activemodel (>= 4.0.0) - activesupport (>= 4.0.0) - mime-types (>= 1.16) - carrierwave-imageoptimizer (1.4.0) - carrierwave (>= 0.8, < 2.0) - image_optimizer (~> 1.6) - childprocess (0.6.3) - ffi (~> 1.0, >= 1.0.11) - chronic (0.10.2) - cliver (0.3.2) - coderay (1.1.1) - concurrent-ruby (1.0.5) - connection_pool (2.2.1) - coveralls (0.8.20) - json (>= 1.8, < 3) - simplecov (~> 0.14.1) - term-ansicolor (~> 1.3) - thor (~> 0.19.4) - tins (~> 1.6) - crack (0.4.3) - safe_yaml (~> 1.0.0) - css_parser (1.5.0.pre2) - addressable - cucumber (2.4.0) - builder (>= 2.1.2) - cucumber-core (~> 1.5.0) - cucumber-wire (~> 0.0.1) - diff-lcs (>= 1.1.3) - gherkin (~> 4.0) - multi_json (>= 1.7.5, < 2.0) - multi_test (>= 0.1.2) - cucumber-core (1.5.0) - gherkin (~> 4.0) - cucumber-rails (1.4.5) - capybara (>= 1.1.2, < 3) - cucumber (>= 1.3.8, < 4) - mime-types (>= 1.16, < 4) - nokogiri (~> 1.5) - railties (>= 3, < 5.1) - cucumber-wire (0.0.1) - daemon-spawn (0.4.2) - daemons (1.2.4) - database_cleaner (1.5.3) - debug_inspector (0.0.2) - diff-lcs (1.3) - diffy (3.2.0) - docile (1.1.5) - domain_name (0.5.20170404) - unf (>= 0.0.5, < 1.0.0) - erubis (2.7.0) - execjs (2.7.0) - factory_girl (4.8.0) - activesupport (>= 3.0.0) - factory_girl_rails (4.8.0) - factory_girl (~> 4.8.0) - railties (>= 3.0.0) - faraday (0.11.0) - multipart-post (>= 1.2, < 3) - ffi (1.9.18-x86-mingw32) - forgery (0.6.0) - formatador (0.2.5) - gdk_pixbuf2 (3.1.1-x86-mingw32) - gio2 (= 3.1.1) - geocoder (1.4.3) - gherkin (4.1.1) - gherkin3 (3.1.2) - gio2 (3.1.1-x86-mingw32) - glib2 (= 3.1.1) - gobject-introspection (= 3.1.1) - git-version-bump (0.15.1) - glib2 (3.1.1-x86-mingw32) - cairo (>= 1.12.8) - pkg-config - globalid (0.4.0) - activesupport (>= 4.2.0) - gobject-introspection (3.1.1-x86-mingw32) - glib2 (= 3.1.1) - guard (2.14.1) - formatador (>= 0.2.4) - listen (>= 2.7, < 4.0) - lumberjack (~> 1.0) - nenv (~> 0.1) - notiffany (~> 0.0) - pry (>= 0.9.12) - shellany (~> 0.0) - thor (>= 0.18.1) - guard-compat (1.2.1) - guard-cucumber (2.1.2) - cucumber (~> 2.0) - guard-compat (~> 1.0) - nenv (~> 0.1) - guard-rspec (4.7.3) - guard (~> 2.1) - guard-compat (~> 1.1) - rspec (>= 2.99.0, < 4.0) - haml (4.0.7) - tilt - haml-lint (0.999.999) - haml_lint - haml_lint (0.24.0) - haml (>= 4.0, < 5.1) - rainbow - rake (>= 10, < 13) - rubocop (>= 0.47.0) - sysexits (~> 1.1) - hashdiff (0.3.2) - htmlentities (4.3.4) - http-cookie (1.0.3) - domain_name (~> 0.5) - http_accept_language (2.1.0) - i18n (0.8.1) - image_optimizer (1.7.0) - json (1.8.6) - jwt (1.5.6) - kgio (2.11.0) - launchy (2.4.3) - addressable (~> 2.3) - letter_opener (1.4.1) - launchy (~> 2.2) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) - loofah (2.0.3) - nokogiri (>= 1.5.9) - lumberjack (1.0.11) - mail (2.6.4) - mime-types (>= 1.16, < 4) - meta_request (0.4.0) - callsite (~> 0.0, >= 0.0.11) - rack-contrib (~> 1.1) - railties (>= 3.0.0, < 5.1.0) - metaclass (0.0.4) - method_source (0.8.2) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) - mini_magick (4.7.0) - mini_portile2 (2.1.0) - minitest (5.10.1) - mocha (1.2.1) - metaclass (~> 0.0.1) - multi_json (1.12.1) - multi_test (0.1.2) - multi_xml (0.6.0) - multipart-post (2.0.0) - nenv (0.3.0) - net-scp (1.2.1) - net-ssh (>= 2.6.5) - net-ssh (4.1.0) - netrc (0.11.0) - nokogiri (1.7.1-x86-mingw32) - mini_portile2 (~> 2.1.0) - notiffany (0.1.1) - nenv (~> 0.1) - shellany (~> 0.0) - oauth (0.5.1) - oauth2 (1.3.1) - faraday (>= 0.8, < 0.12) - jwt (~> 1.0) - multi_json (~> 1.3) - multi_xml (~> 0.5) - rack (>= 1.2, < 3) - pango (3.1.1-x86-mingw32) - cairo (>= 1.14.0) - glib2 (= 3.1.1) - parser (2.4.0.0) - ast (~> 2.2) - pg (0.20.0-x86-mingw32) - pkg-config (1.1.7) - poltergeist (1.14.0) - capybara (~> 2.1) - cliver (~> 0.3.1) - websocket-driver (>= 0.2.0) - powerpack (0.1.1) - premailer (1.10.2) - addressable - css_parser (>= 1.4.10) - htmlentities (>= 4.0.0) - premailer-rails (1.9.5) - actionmailer (>= 3, < 6) - premailer (~> 1.7, >= 1.7.9) - pry (0.10.4) - coderay (~> 1.1.0) - method_source (~> 0.8.1) - slop (~> 3.4) - public_suffix (2.0.5) - rack (1.6.5) - rack-contrib (1.4.0) - git-version-bump (~> 0.15) - rack (~> 1.4) - rack-mini-profiler (0.10.2) - rack (>= 1.2.0) - rack-protection (1.5.3) - rack - rack-test (0.6.3) - rack (>= 1.0) - rails (4.2.0) - actionmailer (= 4.2.0) - actionpack (= 4.2.0) - actionview (= 4.2.0) - activejob (= 4.2.0) - activemodel (= 4.2.0) - activerecord (= 4.2.0) - activesupport (= 4.2.0) - bundler (>= 1.3.0, < 2.0) - railties (= 4.2.0) - sprockets-rails - rails-deprecated_sanitizer (1.0.3) - activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.8) - activesupport (>= 4.2.0.beta, < 5.0) - nokogiri (~> 1.6) - rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.3) - loofah (~> 2.0) - rails-i18n (4.0.9) - i18n (~> 0.7) - railties (~> 4.0) - rails_12factor (0.0.3) - rails_serve_static_assets - rails_stdout_logging - rails_serve_static_assets (0.0.5) - rails_stdout_logging (0.0.5) - railties (4.2.0) - actionpack (= 4.2.0) - activesupport (= 4.2.0) - rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rainbow (2.2.1) - raindrops (0.18.0) - rake (11.1.2) - rb-fsevent (0.9.8) - rb-inotify (0.9.8) - ffi (>= 0.5.0) - redcarpet (3.4.0) - redis (3.3.3) - responders (2.3.0) - railties (>= 4.2.0, < 5.1) - rest-client (2.0.1-x86-mingw32) - ffi (~> 1.9) - http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 4.0) - netrc (~> 0.8) - rspec (3.5.0) - rspec-core (~> 3.5.0) - rspec-expectations (~> 3.5.0) - rspec-mocks (~> 3.5.0) - rspec-core (3.5.4) - rspec-support (~> 3.5.0) - rspec-expectations (3.5.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.5.0) - rspec-mocks (3.5.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.5.0) - rspec-rails (3.5.2) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.5.0) - rspec-expectations (~> 3.5.0) - rspec-mocks (~> 3.5.0) - rspec-support (~> 3.5.0) - rspec-support (3.5.0) - rsvg2 (3.1.1-x86-mingw32) - cairo (>= 1.12.8) - gdk_pixbuf2 (= 3.1.1) - pango (>= 3.1.1) - rubocop (0.48.1) - parser (>= 2.3.3.1, < 3.0) - powerpack (~> 0.1) - rainbow (>= 1.99.1, < 3.0) - ruby-progressbar (~> 1.7) - unicode-display_width (~> 1.0, >= 1.0.1) - ruby-progressbar (1.8.1) - ruby_dep (1.3.1) - rubyzip (1.0.0) - safe_yaml (1.0.4) - sass (3.4.23) - sass-json-vars (0.3.3) - sass (>= 3.1) - sass-rails (5.0.6) - railties (>= 4.0.0, < 6) - sass (~> 3.1) - sprockets (>= 2.8, < 4.0) - sprockets-rails (>= 2.0, < 4.0) - tilt (>= 1.1, < 3) - selenium-webdriver (3.3.0) - childprocess (~> 0.5) - rubyzip (~> 1.0) - websocket (~> 1.0) - shellany (0.0.1) - sidekiq (4.2.10) - concurrent-ruby (~> 1.0) - connection_pool (~> 2.2, >= 2.2.0) - rack-protection (>= 1.5.0) - redis (~> 3.2, >= 3.2.1) - simplecov (0.14.1) - docile (~> 1.1.0) - json (>= 1.8, < 3) - simplecov-html (~> 0.10.0) - simplecov-html (0.10.0) - sitemap_generator (5.3.1) - builder (~> 3.0) - slop (3.6.0) - sprockets (3.7.1) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.0) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - sshkit (1.13.1) - net-scp (>= 1.1.2) - net-ssh (>= 2.8.0) - sysexits (1.2.0) - term-ansicolor (1.6.0) - tins (~> 1.0) - thin (1.7.0) - daemons (~> 1.0, >= 1.0.9) - eventmachine (~> 1.0, >= 1.0.4) - rack (>= 1, < 3) - thor (0.19.4) - thread_safe (0.3.6) - tilt (2.0.7) - tins (1.13.2) - tzinfo (1.2.3) - thread_safe (~> 0.1) - tzinfo-data (1.2017.2) - tzinfo (>= 1.0.0) - uglifier (3.2.0) - execjs (>= 0.3.0, < 3) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.3-x86-mingw32) - unicode-display_width (1.2.1) - unicorn (5.3.0) - kgio (~> 2.6) - raindrops (~> 0.7) - wdm (0.1.1) - webmock (3.0.1) - addressable (>= 2.3.6) - crack (>= 0.3.2) - hashdiff - websocket (1.2.4) - websocket-driver (0.6.5) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.2) - win32console (1.3.2-x86-mingw32) - xpath (2.0.0) - nokogiri (~> 1.3) - -PLATFORMS - x86-mingw32 - -DEPENDENCIES - activerecord-session_store - better_errors - bikecollectives_core! - binding_of_caller - bumbleberry! - capistrano (~> 3.1) - capistrano-faster-assets (~> 1.0) - capistrano-rails (~> 1.1) - capybara-email - carrierwave - carrierwave-imageoptimizer - coveralls - cucumber - cucumber-core - cucumber-rails - daemon-spawn - daemons - database_cleaner - eventmachine! - factory_girl_rails - geocoder - gherkin3 (>= 3.1.0) - guard-cucumber - guard-rspec - haml - haml-lint - launchy - letter_opener - lingua_franca! - marmara! - meta_request - mini_magick - mocha - nokogiri - paypal-express! - pg - poltergeist - premailer-rails - rack-mini-profiler - rails (= 4.2.0) - rails_12factor - rake (= 11.1.2) - redcarpet - rspec - rspec-rails - rubocop - ruby_dep (= 1.3.1) - sass - sass-json-vars - sass-rails - selenium-webdriver - sidekiq - simplecov - sitemap_generator - sorcery! - thin - to_spreadsheet! - tzinfo-data - uglifier (>= 1.3.0) - unicorn - wdm (>= 0.1.0) - webmock - win32console - -BUNDLED WITH - 1.14.6 From a88ca85ca50b82b33267f8020bb2f868717e3106 Mon Sep 17 00:00:00 2001 From: LinguaFrancaTranslator Date: Wed, 19 Apr 2017 20:40:46 +0000 Subject: [PATCH 35/45] Fixed paypal express gem location --- features/support/helper.rb | 4 +- features/support/location_cache.yml | 96 ++++++++++++++--------------- 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/features/support/helper.rb b/features/support/helper.rb index d0acf9c..822c437 100644 --- a/features/support/helper.rb +++ b/features/support/helper.rb @@ -54,13 +54,15 @@ def capture_html(distance_from_root = 3) end def attempt_to(refresh_on_fail = false, &block) + exception = nil begin retries ||= 0 timeout ||= 0 timeout += 1 yield rescue Exception => e - raise e unless (retries += 1) <= 4 + exception ||= e + raise exception unless (retries += 1) <= 4 visit TestState.last_page if TestState.last_page && refresh_on_fail sleep(timeout * timeout) retry diff --git a/features/support/location_cache.yml b/features/support/location_cache.yml index 9badcd9..02508d1 100644 --- a/features/support/location_cache.yml +++ b/features/support/location_cache.yml @@ -251,54 +251,6 @@ Portland OR: !ruby/object:Geocoder::Result::Google - locality - political cache_hit: -Prince Rupert BC: !ruby/object:Geocoder::Result::Google - data: - address_components: - - long_name: Prince Rupert - short_name: Prince Rupert - types: - - locality - - political - - long_name: Skeena-Queen Charlotte - short_name: Skeena-Queen Charlotte - types: - - administrative_area_level_2 - - political - - long_name: British Columbia - short_name: BC - types: - - administrative_area_level_1 - - political - - long_name: Canada - short_name: CA - types: - - country - - political - formatted_address: Prince Rupert, BC, Canada - geometry: - bounds: - northeast: - lat: 54.338083 - lng: -130.2437961 - southwest: - lat: 54.19392 - lng: -130.3634291 - location: - lat: 54.3150367 - lng: -130.3208187 - location_type: APPROXIMATE - viewport: - northeast: - lat: 54.3343706 - lng: -130.2478032 - southwest: - lat: 54.202669 - lng: -130.3608029 - place_id: ChIJaUV_axPVclQRElbZTQ_jB3E - types: - - locality - - political - cache_hit: Regina, SK: !ruby/object:Geocoder::Result::Google data: address_components: @@ -501,3 +453,51 @@ Eldorado, MX: !ruby/object:Geocoder::Result::Google - locality - political cache_hit: +Prince Rupert BC: !ruby/object:Geocoder::Result::Google + data: + address_components: + - long_name: Prince Rupert + short_name: Prince Rupert + types: + - locality + - political + - long_name: Skeena-Queen Charlotte + short_name: Skeena-Queen Charlotte + types: + - administrative_area_level_2 + - political + - long_name: British Columbia + short_name: BC + types: + - administrative_area_level_1 + - political + - long_name: Canada + short_name: CA + types: + - country + - political + formatted_address: Prince Rupert, BC, Canada + geometry: + bounds: + northeast: + lat: 54.338083 + lng: -130.2437961 + southwest: + lat: 54.19392 + lng: -130.3634291 + location: + lat: 54.3150367 + lng: -130.3208187 + location_type: APPROXIMATE + viewport: + northeast: + lat: 54.3343706 + lng: -130.2478032 + southwest: + lat: 54.202669 + lng: -130.3608029 + place_id: ChIJaUV_axPVclQRElbZTQ_jB3E + types: + - locality + - political + cache_hit: From a16a8ed082fe4e3c1efbcd6a5a4fa98592879c60 Mon Sep 17 00:00:00 2001 From: LinguaFrancaTranslator Date: Sat, 20 May 2017 15:06:40 +0000 Subject: [PATCH 36/45] New translations --- config/locales/es.yml | 1420 +++++++++-- config/locales/fr.yml | 5340 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 6487 insertions(+), 273 deletions(-) diff --git a/config/locales/es.yml b/config/locales/es.yml index 684c2f5..ef00bd3 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -32,12 +32,12 @@ es: - viernes - sábado formats: - default: '%d/%m/%Y' - long: '%d de %B de %Y' - short: '%d de %b' + default: "%d/%m/%Y" + long: "%d de %B de %Y" + short: "%d de %b" weekday: "%A" - span_same_month_date_2: '%e, %Y' - span_same_month_date_1: '%B %e' + span_same_month_date_2: "%e, %Y" + span_same_month_date_1: "%B %e" span_same_year_date_1: "%B %e" span_same_year_date_2: "%B %e, %Y" span_different_year_date_1: "%B %e, %Y" @@ -60,7 +60,7 @@ es: - :day - :month - :year - date_span: '%{date_1} – %{date_2}' + date_span: "%{date_1} – %{date_2}" datetime: distance_in_words: about_x_hours: @@ -69,39 +69,47 @@ es: about_x_months: one: alrededor de 1 mes other: alrededor de %{count} meses + zero: '' about_x_years: one: alrededor de 1 año other: alrededor de %{count} años + zero: '' almost_x_years: one: casi 1 año other: casi %{count} años + zero: '' half_a_minute: medio minuto less_than_x_minutes: one: menos de 1 minuto other: menos de %{count} minutos + zero: '' less_than_x_seconds: one: menos de 1 segundo other: menos de %{count} segundos + zero: '' over_x_years: one: más de 1 año other: más de %{count} años + zero: '' x_days: one: 1 día - other: '%{count} días' + other: "%{count} días" + zero: '' x_minutes: one: 1 minuto - other: '%{count} minutos' + other: "%{count} minutos" x_months: one: 1 mes - other: '%{count} meses' + other: "%{count} meses" x_seconds: one: 1 segundo - other: '%{count} segundos' + other: "%{count} segundos" x_hours: one: 1 hora other: "%{count} horas" + zero: '' x_and_y: "%{x} %{y}" - time_ago: "hace %{time}" + time_ago: hace %{time} prompts: day: Día hour: Hora @@ -110,7 +118,7 @@ es: second: Segundos year: Año errors: - format: '%{attribute} %{message}' + format: "%{attribute} %{message}" messages: accepted: debe ser aceptado blank: no puede estar en blanco @@ -147,9 +155,12 @@ es: fields: location: empty: Por favor, introduzca su ubicación - unknown: Por favor, incluya su ciudad o pueblo, no hemos podido encontrar una localidad de "%{value}" + unknown: Por favor, incluya su ciudad o pueblo, no hemos podido encontrar + una localidad de "%{value}" name: empty: Por favor ingrese un nombre valido + payment: + incomplete: Su pago no se completó. template: body: 'Se encontraron problemas con los siguientes campos:' header: @@ -157,10 +168,12 @@ es: other: No se pudo guardar este/a %{model} porque se encontraron %{count} errores warnings: messages: - location_corrected: Tu ubicación fue corregida de "%{original}" a "%{corrected}". Si esto no refleja tu ubicación prevista, puedes cambiarla nuevamente en el paso de "información de contacto". + location_corrected: Tu ubicación fue corregida de "%{original}" a "%{corrected}". + Si esto no refleja tu ubicación prevista, puedes cambiarla nuevamente en el + paso de "información de contacto". housing: - space: - overbooked: Con exceso de reservas + space: + overbooked: Con exceso de reservas helpers: select: prompt: Por favor seleccione @@ -171,22 +184,22 @@ es: number: currency: format: - delimiter: . - format: '%n %u' + delimiter: "." + format: "%n %u" precision: 2 - separator: ',' + separator: "," significant: false strip_insignificant_zeros: false - unit: € + unit: "€" format: - delimiter: . + delimiter: "." precision: 3 - separator: ',' + separator: "," significant: false strip_insignificant_zeros: false human: decimal_units: - format: '%n %u' + format: "%n %u" units: billion: mil millones million: millón @@ -200,7 +213,7 @@ es: significant: true strip_insignificant_zeros: true storage_units: - format: '%n %u' + format: "%n %u" units: byte: one: Byte @@ -217,20 +230,20 @@ es: delimiter: '' support: array: - last_word_connector: ', y ' - two_words_connector: ' y ' - words_connector: ', ' + last_word_connector: ", y " + two_words_connector: " y " + words_connector: ", " time: am: am formats: - default: '%A, %d de %B de %Y %H:%M' - long: '%d de %B de %Y %H:%M' - short: '%H:%M' + default: "%A, %d de %B de %Y %H:%M" + long: "%d de %B de %Y %H:%M" + short: "%H:%M" pm: pm languages: af: afrikáans - ar: árabe - az: azerí + ar: "árabe" + az: azerbaiyano bg: búlgaro bn: bengalí bs: bosnio @@ -260,27 +273,27 @@ es: km: jemer kn: canarés ko: coreano - lo: laosiano + lo: lao lt: lituano lv: letón mk: macedonio mn: mongol ms: malayo - nb: bokmal noruego + nb: noruego bokmal ne: nepalí nl: neerlandés - nn: nynorsk noruego + nn: noruego nynorsk or: oriya pl: polaco pt: portugués - rm: retorrománico + rm: romanche ro: rumano ru: ruso sk: eslovaco sl: esloveno sr: serbio sv: sueco - sw: swahili + sw: suajili ta: tamil th: tailandés tl: tagalo @@ -292,6 +305,476 @@ es: wo: wólof be: bielorruso tt: tártaro + aa: afar + ab: abjasio + ace: acehnés + ach: acoli + ada: adangme + ady: adigué + ae: avéstico + afh: afrihili + agq: aghem + ain: ainu + ak: akan + akk: acadio + ale: aleutiano + alt: altái meridional + am: amárico + an: aragonés + ang: inglés antiguo + anp: angika + ar-001: "árabe estándar moderno" + arc: arameo + arn: mapuche + arp: arapaho + arw: arahuaco + as: asamés + asa: asu + ast: asturiano + av: avar + awa: avadhi + ay: aimara + az-alt-short: azerí + ba: baskir + bal: baluchi + ban: balinés + bas: basaa + bax: bamún + bbj: ghomala + bej: beja + bem: bemba + bez: bena + bfd: bafut + bgn: baluchi occidental + bho: bhoyapurí + bi: bislama + bik: bicol + bin: bini + bkm: kom + bla: siksika + bm: bambara + bo: tibetano + br: bretón + bra: braj + brx: bodo + bss: akoose + bua: buriato + bug: buginés + bum: bulu + byn: blin + byv: medumba + cad: caddo + car: caribe + cay: cayuga + cch: atsam + ce: checheno + ceb: cebuano + cgg: chiga + ch: chamorro + chb: chibcha + chg: chagatái + chk: trukés + chm: marí + chn: jerga chinuk + cho: choctaw + chp: chipewyan + chr: cheroqui + chy: cheyene + ckb: kurdo sorani + co: corso + cop: copto + cr: cree + crh: tártaro de Crimea + crs: criollo seychelense + csb: casubio + cu: eslavo eclesiástico + cv: chuvasio + dak: dakota + dar: dargva + dav: taita + de-AT: alemán austríaco + de-CH: alto alemán suizo + del: delaware + den: slave + dgr: dogrib + din: dinka + dje: zarma + doi: dogri + dsb: bajo sorbio + dua: duala + dum: neerlandés medio + dv: divehi + dyo: jola-fonyi + dyu: diula + dz: dzongkha + dzg: dazaga + ebu: embu + ee: ewé + efi: efik + egy: egipcio antiguo + eka: ekajuk + elx: elamita + en-AU: inglés australiano + en-CA: inglés canadiense + en-GB: inglés británico + en-GB-alt-short: inglés (RU) + en-US: inglés estadounidense + en-US-alt-short: inglés (EE. UU.) + enm: inglés medio + es-419: español latinoamericano + es-ES: español de España + es-MX: español de México + ewo: ewondo + fan: fang + fat: fanti + ff: fula + fil: filipino + fj: fiyiano + fo: feroés + fon: fon + fr-CA: francés canadiense + fr-CH: francés suizo + frm: francés medio + fro: francés antiguo + frr: frisón septentrional + frs: frisón oriental + fur: friulano + fy: frisón occidental + ga: irlandés + gaa: ga + gag: gagauzo + gan: chino gan + gay: gayo + gba: gbaya + gd: gaélico escocés + gez: geez + gil: gilbertés + gmh: alto alemán medio + gn: guaraní + goh: alto alemán antiguo + gon: gondi + gor: gorontalo + got: gótico + grb: grebo + grc: griego antiguo + gsw: alemán suizo + gu: guyaratí + guz: gusii + gv: manés + gwi: kutchin + ha: hausa + hai: haida + hak: chino hakka + haw: hawaiano + hil: hiligaynon + hit: hitita + hmn: hmong + ho: hiri motu + hsb: alto sorbio + hsn: chino xiang + ht: criollo haitiano + hup: hupa + hy: armenio + hz: herero + ia: interlingua + iba: iban + ibb: ibibio + ie: interlingue + ig: igbo + ii: yi de Sichuán + ik: inupiaq + ilo: ilocano + inh: ingush + io: ido + iu: inuktitut + jbo: lojban + jgo: ngomba + jmc: machame + jpr: judeo-persa + jrb: judeo-árabe + jv: javanés + ka: georgiano + kaa: karakalpako + kab: cabila + kac: kachin + kaj: jju + kam: kamba + kaw: kawi + kbd: kabardiano + kbl: kanembu + kcg: tyap + kde: makonde + kea: criollo caboverdiano + kfo: koro + kg: kongo + kha: khasi + kho: kotanés + khq: koyra chiini + ki: kikuyu + kj: kuanyama + kk: kazajo + kkj: kako + kl: groenlandés + kln: kalenjin + kmb: kimbundu + koi: komi permio + kok: konkaní + kos: kosraeano + kpe: kpelle + kr: kanuri + krc: karachay-balkar + krl: carelio + kru: kurukh + ks: cachemiro + ksb: shambala + ksf: bafia + ksh: kölsch + ku: kurdo + kum: kumyk + kut: kutenai + kv: komi + kw: córnico + ky: kirguís + la: latín + lad: ladino + lag: langi + lah: lahnda + lam: lamba + lb: luxemburgués + lez: lezgiano + lg: ganda + li: limburgués + lkt: lakota + ln: lingala + lol: mongo + loz: lozi + lrc: lorí septentrional + lu: luba-katanga + lua: luba-lulua + lui: luiseño + lun: lunda + luo: luo + lus: mizo + luy: luyia + mad: madurés + maf: mafa + mag: magahi + mai: maithili + mak: macasar + man: mandingo + mas: masái + mde: maba + mdf: moksha + mdr: mandar + men: mende + mer: meru + mfe: criollo mauriciano + mg: malgache + mga: irlandés medio + mgh: makhuwa-meetto + mgo: meta’ + mh: marshalés + mi: maorí + mic: micmac + min: minangkabau + ml: malayalam + mnc: manchú + mni: manipuri + moh: mohawk + mos: mossi + mr: maratí + mt: maltés + mua: mundang + mul: varios idiomas + mus: creek + mwl: mirandés + mwr: marwari + my: birmano + mye: myene + myv: erzya + mzn: mazandaraní + na: nauruano + nan: chino min nan + nap: napolitano + naq: nama + nd: ndebele septentrional + nds: bajo alemán + nds-NL: bajo sajón + new: newari + ng: ndonga + nia: nias + niu: niueano + nl-BE: flamenco + nmg: kwasio + nnh: ngiemboon + 'no': noruego + nog: nogai + non: nórdico antiguo + nqo: n’ko + nr: ndebele meridional + nso: sesotho septentrional + nus: nuer + nv: navajo + nwc: newari clásico + ny: nyanja + nym: nyamwezi + nyn: nyankole + nyo: nyoro + nzi: nzima + oc: occitano + oj: ojibwa + om: oromo + os: osético + osa: osage + ota: turco otomano + pa: panyabí + pag: pangasinán + pal: pahlavi + pam: pampanga + pap: papiamento + pau: palauano + pcm: pidgin de Nigeria + peo: persa antiguo + phn: fenicio + pi: pali + pon: pohnpeiano + prg: prusiano + pro: provenzal antiguo + ps: pastún + ps-alt-variant: pastú + pt-BR: portugués de Brasil + pt-PT: portugués de Portugal + qu: quechua + quc: quiché + raj: rajasthani + rap: rapanui + rar: rarotongano + rn: kirundi + ro-MD: moldavo + rof: rombo + rom: romaní + root: raíz + rup: arrumano + rw: kinyarwanda + rwk: rwa + sa: sánscrito + sad: sandawe + sah: sakha + sam: arameo samaritano + saq: samburu + sas: sasak + sat: santali + sba: ngambay + sbp: sangu + sc: sardo + scn: siciliano + sco: escocés + sd: sindhi + sdh: kurdo meridional + se: sami septentrional + see: seneca + seh: sena + sel: selkup + ses: koyraboro senni + sg: sango + sga: irlandés antiguo + sh: serbocroata + shi: tashelhit + shn: shan + shu: "árabe chadiano" + si: cingalés + sid: sidamo + sm: samoano + sma: sami meridional + smj: sami lule + smn: sami inari + sms: sami skolt + sn: shona + snk: soninké + so: somalí + sog: sogdiano + sq: albanés + srn: sranan tongo + srr: serer + ss: suazi + ssy: saho + st: sesotho meridional + su: sundanés + suk: sukuma + sus: susu + sux: sumerio + sw-CD: suajili del Congo + swb: comorense + syc: siríaco clásico + syr: siriaco + te: telugu + tem: temne + teo: teso + ter: tereno + tet: tetún + tg: tayiko + ti: tigriña + tig: tigré + tiv: tiv + tk: turcomano + tkl: tokelauano + tlh: klingon + tli: tlingit + tmh: tamashek + tn: setsuana + to: tongano + tog: tonga del Nyasa + tpi: tok pisin + trv: taroko + ts: tsonga + tsi: tsimshiano + tum: tumbuka + tvl: tuvaluano + tw: twi + twq: tasawaq + ty: tahitiano + tyv: tuviniano + tzm: tamazight del Atlas Central + udm: udmurt + ug: uigur + ug-alt-variant: uygur + uga: ugarítico + umb: umbundu + und: lengua desconocida + vai: vai + ve: venda + vo: volapük + vot: vótico + vun: vunjo + wa: valón + wae: walser + wal: wolayta + war: waray + was: washo + wbp: warlpiri + wuu: chino wu + xal: kalmyk + xh: xhosa + xog: soga + yao: yao + yap: yapés + yav: yangben + ybb: yemba + yi: yidis + yo: yoruba + yue: cantonés + za: zhuang + zap: zapoteco + zbl: símbolos Bliss + zen: zenaga + zgh: tamazight estándar marroquí + zh: chino + zh-Hans: chino simplificado + zh-Hant: chino tradicional + zu: zulú + zun: zuñi + zxx: sin contenido lingüístico + zza: zazaki geography: countries: AD: Andorra @@ -310,7 +793,7 @@ es: AW: Aruba AX: Islas de Åland AZ: Azerbaiyán - BA: Bosnia y Herzegovina + BA: Bosnia-Herzegovina BB: Barbados BD: Bangladesh BE: Bélgica @@ -458,7 +941,7 @@ es: NE: Níger NF: Isla Norfolk NG: Nigeria - NI: República de Nicaragua + NI: Nicaragua NL: Holanda 'NO': Noruega NP: Nepal @@ -478,7 +961,7 @@ es: PR: Puerto Rico PS: Territorios Palestinos PT: Portugal - PW: Palau + PW: Palaos PY: Paraguay QA: Qatar RE: Reunión @@ -547,10 +1030,33 @@ es: subregions: AR: BA: Buenos Aires + CF: Buenos Aires C.F. + CT: Catamarca + CC: Chaco + CH: Chubut + CD: Córdoba + CR: Corrientes + ER: Entre Ríos + FO: Formosa + JY: Jujuy + LP: La Pampa + LR: La Rioja + MZ: Mendoza + MN: Misiones + NQ: Neuquén + RN: Río Negro + SA: Salta + SJ: San Juan + SL: San Luis + SC: Santa Cruz + SF: Santa Fe + SE: Santiago del Estero + TF: Tierra del Fuego + TM: Tucumán AT: Bgld: Burgenland AU: - ACT: Australian Capital Territory + ACT: Territorio de la Capital Australiana NSW: New South Wales NT: Territorio del Norte QLD: Queensland @@ -643,6 +1149,15 @@ es: ZAC: Zacatecas Jal: Jalisco DIF: Federal District + Gto: Guanajuato + MIC: Michoacán de Ocampo + NLE: Nuevo León + Qro: Querétaro de Arteaga + QRoo: Quintana Roo + Camps: Campeche + Dgo: Durango + Tamps: Tamaulipas + TLA: Tlaxcala MY: JHR: Johor KDH: Kedah @@ -664,6 +1179,18 @@ es: RAAN: North Atlantic Autonomous Region (RAAN) NL: GLD: Güeldres + fr: Frisia + gr: Groninga + lb: Limburgo + nb: Brabante Septentrional + nh: Holanda Septentrional + dr: Drente + fl: Flevolanda + gd: Güeldres + ov: Provincie Overijssel + ut: Provincie Utrecht + zl: Zelanda + zh: Holanda Meridional PH: ARMM: Mindanao Musulmán US: @@ -678,7 +1205,7 @@ es: DE: Delaware FL: Florida GA: Georgia - HI: Hawai + HI: Hawái ID: Idaho IL: Illinois IN: Indiana @@ -696,7 +1223,7 @@ es: MT: Montana NE: Nebraska NV: Nevada - NH: Nueva Hampshire + NH: Nuevo Hampshire NJ: Nueva Jersey NM: Nuevo México NY: Nueva York @@ -719,26 +1246,41 @@ es: WV: Virginia Occidental WI: Wisconsin WY: Wyoming + ET: + SNNPR: SNNPR + FR: + ACAL: Grand-Est + NG: + FCTA: FCT + PK: + FATA: "Áreas tribales" + RU: + MO: "Óblast de Moscú" formats: - city_region_country: '%{city}, %{region}, %{country}' - city_country: '%{city}, %{country}' - city_region: '%{city}, %{region}' - region_country: '%{region}, %{country}' + city_region_country: "%{city}, %{region}, %{country}" + city_country: "%{city}, %{country}" + city_region: "%{city}, %{region}" + region_country: "%{region}, %{country}" conference: actions: Register: Regístrate modals: - confirm: Por favor, confirmar + confirm: Por favor confirma yes_button: Sí no_button: 'No' done_button: Cerca info: Más información workshops: facilitators: - confirm_remove: ¿Estás seguro de que quieres eliminar %{user_name} como facilitador de este taller? - confirm_remove_self: ¿Estás seguro de que quieres eliminar a sí mismo como un facilitador de este taller? - confirm_cancel_request: ¿Está seguro de que desea cancelar su solicitud para convertirse en un facilitador de este taller? - confirm_transfer_ownership: Mediante la transferencia de la propiedad, perderá las capacidades administrativas, como la eliminación y la aprobación de nuevos facilitadores. ¿Seguro que deseas transferir la propiedad a %{user_name}? + confirm_remove: "¿Estás segurx de que quieres eliminar a %{user_name} como + facilitador (a) de este taller?" + confirm_remove_self: "¿Estás segurx de querer eliminarte a tí mismx como facilitador(a) + de este taller?" + confirm_cancel_request: "¿Estás segurx de que deseas cancelar tu solicitud + para ayudar a facilitar este taller?" + confirm_transfer_ownership: Al tansferir atributos, perderás las funciones + de administración, como la eliminación y la aprobación de nuevxs facilitadores. + ¿Seguro que deseas transferir atributos a %{user_name}? donate: button_label: Donar articles: @@ -750,55 +1292,54 @@ es: future: Próximas conferencias passed: Conferencias aprobadas paragraphs: - conferences: Bike!Bike! conferencias se llevan a cabo nacionalmente una vez al año en una ciudad diferente en América del Norte; Las conferencias regionales pueden celebrarse en cualquier lugar y en cualquier momento. + conferences: Las conferencias de Bike!Bike! se llevan a cabo nacionalmente + una vez al año en una ciudad diferente en América del Norte; Las conferencias + regionales pueden celebrarse en cualquier lugar y en cualquier momento. conference_registration: headings: - Pre_Register: Pre-Registrarse para %{title} - Register: Registrarse para %{title} Workshops_Looking_For_Facilitators: Talleres que necesitan facilitadorxs Add_Workshop: Proponer un taller workshops: Talleres Registration_Info: Registro - arrival_and_departure: ¿Que día llegas a %{city}? - allergies: ¿Tienes alergias? - languages: ¿Hablas? - food: ¿Que comes? + arrival_and_departure: "¿Que día llegas a %{city}?" + allergies: "¿Tienes alergias?" + languages: "¿Qué idiomas hablas?" + food: "¿Que comes?" Enter_Your_Email: Correo electrónico - location: Ciudad y pais  - name: Nombre - preferred_language: ¿Qué idioma prefieres usar? - Allergies: ¿Tienes alergias? + location: De dónde vienes? + name: Cuál es tu nombre? + preferred_language: "¿Qué idioma prefieres usar?" + Allergies: "¿Tienes alergias?" Workshops: Talleres Your_Workshops: Tus talleres add_facilitator: Añadir un(a) facilitador(a) - Policy_Agreement: Acuerdo del Espacio Mas Seguro + Policy_Agreement: Acuerdo de Espacio Más Seguro Preview: Vista previa Stats: Estadísticas Your_Registration: Tu registro Your_Conferences: Tus conferencias - Youre_Done: ¡Estás listx! + Youre_Done: "¡Estás listx!" arrival: Llegada - bike: ¿Necesitas una bici? + bike: "¿Necesitas una bici?" departure: Salida email_confirm: Confirmar correo electrónico - housing: ¿Necesitas hospedaje? - other: ¿Hay algo más que quisieras decirnos? - Payment: Pago + housing: "¿Necesitas hospedaje?" + other: "¿Hay algo más que quisieras decirnos?" + Payment: Tarifas de registro payment: Contribución payment_confirm: Por favor confirma tu pago city: Ciudad date: Fecha email: Email fees_paid: Cargos pagados - Pre_Registration_Details: ¡El Pre-Registro ya está abierto! - Registration_Details: ¡El Registro ya está abierto! - can_provide_housing: ¿Puedes proporcionar hospedaje a quienes visitan la ciudad? + Pre_Registration_Details: "¡El Pre-Registro ya está abierto!" + Registration_Details: "¡El Registro ya está abierto!" + can_provide_housing: "¿Puedes proporcionar hospedaje a quienes visitan la + ciudad?" Verify_Account: Verifica tu cuenta policy: Normatividad Contact_Info: Información de contacto contact_info: Información de contacto - workshops: Talleres - Workshops: Talleres Administration: Administrar cuenta administration: Administrar cuenta hosting: Hospedaje @@ -813,49 +1354,278 @@ es: space: Espacio Disponible availability: Disponibilidad address: Dirección + Register: Regístrate para %{title} + Back_to: Regresar a  + confirm_payment: Confirmación del Pago de registro + group_ride: Planeas asistir a la rodada grupal? + hosting_address: Cuál es tu dirección? + hosting_attending: Asistirás a la conferencia? + hosting_check: Tienes disposición para ofrecer alojamiento? + hosting_end_date: Fecha en la que concluye el alojamiento + hosting_info: Raglas e Información del alojamiento + hosting_other: Información para organizadores + hosting_phone: Cuál es tu número telefónico? + hosting_space_beds: Camas y espacios para dormir + hosting_space_floor: Espacios en el piso + hosting_space_tent: Espacios para tiendas de campaña + hosting_start_date: Fecha de inicio del alojamiento + housing_arrival_date: Fecha de llegada + housing_bike: Necesitas una bici en préstamo? + housing_companion_check: Acompañante de hospedaje + housing_companion_email: Correo electrónico de tu acompañante + housing_departure_date: Fecha de partida + housing_food: Cuáles son tus hábitos alimenticios? + housing_other: Otras consideraciones + housing_type: Hospedaje + org_create_address: Dirección de tu organización + org_create_email: Correo electrónico de tu organización + org_create_mailing_address: Dirección postal de tu organización (para envío + de correspondencia) + org_create_name: Nombre de tu organización + org_location: Ciudad o localidad en la que está ubicada tu organización + org_location_confirm: Confirma tu ubicación + org_member: Representas a un colectivo ciclista? + org_non_member_interest: Porqué estás interesadx en asistir a Bike!Bike!? + org_select: A qué organización perteneces? + payment_form: Monto de tarifa de registro + payment_type: Modo de pago del registro + pronoun: Pronombre con el que te identificas (él/ella/ellxs) + review: Revisar + your_location: Ciudad o localidad en la que vives? paragraphs: Payment_Made: Usted ya ha realizado un pago de %{fees_paid}. - Payment_Add: ¡Gracias! Puede añadir a esta cantidad si desea realizar otra de pago a continuación. - companion: ¿Quieres que nos aseguremos de que la persona que viaja contigo se hospede en el mismo lugar que tú? - Workshops_You_Have_Requested: La siguiente es una lista de todos los talleres que pediste facilitar. - Workshops_Looking_For_Facilitators: Te gustaría ayudar a facilitar un taller propuesto por otra persona? A continuación se muestra una lista de los talleres que necesitan facilitadorxs. Si estás interesadx en ayudar, puedes facilitar uno de estos talleres. - Your_Workshops: La siguiente es una lista de todos los talleres que se están facilitando. + Payment_Add: "¡Gracias! Abajo puedes agregar más dinero a esta cantidad si + deseas realizar otro pago. " + companion: "¿Quieres que nos aseguremos de que la persona que viaja contigo + se hospede en el mismo lugar que tú?" + Workshops_You_Have_Requested: La siguiente es una lista de todos los talleres + que pediste facilitar. + Workshops_Looking_For_Facilitators: Te gustaría ayudar a facilitar un taller + propuesto por otra persona? A continuación se muestra una lista de los talleres + que necesitan facilitadorxs. Si estás interesadx en ayudar, puedes facilitar + uno de estos talleres. + Your_Workshops: La siguiente es una lista de todos los talleres que se están + facilitando. host: - address: Tu dirección y número telefónico serán compartidos con lxs visitantes y lxs organizadorxs de Bike!Bike! - space: ¿Cuántos espacios tienes para compartir? - availability: Lo más común, es que lxs asistentes lleguen y se vayan los días en que inicia y finaliza la conferencia pero a menudo algunxs visitantes llegan unos días antes y se van unos días después. ¿Cuándo estará disponible tu espacio? - considerations: Lxs organizadores tratarán de emparejar de la mejor manera posible anfitriones con invitadxs. ¿Cuál de las siguientes consideraciones crees que lxs organizadores deben tener en cuenta al momento de escoger a quien vaya a quedarse contigo? - notes: Deja un mensaje a lxs organizadores respecto a lo que deben considerar al momento de escoger a la persona que vas a hospedar. - Policy_Agreement: Asegurar que todos los asistentes se sientan bienvenidos, seguro y respetada en todo momento es especialmente importante para todos nosotros. Por favor asegúrese de que plenamente ha leído y entendido nuestra política de espacios más seguros a continuación, si tiene alguna pregunta o preocupación que usted puede llegar a los organizadores en cualquier momento. - facebook_sign_in: De manera alternativa puedes registrarte usando tu cuenta de Facebook y así evitarás tener que esperar a que te enviemos un correo electrónico - can_provide_housing: Alojar a lxs asistentes a la conferencia es una parte importante de Bike!Bike!, proporciona a mucha gente que normalmente no podría costear el hospedaje, la oportunidad de venir a la conferencia, permitiéndoles conocer nuestra ciudad y a sus residentes. Tendrás que conceder acceso a tu casa a lxs huéspedes durante la noche, teniendo en cuenta que los eventos pueden terminar hasta muy tarde. Si no se puede acceder a tu casa durante el día, debes asegurarte de comunicar estoy muy claramente a tus invitadxs. - not_attending: Si sólo está proporcionando vivienda y no desea recibir más actualizaciones sobre la conferencia, marque esta casilla. - Verify_Account: Con el fin de confirmar que eres una persona real y que podremos contactarte posteriormente, te pedimos que por favor verifiques tu dirección de correo electrónico. Te enviaremos un mensaje para que puedas continuar con el proceso de pre-registro. - Pre_Registration_Details: Al completar el proceso de pre-registro nos estás haciendo saber que estás interesad@ en asistir, lo que nos permite contactarte cuando tengamos noticias, y planear mejor [el evento] sabiendo quienes podrían venir. Una vez que el registro esté completamente abierto, necesitaremos hacerte algunas preguntas más en donde podrás confirmar si asistirás o no. - Registration_Details: Al completar el proceso de registro, estás confirmándonos que vendrás, lo que nos permite contactarte cuando tengamos noticias y nos posibilita planear mejor el hospedaje al saber cuántas personas vendrán. Una vez registradx, tendrás la opción de acceder a facilitar tu propio taller, y a ver detalles de los talleres ya existentes. Si ya estás registradx o preregistradx, puedes utilizas esta forma de inscripción y acceder a tu cuenta ó actualizarla. - Registration_Info: Por favor ayúdanos llenando esta forma de registro y así estar preparadxs para tu llegada a %{city}. Si tienes alguna duda o quieres añadir información que no te hayamos solicitado, te pedimos que llenes el campo de preferencias al final de la página o utilices el enlace “contáctanos”. - Payment: Gracias por completar tu registro. Nos veremos en Bike!Bike! La contribución puedes hacerla ahora o al momento de tu llegada. Sin embargo, te pedimos que lo hagas tan pronto como te sea posible para ayudarnos a financiar la conferencia. - Workshops: 'Título de la taller: Que necesitas? Tipo de espacio? Tema? Quién es responsable? Descripción? Tiempo?' - Create_Workshop: En Bike!Bike! cualquiera puede facilitar un taller o simplemente proponer una idea. Lxs organizadorxs de la conferencia confirmarán si se efectúa el taller, tanto como el lugar y horario. - Confirm_Agreement: Al hacer click en el botón "Estoy de acuerdo", estás comprometiéndote a cumplir el Acuerdo de Espacios Más Seguros de Bike!Bike!. ¡Gracias! - Email_Participants: Esta página se usa para contactar a todxs lxs participantes. El texto se puede ingresar como [Markdown](http://daringfireball.net/projects/markdown/basics). - Presionar "prueba" enviará el correo solamente a tí, asegurate de hacerlo y sé cuidadosx antes de presionar 'enviar'. + address: Tu dirección y número telefónico serán compartidos con lxs visitantes + y lxs organizadorxs de Bike!Bike! + space: "¿Cuántos espacios tienes para compartir?" + availability: Lo más común, es que lxs asistentes lleguen y se vayan los + días en que inicia y finaliza la conferencia pero a menudo algunxs visitantes + llegan unos días antes y se van unos días después. ¿Cuándo estará disponible + tu espacio? + considerations: Lxs organizadores tratarán de emparejar de la mejor manera + posible anfitriones con invitadxs. ¿Cuál de las siguientes consideraciones + crees que lxs organizadores deben tener en cuenta al momento de escoger + a quien vaya a quedarse contigo? + notes: Deja un mensaje a lxs organizadores respecto a lo que deben considerar + al momento de escoger a la persona que vas a hospedar. + Policy_Agreement: Asegurarnos que quienes asisten se sientan bienvenidxs, + segurxs y respetadxs en todo momento es especialmente importante para todxs + nosotrxs. Por favor asegúrate de que has leído y entendido plenamente la + política de espacios más seguros que se presenta a continuación. Si tienes + alguna duda o preocupación, puedes hacerla llegar a lxs organizadores en + cualquier momento. + facebook_sign_in: De manera alternativa puedes registrarte usando tu cuenta + de Facebook y así evitarás tener que esperar a que te enviemos un correo + electrónico + can_provide_housing: Alojar a lxs asistentes a la conferencia es una parte + importante de Bike!Bike!, ya que proporciona a mucha gente que normalmente + no podría costear el hospedaje, la oportunidad de venir a la conferencia, + permitiéndoles conocer nuestra ciudad y a sus residentes. Tendrás que conceder + acceso a tu casa a lxs huéspedes durante la noche, teniendo en cuenta que + los eventos pueden terminar hasta muy tarde. Si no se puede acceder a tu + casa durante el día, debes asegurarte de comunicar esto muy claramente a + tus invitadxs. + not_attending: Si sólo está proporcionando vivienda y no desea recibir más + actualizaciones sobre la conferencia, marque esta casilla. + Verify_Account: Con el fin de confirmar que eres una persona real y que podremos + contactarte posteriormente, te pedimos que por favor verifiques tu dirección + de correo electrónico. Te enviaremos un mensaje para que puedas continuar + con el proceso de pre-registro. + Pre_Registration_Details: Al completar el proceso de pre-registro nos estás + haciendo saber que estás interesad@ en asistir, lo que nos permite contactarte + cuando tengamos noticias, y planear mejor [el evento] sabiendo quienes podrían + venir. Una vez que el registro esté completamente abierto, necesitaremos + hacerte algunas preguntas más en donde podrás confirmar si asistirás o no. + Registration_Details: Al completar el proceso de registro, estás confirmándonos + que vendrás, lo que nos permite contactarte cuando tengamos noticias y nos + posibilita planear mejor el hospedaje al saber cuántas personas vendrán. + Una vez registradx, tendrás la opción de acceder a facilitar tu propio taller, + y a ver detalles de los talleres ya existentes. Si ya estás registradx o + preregistradx, puedes utilizar esta forma de inscripción y acceder a tu + cuenta ó actualizarla. + Registration_Info: Por favor ayúdanos llenando esta forma de registro y así + estar preparadxs para tu llegada a %{city}. Si tienes alguna duda o quieres + añadir información que no te hayamos solicitado, te pedimos que llenes el + campo de preferencias al final de la página o utilices el enlace “contáctanos”. + Payment: Gracias por completar tu registro. Nos veremos en Bike!Bike! La contribución + puedes hacerla ahora o al momento de tu llegada. Sin embargo, te pedimos + que lo hagas tan pronto como te sea posible para ayudarnos a financiar la + conferencia. + Workshops: 'Título de la taller: Que necesitas? Tipo de espacio? Tema? Quién + es responsable? Descripción? Tiempo?' + Create_Workshop: En Bike!Bike! cualquiera puede facilitar un taller o simplemente + proponer una idea. Sin embargo, lxs organizadorxs de la conferencia son + quienes confirmarán si se efectúa el taller, y el lugar y horario en que + este se realiza. + Confirm_Agreement: Al hacer click en el botón "Estoy de acuerdo", estás comprometiéndote + a cumplir el Acuerdo de Espacios Más Seguros de Bike!Bike!. ¡Gracias! + Email_Participants: Esta página se usa para contactar a todxs lxs participantes. + El texto se puede ingresar como [Markdown](http://daringfireball.net/projects/markdown/basics). + Presionar "prueba" enviará el correo solamente a tí, asegurate de hacerlo + y sé cuidadosx antes de presionar 'enviar'. Contact_Info: Por favor déjanos conocerte un poco más. - Stats: Ve quién vendrá y lo que han pagado hasta ahora. Ve en resumen cuánto hospedaje se necesita y qué prefiere comer la gente. - confirm_email_address: Te pedimos confirmar tu dirección de correo electrónico primero, al ser confirmada podrás completar el registro, agregar talleres y pagar el donativo de registro. Si ya te registraste puedes re-confirmar tu dirección - de correo electrónico para modificar los detalles de tu registro. - currency: (cantidades en dólares estadounidenses USD) - done: ¡Gracias por registrarte para Bike!Bike! - email_confirm: ¡Ve a tu bandeja de entrada! Deberías ver un correo electrónico por parte de Bike!Bike! en unos momentos. Contendrá un enlace sobre el cual debes hacer click. Revisa tu bandeja de correo no deseado si no lo ves. Si tienes algún problema, por favor envía un correo electrónico a admin@bikebike.org + Stats: Ve quién vendrá y lo que han pagado hasta ahora. Ve en resumen cuánto + hospedaje se necesita y qué prefiere comer la gente. + confirm_email_address: Te pedimos confirmar tu dirección de correo electrónico + primero, al ser confirmada podrás completar el registro, agregar talleres + y pagar el donativo de registro. Si ya te registraste puedes re-confirmar + tu dirección de correo electrónico para modificar los detalles de tu registro. + currency: "(cantidades en dólares estadounidenses USD)" + done: "¡Gracias por registrarte para Bike!Bike!" + email_confirm: "¡Ve a tu bandeja de entrada! Deberías ver un correo electrónico + por parte de Bike!Bike! en unos momentos. Contendrá un enlace sobre el cual + debes hacer click. Revisa tu bandeja de correo no deseado si no lo ves. + Si tienes algún problema, por favor envía un correo electrónico a admin@bikebike.org" participants_emailed: Tu correo ha sido enviado a todxs lxs participantes de %{conference_title}. - payment_confirm: Estás por confirmar tu pago de %{amount} para el registro. - workshops: Ahora puedes revisar los talleres propuestos e incluso proponer uno si quieres. + payment_confirm: Estás por confirmar tu pago de $%{amount} para el registro. + workshops: Ahora puedes revisar los talleres propuestos e incluso proponer + uno si quieres. + provide_email: Bike!Bike! el correo electrónico para comunicarse entre usted + y sus anfitriones de la conferencia, sin embargo su cuenta de Facebook no + nos proporciona una dirección de correo electrónico. Antes de continuar, + debe proporcionarnos una dirección de correo electrónico. + confirm_payment: Gracias. Por favor confirma tu pago de %{amount} %{currency} + currency_details: Cuando llegues, por favor realiza tu pago en %{currency} + group_ride: Habrá una rodada grupal después de la conferencia, planeas ir? + hosting_address: Necesitaremos compartir tu dirección con tus huéspedes para + que puedan encontrar tu casa. Adicionalmente, los visitantes internacionales + necesitarán proveer una dirección a las autoridades migratorias para poder + entrar al país. + hosting_attending: Estás planeando asistir a la conferencia y eventos de B!B!? + hosting_check: Buscamos voluntarios en %{city} que puedan hospedar visitantes + entre %{date_span} en camas, sofás, en el piso o que tengan espacio en su + patio para instalar tiendas de campaña.Te pediremos que nos proporciones + la ubicación de tu casa, tu número telefónico y las reglas para huéspedes. + Haremos lo posible por emparejarte con huéspedes que coincidan con los criterios + que nos des. + hosting_end_date: Hasta qué día estás dispuestx a tener huéspedes en tu casa? + hosting_info: Por favor, indícanos las reglas de tu casa, instrucciones para + contactarte y cualquier otra información que consideres pertinente acerca + de tu casa o tu barrio. Tu información solo será visible para lxs organizadores + de la conferencia y tus huéspedes. + hosting_other: Por favor, añade cualquier información que te gustaría que + fuera tomada en cuenta por los organizadores al momento de seleccionar a + tus huéspedes. Si tienes restricciones respecto a quienes quieres recibir + en tu casa o si tienes mascotas o quieres señalar algún otro factor que + pudiera causar que tus huéspedes se sientan incómodxs o que no sean aptos + para quedarse en tu casa, escríbelo aquí. + hosting_phone: Por favor proporciona un número telefónico al cual tus huéspedes + puedan contactarte. Puedes añadir detalles respecto a cómo esperas que les + huéspedes te contacten en tus propias reglas. + hosting_space_beds: Por favor, cuéntanos a cuántas personas puedes acomodar + en camas y sofás. + hosting_space_floor: Por favor cuéntanos a cuántas personas puedes recibir + para que se acomoden en el piso. + hosting_space_tent: Estás dispuestx a recibir personas que acomoden tiendas + de campaña en tu patio? + hosting_start_date: Desde qué día estás dispuestx a recibir huéspedes en tu + casa? + housing_arrival_date: Cuándo llegarás a %{city}? Si vas a llegar a la ciudad + antes de %{min_date} y necesitas hospedaje o una bici, por favor selecciona + la fecha en el calendario que está abajo y contáctanos después de que termines + tu registro. No podemos garantizarte hospedaje, pero haremos lo posible. + housing_bike: Para hacernos una idea de cuántas bicicletas necesitamos preparar, + por favor avísanos si te gustaría que te prestáramos una bicicleta. + housing_companion_check: Viajas con tu pareja o con alguien que definitivamente + deba hospedarse junto a ti? Ten en cuenta que tu acompañante debe registrarse + también si quieren ser hospedadxs juntxs? + housing_companion_email: Cuál es la dirección de correo electrónico de tu + acompañante? + housing_departure_date: Cuándo te irás de %{city}? Si deseas quedarte más + tiempo y necesitas hospedaje o una bici después de %{max_date}, por favor + selecciona las fechas en el calendario de bajo y contáctanos después de + concluir tu registro. No podemos garantizarte hospedaje pero haremos lo + posible. + housing_food: Por favor cuéntanos de tus hábitos alimenticios seleccionando + la opción que mejor describa el tipo de comida que comes. Usaremos esto + para decidir el tipo de comidas que prepararemos y la cantidad de alimentos + que necesitaremos. + housing_other: Tienes algún requerimiento especial o consideraciones que creas + que debamos tener respecto a tu estadía, tales como alergias, limitaciones + de movilidad o discapacidades, o si necesitas guardería? Hacérnoslo saber + por adelantado nos ayudará a determinar mejor el lugar en el que te hospedarás + y si es necesario, preparar los mismos lugares en los que se realizará la + conferencia para tu mejor accesibilidad a ellos. + housing_type: Necesitas un lugar para quedarte en %{city} ? Haremos lo posible + por ubicarte con un(a) anfitrión(a) local y con lxs visitantes que mejor + coincidan con tus necesidades.  + languages: Saber cuánta gente habla cada idioma nos ayudará a planear mejor + las traducciones de los talleres, eventos y anuncios. Si eres bilingüe, + se te dará acceso a herramientas que te ayuden a traducir contenidos. Podríamos + pedirte ayuda adicional si la necesitamos, pero no estás obligado a ser + voluntarix.  + name: Cómo te gustaría que nos dirijamos a ti en la conferencia y en este + sitio? + org_create_address: Por favor proporciona la dirección de tu organización + en %{city} sin ingresar estado, país, ni provincia. Si tu organización no + es de %{city}, por favor regresa y cambia tu ubicación. + org_create_email: Para poder contactar a tu organización el próximo año, por + favor proporciona un correo electrónico de la misma. No te enviaremos correos + con regularidad. Te escribiremos si hay alguna conferencia en tu área o + para confirmar que tu organización aún existe. Por favor no des una dirección + electrónica personal. + org_create_mailing_address: Por favor proporciona una dirección de correspondencia + de tu organización, así podremos enviarles una invitación personal y el + poster de Bike!Bike! del siguiente año. + org_create_name: Cómo se llama tu organización? + org_location_confirm: Quisiste decir %{city}? + org_member: Eres voluntarix o trabajas para una organización que anteponga + la enseñanza en bici, el acceso a la misma o el activismo por sobre las + ganancias? Si es así nos gustaría conocer detalles al respecto.  + org_non_member_interest: Cualquiera es bienvenidx para asistir a Bike!Bike! + pero ya que la mayoría de asistentes trabajan o realizan voluntariado con + colectivos ciclistas, nos gustaría conocer mejor lo que les ha atraído particularmente + para asistir este año. + org_select: Por favor selecciona la organización en la que participas. Esta + información será usada para contactar e invitar a tu organización el próximo + año y para llenar la lista de organizaciones conocidas que se desplegará + en bikecollectives.org. Si estás involucradx con múltiples organizaciones, + selecciona una por ahora, tendrás la posibilidad de agregar más organizaciones + en fechas posteriores. + payment_form: Por favor selecciona una de las opciones predefinidas de abajo + o ingresa una cantidad personalizada. + payment_type: Si puedes, por favor realiza el pago ahora por PayPal. Consideramos + la tarifa de registro como donación. Tu donación paga por espacios, comida, + equipo y mucho más. Pagar ahora nos permite prepararnos para la conferencia + sin que el dinero salga de nuestros bolsillos. De otra forma, por favor + comprométete a realiza el pago al momento de tu llegada. Tu compromiso nos + ayudará a hacer un estimado de que podemos costear y qué no. Gracias.  + policy: Asegurarnos que quienes asisten se sientan bienvenidxs, segurxs y + respetadxs en todo momento es especialmente importante para todxs nosotrxs. + Por favor asegúrate de que has leído y entendido plenamente la política + de espacios más seguros que se presenta a continuación. Si tienes alguna + duda o preocupación, puedes hacerla llegar a lxs organizadores en cualquier + momento. + pronoun: Es importante que las comunicaciones contigo y acerca de ti sean + tan respetuosas como sea posible. Si no nos proporcionas un pronombre, lxs + organizadores pueden asumir y referirse a ti como él, ella, o ellos, basados + sólo en tu nombre y en la que asuman como tu identidad de acuerdo a cómo + te ven. + pronoun_optional: Proporcionar un pronombre es opcional. + registration_cancelled: Haz cancelado tu registro. Si lo reconsideras, por + favor abre nuevamente tu registro usando el botón de abajo, teniendo en + cuenta que pueden acabarse los alojamientos y bicicletas disponibles si + esperas demasiado. + review: Haz concluido tu registro, por favor revisa y si es necesario modifica + tus datos.  can_provide_housing: Puedo proporcionar hospedaje - not_attending: Yo no asistirá a la conferencia + not_attending: No asistiré a la conferencia questions: food: - meat: Carnívorx + meat: Omnívorx vegetarian: Vegetarianx vegan: Veganx bike: @@ -863,7 +1633,7 @@ es: medium: Mediana small: Pequeña none: Nada - 'yes': Sí + 'yes': Sí 'no': 'No' housing: house: Espacio interior @@ -885,49 +1655,182 @@ es: Total_Registrations: Registros totales actions: View_Workshops: Ver Talleres - none: (ninguno) + none: "(ninguno)" notes: Test_Email_Sent: Un email fue enviado a %{email_address} + complete: + companion_registered: Tu acompañante ha completado el registro. Haremos lo + posible por mantenerlxs juntxs. + payment_processed: Gracias por tu pago. Haz concluido el registro + error: + address_required: Ingresa una dirección + amount_required: Ingresa una cantidad + bed_space_required: Ingresa la cantidad de camas o espacios para dormir que + puedes ofrecer + city_not_found: No pudimos encontrar una ciudad o pueblo que coincida con + tu búsqueda  + companion_already_has_companion: Esta persona ya tiene acompañante + companion_email_required: Ingresa una dirección de correo electrónico + departure_date_before_arrival: Haz seleccionado una fecha de partida anterior + a tu fecha de llegada + email_required: Necesitas registrar una dirección de correo electrónico + end_date_before_start: Haz seleccionado una fecha de conclusión que es anterior + a la fecha de inicio + floor_space_required: Ingresa la cantidad de espacios que tienes para dormir + en el piso + generic: Ha ocurrido un error inesperado + info_required: Proporciona información sobre ti y tu casa para tus invitados + language_required: Selecciona al menos un idioma en el que puedas comunicarte + location_required: Ingresa una ubicación + mailing_address_required: Proporciona el correo electrónico de tu organización + name_required: Proporciónanos un nombre con el que te identifiques + org_email_matches_personal_email: Este correo electrónico coincide con tu + correo personal. Te pedimos que en su lugar proporciones el de tu organización. + org_name_required: Proporciona el nombre de tu organización + payment_cancelled: Tu pago fue cancelado + payment_denied: Tu pago fue denegado. Por favor verifica tu cuenta e inténtalo + nuevamente. + phone_required: Es requerido un número telefónico válido. + policy_required: Lee atentamente cada enunciado y marca en la casilla una + vez para confirmar que estás de acuerdo y harás respetar el acuerdo. + step_names: + group_ride: Rodada Grupal? + hosting_address: Dirección + hosting_attending: Asistirás? + hosting_check: Anfitrión(a)? + hosting_end_date: "Último día?" + hosting_info: Información de tu casa + hosting_other: Otrxs + hosting_phone: Teléfono + hosting_space_beds: Cama/ Espacio en sofá + hosting_space_floor: Espacio en el piso + hosting_space_tent: Espacio para tienda de campaña + hosting_start_date: Primer día + housing_arrival_date: Día de llegada + housing_companion_email: Acompañante + housing_departure_date: Fecha de partida + housing_food: Hábitos alimenticios + housing_other: Otrxs + housing_type: Alojamiento + languages: Idiomas hablados + name: Nombre + org_location: Ubicación + org_member: Perteneciente a una organización + org_non_member_interest: Interés en la conferencia + org_select: Organización + payment_form: Cantidad a pagar por registro + payment_type: Método de pago para el registro + policy: Normatividad + housing_bike: Necesitas bici? + warning: + companion_unregistered: Tu acompañante no se ha registrado. Por favor aseguráte + que lo haga, para garantizar que están alojadxs juntxs. + payment_pending: Gracias. Tu pago está pendiente. about_bikebike: paragraphs: - Who_is_Invited: No es necesario ser personas o grupos expertas solo tener las ganas y la disposición de compartir lo que sabes de organización estructural, mecánica, del cambio social, de alternativas contra la desigualdad o un mejor acceso al uso de la bici y el conocimiento y otros problemas sociales en tu barrio o ciudad. - bicycle_project_paragraph: La siguiente lista usa el criterio utilizado en la vieja Bicycle Organization Organization Project (Proyecto de Organización de Organizaciones Ciclistas) para definir qué constituye un taller comunitario de bicis. El proyecto no necesariamente debe cumplir con todos los criterios. En cambio, es una lista general de las cualidades que son comunes entre varios proyectos ciclistas. - What_is_BikeBike: 'Bike!Bike! es una reunión internacional anual, organizada por y para proyectos comunitarios que trabajan en proyectos de bici. La conferencia es un espacio para que los participantes, principalmente miembros de talleres de y grupos de activismo ciclista, se reúnan en una ciudad distinta cada año durante cuatro días para llevar a cabo talleres informativos y fortalecer nuestra red comunitaria y social. ' - Types_of_Workshops: 'Los talleres son organizados e impartidos por los participantes. Pueden ser sobre cualquier tema de interés para los proyectos comunitarios de bici y pueden ser en cualquier formato, incluyendo: presentaciones, talleres prácticos, grupos de discusión y rodadas en bici. Si quieres impartir tu propio taller, puedes ingresar los detalles después de registrarse. Los que terminan en el programa final y cuándo son programados, es decisión de los anfitriones de la conferencia. ' - Amenities: 'Tradicionalmente, los anfitriones de la conferencia coordinan hospedaje compartido, proveen una o dos comidas al día y hay bicicletas disponibles para los participantes registrados; sin embargo, debes checar los detalles particulares sobre la conferencia de cada año. Más información sobre cómo puedes obtener hospedaje por tu cuenta y otros lugares recomendados para visitar, también debe ser proveída por el anfitrión en la página de la conferencia o en un documento físico al llegar a la conferencia. ' - Volunteer: '¡Sí, por favor! ' + Who_is_Invited: No es necesario ser personas o grupos expertos solo tener + las ganas y la disposición de compartir lo que sabes de organización estructural, + mecánica, cambio social. Lo que puedas aportar respecto a alternativas para + tener un mejor acceso al uso de la bici, contra la desigualdad y otras problemáticas + sociales de tu barrio o ciudad. + bicycle_project_paragraph: La siguiente lista usa el criterio utilizado en + la vieja Bicycle Organization Organization Project (Proyecto de Organización + de Organizaciones Ciclistas) para definir qué constituye un taller comunitario + de bicis. El proyecto no necesariamente debe cumplir con todos los criterios. + En cambio, es una lista general de las cualidades que son comunes entre + varios proyectos ciclistas. + What_is_BikeBike: 'Bike!Bike! es una reunión internacional anual, organizada + por y para proyectos comunitarios que trabajan en proyectos de bici. La + conferencia es un espacio para que los participantes, principalmente miembros + de talleres de y grupos de activismo ciclista, se reúnan en una ciudad distinta + cada año durante cuatro días para llevar a cabo talleres informativos y + fortalecer nuestra red comunitaria y social. ' + Types_of_Workshops: 'Los talleres son organizados e impartidos por lxs participantes. + Pueden ser sobre cualquier tema de interés para los proyectos comunitarios + de bici y pueden ser en cualquier formato, incluyendo: presentaciones, talleres + prácticos, grupos de discusión y rodadas en bici. Si quieres impartir tu + propio taller, puedes ingresar los detalles después de registrarte. Los + talleres que terminan en el programa final y las fechas y horarios en que + son programados, son decisiones de lxs anfitriones de la conferencia.' + Amenities: Tradicionalmente, lxs anfitriones de la conferencia coordinan hospedaje + compartido, proveen una o dos comidas al día y hay bicicletas disponibles + para lxs participantes registradxs; sin embargo, debes checar los detalles + particulares sobre la conferencia de cada año. Mayor información sobre cómo + puedes obtener hospedaje por tu cuenta y otros lugares recomendados para + visitar, también debe ser proveída por el anfitrión en la página de la conferencia + o en un documento físico al llegar a la conferencia. + Volunteer: "¡Sí, por favor! " headings: - Amenities: ¿Dónde puedo dormir? ¿Qué puedo comer? ¿Cómo puedo moverme? - Types_of_Workshops: ¿Qué tipo de talleres habrá? - Volunteer: ¿Puedo ser voluntario? - What_is_BikeBike: ¿Qué es Bike!Bike!? - Who_is_Invited: ¿Quién es invitado? - bicycle_project: ¿Qué es un proyecto ciclista comunitario? + Amenities: "¿Dónde puedo dormir? ¿Qué puedo comer? ¿Cómo puedo moverme?" + Types_of_Workshops: "¿Qué tipo de talleres habrá?" + Volunteer: "¿Puedo ser voluntario?" + What_is_BikeBike: "¿Qué es Bike!Bike!?" + Who_is_Invited: "¿Quiénes están invitadxs?" + bicycle_project: "¿Qué es un proyecto ciclista comunitario?" term: - education: Los talleres tienen un enfoque en la educación, en enseñar a otros cómo arreglar bicicletas - export_bikes: Organizaciones que se incluyen bicicletas para las comunidades de otros países. - low_cost: Hay organizaciones que envían bicicletas donadas a comunidades en otros países - no_money: Hay organizaciones que reciclan bicicletas y algunas de sus partes - non_profit: Los talleres son accesibles para gente sin dinero - recycle_parts: Son organizaciones sin fines de lucro - volunteer_run: Los talleres son operados por voluntarios + education: Los talleres tienen un enfoque pedagógico, en enseñar a otros cómo + arreglar bicicletas + export_bikes: Organizaciones que envían bicicletas hacia comunidades de otros + países. + low_cost: Talleres que proveen servicio gratuito o de bajo costo en sus comunidades. + no_money: Organizaciones que reciclan bicicletas y algunas de sus partes + non_profit: Talleres que son accesibles para gente sin dinero + recycle_parts: Organizaciones sin fines de lucro + volunteer_run: Talleres que son operados por voluntarios workshops: paragraphs: - Delete_Workshop: La eliminación de un taller no se puede deshacer, ¿está seguro de que desea continuar? - Proposed_Workshops: ¿Te gustaría facilitar tu propio taller? Simplemente regístrate y visita la página de Talleres. Si ya te registraste puedes acceder a la página reiniciando el proceso de registro. - Workshops: ¿Tienes alguna habilidad emocionante que quieres compartir con nosotros? ¿Quieres charlar sobre crear espacios comunitarios seguros? ¿Quieres asegurarte de que hagamos un buen paseo el fin de semana? ¡Propón un taller! No te preocupes si no eres unx expertx, queremos escuchar acerca de las experiencias de todxs dentro de los diferentes talleres comunitarios de los que venimos con sus diferentes configuraciones. - new_workshop: 'Anteriormente los talleres se han podido integrar en una serie de categorías amplias: organización/estructura (cómo iniciar un taller comunitario, cómo registrar una asociación sin fines de lucro, toma de decisiones mediante consenso, etc) mecánica (cómo soldar dos tandems en una tall-bike, cómo construir una masa de cambios internos con accesorios de pesca y calcetines viejos, etc) y recreación (tips de cicloturismo, yoga para ciclistas, etc.) Este año quisieramos intentar páneles, foros u otro tipo de contextos que creas que puedan facilitar el aprendizaje - sé inventivx, ¡Dinos qué quieres ver!' - notes: Las notas se comparten solo con lxs organizadorxs de la conferencia y con lxs facilitadorxs de los talleres. - space: ¿Qué tipo de espacio necesitas para tu taller? - theme: ¿Cuáles de estos temas describen mejor tu taller? Esto ayudará a los anfitriones a evitar conflictos de horario. Seleciona 'otro' si ninguna de las opciones se relaciona de ninguna forma. - facilitate_request: Por favor dile a lxs facilitadorxs actuales quién eres, porqué quieres ayudar en este taller y cómo crees que ayudarás a hacer un mejor taller. Todxs lxs facilitadorxs actuales recibirán un email y pueden hacerte más preguntas antes de aprovar o denegar tu petición. Por favor sabe que este proceso revelará tu direción de email a lxs facilitadorxs. - facilitate_request_sent: Tu petición ha sido enviada. Recibirás un email cuando tu solicutud sea aprobada o denegada o si lxs facilitadorxs tienen alguna pregunta. - languages: ¿En qué idioma se presentará el taller? Habrá traducción o traductor(a) disponible? - needs: Si necesitan algo de lo siguiente, lxs organizadores de la conferencia harán todo lo posible para proporcionarlo. Si tienen cualquier otra solicitud, pueden incluirla en la sección de comentarios. - needs_facilitators: ¿Están buscando ayuda para su taller? Cualquier persona que esté registrada para la conferencia puede ayudar a facilitar un taller en cualquier momento, pero si marcan esta casilla mejorarán las posibilidades de encontrar colaboradores. - info: Describa su taller en detalle. Resaltar el texto para modificar el formato. + Delete_Workshop: La eliminación de un taller no se puede deshacer, ¿estás + segurx de que deseas continuar? + Proposed_Workshops: "¿Te gustaría facilitar tu propio taller? Simplemente + regístrate y visita la página de Talleres. Si ya te registraste puedes acceder + a la página reiniciando el proceso de registro." + Workshops: "¿Tienes alguna habilidad emocionante que quieras compartir con + nosotros? ¿Quieres charlar sobre crear espacios comunitarios seguros? ¿Quieres + asegurarte de que hagamos un buen paseo el fin de semana? ¡Propón un taller! + No te preocupes si no eres unx expertx, queremos escuchar acerca de las + experiencias de todxs, las historias de quiénes venimos y cómo han sido + configurado los diferentes talleres comunitarios." + new_workshop: 'Anteriormente los talleres se han podido integrar en una serie + de categorías amplias: organización/estructura (cómo iniciar un taller comunitario, + cómo registrar una asociación sin fines de lucro, toma de decisiones mediante + consenso, etc) mecánica (cómo soldar dos tandems en una tall-bike, cómo + construir una masa de cambios internos con accesorios de pesca y calcetines + viejos, etc) y recreación (tips de cicloturismo, yoga para ciclistas, etc.) + Este año quisieramos intentar páneles, foros u otro tipo de contextos que + creas que puedan facilitar el aprendizaje - sé inventivx, ¡Dinos qué quieres + ver!' + notes: Las notas se comparten solo con lxs organizadorxs de la conferencia + y con lxs facilitadorxs de los talleres. + space: "¿Qué tipo de espacio necesitas para tu taller?" + theme: "¿Cuáles de estos temas describen mejor tu taller? Esto ayudará a lxs + anfitriones a evitar conflictos de horario. Seleciona 'otro' si ninguna + de las opciones se relaciona de ninguna forma." + facilitate_request: Por favor dile a lxs facilitadorxs actuales quién eres, + porqué quieres ayudar en su taller y cómo crees que puedes contribuir a + que salga mejor. Todxs lxs facilitadorxs actuales recibirán tu correo electrónico + y pueden hacerte más preguntas antes de aprobar o denegar tu petición. Por + favor ten en cuenta que este proceso revelará tu direción de correo electrónico + a lxs facilitadorxs del taller. + facilitate_request_sent: Tu petición ha sido enviada. Recibirás un correo + electrónico cuando tu solicitud sea aprobada o denegada o si lxs facilitadorxs + tienen alguna pregunta. + languages: "¿En qué idioma se presentará el taller? Habrá traducción o traductor(a) + disponible?" + needs: Si necesitan algo de lo siguiente, lxs organizadores de la conferencia + harán todo lo posible para proporcionarlo. Si tienen cualquier otra solicitud, + pueden incluirla en la sección de comentarios. + needs_facilitators: "¿Están buscando ayuda para su taller? Cualquier persona + que esté registrada para la conferencia puede ayudar a facilitar un taller + en cualquier momento, pero si marcan esta casilla mejorarán las posibilidades + de encontrar colaboradores." + info: Describe tu taller a detalle. Selecciona el texto para modificar el + formato. + Workshops_that_you_would_like_to_attend: Agrega talleres a tu calendario presionando + el botón “+1” junto al nombre del taller. Esto servirá para llevar la cuenta + de los talleres a los que quieres asistir, y ayudará a lxs organizadores + a planear los horarios. headings: - needs_facilitators: ¿Necesitan ayuda? + needs_facilitators: "¿Necesitan ayuda?" add_facilitator: Añadir un(a) facilitador(a) Comments: Comentarios Proposed_Workshops: Talleres propuestos @@ -942,11 +1845,12 @@ es: theme: Tema facilitate: Solicita ser facilitador(a) de ‘%{workshop_title}’ Schedule: Programar + Workshops_that_you_would_like_to_attend: Talleres a los que te gustaría asistir info: read_more: Leer más interested_count: one: Una persona está interesadx en este taller. - other: '%{count} personas están interesadxs en este taller.' + other: "%{count} personas están interesadxs en este taller." zero: Nadie está interesadx en este taller aún. you_are_interested_count: other: Tú y otras %{count} personas están interesadxs en este taller. @@ -954,31 +1858,47 @@ es: one: Tú y otra persona están interesadxs en este taller. user_settings: headings: - Your_Account: Configuraciones de la cuenta + Your_Account: Configuración de la cuenta email_subscribe: Notificaciones Your_Conferences: Tus conferencias paragraphs: conference_registration: Buscando dar de alta o modificar su registro? - email_subscribe: ¿Te gustaría ser notificadx acerca de próximas conferencias? Si te registraste en alguna conferencia, seguiremos enviándote notificaciones y anuncios acerca de la misma aun después de su realización. + email_subscribe: "¿Te gustaría ser notificadx acerca de próximas conferencias? + Si te registraste en alguna conferencia, seguiremos enviándote notificaciones + y anuncios acerca de la misma aun después de su realización." email_subscribe: Suscribirse policy: headings: - The_Agreement: Acuerdo de Espacios Más Seguros - How: ¿Cómo se hace cumplir esta política? - Why: ¿Porqué tener un Acuerdo de Espacios Más Seguros? + The_Agreement: El Acuerdo + How: "¿Cómo se hace cumplir esta normatividad?" + Why: "¿Porqué tener un Acuerdo de Espacios Más Seguros?" paragraphs: - How: La ciudad anfitriona tiene la responsabilidad de mediar en cualquier situación concerniente a Espacios Más Seguros. Ellxs decidirán qué amerita una advertencia y qué amerita una expulsión de la conferencia. - Why: Para establecer un punto de partida, declaramos nuestras creencias y deseos compartidos para el espacio que es Bike!Bike!. Nos reunimos alrededor de estas ideas para inspirarnos y para asegurarnos que si existen conductas que no nos hagan sentir segurxs haya algo en que basarnos. Al aceptar un compromiso de respeto mutuo esperamos que, de surgir un conflicto, recordaremos a qué nos comprometimos y actuaremos en consecuencia. + How: La ciudad anfitriona tiene la responsabilidad de mediar en cualquier + situación concerniente a Espacios Más Seguros. Ellxs decidirán qué amerita + una advertencia y qué amerita una expulsión de la conferencia. + Why: Para establecer un punto de partida, declaramos nuestras creencias y + deseos compartidos para el espacio que es Bike!Bike!. Nos reunimos alrededor + de estas ideas para inspirarnos y para asegurarnos que si existen conductas + que no nos hagan sentir segurxs haya algo en que basarnos. Al aceptar un + compromiso de respeto mutuo esperamos que, de surgir un conflicto, recordaremos + a qué nos comprometimos y actuaremos en consecuencia. term: - empowerment: Apoyamos el empoderamiento de cada persona y cada grupo. + empowerment: Apoyamos el fortalecimiento de cada persona y cada grupo. peaceful: Somos pacíficxs y honestxs. spaces: Respetamos los cuerpos y espacios de cada quién. - hearing: Nos comprometemos a escucharnos y a crear oportunidades para que todas las voces sean escuchadas. - commitment: Entramos con un compromiso hacia el respeto y apoyo mutuos, la defensa contra la opresión, la resolución de conflictos, la no-violencia y la construcción de comunidad. - respect: Respetamos los nombres de todxs, sus pronombres de género, sus identidades expresas y sus experiencias. - accessible: Nos comprometemos a hacer los espacios tan accesibles como sea posible; fisica, social y personalmente. - intent: Aceptamos una responsabilidad compartida para hacernos responsables a nosotrxs mismxs y a lxs demás de los propósitos de este acuerdo. - learning: Con el espíritu de fomentar el crecimiento personal, promovemos los espacios de aprendizaje y la formulación de preguntas en lenguaje incluyente. + hearing: Nos comprometemos a escucharnos y a crear oportunidades para que + todas las voces sean escuchadas. + commitment: Entramos con un compromiso hacia el respeto y apoyo mutuos, la + defensa contra la opresión, la resolución de conflictos, la no-violencia + y la construcción de comunidad. + respect: Respetamos los nombres de todxs, sus pronombres de género, sus identidades + expresas y sus experiencias. + accessible: Nos comprometemos a hacer los espacios tan accesibles como sea + posible; fisica, social y personalmente. + intent: Aceptamos una responsabilidad compartida para hacernos responsables + a nosotrxs mismxs y a lxs demás de los propósitos de este acuerdo. + learning: Con el espíritu de fomentar el crecimiento personal, promovemos + los espacios de aprendizaje y la formulación de preguntas en lenguaje incluyente. open_minds: Alentamos la apertura de mentes y de corazones. permission_denied: headings: @@ -987,19 +1907,20 @@ es: login_required: Necesario iniciar sesión contact: headings: - contact: Envíenos una pregunta o un complemento - reason: ¿Porqué motivo nos estás contactando? + contact: Envíanos una pregunta o un complemento + reason: "¿Porqué motivo nos estás contactando?" sent: Gracias por contactarnos paragraphs: - sent: Gracias por comunicarte. Te responderemos vía correo electrónico tan pronto como podamos. + sent: Gracias por comunicarte. Te responderemos vía correo electrónico tan + pronto como podamos. images: conference: - poster: '%{conference_title} póster' + poster: "%{conference_title} póster" forms: labels: generic: - hosting_dates: ¿Cuándo estarás en la ciudad? - title: Título de la taller + hosting_dates: "¿Cuándo estarás en la ciudad?" + title: Título del taller info: Descripción allergies: Alérgicx email: Correo electrónico @@ -1021,14 +1942,23 @@ es: floor_space: Espacios para dormir en el piso tent_space: Espacios para tienda de campaña first_day: Primer día - last_day: Último día + last_day: "Último día" companion: Acompañante + space: Espacio disponible + city: Ciudad + country: País + payment_type: + none: Ninguno + on_arrival: En persona + paypal: En línea + pronoun: Pronombre + territory: Estado, Provincia actions: generic: create: Crear administrate: Administrar add_comment: Añadir comentario - agree: Estoy en acuerdo + agree: Estoy de acuerdo register: Registrarse edit_registration: Cambiar mi registracion custom_amount: Cantidad diferente @@ -1043,18 +1973,35 @@ es: send: Enviar test: Prueba skip: Omitir - remove_interest: '-1' - show_interest: '+1' - add: + + remove_interest: "-1" + show_interest: "+1" + add: "+" facebook_sign_in: Iniciar sesión usando Facebook continue: Continuar previous: Anterior next: Siguiente reply: Responder - close: Cerca + close: Cerrar + back: Volver + cancel_registration: Cancelar mi registro + food_meat: Consumo carne y lácteos + food_vegan: Soy veganx + food_vegetarian: Soy vegetarianx + housing_house: Sí, me gustaría solicitar hospedaje + housing_none: No necesito hospedaje + housing_tent: Necesito un lugar para acampar + maybe: Tal vez + 'no': 'No' + organization_none: Ningunx de lxs anteriores + payment_none: Por ahora no + payment_on_arrival: Puedo comprometerme a pagar a mi llegada + payment_paypal: Puedo pagar ahora con Paypal + reopen_registration: Re-abrir mi registro + review: Revisar + 'yes': Sí aria: - remove_interest: Haga clic en si ya no está interesado en este taller - show_interest: Haga clic en si usted está interesado en este taller + remove_interest: Haga clic si ya no está interesadx en este taller + show_interest: Haga clic si usted está interesadx en este taller add: Añadir nuevo links: footer: @@ -1072,7 +2019,7 @@ es: Conferences: Conferencias About_BikeBike: Acerca de Bike!Bike! about: - About_BikeBike: Quienes Bike!Bike! + About_BikeBike: Acerca de Bike!Bike! Safe_Space_Policy: Acuerdo de Espacio Más Seguro conferences: Conference_Registration: Registro @@ -1084,24 +2031,24 @@ es: View_Workshop: Ver talleres Workshops: Talleres Edit: Editar conferencia - Facilitate_Workshop: Solicitud para ser facilitador(a) de taller. + Facilitate_Workshop: Solicitud para facilitar taller. Translate_Workshop: Editar la traducción taller %{language} policy: - Safer_Spaces_Policy: Acuerdo del Espacio Mas Seguro + Safer_Spaces_Policy: Acuerdo del Espacio Más Seguro '403': Access_Denied: Acceso Denegado Please_Check_Email: Por favor confirma tu correo electrónico Please_Confirm_Email: Confirma tu correo electrónico Please_Login: Por favor Iniciar sesión '404': - Page_Not_Found: Crear un taller + Page_Not_Found: Página no encontrada Locale_Not_Available: Locale no disponible '500': An_Error_Occurred: Ocurrió un error - Policy: 'Política' - About: 'Acerca de' - Register: 'Regístrate' - Pre_Register: 'Pre-Regístrate' + Policy: Normatividad + About: Acerca de + Register: Regístrate + Pre_Register: Pre-Regístrate user_settings: Your_Account: Tu cuenta contact: @@ -1127,31 +2074,41 @@ es: error: '404': title: '404: Esta página no existe' - description: La página que buscas no pudo ser encontrada. Si piensas que esto fue un error, por favor contáctanos. - token_not_found: El token de inicio de sesión se ha caducado o no se encuentra. Por favor intenta acceder de nuevo. + description: La página que buscas no pudo ser encontrada. Si piensas que esto + fue un error, por favor contáctanos. + token_not_found: El token de inicio de sesión se ha caducado o no se encuentra. + Por favor intenta acceder de nuevo. '403': title: Lo sentimos. Por el momento no tienes acceso a esta página - description: Por el momento no tienes los permisos suficientes para acceder a esta página. Si crees que esto es un error, por favor contáctanos. + description: Por el momento no tienes los permisos suficientes para acceder + a esta página. Si crees que esto es un error, por favor contáctanos. '500': title: Tenemos un problema - description: Ha ocurrido un error, los detalles sobre el mismo han sido enviados a nuestro equipo de desarrolladores. También puedes contactarnos a través del formulario en la parte inferior, para ayudarnos a entender qué pasó y como esto te afectó + description: Ha ocurrido un error, los detalles sobre el mismo han sido enviados + a nuestro equipo de desarrolladores. También puedes contactarnos a través + del formulario en la parte inferior, para ayudarnos a entender qué pasó y + como esto te afectó locale_not_available: title: '404: No tenemos traducción al %{language}' - description: El sitio aún debe ser traducido al %{language}. Estamos en la búsqueda de voluntari@s que puedan traducir la versión existente y la nueva versión, a medida que le agregamos nuevas características. Si piensas que puedes ayudar, por favor contáctanos. + description: El sitio aún debe ser traducido al %{language}. Estamos en la búsqueda + de voluntarixs que puedan traducir la versión existente y la nueva versión, + a medida que le agregamos nuevas características. Si piensas que puedes ayudar, + por favor contáctanos. volunteer: - title: ¿Puedes ayudarnos a traducir? - description: 'Si puedes ayudarnos a traducir al francés u otro idioma, por favor háznoslo saber contactándonos a través del siguiente formulario:' + title: "¿Puedes ayudarnos a traducir?" + description: 'Si puedes ayudarnos a traducir al francés u otro idioma, por + favor háznoslo saber contactándonos a través del siguiente formulario:' translate: content: translate_now: Translar ahora - can_you_help: ¿Se puede traducir del %{language_a} al %{language_b}? + can_you_help: "¿Se puede traducir del %{language_a} al %{language_b}?" outdated_translation: El contenido que ha sido superado translation_out_of_date: Contenido traducido en esta página puede no ser exacta un_translated: Contenido sin traducir not_translation: No es una traducción translation_last_modified: El texto en el idioma original fue modificada por última vez el %{date} - change_locale: "Leer en %{language}" + change_locale: Leer en %{language} Translation_of: Traducción de pages: contexts: @@ -1170,12 +2127,12 @@ es: other: plural two: dos Incomplete_Locales: Incomplete Languages - Locale_Translations: '%{language} Traducciones' + Locale_Translations: "%{language} Traducciones" context: - other: '%{context} (× %{count})' - one: '%{context}' + other: "%{context} (× %{count})" + one: "%{context}" content: - change_locale: 'Leer en %{locale}' + change_locale: Leer en %{locale} context_title: 'Contexto:' default_value: Predeterminado no_value: Indefinido @@ -1193,12 +2150,12 @@ es: variables: Variables site: go_to_locale: Ver el sitio %{language} ahora - locale_is_available: '%{site_name} está disponible en %{language}' - locale_needs_help: ¿Puedes ayudar a traducir el sitio al %{language}? + locale_is_available: "%{site_name} está disponible en %{language}" + locale_needs_help: "¿Puedes ayudar a traducir el sitio al %{language}?" locale_not_available: Lo sentimos, %{site_name} no está disponible en %{language}. volunteer: become_a_volunteer: Conviértete en un traductor voluntario - volunteer: ¿Puedes ayudar a traducir esto? + volunteer: "¿Puedes ayudar a traducir esto?" menu: submenu: registration: @@ -1208,61 +2165,80 @@ es: Edit: Editar conferencia Stats: Estadísticas page_descriptions: - home: Bike!Bike! Una conferencia de colectivos ciclistas, cooperativas y talleres de bicicletas autogestivos sin fines de lucro. + home: Bike!Bike! Una conferencia de colectivos ciclistas, cooperativas y talleres + de bicicletas autogestivos sin fines de lucro. email: confirmation: link: please_confirm: Confirmar ahora paragraph: - please_confirm: ¡Hola! Para ingresar al registro y otras funciones de Bike!Bike! confirma tu dirección de correo electrónico por favor. + please_confirm: "¡Hola! Para ingresar al registro y otras funciones de Bike!Bike! + confirma tu dirección de correo electrónico por favor." subject: - confirm_email: E-mail de confirmación - pre_registration_confirmed: ¡Gracias por pre-registrarte para %{conference_title}! - registration_confirmed: ¡Gracias por registrarte para %{conference_title}! + confirm_email: correo electrónico de confirmación + pre_registration_confirmed: "¡Gracias por pre-registrarte para %{conference_title}!" + registration_confirmed: Gracias por pre - registrarte en %{conference_title} workshop_facilitator_request: Solicitud para facilitar ‘%{workshop_title}’ por parte de %{requester_name} workshop_request_approved: Has sido agregadx como facilitador(a) a ‘%{workshop_title}’ workshop_request_denied: Tu petición para facilitar ‘%{workshop_title}’ ha sido denegada. - registration_confirmed: Gracias por pre - registrarte en %{conference_title} workshop_comment: - comment: '%{user_name} ha comentado acerca de ‘%{workshop_title}’' - reply: '%{user_name} respondido a tu comentario' - workshop_original_content_changed: El contenido original de ‘%{workshop_title}’ ha sido modificado - workshop_translated: La traducción al %{language} de ‘%{workshop_title}’ ha sido modificada + comment: "%{user_name} ha comentado acerca de ‘%{workshop_title}’" + reply: "%{user_name} ha respondido a tu comentario" + workshop_original_content_changed: El contenido original de ‘%{workshop_title}’ + ha sido modificado + workshop_translated: La traducción al %{language} de ‘%{workshop_title}’ ha + sido modificada translations: headings: - new_value: 'Nuevo valor: ' - old_value: 'Viejo Valor: ' + new_value: 'Nuevo contenido:' + old_value: 'Viejo Contenido:' diff: 'Diferencia: ' paragraph: - workshop_translated: '%{user_name} ha modificado la traducción al %{language} de ‘%{workshop_title}’.' - workshop_original_content_changed: '%{user_name} ha modificado el contenido original de Mi Increíble Taller. Puede ser que las traducciones requieran actualizarse.' + workshop_translated: "%{user_name} ha modificado la traducción al %{language} + de ‘%{workshop_title}’." + workshop_original_content_changed: "%{user_name} ha modificado el contenido + original de Mi Increíble Taller. Puede ser que las traducciones requieran + actualizarse." workshop_comment: paragraph: - user_said: '%{user_name} escribió' + user_said: "%{user_name} escribió" general: paragraph: - see_you: ¡Nos vemos en %{conference_location}! + see_you: "¡Nos vemos en %{conference_location}!" thank_you: Gracias %{name}, registration: paragraph: - confirmed: Te has registrado exitosamente para %{conference_title}, Puedes modificar los detalles de tu registro, pagar o agregar talleres en cualquier momento reiniciando el proceso de registro. Si aún tienes que pagar o agregar tus talleres y quieres hacerlo te pedimos que lo hagas lo más pronto posible para ayudarnos a prepararnos para tu llegada. - pre_confirmed: Te has pre-registrado exitosamente para %{conference_title}. Te haremos saber cuando el registro completo esté disponible y si hay alguna novedad acerca de la conferencia sobre la que debas saber. Te invitamos a inscribirte como voluntario o facilitador de un taller tan pronto como puedas. Puedes inscribirte ahora, o cambiar tus detalles de registro en cualquier momento haciendo clic en el enlace de pre-registro nuevamente. + confirmed: Te has registrado exitosamente para %{conference_title}, Puedes + modificar los detalles de tu registro, pagar o agregar talleres en cualquier + momento reiniciando el proceso de registro. Si aún tienes que pagar o agregar + tus talleres y quieres hacerlo te pedimos que lo hagas lo más pronto posible + para ayudarnos a prepararnos para tu llegada. + pre_confirmed: Te has pre-registrado exitosamente para %{conference_title}. + Te haremos saber cuando el registro completo esté disponible y si hay alguna + novedad acerca de la conferencia sobre la que debas saber. Te invitamos + a inscribirte como voluntario o facilitador de un taller tan pronto como + puedas. Puedes inscribirte ahora, o cambiar tus detalles de registro en + cualquier momento haciendo clic en el enlace de pre-registro nuevamente. workshop: paragraph: request_approved: Has sido agregado como facilitador(a) para ‘%{workshop_title}’ - request_denied: Tu petición para ser facilitador(a) de ‘%{workshop_title}’ ha sido denegada. Si crees que esto es un error, puedes contactar a lxs facilitadorxs actuales haciendo otra petición para ser facilitador(a). - request_instructions: Puedes aprobar o denegar esta petición en la página de tu taller. - request_message: '%{user_name} ha solicitado ser facilitador(a) para ‘%{workshop_title}’' - request_reply_instructions: También puedes responder directamente a este email para hacer preguntas adicionales. + request_denied: Tu petición para ser facilitador(a) de ‘%{workshop_title}’ ha + sido denegada. Si crees que esto es un error, puedes contactar a lxs facilitadorxs + actuales haciendo otra petición para ser facilitador(a). + request_instructions: Puedes aprobar o denegar esta petición en la página + de tu taller. + request_message: "%{user_name} ha solicitado ser facilitador(a) para ‘%{workshop_title}’" + request_reply_instructions: También puedes responder directamente a este correo + electrónico para hacer preguntas adicionales. view_workshop: Ver el taller aquí roles: workshops: facilitator: - creator: Creador - collaborator: Colaborador - requested: Solicitó + creator: Creador(a) + collaborator: Colaborador(a) + requested: Solicitado unregistered: No registrado facilitator: Facilitador(a) workshop: @@ -1282,4 +2258,4 @@ es: organization: Asuntos organizacionales other: Otro race_gender: Políticas de raza, género o clase - needs_facilitators: Se necesitan mas facilitadorxs + needs_facilitators: Se necesitan más facilitadorxs diff --git a/config/locales/fr.yml b/config/locales/fr.yml index d26c876..7ade8d9 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1088,93 +1088,5331 @@ fr: WV: Virginie-Occidentale WI: Wisconsin WY: Wyoming + formats: + city_region_country: "%{city}, %{region}, %{country}" articles: about_bikebike: paragraphs: What_is_BikeBike: Bike!Bike!, c’est un congrès international annuel organisé - par et pour les projets de vélos communautaires. Ce congrès invite les participantes - d’ateliers de vélos et d’organismes revendicateurs connexes de se rencontrer - pendant quatre jours dans une différente ville à chaque année afin de suivre - des formations et de faire du réseautage. + par et pour les ateliers de vélo communautaires. Cet événement invite les + personnes qui travaillent ou font du bénévolat pour des ateliers de vélo + et des groupes de soutien connexes à se rencontrer pendant quatre jours + dans une différente ville chaque année afin de participer à des présentations + et de faire du réseautage. bicycle_project_paragraph: Il y a des collectifs qui utilisent le vélo comme - excuse pour changer la société, l’économie et l’environnement. Il y a aussi - des organismes à but non lucratif, des coopératives et d’autres projets - qui font la promotion de l’utilisation du vélo. Ce sont tous ceux et toutes - celles qui se rassemblent afin de transformer leur communauté pour que le - cyclisme devienne plus facile, plus inclusif, plus sécuritaire et plus amusant. - La liste de critères pour un projet de vélo est tirée de l’ancien Bicycle + vecteur de changement de la société, de l’économie et de l’environnement. + Il y a aussi des organismes à but non lucratif, des coopératives et d’autres + projets qui font la promotion du vélo. Ce sont tous ceux et toutes celles + qui se rassemblent afin de transformer leur communauté pour que le cyclisme + devienne plus facile, plus inclusif, plus sécuritaire et plus amusant. La + liste de critères pour un projet de vélo est tirée de l’ancien Bicycle Organization Project. Votre projet n’a pas besoin de satisfaire tous les critères. C’est plutôt une liste générale de caractéristiques communes parmi les projets de vélo. - Who_is_Invited: Vous n’avez pas besoin d’être experte ni d’appartenir à un - gros organisme. Vous avez seulement qu’à avoir la volonté de partager ce - que vous savez au sujet de la gestion, la mécanique, l’impact social, la - lutte contre l’inégalité et l’accessibilité au cyclisme dans votre communauté. - Types_of_Workshops: 'Les formations sont organisées par les participants.es. - Les sujets inclus tous ce qui concerne les projets de vélo communautaires. - Les formations peuvent prendre toutes formes : des présentations, des ateliers - interactifs, des groupes de discussion ou des randonnées à vélo. Si vous - voulez offrir une formation, vous pouvez le faire en vous inscrivant. Ce - sont les hôtes du congrès qui ont le dernier mot sur les sujets et l’horaire - des formations.' - Amenities: Habituellement, les hôtes du congrès organisent de l’hébergement - partagés, un ou deux repas par jour et un vélo est disponible pour chaque - participant.e inscrit.e. Par contre, vous devriez vérifier les détails du - congrès, afin de confirmer les détails spécifique à celui ci. Si vous désirez - savoir comment vous pourrez organiser votre propre hébergement et connaître - d’autres endroits à visiter, il y aura plus d’information sur la page du - congrès ou, sinon, elle sera disponible en format papier lors de votre arrivée. + Who_is_Invited: Vous n’avez pas besoin d’être expert(e) ni d’appartenir à + un gros organisme. Il vous suffit d’avoir la volonté de partager ce que + vous savez au sujet de la gestion, de la mécanique, de l’impact social, + de la lutte contre l’inégalité et de l’accessibilité au cyclisme dans votre + communauté. + Types_of_Workshops: 'Les présentations sont organisées et données par les + participants et les participantes. Les sujets incluent tout ce qui concerne + les ateliers de vélo communautaires. Les présentations peuvent prendre des + formes diverses : exposés, ateliers interactifs, groupes de discussion ou + randonnées à vélo. Si vous voulez faire une présentation, vous pouvez le + faire en vous inscrivant. Ce sont les organisateurs et organisatrices du + Congrès qui ont le dernier mot sur le sujet et l’horaire des présentations.' + Amenities: D’habitude, les personnes qui organisent le Congrès offrent de + l’hébergement partagé, entre un et deux repas par jour, et un vélo par participant + ou participante. Assurez-vous toutefois de consulter les renseignements + du congrès de cette année. D’autres informations sur la façon de trouver + votre propre logement et sur les endroits qu’il est conseillé de visiter + devraient aussi vous être données par les organisateurs et organisatrices + sur le site Web, ou sur papier dès votre arrivée. Volunteer: Oui, bien sûr! headings: What_is_BikeBike: C’est quoi Bike!Bike!? - bicycle_project: C’est quoi un projet de vélo communautaire? + bicycle_project: Qu’est-ce qu’un atelier de vélo communautaire? Who_is_Invited: Qui est invité? - Types_of_Workshops: Quelles formations va-t-il y avoir? + Types_of_Workshops: Quels types de présentations va-t-il y avoir? Amenities: Où vais-je dormir? Que vais-je manger? Comment vais-je me déplacer? Volunteer: Puis-je être bénévole? term: - non_profit: Des ateliers de vélo qui sont accessibles aux gens qui n’ont pas - d’argent - no_money: Des organismes qui recyclent des vélos et des pièces de vélo - education: Des ateliers qui priorisent l’éducation; enseigner aux autres comment + non_profit: Ateliers de vélo accessibles aux gens qui n’ont pas d’argent + no_money: Organismes qui recyclent des vélos et des pièces de vélo + education: Ateliers qui priorisent l’éducation en enseignant aux autres comment réparer leur vélo - volunteer_run: Des ateliers qui sont menés par une équipe de bénévoles - export_bikes: Des organismes qui envoient des vélos à des communautés dans - d’autres pays - low_cost: Des ateliers qui fournissent des services gratuits ou à faible coût + volunteer_run: Ateliers menés par une équipe de bénévoles + export_bikes: Organismes qui envoient des vélos à des communautés dans d’autres + pays + low_cost: Ateliers qui fournissent des services gratuits ou à faible coût à la communauté - recycle_parts: Des organismes de vélo à but non lucratif + recycle_parts: Organismes de vélo à but non lucratif conference_registration: headings: policy: Politique Policy_Agreement: Accord d’espace positif + contact_info: Coordonnées + Add_Workshop: Proposer une présentation + All_Workshops: Toutes les autres présentations + Contact_Info: Coordonnées + Enter_Your_Email: Saisissez votre adresse électronique + Hosting: Offre de logements + Payment: Frais d’inscription + Register: S’inscrire à %{title} + Registration_Details: Les inscriptions viennent de commencer! + Registration_Info: Renseignements sur l’inscription + Verify_Account: Vérification du compte + Workshops: Présentations + Your_Workshops: Vos présentations + allergies: Avez-vous des allergies? + bike: Avez-vous besoin d’un vélo? + can_provide_housing: Pouvez-vous loger des participants et des participantes + qui n’habitent pas à Winnipeg? + email_confirm: Confirmer le courriel + food: Que mangez-vous habituellement? + host: + address: Adresse + considerations: Besoins spécifiques + housing: Vous faut-il un logement? + hosting: Offre de logement + languages: Quelles langues parlez-vous? + location: D’où venez-vous? + name: Quel est votre nom? + payment_confirm: Merci de confirmer le paiement + companion: Autre personne avec qui je voudrais être logé(e) + other: Avez-vous d’autres précisions à apporter? + payment: Don + preferred_language: En quelle langue préférez-vous communiquer? + questions: Votre venue + workshops: Présentations + can_provide_housing: Je peux héberger des gens. + host: + considerations: + pets: Présence d’animaux de compagnie sur les lieux (chiens ou chats) + smoking: Fumer est autorisé + vegan: Végétalien ou végétarien seulement + quiet: Maison calme + not_attending: Je n’assisterai pas à la conférence + paragraphs: + Contact_Info: Parlez-nous un peu de vous + Confirm_Agreement: En cliquant sur « J’accepte », vous vous engagez à faire + votre possible pour appliquer l’Accord d’espace positif de Bike!Bike! Merci. + Create_Workshop: Pendant Bike!Bike!, n’importe qui est libre de faire une + présentation ou de proposer des idées de présentation pouvant être données + par d’autres personnes. Les organisateurs et organisatrices du Congrès décideront + plus tard de si oui ou non une présentation peut être donnée, et, le cas + échéant, de son endroit et de sa date. + Payment: Merci de vous être inscrit(e). À très bientôt! Pour payer, il vous + suffit de faire un don maintenant ou au moment de votre arrivée. Ceci dit, + nous vous prions de nous faire parvenir votre don au plus tôt afin de nous + aider à financer le Congrès. + Payment_Add: Merci. Si vous voulez, vous pouvez faire un don supplémentaire + ci-dessous. + Payment_Made: Vous venez de faire un paiement de %{fees_paid}. + Policy_Agreement: Nous avons à cœur de veiller à offrir un environnement accueillant, + respectueux et sûr à tous les participants et participantes. Merci de vous + assurer d’avoir entièrement lu et compris notre politique sur l’espace positif + ci-dessous, et si vous avez des questions ou des préoccupations, vous pouvez + vous adresser en tout temps aux organisateurs ou aux organisatrices. + Your_Workshops: Voici la liste des présentations que vous avez décidé d'animer. + currency: "(montants en $ US)" + companion: Y a-t-il une autre personne avec qui vous souhaitez être hébergé(e)? + host: + space: Combien de personnes pouvez-vous héberger? + address: Les organisateurs et organisatrices du Congrès et les personnes + que vous hébergez pourront voir votre adresse et votre numéro de téléphone. + availability: Souvent, les participants et participantes arrivent le premier + jour du Congrès et repartent le dernier, mais il n’est pas rare que les + gens arrivent plus tôt ou repartent plus tard. Quand pouvez-vous offrir + de l’hébergement? + considerations: Le maximum sera fait pour attribuer aux personnes qui ont + des besoins particuliers un hébergement adapté. Lesquels de vos besoins + doivent être pris en compte au moment de vous attribuer un hébergement? + notes: Indiquez lesquels de vos besoins doivent être pris en compte au moment + de vous attribuer un hébergement. + payment_confirm: Vous vous apprêtez à confirmer votre paiement des frais d'inscription + de %{amount} $. + Registration_Details: 'En vous inscrivant, vous confirmerz votre venue, vous + nous autorisez à vous faire suivre des nouvelles, et vous nous aidez à mieux + planifier l’hébergement. Une fois inscrit(e), vous pourrez enregistrer votre + présentation et accéder aux détails portant sur d’autres présentations. + Si vous êtes déjà inscrit(e) ou préinscrit(e), vous pouvez vous connecter + à partir de cette page pour accéder à votre compte et en modifier les paramètres. ' + Registration_Info: 'Veuillez remplir le formulaire d''inscription afin de + nous aider à préparer votre arrivée à %{city}. Si vous voulez poser des + questions ou nous donner des renseignements que nous n''avons pas demandés, + venez écrire dans le champ en bas de cette page ou communiquer avec nous. ' + Verify_Account: 'Pour confirmer que vous n''êtes pas un robot, et que nous + pourrons effectivement communiquer avec vous ultérieurement, veuillez consulter + votre boîte de messagerie électronique. Nous vous enverrons un message qui + vous permettra de poursuivre votre inscription. ' + can_provide_housing: Héberger des participants et des partipantes est une + composante essentielle de Bike!Bike! Cela permet à des gens qui n'auraient + pas les moyens de se payer un hébergement de prendre part au Congrès et + de d'apprendre à connaître la ville et les personnes qui y vivent. Vous + devrez permettre à vos hôtes d'accéder à votre domicile pendant la nuit, + vu que certains événements ont lieu tard le soir. Si votre domicile n'est + pas accessible pendant le jour, assurez-vous d'en aviser les personnes qui + séjournent chez vous. + facebook_sign_in: 'Vous pouvez aussi vous connecter par le biais de Facebook. + Ainsi, vous n''aurez pas à attendre qu''on vous envoie un courriel. ' + email_confirm: 'Vérifiez votre boîte de réception! Un courriel en provenance + de Bike!Bike! devrait bientôt y apparaître. Il contiendra un lien. Le courriel + pourrait atterir dans votre dossier de courrier indésirable. En cas de problème, + contactez-nous. ' + not_attending: Si vous ne faites qu'offrir de l'hébergement et que vous ne + souhaitez pas recevoir des mises à jour, veuillez cocher cette case. + provide_email: Dans le cadre de Bike!Bike!, on vous connecte aux organisateurs + et aux organisatrices par courrier électronique. Si vous vous connectez + depuis votre compte Facebook, aucune adresse de courriel ne nous est fournie. + Veuillez donner votre adresse de courriel afin de pouvoir continuer. + questions: + bike: + 'no': Non + 'yes': Oui + food: + meat: Omnivore + vegan: Végétalien + vegetarian: Végétarien + housing: + none: Je m’en occuperai + tent: 'Un endroit pour plantr ma tente ' + house: Salle intérieure policy: headings: - The_Agreement: L’Accord + The_Agreement: L’ Accord + Why: "À quoi sert l’Accord d’espace positif?" + How: Comment l’Accord est-il mis en application? term: - commitment: Nous sommes engagés envers le respect mutuel, l’aide mutuelle, - la revendication de l’anti-oppression, la résolution de conflit, la non - violence et le renforcement des communautés. - respect: Nous respectons le nom de tous le monde, leur genre, leur pronom + commitment: Nous nous engageons au respect et à l’aide mutuels, à la non violence et + au renforcement des communautés, et nous faisons vœu de revendiquer l’anti-oppression + et de résoudre les conflits. + respect: Nous respectons le nom de tout le monde, leur genre, leur pronom et l’expression de leur identité et de leurs expériences. - empowerment: Nous encourageons chaque personne et chaque groupe pour qu’il - ou elle devienne autonome. - accessible: 'Nous sommes engagés à assurer, autant que possible, l’accessibilité - à l’espace; physiquement, socialement et personnellement. ' + empowerment: Nous encourageons chaque personne et chaque groupe à devenir + autonome. + accessible: Nous nous engageons à assurer, autant que possible, l’accessibilité + à l’espace; physiquement, socialement et personnellement. peaceful: Nous sommes paisibles et honnêtes. spaces: Nous respectons le corps et l’espace de chacun.e. - hearing: Nous sommes engagés à s’écouter l’un.e l’autre et de créer des occasions - pour que toutes les voix soient entendues. + hearing: Nous nous engageons à nous écouter mutuellement et à donner à toutes + les voix l’occasion d’être entendues. intent: Nous acceptons une responsabilité partagée et nous sommes redevables nous-mêmes ainsi que l’un.e envers l’autre afin de défendre l’intention de cet accord. open_minds: Nous encourageons l’esprit ouvert et le cœur ouvert. learning: Dans une optique de croissance personnelle, nous faisons la promotion d’espaces d’apprentissages inclusifs et nous encourageons les questions. + paragraphs: + How: La ville hôte est chargée des questions ayant trait à l'Accord d'espace + positif et de la gestion des avertissements et des expulsions. + Why: "Afin de mettre tout le monde à la + même page, nous énonçons nos convictions et nos intentions relativement + à l’espace + de Bike!Bike! Nous nous rangeons derrière ces idées pour nous inspirer et + assurer de pouvoir nous appuyer sur cet accord en cas de comportement dangereux. + Nous espérons qu’une + entente de respect mutuel servira de ligne de conduite en cas de conflit." + user_settings: + headings: + email_subscribe: Avis + Your_Conferences: Vos congrès + Your_Account: Paramètres du compte + email_subscribe: Inscription aux annonces par courriel + paragraphs: + email_subscribe: Voulez-vous qu'on vous tienne au courant des congrès à venir? + Si vous y êtes inscrit(e) à un congrès, vous recevrez des avis et des nouvelles + qui s'y rapportent jusqu'à son commencement. + workshops: + headings: + Delete_Workshop: Supprimer la présentation + Comments: Commentaires + Schedule: Horaire + Workshops: Présentations + Your_Workshops: Vos présentations + Proposed_Workshops: Présentations suggérés + add_facilitator: Ajouter un animateur ou une animatrice + facilitate: Proposer d'animer %{workshop_title} + facilitators: Animateurs ou animatrices + languages: Langues + needs: Besoins + needs_facilitators: Avez-vous demandé de l'aide? + notes: Remarques + space: Lieu + theme: Thèmes + info: + interested_count: + one: Cette présentation intéresse une personne. + zero: Personne ne s'intéresse à cette présentation pour le moment. + other: "%{count} personnes s'intéressent à cette présentation." + you_are_interested_count: + zero: Cette présentation vous intéresse. + one: Vous et une autre personne vous intéressez à cette présentation. + other: Vous et %{count} autres personnes vous intéressez à cette présentation. + read_more: Plus d'information + paragraphs: + info: Décrivez votre présentation en détail. Surbrillez le texte pour pouvoir + le formater. + languages: En quelle langue la présentation sera-t-elle donnée? Une traduction + sera-t-elle fournie? + space: Dans quel type de lieu votre présentation peut-elle être donnée? + Delete_Workshop: Toute suppression de présentation est définitive. Vous voulez + quand même continuer? + Proposed_Workshops: Voulez-vous animer votre propre présentation? Il vous + suffit de vous inscrire et de vous rendre à la page des présentations. Si + vous êtes déjà inscrit(e), vous pouvez accéder à la page en recommençant + le processus d'inscription. + Workshops: Avez-vous des compétences dont vous voulez nous faire profiter? + Vous voulez parler de la création d'espaces communautaires positifs? Vous + voulez vous assurer qu'il y aura une belle sortie à vélo pendant le Congrès? + Suggérez une présentation! Vous n'avez pas à être expert(e). L' avis du + plus grand nombre possible des membres des ateliers communautaires nous + intéresse. + facilitate_request: Veuillez expliquer qui vous êtes aux animateurs et aux + animatrices de la présentation, les raisons pour lesquelles vous voulez + animer cette présentation, ainsi que la façon dont vous pensez pouvoir l’améliorer. + Les animateurs et animatrices actuels recevront un courriel et pourront + poser des questions avant de décider si oui ou non ils acceptent votre demande. + Veuilez noter que les animateurs et les animatrices auront alors accès à + votre adresse de courriel. + facilitate_request_sent: Votre demande a été envoyée. Vous recevrez un courriel + une fois qu’elle aura été approuvée ou rejetée, ou si l’un des animateurs + ou l’une des animatrices a des questions. + notes: Les remarques ne peuvent être vues que par organisateurs et les organisatrices + ainsi que par les personnes qui animent les présentations. + needs: "Si vous avez besoin du + matériel qui suit, les organisateurs et les organisatrices feront leur possible + pour vous le procurer. Si vous avez d’autres + besoins, veuillez l’indiquer + \r\nsous la rubrique \r\n\r\n«\r\nRemarques + ». " + needs_facilitators: "Vous cherchez activement de l’aide + pour cette présentation? Quiconque est inscrit au Congrès peut se proposer + en tout temps comme animateur ou animatrice, mais si vous cochez cette case, + vous aurez de meilleures chances de trouver de l’aide. " + theme: Lequel de ces thèmes correspond le mieux à votre présentation? Veuillez + le préciser pour nous aider à faire en sorte que les thèmes se chevauchent + le moins possible. + contact: + headings: + sent: Merci d'avoir communiqué avec nous. + contact: Posez-nous une question ou faites-nous un compliment + reason: Motif de la communication + paragraphs: + sent: Merci pour votre message. Nous vous répondrons par courriel dès que + possible. + conferences: + headings: + Conference_List: Congrès + types: + future: Congrès à venir + passed: Congrès passés + conferences: Congrès nationaux et régionaux + paragraphs: + conferences: Les congrès Bike!Bike! sont tenus dans une ville nord-américaine + différente tous les ans. Les congrès régionaux ont lieu n'importe où et + à tout moment. + permission_denied: + headings: + confirm_email: Merci de confirmer votre adresse de courriel + confirmation_sent: Confirmation envoyée page_titles: About: "À propos" about: - About_BikeBike: Au sujet de Bike!Bike! + About_BikeBike: "À propose de Bike!Bike!" Policy: Politique policy: Safer_Spaces_Policy: Accord d’espace positif + conferences: + Delete_Workshop: Supprimer la présentation + Conference_Registration: Inscription au Congrès + Edit_Workshop: Modifier la présentation + View_Workshop: Voir la présentation + Workshops: Présentations + Translate_Workshop: 'Modifier la traduction en %{language} pour cet atelier ' + Facilitate_Workshop: Demander à animer une présentation + Create_Workshop: Ajouter une présentation + contact: + Contact_Us: Communiquez avec nous + user_settings: + Your_Account: Votre compte + '403': + Access_Denied: Accès refusé + Please_Login: Veuillez vous connecter + '500': + An_Error_Occurred: Une erreur s’est produite. + '404': + Page_Not_Found: Page non trouvée + Locale_Not_Available: Lieu non disponible + Conferences: Congrès + actions: + workshops: + Cancel_Request: Annuler la demande + Approve: Approuver + Delete: Supprimer la présentation + Deny: Rejeter + Edit: Modifier + create: Nouvelle présentation + View_All: Aperçu de toutes les présentations + Translate: Traduire en %{language} + View: Aperçu de cette présentation + Facilitate: Demander à animer une présentation + Leave: Se retirer + Make_Owner: Changer de responsable + Remove: Supprimer + datetime: + distance_in_words: + x_days: + one: 1 jour + other: "%{count} jours" + x_hours: + one: 1 heure + other: "%{count} heures" + x_minutes: + one: 1 minute + other: " %{count} minutes" + x_months: + one: 1 mois + other: "%{count} mois" + x_seconds: + one: 1 seconde + other: "%{count} secondes" + about_x_hours: + one: environ 1 heure + other: environ %{count} heures + about_x_months: + other: environ %{count} mois + one: environ 1 mois + about_x_years: + other: " environ %{count} ans" + one: environ un an + less_than_x_minutes: + other: moins de %{count} minutes + one: 'moins d''une minute ' + less_than_x_seconds: + other: moins de %{count} secondes + one: moins d'1 seconde + over_x_years: + other: plus de %{count} ans + one: plus d'1 an + almost_x_years: + one: près d'1 an + other: près de %{count} ans + half_a_minute: 'une demi-minute ' + x_and_y: "%{x} %{y}" + time_ago: 'Il y a %{time} ' + email: + general: + paragraph: + thank_you: Merci %{name} + see_you: Rendez-vous à %{conference_location}! + subject: + workshop_request_denied: Votre demande d’animer %{workshop_title} a été refusée. + confirm_email: Courriel de confirmation + registration_confirmed: Merci pour votre inscription à %{conference_title} + workshop_comment: + comment: "%{user_name} a laissé un commentaire sur %{workshop_title}" + reply: "%{user_name} a répondu à votre commentaire" + workshop_request_approved: Votre nom a été ajouté à la liste des animateurs + et des animatrices de %{workshop_title} + workshop_facilitator_request: Demande d'animer %{workshop_title} réparation + de %{requester_name} + workshop_translated: La traduction en %{language} de %{workshop_title} a été + modifiée. + confirmation: + link: + please_confirm: Confirmer maintenant + paragraph: + please_confirm: 'Bonjour! Pour vous inscrire ou accéder aux autres options + offertes pendant Bike!Bike!, veuillez confirmer votre adresse de courriel + en cliquant sur le lien suivant : ' + workshop: + paragraph: + view_workshop: 'Détails sur la présentation à :' + request_approved: Votre nom a été ajouté à la liste des animateurs/animatrices + de %{workshop_title} + request_instructions: Vous pouvez approuver ou rejeter cette demande à partir + de la page de votre présentation. + request_message: "%{user_name} a proposé d’animer + %{workshop_title}." + request_reply_instructions: Vous pouvez aussi poser des questions supplémentaires + en répondant directement à ce courriel. + request_denied: "Vous avez demandé à animer %{workshop_title}; votre demande + a été rejetée. Si vous pensez qu’il + s’agit + d’une + erreur, contactez les personnes actuellement en charge de cette présentation + en soumettant une nouvelle demande. " + workshop_comment: + paragraph: + user_said: "%{user_name} a dit :" + translations: + headings: + new_value: 'Nouveau titre :' + old_value: 'Ancien titre :' + paragraph: + workshop_translated: "%{user_name} a modifié la traduction en %{language} + de %{workshop_title} " + registration: + paragraph: + confirmed: "Vous avez terminé votre inscription à %{conference_title}. Vous + pouvez modifier les détails sur votre inscription, effectuer un paiement + ou ajouter une présentation à tout moment en recommençant le processus d’inscription. + Si vous n’avez + pas encore payé, ou proposé une présentation, et que vous prévoyez de le + faire, veuillez le faire le plus vite possible afin de nous aider à bien + nous préparer à votre visite. " + forms: + actions: + generic: + facebook_sign_in: Connexion à Facebook + login: Se connecter + edit: Modifier + previous: Précédent + remove_interest: "-1" + reply: Répondre + send: Envoyer + show_interest: "+1" + add_comment: Ajouter un commentaire + agree: J’accepte + cancel: Annuler + close: Fermer + confirm: Confirmer + confirm_amount: Confirmer + continue: Continuer + create: Créer + custom_amount: Montantpersonnalisé + next: Suivant + save: Enregistrer + Log_out: Déconnexion + add: "+" + register: S’inscrire + skip: Sauter + administrate: Administrer + aria: + remove_interest: Cliquez si la présentation ne vous intéresse plus. + show_interest: Cliquez si cette présentation vous intéresse. + add: Ajouter une nouvelle présentation + labels: + generic: + address: Adresse postale + allergies: Allergies + arrival: Date d’arrivée + companion: Adresse de courriel + departure: Date de départ + email: Adresse de courriel + info: Description + name: Nom + phone: Numéro de téléphone + bed_space: Lit/sofa + location: Ville, état/Province, pays + title: Titre + hosting_dates: Quand arriverez-vous en ville? + last_day: "à" + space: Hébergement + subject: Objet + tent_space: Tente + reasons: + website: Commentaire sur le site Web + conference: Commentaire sur le Congrès + floor_space: Surface de plancher disponible + first_day: De + modals: + no_button: Non + yes_button: Oui + confirm: Veuillez confirmer + workshops: + facilitators: + confirm_cancel_request: Voulez-vous vraiment annuler votre demande d'animer + cette présentation? + confirm_remove_self: Vous voulez vraiment annuler votre participation en tant + qu’animateur ou animatrice de cette présentation? + confirm_remove: 'Vous voulez vraiment retirer %{user_name} de la liste des + animateurs et ddes animatrices de cette présentation? ' + confirm_transfer_ownership: "Si vous chargez une autre personne de cette présentation, + vous perdrez votre capacité à supprimer et à accepter les personnes qui + proposent de l’animer. + Souhaitez-vous quand même désigner %{user_name} comme responsable? " + workshop: + options: + theme: + other: Autre + funding: Financement + race_gender: Race, genre ou politique des classes + organization: Questions d’ordre organisationnel + mechanics: Mécanique + community: Approche communautaire + space: + meeting_room: Salle de réunion + workshop: Lieu de réparation + outdoor_meeting: "À l’extérieur" + needs: + tools: Outils + projector: Projecteur + sound: Système audio + needs_facilitators: Il faut d’autres personnes pour animer la présentation. + translate: + content: + Translation_of: Traduction de + change_locale: Lire en %{language} + time: + pm: "(après-midi)" + am: "(matin)" + formats: + short: "%k%M" + roles: + workshops: + facilitator: + unregistered: Non inscrit(e) + creator: Responsable + requested: Demandé + collaborator: Collaborateur ou collaboratrice + warnings: + messages: + location_corrected: L’adresse "%{original}" a été changée à "%{corrected}". + Si cela ne correspond pas à l’adresse que vous désirez soumettre, vous pouvez + la modifier à nouveau à l’étape « Coordonnées ». + error: + '500': + title: Il y a un problème + description: "Une erreur s’est produite. Un message expliquant l’erreur + a été envoyé à l’équipe + de développement Web. De plus, vous pouvez communiquer avec nous en expliquant + dans la boîte ci-dessous ce qui s’est + passé. " + '403': + title: Vous ne pouvez pas accéder à cette page. + description: Vous n’avez présetentement pas la permission d’accéder à cette + page. Si vous pensez qu’il s’agit d’une erreur, veuillez communiquer avec + nous. + '404': + title: '404 : Cette page est introuvable.' + description: Page introuvable. Si vous pensez qu'il s'agit d'une erreur, veuillez + nous contacter. + locale_not_available: + volunteer: + title: Pouvez-vous nous aider à traduire? + description: Si vous pouvez nous aider à traduire ce contenu ou à traduire + dans une autre langue, veuillez nous en aviser à l’aide du formulaire ci-dessous. + description: Ce site n’a pas encore été traduit en %{language}. Nous recherchons + des bénévoles pour en traduire la version existante, ainsi que les mises à + jour qui y seront faites. Si vous pouvez nous aider, faites-le nous savoir. + title: '404 : traductions en %{language} manquantes' + date: + date_span: "%{date_1} – %{date_2}" + formats: + span_same_month_date_1: "%d %m " + span_same_month_date_2: "%d %Y " + weekday: "%A" + span_same_year_date_1: "%e %B" + errors: + messages: + fields: + payment: + incomplete: Votre paiement n'a pas été finalisé. + images: + conference: + poster: Affiche de %{conference_title} + links: + footer: + help_text: + contact: Communiquez avec nous + facebook: Joignez-vous à notre groupe sur Facebook. + contributors: Contributions + text: + Help_contribute: Apporter une contribution + number: + currency: + format: + format: "%n %u" + separator: ", " + page_descriptions: + home: Bike!Bike! est un congrès pour les associations, les coopératives et les + ateliers de vélo à but non lucratif. From 8e90f4545b2b49e72209d77ee7cf9ce39fee0807 Mon Sep 17 00:00:00 2001 From: Godwin Date: Thu, 18 May 2017 17:01:10 -0700 Subject: [PATCH 37/45] Fixed email issues --- app/controllers/application_controller.rb | 129 +++++++------ .../conference_administration_controller.rb | 169 ++++++++++++++++-- app/controllers/workshops_controller.rb | 28 ++- features/support/location_cache.yml | 96 +++++----- 4 files changed, 285 insertions(+), 137 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8dc06bd..2ae5dcf 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,7 +3,7 @@ class ApplicationController < BaseController before_filter :capture_page_info - helper_method :protect + helper_method :protect, :policies # @@test_host # @@test_location @@ -14,22 +14,22 @@ class ApplicationController < BaseController def capture_page_info # capture request info in case an error occurs - if request.method == "GET" && (params[:controller] != 'application' || params[:action] != 'contact') - session[:last_request] - request_info = { - 'params' => params, - 'request' => { - 'remote_ip' => request.remote_ip, - 'uuid' => request.uuid, - 'original_url' => request.original_url, - 'env' => Hash.new - } - } - request.env.each do |key, value| - request_info['request']['env'][key.to_s] = value.to_s - end - session['request_info'] = request_info - end + # if request.method == "GET" && (params[:controller] != 'application' || params[:action] != 'contact') + # session[:last_request] + # request_info = { + # 'params' => params, + # 'request' => { + # 'remote_ip' => request.remote_ip, + # 'uuid' => request.uuid, + # 'original_url' => request.original_url, + # 'env' => Hash.new + # } + # } + # request.env.each do |key, value| + # request_info['request']['env'][key.to_s] = value.to_s + # end + # # session['request_info'] = request_info + # end # get the current conferences and set them globally status_hierarchy = { @@ -57,7 +57,7 @@ class ApplicationController < BaseController } @alt_lang_urls = {} - I18n.backend.enabled_locales.each do |locale| + I18n.backend.enabled_locales.sort.each do |locale| locale = locale.to_s @alt_lang_urls[locale] = view_context.url_for_locale(locale) # don't show the current locale end @@ -82,18 +82,6 @@ class ApplicationController < BaseController @is_policy_page = true end - # def self.set_host(host) - # @@test_host = host - # end - - # def self.set_location(location) - # @@test_location = location - # end - - # def self.get_location() - # @@test_location - # end - def js_error # send and email if this is production report = "A JavaScript error has occurred on #{params[:location]}" @@ -108,14 +96,14 @@ class ApplicationController < BaseController logger.info "A JavaScript error has occurred on #{params[:location]}:#{params[:lineNumber]}: #{params[:message]}" if Rails.env.preview? || Rails.env.production? - requestHash = { - 'remote_ip' => arg.remote_ip, - 'uuid' => arg.uuid, - 'original_url' => arg.original_url, + request_info = { + 'remote_ip' => request.remote_ip, + 'uuid' => request.uuid, + 'original_url' => request.original_url, 'env' => Hash.new } - request.env.each do | key, value | - requestHash['env'][key.to_s] = value.to_s + request.env.each do |key, value| + request_info['env'][key.to_s] = value.to_s end send_mail(:error_report, @@ -123,7 +111,7 @@ class ApplicationController < BaseController report, params[:message], nil, - requestHash, + request_info, params, current_user, Time.now.strftime("%d/%m/%Y %H:%M") @@ -164,6 +152,15 @@ class ApplicationController < BaseController @page_title = 'page_titles.404.Locale_Not_Available' @main_title_vars = { vars: { language: view_context.language_name(locale) } } @main_title = 'error.locale_not_available.title' + + unless @alt_lang_urls.present? + @alt_lang_urls = {} + I18n.backend.enabled_locales.sort.each do |locale| + locale = locale.to_s + @alt_lang_urls[locale] = view_context.url_for_locale(locale) # don't show the current locale + end + end + render 'application/locale_not_available', status: 404 end @@ -184,21 +181,21 @@ class ApplicationController < BaseController # send and email if this is production if Rails.env.preview? || Rails.env.production? suppress(Exception) do - requestHash = { - 'remote_ip' => arg.remote_ip, - 'uuid' => arg.uuid, - 'original_url' => arg.original_url, + request_info = { + 'remote_ip' => request.remote_ip, + 'uuid' => request.uuid, + 'original_url' => request.original_url, 'env' => Hash.new } - request.env.each do | key, value | - requestHash['env'][key.to_s] = value.to_s + request.env.each do |key, value| + request_info['env'][key.to_s] = value.to_s end send_mail(:error_report, "An error has occurred in #{Rails.env}", nil, exception.to_s, exception.backtrace.join("\n"), - requestHash, + request_info, params, current_user, Time.now.strftime("%d/%m/%Y %H:%M") @@ -234,6 +231,16 @@ class ApplicationController < BaseController end end + request_info = { + 'remote_ip' => request.remote_ip, + 'uuid' => request.uuid, + 'original_url' => request.original_url, + 'env' => Hash.new + } + request.env.each do |key, value| + request_info['env'][key.to_s] = value.to_s + end + send_mail(:contact, current_user || params[:email], params[:subject], @@ -241,13 +248,12 @@ class ApplicationController < BaseController email_list ) - request_info = session['request_info'] || { 'request' => request, 'params' => params } send_mail(:contact_details, current_user || params[:email], params[:subject], params[:message], - request_info['request'], - request_info['params'] + request_info, + params ) redirect_to contact_sent_path @@ -351,21 +357,21 @@ class ApplicationController < BaseController # send an email if this is production if Rails.env.preview? || Rails.env.production? begin - requestHash = { - 'remote_ip' => arg.remote_ip, - 'uuid' => arg.uuid, - 'original_url' => arg.original_url, + request_info = { + 'remote_ip' => request.remote_ip, + 'uuid' => request.uuid, + 'original_url' => request.original_url, 'env' => Hash.new } - request.env.each do | key, value | - requestHash['env'][key.to_s] = value.to_s + request.env.each do |key, value| + request_info['env'][key.to_s] = value.to_s end send_mail(:error_report, "A missing translation found in #{Rails.env}", "

    A translation for #{key} in #{locale.to_s} was found. The text that was rendered to the user was:

    #{str || 'nil'}
    ", exception.to_s, nil, - requestHash, + request_info, params, current_user.id, Time.now.strftime("%d/%m/%Y %H:%M") @@ -646,4 +652,19 @@ class ApplicationController < BaseController UserMailer.send(*args).deliver_now end end + + def policies + [ + :commitment, + :respect, + :empowerment, + :accessible, + :peaceful, + :spaces, + :hearing, + :intent, + :open_minds, + :learning + ] + end end diff --git a/app/controllers/conference_administration_controller.rb b/app/controllers/conference_administration_controller.rb index 236c0c8..640273b 100644 --- a/app/controllers/conference_administration_controller.rb +++ b/app/controllers/conference_administration_controller.rb @@ -70,6 +70,17 @@ class ConferenceAdministrationController < ApplicationController end end + def previous_stats + set_conference + conference = Conference.find_by_slug(params[:conference_slug]) + return do_403 unless conference.is_public + get_stats(false, nil, conference) + logger.info "Generating #{conference.slug}.xls" + return respond_to do |format| + format.xlsx { render xlsx: '../conferences/stats', filename: "stats-#{conference.slug}" } + end + end + rescue_from ActiveRecord::PremissionDenied do |exception| do_403 end @@ -99,6 +110,9 @@ class ConferenceAdministrationController < ApplicationController def administrate_description end + def administrate_group_ride + end + def administrate_poster end @@ -119,6 +133,30 @@ class ConferenceAdministrationController < ApplicationController def administrate_payment_message end + def administrate_housing_info + end + + def administrate_workshop_info + end + + def administrate_schedule_info + end + + def administrate_travel_info + end + + def administrate_city_info + end + + def administrate_what_to_bring + end + + def administrate_volunteering_info + end + + def administrate_additional_details + end + def administrate_suggested_amounts end @@ -168,7 +206,7 @@ class ConferenceAdministrationController < ApplicationController end end return respond_to do |format| - format.xlsx { render xlsx: :stats, filename: "organizations" } + format.xlsx { render xlsx: '../conferences/stats', filename: "organizations" } end end end @@ -184,7 +222,7 @@ class ConferenceAdministrationController < ApplicationController if request.format.xlsx? logger.info "Generating stats.xls" return respond_to do |format| - format.xlsx { render xlsx: :stats, filename: "stats-#{DateTime.now.strftime('%Y-%m-%d')}" } + format.xlsx { render xlsx: '../conferences/stats', filename: "stats-#{DateTime.now.strftime('%Y-%m-%d')}" } end end @@ -224,7 +262,7 @@ class ConferenceAdministrationController < ApplicationController if request.format.xlsx? logger.info "Generating stats.xls" return respond_to do |format| - format.xlsx { render xlsx: :stats, filename: "stats-#{DateTime.now.strftime('%Y-%m-%d')}" } + format.xlsx { render xlsx: '../conferences/stats', filename: "stats-#{DateTime.now.strftime('%Y-%m-%d')}" } end else @registration_count = @registrations.size @@ -250,6 +288,11 @@ class ConferenceAdministrationController < ApplicationController end end end + + @past_conferences = [] + Conference.all.order("start_date DESC").each do |conference| + @past_conferences << conference if conference.is_public && @this_conference.id != conference.id + end end end @@ -327,7 +370,7 @@ class ConferenceAdministrationController < ApplicationController @excel_data[:data] << host_data end return respond_to do |format| - format.xlsx { render xlsx: :stats, filename: "housing" } + format.xlsx { render xlsx: '../conferences/stats', filename: "housing" } end end end @@ -379,8 +422,8 @@ class ConferenceAdministrationController < ApplicationController def administrate_publish_schedule end - def get_stats(html_format = false, id = nil) - @registrations = ConferenceRegistration.where(:conference_id => @this_conference.id).sort { |a,b| (a.user.present? ? (a.user.firstname || '') : '').downcase <=> (b.user.present? ? (b.user.firstname || '') : '').downcase } + def get_stats(html_format = false, id = nil, conference = @this_conference) + @registrations = ConferenceRegistration.where(conference_id: conference.id).sort { |a,b| (a.user.present? ? (a.user.firstname || '') : '').downcase <=> (b.user.present? ? (b.user.firstname || '') : '').downcase } @excel_data = { columns: [ :name, @@ -402,7 +445,6 @@ class ConferenceAdministrationController < ApplicationController :food, :companion, :companion_email, - :allergies, :other, :can_provide_housing, :first_day, @@ -421,7 +463,6 @@ class ConferenceAdministrationController < ApplicationController arrival: [:date, :day], departure: [:date, :day], registration_fees_paid: :money, - allergies: :text, other: :text, first_day: [:date, :day], last_day: [:date, :day], @@ -443,7 +484,6 @@ class ConferenceAdministrationController < ApplicationController food: 'forms.labels.generic.food', companion: 'articles.conference_registration.terms.companion', companion_email: 'articles.conference_registration.terms.companion_email', - allergies: 'forms.labels.generic.allergies', registration_fees_paid: 'articles.conference_registration.headings.fees_paid', other: 'forms.labels.generic.other_notes', can_provide_housing: 'articles.conference_registration.can_provide_housing', @@ -458,6 +498,11 @@ class ConferenceAdministrationController < ApplicationController }, data: [] } + + if conference.id != @this_conference.id + @excel_data[:columns] -= [:name, :email] + end + User.AVAILABLE_LANGUAGES.each do |l| @excel_data[:keys]["language_#{l}".to_sym] = "languages.#{l.to_s}" end @@ -497,10 +542,9 @@ class ConferenceAdministrationController < ApplicationController bike: r.bike.present? ? (view_context._"articles.conference_registration.questions.bike.#{r.bike}") : '', food: r.food.present? ? (view_context._"articles.conference_registration.questions.food.#{r.food}") : '', companion: companion, - companion_email: (housing_data['companions'] || ['']).first, - allergies: r.allergies, + companion_email: (housing_data['companion'] || { 'email' => ''})['email'], registration_fees_paid: r.registration_fees_paid, - other: r.other, + other: r.allergies.present? ? "#{r.allergies}\n\n#{r.other}" : r.other, can_provide_housing: r.can_provide_housing ? (view_context._'articles.conference_registration.questions.bike.yes') : '', first_day: availability[0].present? ? availability[0].strftime("%F %T") : '', last_day: availability[1].present? ? availability[1].strftime("%F %T") : '', @@ -650,12 +694,16 @@ class ConferenceAdministrationController < ApplicationController @housing_data[host_id][:guest_data][guest_id][:warnings][:space] = { actual: (view_context._"forms.labels.generic.#{space.to_s}"), expected: (view_context._"articles.conference_registration.questions.housing.#{guest.housing}")} end - companions = data['companions'] || [] - companions.each do |companion| - user = User.find_user(companion) - if user.present? + if data['companion'].present? + companion = if data['companion']['id'].present? + User.find(data['companion']['id']) + else + User.find_user(data['companion']['email']) + end + + if companion.present? reg = ConferenceRegistration.find_by( - user_id: user.id, + user_id: companion.id, conference_id: @this_conference.id ) if reg.present? && @guests[reg.id].present? @@ -798,6 +846,87 @@ class ConferenceAdministrationController < ApplicationController return false end + def admin_update_group_ride + params[:group_ride_info].each do |locale, value| + @this_conference.set_column_for_locale(:group_ride_info, locale, html_value(value)) + end + @this_conference.save + set_success_message @admin_step + return false + end + + def admin_update_housing_info + params[:housing_info].each do |locale, value| + @this_conference.set_column_for_locale(:housing_info, locale, html_value(value)) + end + @this_conference.save + set_success_message @admin_step + return false + end + + def admin_update_workshop_info + params[:workshop_info].each do |locale, value| + @this_conference.set_column_for_locale(:workshop_info, locale, html_value(value)) + end + @this_conference.save + set_success_message @admin_step + return false + end + + def admin_update_schedule_info + params[:schedule_info].each do |locale, value| + @this_conference.set_column_for_locale(:schedule_info, locale, html_value(value)) + end + @this_conference.save + set_success_message @admin_step + return false + end + + def admin_update_travel_info + params[:travel_info].each do |locale, value| + @this_conference.set_column_for_locale(:travel_info, locale, html_value(value)) + end + @this_conference.save + set_success_message @admin_step + return false + end + + def admin_update_city_info + params[:city_info].each do |locale, value| + @this_conference.set_column_for_locale(:city_info, locale, html_value(value)) + end + @this_conference.save + set_success_message @admin_step + return false + end + + def admin_update_what_to_bring + params[:what_to_bring].each do |locale, value| + @this_conference.set_column_for_locale(:what_to_bring, locale, html_value(value)) + end + @this_conference.save + set_success_message @admin_step + return false + end + + def admin_update_volunteering_info + params[:volunteering_info].each do |locale, value| + @this_conference.set_column_for_locale(:volunteering_info, locale, html_value(value)) + end + @this_conference.save + set_success_message @admin_step + return false + end + + def admin_update_additional_details + params[:additional_details].each do |locale, value| + @this_conference.set_column_for_locale(:additional_details, locale, html_value(value)) + end + @this_conference.save + set_success_message @admin_step + return false + end + def admin_update_poster begin @this_conference.poster = params[:poster] @@ -902,7 +1031,9 @@ class ConferenceAdministrationController < ApplicationController registration.send("#{key.to_s}=", value.present? ? Date.parse(value) : nil) when :companion_email registration.housing_data ||= {} - registration.housing_data['companions'] = [value] + registration.housing_data['companion'] ||= {} + registration.housing_data['companion']['email'] = value + registration.housing_data['companion']['id'] = User.find_user(value) when :preferred_language registration.user.locale = value user_changed = true @@ -1004,7 +1135,7 @@ class ConferenceAdministrationController < ApplicationController do_404 end - return true + return nil end def admin_update_broadcast diff --git a/app/controllers/workshops_controller.rb b/app/controllers/workshops_controller.rb index ea7a64b..e835e70 100644 --- a/app/controllers/workshops_controller.rb +++ b/app/controllers/workshops_controller.rb @@ -217,11 +217,7 @@ class WorkshopsController < ApplicationController # create the request by making the user a facilitator but making their role 'requested' WorkshopFacilitator.create(user_id: current_user.id, workshop_id: workshop.id, role: :requested) - UserMailer.send_mail :workshop_facilitator_request do - { - :args => [ workshop, current_user, params[:message] ] - } - end + UserMailer.workshop_facilitator_request(workshop.id, current_user.id, params[:message]).deliver_later redirect_to sent_facilitate_workshop_url(@this_conference.slug, workshop.id) end @@ -254,18 +250,18 @@ class WorkshopsController < ApplicationController workshop.id, user_id) f.role = :collaborator f.save - UserMailer.send_mail :workshop_facilitator_request_approved, user.locale do - [ workshop, user ] + LinguaFranca.with_locale(user.locale) do + UserMailer.workshop_facilitator_request_approved(workshop.id, user.id).deliver_later end - return redirect_to view_workshop_url(@this_conference.slug, workshop.id) + return redirect_to view_workshop_url(@this_conference.slug, workshop.id) end when :deny if workshop.active_facilitator?(current_user) && workshop.requested_collaborator?(User.find(user_id)) WorkshopFacilitator.delete_all( :workshop_id => workshop.id, :user_id => user_id) - UserMailer.send_mail :workshop_facilitator_request_denied, user.locale do - [ workshop, user ] + LinguaFranca.with_locale user.locale do + UserMailer.workshop_facilitator_request_denied(workshop.id, user.id).deliver_later end return redirect_to view_workshop_url(@this_conference.slug, workshop.id) end @@ -312,8 +308,8 @@ class WorkshopsController < ApplicationController unless workshop.facilitator?(user) WorkshopFacilitator.create(user_id: user.id, workshop_id: workshop.id, role: :collaborator) - UserMailer.send_mail :workshop_facilitator_request_approved, user.locale do - [ workshop, user ] + LinguaFranca.with_locale user.locale do + UserMailer.workshop_facilitator_request_approved(workshop.id, user.id).deliver_later end end @@ -332,8 +328,8 @@ class WorkshopsController < ApplicationController new_comment = comment.add_comment(current_user, params[:reply]) unless comment.user.id == current_user.id - UserMailer.send_mail :workshop_comment, comment.user.locale do - [ workshop, new_comment, comment.user ] + LinguaFranca.with_locale comment.user.locale do + UserMailer.workshop_comment(workshop.id, new_comment.id, comment.user.id).deliver_later end end elsif params[:button] = 'add_comment' @@ -341,8 +337,8 @@ class WorkshopsController < ApplicationController workshop.active_facilitators.each do | u | unless u.id == current_user.id - UserMailer.send_mail :workshop_comment, u.locale do - [ workshop, new_comment, u ] + LinguaFranca.with_locale u.locale do + UserMailer.workshop_comment(workshop.id, new_comment.id, u.id).deliver_later end end end diff --git a/features/support/location_cache.yml b/features/support/location_cache.yml index 02508d1..9badcd9 100644 --- a/features/support/location_cache.yml +++ b/features/support/location_cache.yml @@ -251,6 +251,54 @@ Portland OR: !ruby/object:Geocoder::Result::Google - locality - political cache_hit: +Prince Rupert BC: !ruby/object:Geocoder::Result::Google + data: + address_components: + - long_name: Prince Rupert + short_name: Prince Rupert + types: + - locality + - political + - long_name: Skeena-Queen Charlotte + short_name: Skeena-Queen Charlotte + types: + - administrative_area_level_2 + - political + - long_name: British Columbia + short_name: BC + types: + - administrative_area_level_1 + - political + - long_name: Canada + short_name: CA + types: + - country + - political + formatted_address: Prince Rupert, BC, Canada + geometry: + bounds: + northeast: + lat: 54.338083 + lng: -130.2437961 + southwest: + lat: 54.19392 + lng: -130.3634291 + location: + lat: 54.3150367 + lng: -130.3208187 + location_type: APPROXIMATE + viewport: + northeast: + lat: 54.3343706 + lng: -130.2478032 + southwest: + lat: 54.202669 + lng: -130.3608029 + place_id: ChIJaUV_axPVclQRElbZTQ_jB3E + types: + - locality + - political + cache_hit: Regina, SK: !ruby/object:Geocoder::Result::Google data: address_components: @@ -453,51 +501,3 @@ Eldorado, MX: !ruby/object:Geocoder::Result::Google - locality - political cache_hit: -Prince Rupert BC: !ruby/object:Geocoder::Result::Google - data: - address_components: - - long_name: Prince Rupert - short_name: Prince Rupert - types: - - locality - - political - - long_name: Skeena-Queen Charlotte - short_name: Skeena-Queen Charlotte - types: - - administrative_area_level_2 - - political - - long_name: British Columbia - short_name: BC - types: - - administrative_area_level_1 - - political - - long_name: Canada - short_name: CA - types: - - country - - political - formatted_address: Prince Rupert, BC, Canada - geometry: - bounds: - northeast: - lat: 54.338083 - lng: -130.2437961 - southwest: - lat: 54.19392 - lng: -130.3634291 - location: - lat: 54.3150367 - lng: -130.3208187 - location_type: APPROXIMATE - viewport: - northeast: - lat: 54.3343706 - lng: -130.2478032 - southwest: - lat: 54.202669 - lng: -130.3608029 - place_id: ChIJaUV_axPVclQRElbZTQ_jB3E - types: - - locality - - political - cache_hit: From dc024cb2d29f5f33c86706d29f91aa38067a6fbb Mon Sep 17 00:00:00 2001 From: Godwin Date: Fri, 19 May 2017 19:31:41 -0700 Subject: [PATCH 38/45] Fix for Quill editor --- app/assets/javascripts/editor.js | 1 + app/assets/stylesheets/_application.scss | 4 +- app/controllers/application_controller.rb | 4 + config/environments/production.rb | 104 ++++++++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/editor.js b/app/assets/javascripts/editor.js index 72cbdd4..5ba62e5 100644 --- a/app/assets/javascripts/editor.js +++ b/app/assets/javascripts/editor.js @@ -12,6 +12,7 @@ modules: { toolbar: [ [{ 'header': [1, 2, false] }], + ['link', 'image'], ['bold', 'italic', 'underline', 'strike'], [{ 'script': 'sub'}, { 'script': 'super' }], [{ 'list': 'ordered'}, { 'list': 'bullet' }, 'blockquote'] diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss index d38248c..5ff7ea0 100644 --- a/app/assets/stylesheets/_application.scss +++ b/app/assets/stylesheets/_application.scss @@ -1205,7 +1205,7 @@ fieldset { } } - ul { + .locale-select, .text-editors { list-style: none; padding: 0; margin: 0; @@ -1253,7 +1253,7 @@ fieldset { position: relative; background-color: $white; - li { + li[data-locale] { display: none; &.selected { diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2ae5dcf..2649ab3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -12,6 +12,10 @@ class ApplicationController < BaseController { host: "#{request.protocol}#{request.host_with_port}", trailing_slash: true } end + def default_url_options + { host: "#{request.protocol}#{request.host_with_port}" } + end + def capture_page_info # capture request info in case an error occurs # if request.method == "GET" && (params[:controller] != 'application' || params[:action] != 'contact') diff --git a/config/environments/production.rb b/config/environments/production.rb index 82fd268..782b53a 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,4 +1,5 @@ BikeBike::Application.configure do +<<<<<<< HEAD # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. @@ -99,4 +100,107 @@ BikeBike::Application.configure do config.action_mailer.raise_delivery_errors = true config.action_mailer.perform_deliveries = true I18n.config.language_detection_method = I18n::Config::DETECT_LANGUAGE_FROM_SUBDOMAIN +======= + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both thread web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Enable Rack::Cache to put a simple HTTP cache in front of your application + # Add `rack-cache` to your Gemfile before enabling this. + # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. + # config.action_dispatch.rack_cache = true + + # Disable Rails's static asset server (Apache or nginx will already do this). + config.serve_static_files = true + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + #config.assets.compile = true + + # Generate digests for assets URLs. + config.assets.digest = true + + # Version of your assets, change this if you want to expire all your assets. + config.assets.version = '1.01' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + #config.force_ssl = true + + # Set to :debug to see everything in the log. + config.log_level = :info + + #config.cache_classes = true + #config.serve_static_assets = true + #config.assets.compile = true + # config.assets.digest = true + + # Prepend all log lines with the following tags. + # config.log_tags = [ :subdomain, :uuid ] + + # Use a different logger for distributed setups. + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = "http://assets.example.com" + + # Precompile additional assets. + # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. + # config.assets.precompile += %w( search.js ) + #config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif) + config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif *.svg) + #config.action_controller.asset_host = "https://cdn.bikebike.org" + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation can not be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Disable automatic flushing of the log to improve performance. + # config.autoflush_log = false + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + config.action_mailer.delivery_method = :smtp + config.action_mailer.smtp_settings = { + :address => 'smtp.gmail.com', + :domain => 'bikebike.org', + :port => 587, + :authentication => :plain, + :enable_starttls_auto => true, + :openssl_verify_mode => 'none', + :user_name => '', + :password => '' + } + config.action_mailer.raise_delivery_errors = true + config.action_mailer.perform_deliveries = true + I18n.config.language_detection_method = I18n::Config::DETECT_LANGUAGE_FROM_SUBDOMAIN +>>>>>>> d08e8a9... Fix for Quill editor end From 608a0e3a826ccdf4fa82103a8dc898e0a572745d Mon Sep 17 00:00:00 2001 From: Godwin Date: Sat, 20 May 2017 08:15:10 -0700 Subject: [PATCH 39/45] Fixed loale links --- app/helpers/i18n_helper.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/helpers/i18n_helper.rb b/app/helpers/i18n_helper.rb index 62777d5..5e94063 100644 --- a/app/helpers/i18n_helper.rb +++ b/app/helpers/i18n_helper.rb @@ -2,18 +2,18 @@ module I18nHelper def url_for_locale(locale, url = nil) return url unless locale.present? - url ||= current_path + url ||= current_path(true) return url if Rails.env.development? || Rails.env.test? return "https://preview-#{locale.to_s}.bikebike.org#{url}" if Rails.env.preview? "https://#{locale.to_s}.bikebike.org#{url}" end - def current_path + def current_path(relative = false) new_params = params.merge({action: (params[:_original_action] || params[:action])}) new_params.delete(:_original_action) - if Rails.env.development? || Rails.env.test? + if relative || Rails.env.development? || Rails.env.test? return url_for(new_params.merge({lang: locale.to_s})) end From 529e3ab764879ec1615cae3435fadca47cc6421e Mon Sep 17 00:00:00 2001 From: Godwin Date: Sat, 20 May 2017 08:19:05 -0700 Subject: [PATCH 40/45] Remove locale parameter from locale nav --- app/helpers/i18n_helper.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/helpers/i18n_helper.rb b/app/helpers/i18n_helper.rb index 5e94063..53ef70a 100644 --- a/app/helpers/i18n_helper.rb +++ b/app/helpers/i18n_helper.rb @@ -13,9 +13,12 @@ module I18nHelper new_params = params.merge({action: (params[:_original_action] || params[:action])}) new_params.delete(:_original_action) - if relative || Rails.env.development? || Rails.env.test? + if Rails.env.development? || Rails.env.test? return url_for(new_params.merge({lang: locale.to_s})) end + + # return now if this is a relative path, don't add the host + return url_for(new_params) if relative subdomain = Rails.env.preview? ? "preview-#{locale.to_s}" : locale.to_s url_for(new_params.merge(host: "#{subdomain}.bikebike.org")) From 06918e1799fe464340e1b7edf9573fcbf3acfe63 Mon Sep 17 00:00:00 2001 From: Godwin Date: Sun, 21 May 2017 15:55:49 -0700 Subject: [PATCH 41/45] Fixed issues with email --- app/controllers/workshops_controller.rb | 12 +-- app/mailers/user_mailer.rb | 8 ++ config/application.rb | 1 - config/environments/development.rb | 1 + config/environments/preview.rb | 18 ++-- config/environments/production.rb | 122 +++--------------------- 6 files changed, 37 insertions(+), 125 deletions(-) diff --git a/app/controllers/workshops_controller.rb b/app/controllers/workshops_controller.rb index e835e70..c23dd80 100644 --- a/app/controllers/workshops_controller.rb +++ b/app/controllers/workshops_controller.rb @@ -217,7 +217,7 @@ class WorkshopsController < ApplicationController # create the request by making the user a facilitator but making their role 'requested' WorkshopFacilitator.create(user_id: current_user.id, workshop_id: workshop.id, role: :requested) - UserMailer.workshop_facilitator_request(workshop.id, current_user.id, params[:message]).deliver_later + UserMailer.send_mail(:workshop_facilitator_request, workshop.id, current_user.id, params[:message]) redirect_to sent_facilitate_workshop_url(@this_conference.slug, workshop.id) end @@ -251,7 +251,7 @@ class WorkshopsController < ApplicationController f.role = :collaborator f.save LinguaFranca.with_locale(user.locale) do - UserMailer.workshop_facilitator_request_approved(workshop.id, user.id).deliver_later + UserMailer.send_mail(:workshop_facilitator_request_approved, workshop.id, user.id) end return redirect_to view_workshop_url(@this_conference.slug, workshop.id) end @@ -261,7 +261,7 @@ class WorkshopsController < ApplicationController :workshop_id => workshop.id, :user_id => user_id) LinguaFranca.with_locale user.locale do - UserMailer.workshop_facilitator_request_denied(workshop.id, user.id).deliver_later + UserMailer.send_mail(:workshop_facilitator_request_denied, workshop.id, user.id) end return redirect_to view_workshop_url(@this_conference.slug, workshop.id) end @@ -309,7 +309,7 @@ class WorkshopsController < ApplicationController WorkshopFacilitator.create(user_id: user.id, workshop_id: workshop.id, role: :collaborator) LinguaFranca.with_locale user.locale do - UserMailer.workshop_facilitator_request_approved(workshop.id, user.id).deliver_later + UserMailer.send_mail(:workshop_facilitator_request_approved, workshop.id, user.id) end end @@ -329,7 +329,7 @@ class WorkshopsController < ApplicationController unless comment.user.id == current_user.id LinguaFranca.with_locale comment.user.locale do - UserMailer.workshop_comment(workshop.id, new_comment.id, comment.user.id).deliver_later + UserMailer.send_mail(:workshop_comment, workshop.id, new_comment.id, comment.user.id) end end elsif params[:button] = 'add_comment' @@ -338,7 +338,7 @@ class WorkshopsController < ApplicationController workshop.active_facilitators.each do | u | unless u.id == current_user.id LinguaFranca.with_locale u.locale do - UserMailer.workshop_comment(workshop.id, new_comment.id, u.id).deliver_later + UserMailer.send_mail(:workshop_comment, workshop.id, new_comment.id, u.id) end end end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index bdd9ea2..7a616ee 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -167,6 +167,14 @@ class UserMailer < ActionMailer::Base mail to: 'goodgodwin@hotmail.com', subject: @subject end + def self.send_mail(*args) + if Rails.env.preview? || Rails.env.production? + delay(Rails.env).send(*args) + else + send(*args).deliver_now + end + end + private def set_host(*args) if Rails.env.production? diff --git a/config/application.rb b/config/application.rb index 835f28b..ff374bc 100644 --- a/config/application.rb +++ b/config/application.rb @@ -21,7 +21,6 @@ module BikeBike # 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.action_controller.default_url_options = { :trailing_slash => true } config.i18n.default_locale = :en config.i18n.enforce_available_locales = false self.paths['config/database'] = Rails.root.join('config', 'database.yml') diff --git a/config/environments/development.rb b/config/environments/development.rb index dce186a..1754556 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -58,4 +58,5 @@ BikeBike::Application.configure do config.action_mailer.delivery_method = :letter_opener Paypal.sandbox! + config.action_controller.default_url_options = { trailing_slash: true } end diff --git a/config/environments/preview.rb b/config/environments/preview.rb index f39d999..bb95af1 100644 --- a/config/environments/preview.rb +++ b/config/environments/preview.rb @@ -1,4 +1,5 @@ BikeBike::Application.configure do + config.app_config = config_for(:app_config) # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. @@ -87,13 +88,14 @@ BikeBike::Application.configure do config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { - :address => 'smtp.gmail.com', - :domain => 'localhost:3000', - :port => 587, - :authentication => :plain, - :enable_starttls_auto => true, - :user_name => ENV['MAILER_USER'], - :password => ENV['MAILER_PASSWORD'] + address: 'smtp.gmail.com', + domain: 'bikebike.org', + port: 587, + authentication: :plain, + enable_starttls_auto: true, + openssl_verify_mode: 'none', + user_name: 'info@bikebike.org', + password: config.app_config['email_password'] } config.action_mailer.raise_delivery_errors = true config.action_mailer.perform_deliveries = true @@ -101,4 +103,6 @@ BikeBike::Application.configure do I18n.config.language_detection_method = I18n::Config::DETECT_LANGUAGE_FROM_SUBDOMAIN I18n.config.subdomain_format = 'preview-%' I18n.config.host_locale_regex = /^preview\-([a-z]{2})\.[^\.]+\..*$/ + config.action_controller.default_url_options = { host: 'https://bikebike.org', trailing_slash: true } + Sidekiq::Extensions.enable_delay! end diff --git a/config/environments/production.rb b/config/environments/production.rb index 782b53a..2afbc8f 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,5 +1,5 @@ BikeBike::Application.configure do -<<<<<<< HEAD + config.app_config = config_for(:app_config) # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. @@ -89,118 +89,18 @@ BikeBike::Application.configure do config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { - :address => 'smtp.gmail.com', - :domain => 'localhost:3000', - :port => 587, - :authentication => :plain, - :enable_starttls_auto => true, - :user_name => ENV['MAILER_USER'], - :password => ENV['MAILER_PASSWORD'] + address: 'smtp.gmail.com', + domain: 'bikebike.org', + port: 587, + authentication: :plain, + enable_starttls_auto: true, + openssl_verify_mode: 'none', + user_name: 'info@bikebike.org', + password: config.app_config['email_password'] } config.action_mailer.raise_delivery_errors = true config.action_mailer.perform_deliveries = true I18n.config.language_detection_method = I18n::Config::DETECT_LANGUAGE_FROM_SUBDOMAIN -======= - # Settings specified here will take precedence over those in config/application.rb. - - # Code is not reloaded between requests. - config.cache_classes = true - - # Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both thread web servers - # and those relying on copy on write to perform better. - # Rake tasks automatically ignore this option for performance. - config.eager_load = true - - # Full error reports are disabled and caching is turned on. - config.consider_all_requests_local = false - config.action_controller.perform_caching = true - - # Enable Rack::Cache to put a simple HTTP cache in front of your application - # Add `rack-cache` to your Gemfile before enabling this. - # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. - # config.action_dispatch.rack_cache = true - - # Disable Rails's static asset server (Apache or nginx will already do this). - config.serve_static_files = true - - # Compress JavaScripts and CSS. - config.assets.js_compressor = :uglifier - # config.assets.css_compressor = :sass - - # Do not fallback to assets pipeline if a precompiled asset is missed. - #config.assets.compile = true - - # Generate digests for assets URLs. - config.assets.digest = true - - # Version of your assets, change this if you want to expire all your assets. - config.assets.version = '1.01' - - # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache - # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx - - # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - #config.force_ssl = true - - # Set to :debug to see everything in the log. - config.log_level = :info - - #config.cache_classes = true - #config.serve_static_assets = true - #config.assets.compile = true - # config.assets.digest = true - - # Prepend all log lines with the following tags. - # config.log_tags = [ :subdomain, :uuid ] - - # Use a different logger for distributed setups. - # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) - - # Use a different cache store in production. - # config.cache_store = :mem_cache_store - - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.action_controller.asset_host = "http://assets.example.com" - - # Precompile additional assets. - # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. - # config.assets.precompile += %w( search.js ) - #config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif) - config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif *.svg) - #config.action_controller.asset_host = "https://cdn.bikebike.org" - - # Ignore bad email addresses and do not raise email delivery errors. - # Set this to true and configure the email server for immediate delivery to raise delivery errors. - # config.action_mailer.raise_delivery_errors = false - - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation can not be found). - config.i18n.fallbacks = true - - # Send deprecation notices to registered listeners. - config.active_support.deprecation = :notify - - # Disable automatic flushing of the log to improve performance. - # config.autoflush_log = false - - # Use default logging formatter so that PID and timestamp are not suppressed. - config.log_formatter = ::Logger::Formatter.new - - config.action_mailer.delivery_method = :smtp - config.action_mailer.smtp_settings = { - :address => 'smtp.gmail.com', - :domain => 'bikebike.org', - :port => 587, - :authentication => :plain, - :enable_starttls_auto => true, - :openssl_verify_mode => 'none', - :user_name => '', - :password => '' - } - config.action_mailer.raise_delivery_errors = true - config.action_mailer.perform_deliveries = true - I18n.config.language_detection_method = I18n::Config::DETECT_LANGUAGE_FROM_SUBDOMAIN ->>>>>>> d08e8a9... Fix for Quill editor + config.action_controller.default_url_options = { host: 'https://bikebike.org', trailing_slash: true } + Sidekiq::Extensions.enable_delay! end From 23e839bedfef5e6379f75e78ba6a803cdf646b83 Mon Sep 17 00:00:00 2001 From: Godwin Date: Sun, 21 May 2017 16:01:06 -0700 Subject: [PATCH 42/45] Fixed delayed job issue --- app/mailers/user_mailer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 7a616ee..2b42f30 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -169,7 +169,7 @@ class UserMailer < ActionMailer::Base def self.send_mail(*args) if Rails.env.preview? || Rails.env.production? - delay(Rails.env).send(*args) + delay(queue: Rails.env.to_s).send(*args) else send(*args).deliver_now end From 9e613e0b69563d37102808fdb9b08943fb5425d9 Mon Sep 17 00:00:00 2001 From: Godwin Date: Tue, 23 May 2017 15:50:35 -0700 Subject: [PATCH 43/45] Another fix for emails --- app/controllers/workshops_controller.rb | 12 ++++++------ app/mailers/user_mailer.rb | 8 -------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/app/controllers/workshops_controller.rb b/app/controllers/workshops_controller.rb index c23dd80..49792ba 100644 --- a/app/controllers/workshops_controller.rb +++ b/app/controllers/workshops_controller.rb @@ -217,7 +217,7 @@ class WorkshopsController < ApplicationController # create the request by making the user a facilitator but making their role 'requested' WorkshopFacilitator.create(user_id: current_user.id, workshop_id: workshop.id, role: :requested) - UserMailer.send_mail(:workshop_facilitator_request, workshop.id, current_user.id, params[:message]) + send_mail(:workshop_facilitator_request, workshop.id, current_user.id, params[:message]) redirect_to sent_facilitate_workshop_url(@this_conference.slug, workshop.id) end @@ -251,7 +251,7 @@ class WorkshopsController < ApplicationController f.role = :collaborator f.save LinguaFranca.with_locale(user.locale) do - UserMailer.send_mail(:workshop_facilitator_request_approved, workshop.id, user.id) + send_mail(:workshop_facilitator_request_approved, workshop.id, user.id) end return redirect_to view_workshop_url(@this_conference.slug, workshop.id) end @@ -261,7 +261,7 @@ class WorkshopsController < ApplicationController :workshop_id => workshop.id, :user_id => user_id) LinguaFranca.with_locale user.locale do - UserMailer.send_mail(:workshop_facilitator_request_denied, workshop.id, user.id) + send_mail(:workshop_facilitator_request_denied, workshop.id, user.id) end return redirect_to view_workshop_url(@this_conference.slug, workshop.id) end @@ -309,7 +309,7 @@ class WorkshopsController < ApplicationController WorkshopFacilitator.create(user_id: user.id, workshop_id: workshop.id, role: :collaborator) LinguaFranca.with_locale user.locale do - UserMailer.send_mail(:workshop_facilitator_request_approved, workshop.id, user.id) + send_mail(:workshop_facilitator_request_approved, workshop.id, user.id) end end @@ -329,7 +329,7 @@ class WorkshopsController < ApplicationController unless comment.user.id == current_user.id LinguaFranca.with_locale comment.user.locale do - UserMailer.send_mail(:workshop_comment, workshop.id, new_comment.id, comment.user.id) + send_mail(:workshop_comment, workshop.id, new_comment.id, comment.user.id) end end elsif params[:button] = 'add_comment' @@ -338,7 +338,7 @@ class WorkshopsController < ApplicationController workshop.active_facilitators.each do | u | unless u.id == current_user.id LinguaFranca.with_locale u.locale do - UserMailer.send_mail(:workshop_comment, workshop.id, new_comment.id, u.id) + send_mail(:workshop_comment, workshop.id, new_comment.id, u.id) end end end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 2b42f30..bdd9ea2 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -167,14 +167,6 @@ class UserMailer < ActionMailer::Base mail to: 'goodgodwin@hotmail.com', subject: @subject end - def self.send_mail(*args) - if Rails.env.preview? || Rails.env.production? - delay(queue: Rails.env.to_s).send(*args) - else - send(*args).deliver_now - end - end - private def set_host(*args) if Rails.env.production? From 6ffcfdcba65169d7851e09f7021d596b073e0fdf Mon Sep 17 00:00:00 2001 From: Godwin Date: Tue, 23 May 2017 15:51:43 -0700 Subject: [PATCH 44/45] Another fix for emails --- app/mailers/user_mailer.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index bdd9ea2..3e0f999 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -176,5 +176,6 @@ class UserMailer < ActionMailer::Base else @host = UserMailer.default_url_options[:host] end + default_url_options[:host] = @host end end From 39ebae4855a585c0836a40dd055d58f86c625b86 Mon Sep 17 00:00:00 2001 From: Godwin Date: Sat, 15 Jul 2017 07:55:46 -0700 Subject: [PATCH 45/45] Merged with development branch --- Gemfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 0fec91c..43a7aac 100644 --- a/Gemfile +++ b/Gemfile @@ -30,8 +30,8 @@ gem 'launchy' # bundle config local.lingua_franca ../lingua_franca # bundle config local.marmara ../marmara gem 'bikecollectives_core', git: 'https://github.com/bikebike/bikecollectives_core.git', branch: 'master' -gem 'bumbleberry', git: 'https://github.com/bumbleberry/bumbleberry.git', branch: '2017' -gem 'lingua_franca', git: 'https://github.com/lingua-franca/lingua_franca.git', branch: '2017' +gem 'bumbleberry', git: 'https://github.com/bumbleberry/bumbleberry.git', branch: 'master' +gem 'lingua_franca', git: 'https://github.com/lingua-franca/lingua_franca.git', branch: 'master' gem 'marmara', git: 'https://github.com/lingua-franca/marmara.git', branch: 'master' # Bike!Bike! specific stuff