Browse Source

Pre-Registration

development
Godwin 9 years ago
parent
commit
9ae5da70fb
  1. 28
      Capfile
  2. 14
      Gemfile
  3. 1
      app/assets/images/edit.svg
  4. 1
      app/assets/images/img.svg
  5. 1
      app/assets/images/link.svg
  6. 1
      app/assets/images/ol.svg
  7. 1
      app/assets/images/quote.svg
  8. 1
      app/assets/images/ul.svg
  9. 79
      app/assets/javascripts/editor.js
  10. 37
      app/assets/javascripts/editor.js.coffee
  11. 16
      app/assets/javascripts/main.js
  12. 813
      app/assets/stylesheets/_application.scss
  13. 212
      app/assets/stylesheets/_editor.scss
  14. 3
      app/assets/stylesheets/_settings.scss
  15. 6
      app/assets/stylesheets/bumbleberry-settings.json
  16. 183
      app/assets/stylesheets/user-mailer.scss
  17. 32
      app/controllers/application_controller.rb
  18. 576
      app/controllers/conferences_controller.rb
  19. 39
      app/controllers/oauths_controller.rb
  20. 71
      app/helpers/application_helper.rb
  21. 97
      app/mailers/user_mailer.rb
  22. 19
      app/models/conference.rb
  23. 6
      app/models/conference_registration.rb
  24. 14
      app/models/user.rb
  25. 32
      app/models/workshop.rb
  26. 3
      app/views/application/404.html.haml
  27. 13
      app/views/application/_header.html.haml
  28. 2
      app/views/application/_login_confirmation_sent.html.haml
  29. 26
      app/views/application/home.html.haml
  30. 8
      app/views/application/permission_denied.html.haml
  31. 15
      app/views/conferences/_confirm_email.html.haml
  32. 26
      app/views/conferences/_contact_info.html.haml
  33. 2
      app/views/conferences/_email_confirm.html.haml
  34. 4
      app/views/conferences/_header.html.haml
  35. 19
      app/views/conferences/_policy.html.haml
  36. 58
      app/views/conferences/_workshops.html.haml
  37. 30
      app/views/conferences/edit.html.haml
  38. 18
      app/views/conferences/register.html.haml
  39. 26
      app/views/layouts/application.html.haml
  40. 70
      app/views/layouts/user_mailer.html.haml
  41. 3
      app/views/shared/_donate_button.html.haml
  42. 21
      app/views/shared/_navbar.html.haml
  43. 8
      app/views/user_mailer/registration_confirmation.html.haml
  44. 7
      app/views/user_mailer/registration_confirmation.text.haml
  45. 1
      app/views/user_mailer/test_email.text.haml
  46. 15
      app/views/user_mailer/workshop_original_content_changed.html.haml
  47. 25
      app/views/user_mailer/workshop_original_content_changed.text.haml
  48. 15
      app/views/user_mailer/workshop_translated.html.haml
  49. 25
      app/views/user_mailer/workshop_translated.text.haml
  50. 50
      app/views/workshops/_show.html.haml
  51. 18
      app/views/workshops/_workshop_previews.html.haml
  52. 6
      app/views/workshops/facilitate_request_sent.html.haml
  53. 11
      app/views/workshops/index.html.haml
  54. 94
      app/views/workshops/new.html.haml
  55. 18
      app/views/workshops/show.html.haml
  56. 49
      config/deploy.rb
  57. 61
      config/deploy/production.rb
  58. 61
      config/deploy/staging.rb
  59. 12
      config/environments/development.rb
  60. 1
      config/environments/preview.rb
  61. 1
      config/environments/production.rb
  62. 11
      config/initializers/assets.rb
  63. 24
      config/initializers/sorcery.rb
  64. 45
      config/locales/en.yml
  65. 7
      config/locales/es.yml
  66. 8
      config/routes.rb
  67. 9
      db/migrate/20160515210708_add_registration_status_to_conferences.rb
  68. 5
      db/migrate/20160521230653_add_highest_step_to_conference_registrations.rb
  69. 5
      db/migrate/20160522022034_add_steps_completed_to_conference_registrations.rb
  70. 5
      db/migrate/20160526044849_add_needs_facilitators_to_workshops.rb
  71. 5
      db/migrate/20160529225253_add_languages_to_users.rb
  72. 5
      db/migrate/20160530175805_change_date_format_in_dynamic_translation_records.rb
  73. 23
      db/schema.rb
  74. 78
      vendor/assets/javascripts/markdown.js
  75. 835
      vendor/assets/javascripts/pen.js
  76. 1675
      vendor/assets/javascripts/world-110m.json
  77. 1675
      vendor/assets/javascripts/world-50m.json
  78. 159
      vendor/assets/stylesheets/pen.css

28
Capfile

@ -0,0 +1,28 @@
# Load DSL and set up stages
require "capistrano/setup"
# Include default deployment tasks
require "capistrano/deploy"
# Include tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
# https://github.com/capistrano/rvm
# https://github.com/capistrano/rbenv
# https://github.com/capistrano/chruby
# https://github.com/capistrano/bundler
# https://github.com/capistrano/rails
# https://github.com/capistrano/passenger
#
# require 'capistrano/rvm'
# require 'capistrano/rbenv'
# require 'capistrano/chruby'
# require 'capistrano/bundler'
# require 'capistrano/rails/assets'
# require 'capistrano/rails/migrations'
# require 'capistrano/passenger'
require 'capistrano/faster_assets'
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }

14
Gemfile

@ -6,9 +6,6 @@ gem 'pg'
gem 'rack-mini-profiler' gem 'rack-mini-profiler'
gem 'haml' gem 'haml'
# gem 'jquery-rails'
# gem 'jquery-ui-rails'
# gem 'coffee-rails', '~> 4.0.0'
gem 'nokogiri', '~> 1.6.8.rc2' gem 'nokogiri', '~> 1.6.8.rc2'
if Dir.exists?('../lingua_franca') if Dir.exists?('../lingua_franca')
@ -33,8 +30,6 @@ gem 'oauth2', '~> 0.8.0'
gem 'carrierwave' gem 'carrierwave'
gem 'carrierwave-imageoptimizer' gem 'carrierwave-imageoptimizer'
gem 'mini_magick' gem 'mini_magick'
# gem 'nested_form'
# gem 'acts_as_list'
gem 'geocoder' gem 'geocoder'
gem 'paper_trail', '~> 3.0.5' gem 'paper_trail', '~> 3.0.5'
gem 'sitemap_generator' gem 'sitemap_generator'
@ -55,8 +50,13 @@ group :development do
gem 'better_errors' gem 'better_errors'
gem 'binding_of_caller' gem 'binding_of_caller'
gem 'meta_request' gem 'meta_request'
# gem 'haml-rails'
# gem 'awesome_print' 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 end
group :test do group :test do

1
app/assets/images/edit.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path fill="#333" d="M78.995,0L13.204,64.781c8.2,5.467,16.085,13.267,21.552,21.468l65.706-64.802C97.014,11.333,89.235,3.427,78.995,0z M7.338,78.343L-0.463,100l21.552-7.885C17.557,87.763,11.69,81.897,7.338,78.343z"></path></svg>

After

Width:  |  Height:  |  Size: 421 B

1
app/assets/images/img.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path fill="#FAF5F0" d="M35.417,33.351l18.75,27.083L68.75,45.851l18.75,25h-75L35.417,33.351z M4.166,8.315C0,8.314,0,8.351,0,12.518v75 c0,4.167,0,4.167,4.167,4.167h91.667c4.167,0,4.167,0,4.167-4.167v-75c0-4.167,0-4.167-4.167-4.167L4.166,8.315z"></path></svg>

After

Width:  |  Height:  |  Size: 450 B

1
app/assets/images/link.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path fill="#FAF5F0" d="M92.071,7.974C81.305-2.793,64.024-2.431,53.529,8.065L41.043,20.55c7.329-2.352,13.028-0.724,18.005,1.357l3.619-3.619 c5.157-5.157,13.752-4.886,18.819,0.181c5.157,5.157,5.519,13.843,0.362,19L65.019,54.298c-1.9,1.9-4.071,2.986-6.514,3.619 c-6.514,0.905-12.757-2.352-15.109-8.052L32.357,60.54c7.6,11.49,23.343,15.381,35.828,8.324c2.081-1.086,4.071-2.714,5.881-4.524 l17.914-17.914C102.476,35.931,102.838,18.741,92.071,7.974z M32.538,30.503c-2.171,1.086-4.162,2.714-5.881,4.524L8.019,53.574 C-2.476,64.069-2.838,81.259,7.929,92.026s28.047,10.405,38.543-0.09L58.957,79.45c-7.329,2.352-13.028,0.724-18.005-1.357 l-3.619,3.619c-5.157,5.157-13.752,4.886-18.819-0.181c-5.157-5.157-5.519-13.843-0.362-19l17.462-17.462 c1.9-1.9,4.071-2.986,6.514-3.619c6.514-0.905,12.757,2.352,15.109,8.052l11.129-10.676C60.676,27.336,44.933,23.445,32.538,30.503z"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

1
app/assets/images/ol.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path fill="#FAF5F0" d="M95.833,25h-50c-4.167,0-4.167,0-4.167,4.167v4.167c0,4.167,0,4.167,4.167,4.167h50c4.167,0,4.167,0,4.167-4.167v-4.167 C100,25,100,25,95.833,25z M95.833,62.5h-50c-4.167,0-4.167,0-4.167,4.167v4.167c0,4.167,0,4.167,4.167,4.167h50 C100,75,100,75,100,70.833v-4.167C100,62.5,100,62.5,95.833,62.5z M95.833,87.5h-50c-4.167,0-4.167,0-4.167,4.167v4.167 c0,4.167,0,4.167,4.167,4.167h50C100,100,100,100,100,95.833v-4.167C100,87.5,100,87.5,95.833,87.5z M95.833,0h-50 c-4.167,0-4.167,0-4.167,4.167v4.167c0,4.167,0,4.167,4.167,4.167h50c4.167,0,4.167,0,4.167-4.167V4.167C100,0,100,0,95.833,0z M22.917,0H6.25L4.167,10.417H12.5V37.5h10.417V0z M14.583,61.75C6.25,61.75,0,67.333,0,75.667h9.167 c0-2.917,1.667-5.583,5.417-5.583c3.375,0,5.167,2.146,5.125,4.771c-0.021,1.708-1.188,3.292-6,7.333L0,93.75V100h29.167v-9.5H18 l5.104-5.125c4.688-4.688,6.062-7.396,6.062-11.375C29.167,67.667,22.917,61.75,14.583,61.75z"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
app/assets/images/quote.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="17 -18 100 100" enable-background="new 17 -18 100 100" xml:space="preserve"><path fill="#FAF5F0" d="M93.2-3.9C106-3.9,117,4.9,117,23.9c0,26.7-21.8,42.4-34.2,44l-2.6-7.4c7.2-4.7,13.1-12.8,12.5-17.5 c-8.1-0.6-21.2-4.9-21.2-22.5C71.4,7.2,78.9-3.9,93.2-3.9z M38.9-3.9c12.8,0,23.8,8.8,23.8,27.8c0,26.7-21.8,42.4-34.2,44l-2.6-7.4 C33,55.8,38.9,47.8,38.2,43C30.2,42.4,17,38.1,17,20.5C17,7.2,24.6-3.9,38.9-3.9z"></path></svg>

After

Width:  |  Height:  |  Size: 541 B

1
app/assets/images/ul.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path fill="#FAF5F0" d="M12.5,87.5C19.404,87.5,25,81.904,25,75s-5.596-12.5-12.5-12.5C5.596,62.5,0,68.096,0,75S5.596,87.5,12.5,87.5z M12.5,25 C19.396,25,25,19.396,25,12.5S19.396,0,12.5,0S0,5.604,0,12.5S5.604,25,12.5,25z M95.833,25h-50c-4.167,0-4.167,0-4.167,4.167v4.167 c0,4.167,0,4.167,4.167,4.167h50c4.167,0,4.167,0,4.167-4.167v-4.167C100,25,100,25,95.833,25z M95.833,62.5h-50 c-4.167,0-4.167,0-4.167,4.167v4.167c0,4.167,0,4.167,4.167,4.167h50C100,75,100,75,100,70.833v-4.167 C100,62.5,100,62.5,95.833,62.5z M95.833,87.5h-50c-4.167,0-4.167,0-4.167,4.167v4.167c0,4.167,0,4.167,4.167,4.167h50 C100,100,100,100,100,95.833v-4.167C100,87.5,100,87.5,95.833,87.5z M95.833,0h-50c-4.167,0-4.167,0-4.167,4.167v4.167 c0,4.167,0,4.167,4.167,4.167h50c4.167,0,4.167,0,4.167-4.167V4.167C100,0,100,0,95.833,0z"></path></svg>

After

Width:  |  Height:  |  Size: 1007 B

79
app/assets/javascripts/editor.js

@ -0,0 +1,79 @@
(function() {
var pens = {};
Array.prototype.forEach.call(document.querySelectorAll('.textarea'), function(editor) {
startEditing(editor);
});
function startEditing(editor) {
var name = editor.dataset.name;
pens[name] = new Pen({
editor: editor,
class: 'pen',
textarea: '<textarea name="' + name + '"></textarea>',
list: ['p', 'h3', 'h4', 'blockquote', 'insertorderedlist', 'insertunorderedlist', 'bold', 'italic', 'underline', 'strikethrough', 'createlink', 'insertimage'],
title: {
'p': 'Paragraph',
'h3': 'Major Heading',
'h4': 'Minor Heading',
'blockquote': 'Quotation',
'insertorderedlist': 'Ordered List',
'insertunorderedlist': 'Unordered List',
'bold': 'Bold',
'italic': 'Italic',
'underline': 'Underline',
'strikethrough': 'Strikethrough',
'createlink': 'Link',
'insertimage': 'Image'
}
});
}
Array.prototype.forEach.call(document.querySelectorAll('form'), function(form) {
var shouldAllowAlert = false;
form.addEventListener('submit', function() {
if (shouldAllowAlert) {
return;
}
Array.prototype.forEach.call(document.querySelectorAll('.textarea'), function(editor) {
var name = editor.dataset.name;
var textarea = document.querySelector('textarea[name="' + name + '"]');
if (!textarea) {
textarea = document.createElement('textarea');
textarea.name = name;
textarea.style.display = 'none';
form.appendChild(textarea);
}
textarea.value = editor.innerHTML;
pens[name].destroy();
});
}, false);
Array.prototype.forEach.call(form.querySelectorAll('button'), function(button) {
form.addEventListener('click', function(event) {
shouldAllowAlert = (event.target.value === 'cancel');
});
});
});
Array.prototype.forEach.call(document.querySelectorAll('.check-box-field .other input'), function(input) {
var checkbox = document.getElementById(input.parentElement.parentElement.attributes.for.value);
input.addEventListener('keyup', function(event) {
if (event.target.value) {
checkbox.checked = true;
}
});
input.addEventListener('click', function(event) {
checkbox.checked = true;
});
var setRequired = function() {
if (checkbox.checked) {
input.setAttribute('required', 'required');
} else {
input.removeAttribute('required');
}
};
Array.prototype.forEach.call(document.querySelectorAll('.check-box-field input'), function(_input) {
_input.addEventListener('change', function(event) { setRequired(); });
});
});
})();

37
app/assets/javascripts/editor.js.coffee

@ -1,37 +0,0 @@
#= require froala_editor.min.js
$ ->
$('[data-editable]').editable({inlineMode: true, blockTags: ["n", "p", "h2", "blockquote", "pre"], buttons: ["formatBlock", "bold", "italic", "underline", "insertOrderedList", "insertUnorderedList", "sep", "createLink", "insertImage", "insertVideo", "html", "undo", "redo"]})
$('[data-editor]').editable({inlineMode: false, blockTags: ["n", "p", "h2", "blockquote", "pre"], buttons: ["formatBlock", "bold", "italic", "underline", "insertOrderedList", "insertUnorderedList", "sep", "createLink", "html", "undo", "redo"]})
$('.field.country-select-field select').change () ->
$country = $(this)
country = $country.val()
$territory = $('.field.subregion-select-field select')
if $territory.data().country == country
$territory.removeClass('can cant').addClass('can')
return
$.post '/location/territories', {country: country},
(json) ->
$territory.html('')
if json && Object.keys(json).length
$.each json, (code, name) ->
$territory.append($('<option>').text(name).attr('value', code))
return
$territory.removeClass('can cant').addClass('can')
$territory.data().country = country
else
$territory.removeClass('can cant').addClass('cant')
return
, 'json'
$('img + input[type="file"]').change () ->
readURL(this);
return
readURL = (input) ->
reader = null
if input.files && input.files[0]
reader = new FileReader()
reader.readAsDataURL input.files[0]
reader.onload = (e) ->
$(input).prev().attr('src', e.target.result)
return

16
app/assets/javascripts/main.js

@ -0,0 +1,16 @@
(function() {
Array.prototype.forEach.call(document.querySelectorAll('.number-field,.email-field,.text-field'), function(field) {
var input = field.querySelector('input');
var positionLabel = function(input) {
field.classList[input.value ? 'remove' : 'add']('empty');
}
positionLabel(input);
input.addEventListener('keyup', function(event) { positionLabel(event.target); });
input.addEventListener('blur', function(event) { field.classList.remove('focused'); });
input.addEventListener('focus', function(event) { field.classList.add('focused'); });
});
var errorField = document.querySelector('.input-field.has-error input, .input-field.has-error textarea');
if (errorField) {
errorField.focus();
}
})();

813
app/assets/stylesheets/_application.scss

File diff suppressed because it is too large

212
app/assets/stylesheets/_editor.scss

@ -0,0 +1,212 @@
$bumbleberry-no-markup: true;
@import "bumbleberry";
@import "settings";
$white: #FFF !default;
$black: #333 !default;
.pen-icon {
font-size: 16px;
line-height: 40px;
min-width: 20px;
font-style: normal;
display: block;
float: left;
padding: 0 10px;
height: 2.2em;
overflow: hidden;
color: $white;
text-align: center;
cursor: pointer;
@include _(user-select, none);
}
.pen {
position: relative;
&:focus {
outline: none;
}
}
.pen-menu, .pen-input {
font-size: 14px;
line-height:1;
}
.pen-input {
display :none;
position: absolute;
width: 100%;
left: 0;
top: 0;
height: 2.6em;
line-height: 20px;
background-color: $black;
color: $white;
border: none;
text-align: center;
&:focus {
outline: none;
}
}
.pen-textarea {
display: block;
background: $white;
padding: 20px;
}
.pen textarea {
font-size: 14px;
border: none;
background: none;
width: 100%;
_height: 200px;
min-height: 200px;
resize: none;
}
.pen-menu-below {
@include after {
top: -11px;
display: block;
@include _(transform, rotate(180deg));
}
}
.pen-menu {
white-space: nowrap;
box-shadow:1px 2px 3px -2px #222;
background: #333;
background-image: linear-gradient(to bottom, #222, #333);
opacity: 0.9;
position: fixed;
height: 5.2em;
border: 1px solid #333;
border-radius: 0.5em;
display:none;
z-index: 1000;
@include after {
content: " ";
top: 100%;
border: solid transparent;
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: rgba(51, 51, 51, 0);
border-top-color: #333;
border-width: 6px;
left: 50%;
margin-left: -6px;
}
[class^="icon-"], [class*=" icon-"] {
@include before {
speak: none;
display: inline-block;
width: 1em;
margin-right: .2em;
text-align: center;
font-variant: normal;
text-transform: none;
line-height: 1em;
margin-left: .2em;
vertical-align: text-top;
}
}
.pen-icon {
&:hover {
background:#000;
}
&.active {
background: #000;
@include _(box-shadow, #{inset 2px 2px 4px #000, 0 0 0.25em #000});
}
}
.icon-h1,
.icon-h2,
.icon-h3,
.icon-h4,
.icon-h5,
.icon-h6 {
@include before { @include font-family(secondary); }
}
.icon-h1 {
@include before { content: 'H1'; }
}
.icon-h2 {
@include before { content: 'H2'; }
}
.icon-h3 {
@include before { content: 'H1'; }
}
.icon-h4 {
@include before { content: 'H2'; }
}
.icon-h5 {
@include before { content: 'H5'; }
}
.icon-h6 {
@include before { content: 'H6'; }
}
.icon-bold {
border-bottom-left-radius: 0.5em;
clear: left;
@include before { content: 'B'; font-weight: bold; }
}
.icon-italic {
@include before { content: 'I'; font-style: italic;; }
}
.icon-underline {
@include before { content: 'U'; text-decoration: underline; }
}
.icon-p {
@include font-family(secondary);
@include before { content: ''; }
}
.icon-blockquote {
@include before {
background-image: inline-svg-image('quote.svg');
}
}
.icon-insertorderedlist {
border-top-right-radius: 0.5em;
@include before {
background-image: inline-svg-image('ol.svg');
}
}
.icon-insertunorderedlist { @include before {
background-image: inline-svg-image('ul.svg');
} }
.icon-strikethrough { @include before { content: 'S'; text-decoration: line-through; } }
.icon-insertunorderedlist,
.icon-insertorderedlist,
.icon-blockquote,
.icon-insertimage,
.icon-createlink {
@include before {
content: '';
background-size: contain;
height: 1em;
}
}
.icon-createlink {
@include before {
background-image: inline-svg-image('link.svg');
}
}
.icon-insertimage {
@include before {
background-image: inline-svg-image('img.svg');
}
}
}

3
app/assets/stylesheets/_settings.scss

@ -13,6 +13,9 @@ $colour-4: #D89E59; // orange
$colour-5: #02CA9E; // green $colour-5: #02CA9E; // green
$white: #FFFEFE; $white: #FFFEFE;
$black: #333;
$link-colour: darken($colour-1, 13%);
@mixin default-box-shadow($direction: top, $distance: 1, $inset: false, $additional-shadow: false) { @mixin default-box-shadow($direction: top, $distance: 1, $inset: false, $additional-shadow: false) {
@if capable_of(box-shadow) { @if capable_of(box-shadow) {

6
app/assets/stylesheets/bumbleberry-settings.json

@ -1,11 +1,15 @@
{ {
"stylesheets": ["application", "translations", "email-example"], "stylesheets": ["application", "editor"],
"precompile": { "precompile": {
"test": { "test": {
"chrome": ["50"] "chrome": ["50"]
}, },
"development": { "development": {
"and_chr": ["50"],
"chrome": ["50"], "chrome": ["50"],
"edge": ["13"],
"firefox": ["44"],
"ie": ["11"],
"ios_saf": ["8", "9"] "ios_saf": ["8", "9"]
} }
}, },

183
app/assets/stylesheets/user-mailer.scss

@ -0,0 +1,183 @@
@import "settings";
body {
width: 100% !important;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
margin: 0;
padding: 0;
}
.ExternalClass {
width: 100%;
line-height: 100%;
p, span, font, td, div {
line-height: 100%;
}
}
#backgroundTable {
margin: 0;
padding: 0;
width: 100% !important;
line-height: 100% !important;
}
img {
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
a & {
border: none;
}
}
.image_fix {
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;
}
}
}
p {
margin: 1em;
color: $black !important;
}
h1, h2, h3, h4, h5, h6 {
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;
}
}
table#bb_full_width,
table#ecxbb_full_width {
&, table {
max-width: none;
width: 100%;
}
}
.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;
background-color: lighten($colour-3, 33%);
border: 2px solid #DDD;
white-space: nowrap;
&:nth-child(odd) {
margin-right: -3px;
float: left;
}
&:nth-child(even) {
background-color: lighten($colour-5 , 33%);
}
}
}
// 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%);
}
}
}
@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;
}
}
}

32
app/controllers/application_controller.rb

@ -18,6 +18,7 @@ class ApplicationController < LinguaFrancaApplicationController
def capture_page_info def capture_page_info
# set the translator to the current user if we're logged in # set the translator to the current user if we're logged in
I18n.config.translator = current_user I18n.config.translator = current_user
I18n.config.callback = self
# get the current confernece and set it globally # get the current confernece and set it globally
@conference = Conference.order("start_date DESC").first @conference = Conference.order("start_date DESC").first
@ -78,7 +79,8 @@ class ApplicationController < LinguaFrancaApplicationController
end end
def do_404 def do_404
params[:action] = 'error-403' params[:_original_action] = params[:action]
params[:action] = 'error-404'
render 'application/404', status: 404 render 'application/404', status: 404
end end
@ -88,6 +90,7 @@ class ApplicationController < LinguaFrancaApplicationController
def do_403(template = nil) def do_403(template = nil)
@template = template @template = template
params[:_original_action] = params[:action]
params[:action] = 'error-403' params[:action] = 'error-403'
render 'application/permission_denied', status: 403 render 'application/permission_denied', status: 403
end end
@ -143,13 +146,12 @@ class ApplicationController < LinguaFrancaApplicationController
end end
user = User.find_by_email(params[:email]) user = User.find_by_email(params[:email])
if !user unless user
# not really a good UX so we should fix this later # not really a good UX so we should fix this later
#do_404 #do_404
#return #return
user = User.new(:email => params[:email]) user = User.new(:email => params[:email])
user.save! user.save!
user = User.find_by_email(params[:email])
end end
# genereate the confirmation, send the email and show the 403 # genereate the confirmation, send the email and show the 403
@ -218,4 +220,28 @@ class ApplicationController < LinguaFrancaApplicationController
auto_login(u) auto_login(u)
end end
def on_translation_change(object, data, locale, translator_id)
translator = User.find(translator_id)
mailer = "#{object.class.table_name.singularize}_translated"
object.get_translators(data, locale).each do |id, user|
if user.id != current_user.id && user.id != translator_id
UserMailer.send_mail mailer do
{ :args => [object, data, locale, user, translator] }
end
end
end
end
def on_translatable_content_change(object, data)
mailer = "#{object.class.table_name.singularize}_original_content_changed"
object.get_translators(data).each do |id, user|
if user.id != current_user.id
UserMailer.send_mail mailer do
{ :args => [object, data, user, current_user] }
end
end
end
end
end end

576
app/controllers/conferences_controller.rb

@ -514,150 +514,219 @@ class ConferencesController < ApplicationController
end end
def register def register
is_post = request.post? || session[:registration_step] # is_post = request.post? || session[:registration_step]
set_conference set_conference
#if !@this_conference.registration_open
# do_404
# return
#end
set_conference_registration
@register_template = nil @register_template = nil
if logged_in? if logged_in?
unless @this_conference.registration_open || @registration set_or_create_conference_registration
do_404
return
end
# if the user is logged in start them off on the policy
# page, unless they have already begun registration then
# start them off with questions
@register_template = @registration ? (@registration.registration_fees_paid ? :done : :payment) : :policy
@name = current_user.firstname @name = current_user.firstname
# we should phase out last names # we should phase out last names
@name += " #{current_user.lastname}" if current_user.lastname @name += " #{current_user.lastname}" if current_user.lastname
@name ||= current_user.username
@is_host = @this_conference.host? current_user @is_host = @this_conference.host? current_user
steps = registration_steps
return do_404 unless steps.present?
else
@register_template = :confirm_email
end end
# process data from the last view @errors = {}
case (params[:button] || '').to_sym @warnings = []
when :confirm_email form_step = params[:button] ? params[:button].to_sym : nil
@register_template = :email_sent if is_post
when :policy
@register_template = :questions if is_post
when :save
if is_post
if (new_registration = (!@registration))
@registration = ConferenceRegistration.new
end
@registration.conference_id = @this_conference.id if form_step
@registration.user_id = current_user.id if form_step.to_s =~ /^prev_(.+)$/
@registration.is_attending = 'yes' @register_template = steps[steps.find_index($1.to_sym) - 1]
@registration.is_confirmed = true else
@registration.city = params[:location]
@registration.arrival = params[:arrival] case form_step
@registration.languages = params[:languages].keys.to_json when :confirm_email
@registration.departure = params[:departure] return do_confirm
@registration.housing = params[:housing] when :contact_info
@registration.bike = params[:bike] if params[:name].present? && params[:name].gsub(/[\s\W]/, '').present?
@registration.food = params[:food] current_user.firstname = params[:name].squish
@registration.allergies = params[:allergies] current_user.lastname = nil
@registration.other = params[:other] else
@registration.save @errors[:name] = :empty
current_user.firstname = params[:name].squish
current_user.lastname = nil
current_user.save
if new_registration
UserMailer.send_mail :registration_confirmation do
{
:args => @registration
}
end end
end
@register_template = @registration.registration_fees_paid ? :done : :payment if params[:location].present? && params[:location].gsub(/[\s\W]/, '').present? && (l = Geocoder.search(params[:location])).present?
end corrected = view_context.location(l.first)
when :payment
if is_post && @registration if corrected.present?
amount = params[:amount].to_f @registration.city = corrected
if params[:location].gsub(/[\s,]/, '').downcase != @registration.city.gsub(/[\s,]/, '').downcase
if amount > 0 @warnings << view_context._('warnings.messages.location_corrected',"Your location was corrected from \"#{params[:location]}\" to \"#{corrected}\". If this doesn't reflect your intended location, you can change this again in the contact info step.", vars: {original: params[:location], corrected: corrected})
@registration.payment_confirmation_token = ENV['RAILS_ENV'] == 'test' ? 'token' : Digest::SHA256.hexdigest(rand(Time.now.to_f * 1000000).to_i.to_s) end
@registration.save else
@errors[:location] = :unknown
host = "#{request.protocol}#{request.host_with_port}" end
response = PayPal!.setup( else
PayPalRequest(amount), @errors[:location] = :empty
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)
)
if ENV['RAILS_ENV'] != 'test'
redirect_to response.redirect_uri
end end
return
end
@register_template = :done
end
when :paypal_confirm
if @registration && @registration.payment_confirmation_token == params[:confirmation_token]
if ENV['RAILS_ENV'] == 'test' if params[:languages].present?
@amount = YAML.load(@registration.payment_info)[:amount] current_user.languages = params[:languages].keys
else else
@amount = PayPal!.details(params[:token]).amount.total @errors[:languages] = :empty
# testing this does't work in test but it works in devo and prod end
@registration.payment_info = {:payer_id => params[:PayerID], :token => params[:token], :amount => @amount}.to_yaml
current_user.save! unless @errors.present?
end end
@amount = (@amount * 100).to_i.to_s.gsub(/^(.*)(\d\d)$/, '\1.\2') if @errors.present?
@register_template = form_step
else
unless @registration.nil?
@register_template = steps[steps.find_index(form_step) + 1]
# have we reached a new level?
unless @registration.steps_completed.include? form_step.to_s
@registration.steps_completed ||= []
@registration.steps_completed << form_step
# workshops is the last step
if @register_template == :workshops
UserMailer.send_mail :registration_confirmation do
{
:args => @registration
}
end
end
end
@registration.save! @registration.save!
@register_template = :paypal_confirm end
end end
when :paypal_confirmed
info = YAML.load(@registration.payment_info)
@amount = nil
status = nil
if ENV['RAILS_ENV'] == 'test'
status = info[:status]
@amount = info[:amount]
else
paypal = PayPal!.checkout!(info[:token], info[:payer_id], PayPalRequest(info[:amount]))
status = paypal.payment_info.first.payment_status
@amount = paypal.payment_info.first.amount.total
end
if status == 'Completed'
@registration.registration_fees_paid = @amount
@registration.save!
@register_template = :done
else
@register_template = :payment
end
when :paypal_cancel
if @registration
@registration.payment_confirmation_token = nil
@registration.save
@register_template = :payment
end end
when :register
@register_template = :questions
end end
if @register_template == :payment && !@this_conference.paypal_username @register_template ||= (params[:step] || current_step).to_sym
@register_template = :done
end
# don't let the user edit registration if registration is closed return redirect_to register_path(@this_conference.slug) if
if !@conference.registration_open && @register_template == :questions logged_in? && @register_template != current_step &&
@register_template = :done !@registration.steps_completed.include?(@register_template.to_s)
end
# process data from the last view
# case (params[:button] || '').to_sym
# when :confirm_email
# @register_template = :email_sent if is_post
# when :policy
# @register_template = :questions if is_post
# when :save
# if is_post
# new_registration = !@registration
# @registration = ConferenceRegistration.new if new_registration
# @registration.conference_id = @this_conference.id
# @registration.user_id = current_user.id
# @registration.is_attending = 'yes'
# @registration.is_confirmed = true
# @registration.city = params[:location]
# @registration.arrival = params[:arrival]
# @registration.languages = params[:languages].keys.to_json
# @registration.departure = params[:departure]
# @registration.housing = params[:housing]
# @registration.bike = params[:bike]
# @registration.food = params[:food]
# @registration.allergies = params[:allergies]
# @registration.other = params[:other]
# @registration.save
# current_user.firstname = params[:name].squish
# current_user.lastname = nil
# current_user.save
# if new_registration
# UserMailer.send_mail :registration_confirmation do
# {
# :args => @registration
# }
# end
# end
# @register_template = @registration.registration_fees_paid ? :done : :payment
# end
# when :payment
# if is_post && @registration
# 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)
# )
# if ENV['RAILS_ENV'] != 'test'
# redirect_to response.redirect_uri
# end
# return
# end
# @register_template = :done
# end
# when :paypal_confirm
# if @registration && @registration.payment_confirmation_token == params[:confirmation_token]
# if ENV['RAILS_ENV'] == 'test'
# @amount = YAML.load(@registration.payment_info)[:amount]
# else
# @amount = PayPal!.details(params[:token]).amount.total
# # testing this does't work in test but it works in devo and prod
# @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')
# @registration.save!
# @register_template = :paypal_confirm
# end
# when :paypal_confirmed
# info = YAML.load(@registration.payment_info)
# @amount = nil
# status = nil
# if ENV['RAILS_ENV'] == 'test'
# status = info[:status]
# @amount = info[:amount]
# else
# paypal = PayPal!.checkout!(info[:token], info[:payer_id], PayPalRequest(info[:amount]))
# status = paypal.payment_info.first.payment_status
# @amount = paypal.payment_info.first.amount.total
# end
# if status == 'Completed'
# @registration.registration_fees_paid = @amount
# @registration.save!
# @register_template = :done
# else
# @register_template = :payment
# end
# when :paypal_cancel
# if @registration
# @registration.payment_confirmation_token = nil
# @registration.save
# @register_template = :payment
# end
# when :register
# @register_template = :questions
# end
# if @register_template == :payment && !@this_conference.paypal_username
# @register_template = :done
# end
# # don't let the user edit registration if registration is closed
# if !@conference.registration_open && @register_template == :questions
# @register_template = :done
# end
# prepare data for the next view # prepare data for the next view
case @register_template case @register_template
@ -680,12 +749,17 @@ class ConferencesController < ApplicationController
@languages = JSON.parse(@registration.languages).map &:to_sym @languages = JSON.parse(@registration.languages).map &:to_sym
end end
when :workshops when :workshops
@my_workshops = [1,2,3,4].map { |i| @page_title = 'articles.conference_registration.headings.Workshops'
{ @workshops = Workshop.where(conference_id: @this_conference.id)
:title => (Forgery::LoremIpsum.sentence({:random => true}).gsub(/\.$/, '').titlecase), @my_workshops = Workshop.joins(:workshop_facilitators).where(
:info => (Forgery::LoremIpsum.sentences(rand(1...5), {:random => true})) workshop_facilitators: { user_id: current_user.id },
} conference_id: @this_conference.id
} )
@workshops_in_need = Workshop.where(conference_id: @this_conference.id, needs_facilitators: true)
when :contact_info
@page_title = 'articles.conference_registration.headings.Contact_Info'
when :policy
@page_title = 'articles.conference_registration.headings.Policy_Agreement'
when :done when :done
@amount = ((@registration.registration_fees_paid || 0) * 100).to_i.to_s.gsub(/^(.*)(\d\d)$/, '\1.\2') @amount = ((@registration.registration_fees_paid || 0) * 100).to_i.to_s.gsub(/^(.*)(\d\d)$/, '\1.\2')
end end
@ -709,7 +783,7 @@ class ConferencesController < ApplicationController
session[:registration_step] = 'confirm' session[:registration_step] = 'confirm'
redirect_to action: 'register' redirect_to action: 'register'
else else
do_404 return do_404
end end
end end
@ -734,7 +808,7 @@ class ConferencesController < ApplicationController
redirect_to action: 'register' redirect_to action: 'register'
end end
else else
do_404 return do_404
end end
end end
@ -754,7 +828,7 @@ class ConferencesController < ApplicationController
# session[:registration_step] = 'paypal-confirmed' # session[:registration_step] = 'paypal-confirmed'
# redirect_to action: 'register' # redirect_to action: 'register'
# else # else
# do_404 # return do_404
# end # end
# end # end
@ -825,32 +899,67 @@ class ConferencesController < ApplicationController
set_conference set_conference
set_conference_registration set_conference_registration
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
do_404 unless @workshop return do_404 unless @workshop
@translations_available_for_editing = []
I18n.backend.enabled_locales.each do |locale|
@translations_available_for_editing << locale if @workshop.can_translate?(current_user, locale)
end
@page_title = 'page_titles.conferences.View_Workshop'
render 'workshops/show' render 'workshops/show'
end end
def create_workshop def create_workshop
set_conference set_conference
set_conference_registration set_conference_registration
@languages = [] @workshop = Workshop.new
@languages = [I18n.locale.to_sym]
@needs = [] @needs = []
@page_title = 'page_titles.conferences.Create_Workshop'
render 'workshops/new' render 'workshops/new'
end end
def translate_workshop
@is_translating = true
@translation = params[:locale]
@page_title = 'page_titles.conferences.Translate_Workshop'
@page_title_vars = { language: view_context.language_name(@translation) }
edit_workshop
end
def edit_workshop def edit_workshop
set_conference set_conference
set_conference_registration set_conference_registration
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
do_404 unless @workshop
return do_404 unless @workshop.present?
@page_title ||= 'page_titles.conferences.Edit_Workshop'
@can_edit = @workshop.can_edit?(current_user) @can_edit = @workshop.can_edit?(current_user)
do_403 unless @can_edit || @workshop.can_translate?(current_user, I18n.locale)
@title = @workshop.title @is_translating ||= false
@info = @workshop.info if @is_translating
return do_404 if @translation.to_s == @workshop.locale.to_s || !I18n.backend.enabled_locales.include?(@translation.to_s)
return do_403 unless @workshop.can_translate?(current_user, @translation)
@title = @workshop._title(@translation)
@info = @workshop._info(@translation)
else
return do_403 unless @can_edit
@title = @workshop.title
@info = @workshop.info
end
@needs = JSON.parse(@workshop.needs || '[]').map &:to_sym @needs = JSON.parse(@workshop.needs || '[]').map &:to_sym
@languages = JSON.parse(@workshop.languages || '[]').map &:to_sym @languages = JSON.parse(@workshop.languages || '[]').map &:to_sym
@space = @workshop.space.to_sym if @workshop.space @space = @workshop.space.to_sym if @workshop.space
@theme = @workshop.theme.to_sym if @workshop.theme @theme = @workshop.theme.to_sym if @workshop.theme
@notes = @workshop.notes @notes = @workshop.notes
render 'workshops/new' render 'workshops/new'
end end
@ -859,15 +968,8 @@ class ConferencesController < ApplicationController
set_conference_registration set_conference_registration
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
if !@workshop return do_404 unless @workshop.present?
do_404 return do_403 unless @workshop.can_delete?(current_user)
return
end
if !@workshop.can_delete?(current_user)
do_403
return
end
if request.post? if request.post?
if params[:button] == 'confirm' if params[:button] == 'confirm'
@ -876,11 +978,9 @@ class ConferencesController < ApplicationController
@workshop.destroy @workshop.destroy
end end
redirect_to workshops_url return redirect_to workshops_url
return
end end
redirect_to edit_workshop_url(@this_conference.slug, @workshop.id) return redirect_to edit_workshop_url(@this_conference.slug, @workshop.id)
return
end end
render 'workshops/delete' render 'workshops/delete'
@ -890,30 +990,57 @@ class ConferencesController < ApplicationController
set_conference set_conference
set_conference_registration set_conference_registration
if params[:workshop_id] if params[:button].to_sym != :save
if params[:workshop_id].present?
return redirect_to view_workshop_url(@this_conference.slug, params[:workshop_id])
end
return redirect_to register_step_path(@this_conference.slug, 'workshops')
end
if params[:workshop_id].present?
workshop = Workshop.find(params[:workshop_id]) workshop = Workshop.find(params[:workshop_id])
do_404 unless workshop return do_404 unless workshop.present?
can_edit = workshop.can_edit?(current_user)
else else
workshop = Workshop.new(:conference_id => @this_conference.id) workshop = Workshop.new(:conference_id => @this_conference.id)
workshop.workshop_facilitators = [WorkshopFacilitator.new(:user_id => current_user.id, :role => :creator)] workshop.workshop_facilitators = [WorkshopFacilitator.new(:user_id => current_user.id, :role => :creator)]
can_edit = true
end end
can_edit = workshop.can_edit?(current_user) title = params[:title]
do_403 unless can_edit || workshop.can_translate?(current_user, I18n.locale) info = params[:info].gsub(/^\s*(.*?)\s*$/, '\1')
workshop.title = params[:title] if params[:translation].present? && workshop.can_translate?(current_user, params[:translation])
workshop.info = params[:info] old_title = workshop._title(params[:translation])
old_info = workshop._info(params[:translation])
do_save = false
unless title == old_title
workshop.set_column_for_locale(:title, params[:translation], title, current_user.id)
do_save = true
end
unless info == old_info
workshop.set_column_for_locale(:info, params[:translation], info, current_user.id)
do_save = true
end
if can_edit # only save if the text has changed, if we want to make sure only to update the translator id if necessary
# dont allow translators to edit these fields workshop.save_translations if do_save
workshop.languages = (params[:languages] || {}).keys.to_json elsif can_edit
workshop.needs = (params[:needs] || {}).keys.to_json workshop.title = title
workshop.theme = params[:theme] == 'other' ? params[:other_theme] : params[:theme] workshop.info = info
workshop.space = params[:space] workshop.languages = (params[:languages] || {}).keys.to_json
workshop.notes = params[:notes] workshop.needs = (params[:needs] || {}).keys.to_json
workshop.theme = params[:theme] == 'other' ? params[:other_theme] : params[:theme]
workshop.space = params[:space]
workshop.notes = params[:notes]
workshop.needs_facilitators = params[:needs_facilitators].present?
workshop.save
else
return do_403
end end
workshop.save
redirect_to view_workshop_url(@this_conference.slug, workshop.id) redirect_to view_workshop_url(@this_conference.slug, workshop.id)
end end
@ -921,7 +1048,7 @@ class ConferencesController < ApplicationController
set_conference set_conference
set_conference_registration set_conference_registration
workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
do_404 unless workshop return do_404 unless workshop
# save the current state # save the current state
interested = workshop.interested? current_user interested = workshop.interested? current_user
@ -939,8 +1066,8 @@ class ConferencesController < ApplicationController
set_conference set_conference
set_conference_registration set_conference_registration
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
do_404 unless @workshop return do_404 unless @workshop
do_403 if @workshop.facilitator?(current_user) || !current_user return do_403 if @workshop.facilitator?(current_user) || !current_user
render 'workshops/facilitate' render 'workshops/facilitate'
end end
@ -949,8 +1076,8 @@ class ConferencesController < ApplicationController
set_conference set_conference
set_conference_registration set_conference_registration
workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
do_404 unless workshop return do_404 unless workshop
do_403 if workshop.facilitator?(current_user) || !current_user return do_403 if workshop.facilitator?(current_user) || !current_user
# create the request by making the user a facilitator but making their role 'requested' # 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) WorkshopFacilitator.create(user_id: current_user.id, workshop_id: workshop.id, role: :requested)
@ -968,8 +1095,8 @@ class ConferencesController < ApplicationController
set_conference set_conference
set_conference_registration set_conference_registration
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
do_404 unless @workshop return do_404 unless @workshop
do_403 unless @workshop.requested_collaborator?(current_user) return do_403 unless @workshop.requested_collaborator?(current_user)
render 'workshops/facilitate_request_sent' render 'workshops/facilitate_request_sent'
end end
@ -978,7 +1105,7 @@ class ConferencesController < ApplicationController
set_conference set_conference
set_conference_registration set_conference_registration
workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
do_404 unless workshop && current_user return do_404 unless workshop && current_user
user_id = params[:user_id].to_i user_id = params[:user_id].to_i
action = params[:approve_or_deny].to_sym action = params[:approve_or_deny].to_sym
@ -1019,7 +1146,7 @@ class ConferencesController < ApplicationController
end end
end end
do_403 return do_403
end end
def add_workshop_facilitator def add_workshop_facilitator
@ -1029,7 +1156,7 @@ class ConferencesController < ApplicationController
set_conference_registration set_conference_registration
workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
do_404 unless workshop && current_user return do_404 unless workshop && current_user
unless workshop.facilitator?(user) unless workshop.facilitator?(user)
WorkshopFacilitator.create(user_id: user.id, workshop_id: workshop.id, role: :collaborator) WorkshopFacilitator.create(user_id: user.id, workshop_id: workshop.id, role: :collaborator)
@ -1046,7 +1173,7 @@ class ConferencesController < ApplicationController
def schedule def schedule
set_conference set_conference
do_404 unless @this_conference.workshop_schedule_published || @this_conference.host?(current_user) return do_404 unless @this_conference.workshop_schedule_published || @this_conference.host?(current_user)
@events = Event.where(:conference_id => @this_conference.id) @events = Event.where(:conference_id => @this_conference.id)
@locations = EventLocation.where(:conference_id => @this_conference.id) @locations = EventLocation.where(:conference_id => @this_conference.id)
@ -1057,7 +1184,7 @@ class ConferencesController < ApplicationController
def edit_schedule def edit_schedule
set_conference set_conference
set_conference_registration set_conference_registration
do_404 unless @this_conference.host?(current_user) return do_404 unless @this_conference.host?(current_user)
@workshops = Workshop.where(:conference_id => @this_conference.id) @workshops = Workshop.where(:conference_id => @this_conference.id)
@events = Event.where(:conference_id => @this_conference.id) @events = Event.where(:conference_id => @this_conference.id)
@ -1126,7 +1253,7 @@ class ConferencesController < ApplicationController
def save_schedule def save_schedule
set_conference set_conference
do_404 unless @this_conference.host?(current_user) return do_404 unless @this_conference.host?(current_user)
@days = Array.new @days = Array.new
start_day = @this_conference.start_date.strftime('%u').to_i start_day = @this_conference.start_date.strftime('%u').to_i
@ -1164,7 +1291,6 @@ class ConferencesController < ApplicationController
session[:workshops][id] = { session[:workshops][id] = {
:start_time => @workshops[i].start_time, :start_time => @workshops[i].start_time,
:end_time => @workshops[i].end_time, :end_time => @workshops[i].end_time,
:end_time => @workshops[i].end_time,
:event_location_id => @workshops[i].event_location_id :event_location_id => @workshops[i].event_location_id
} }
end end
@ -1189,7 +1315,6 @@ class ConferencesController < ApplicationController
session[:events][id] = { session[:events][id] = {
:start_time => @events[i].start_time, :start_time => @events[i].start_time,
:end_time => @events[i].end_time, :end_time => @events[i].end_time,
:end_time => @events[i].end_time,
:event_location_id => @events[i].event_location_id :event_location_id => @events[i].event_location_id
} }
end end
@ -1228,7 +1353,7 @@ class ConferencesController < ApplicationController
def add_event def add_event
set_conference set_conference
set_conference_registration set_conference_registration
do_404 unless @this_conference.host?(current_user) return do_404 unless @this_conference.host?(current_user)
render 'events/edit' render 'events/edit'
end end
@ -1236,22 +1361,22 @@ class ConferencesController < ApplicationController
def edit_event def edit_event
set_conference set_conference
set_conference_registration set_conference_registration
do_404 unless @this_conference.host?(current_user) return do_404 unless @this_conference.host?(current_user)
@event = Event.find(params[:id]) @event = Event.find(params[:id])
do_403 unless @event.conference_id == @this_conference.id return do_403 unless @event.conference_id == @this_conference.id
render 'events/edit' render 'events/edit'
end end
def save_event def save_event
set_conference set_conference
do_404 unless @this_conference.host?(current_user) return do_404 unless @this_conference.host?(current_user)
if params[:event_id] if params[:event_id]
event = Event.find(params[:event_id]) event = Event.find(params[:event_id])
do_403 unless event.conference_id == @this_conference.id return do_403 unless event.conference_id == @this_conference.id
else else
event = Event.new(:conference_id => @this_conference.id) event = Event.new(:conference_id => @this_conference.id)
end end
@ -1268,7 +1393,7 @@ class ConferencesController < ApplicationController
def add_location def add_location
set_conference set_conference
set_conference_registration set_conference_registration
do_404 unless @this_conference.host?(current_user) return do_404 unless @this_conference.host?(current_user)
render 'event_locations/edit' render 'event_locations/edit'
end end
@ -1276,10 +1401,10 @@ class ConferencesController < ApplicationController
def edit_location def edit_location
set_conference set_conference
set_conference_registration set_conference_registration
do_404 unless @this_conference.host?(current_user) return do_404 unless @this_conference.host?(current_user)
@location = EventLocation.find(params[:id]) @location = EventLocation.find(params[:id])
do_403 unless @location.conference_id == @this_conference.id return do_403 unless @location.conference_id == @this_conference.id
@amenities = JSON.parse(@location.amenities || '[]').map &:to_sym @amenities = JSON.parse(@location.amenities || '[]').map &:to_sym
@ -1288,12 +1413,12 @@ class ConferencesController < ApplicationController
def save_location def save_location
set_conference set_conference
do_404 unless @this_conference.host?(current_user) return do_404 unless @this_conference.host?(current_user)
if params[:location_id] if params[:location_id]
location = EventLocation.find(params[:location_id]) location = EventLocation.find(params[:location_id])
do_403 unless location.conference_id == @this_conference.id return do_403 unless location.conference_id == @this_conference.id
else else
location = EventLocation.new(:conference_id => @this_conference.id) location = EventLocation.new(:conference_id => @this_conference.id)
end end
@ -1313,6 +1438,61 @@ class ConferencesController < ApplicationController
# redirect_to conferences_url, notice: 'Conference was successfully destroyed.' # redirect_to conferences_url, notice: 'Conference was successfully destroyed.'
#end #end
helper_method :registration_steps
helper_method :current_registration_steps
helper_method :registration_complete?
def registration_steps(conference = nil)
conference ||= @this_conference || @conference
{
pre: [:policy, :contact_info, :workshops],
open: [:policy, :contact_info, :questions, :payment, :workshops]
}[conference.registration_status]
end
def required_steps(conference = nil)
# return the intersection of current steps and reuired steps
registration_steps(conference || @this_conference || @conference) & # current steps
[:policy, :contact_info, :questions] # all required steps
end
def registration_complete?(registration = @registration)
completed_steps = registration.steps_completed || []
required_steps(registration.conference).each do | step |
return false unless completed_steps.include?(step.to_s)
end
return true
end
def current_registration_steps(registration = @registration)
return nil unless registration.present?
steps = registration_steps(registration.conference)
current_steps = []
disable_steps = false
completed_steps = registration.steps_completed || []
steps.each do | step |
# disable the step if we've already found an incomplete step
enabled = !disable_steps
# record whether or not we've found an incomplete step
disable_steps ||= !completed_steps.include?(step.to_s)
current_steps << {
name: step,
enabled: enabled
}
end
return current_steps
end
def current_step(registration = @registration)
completed_steps = registration.steps_completed || []
(registration_steps(registration.conference) || []).each do | step |
return step unless completed_steps.include?(step.to_s)
end
return registration_steps(registration.conference).last
end
private private
# Use callbacks to share common setup or constraints between actions. # Use callbacks to share common setup or constraints between actions.
def set_conference def set_conference
@ -1321,24 +1501,20 @@ class ConferencesController < ApplicationController
def set_conference_registration def set_conference_registration
@registration = logged_in? ? ConferenceRegistration.find_by(:user_id => current_user.id, :conference_id => @this_conference.id) : nil @registration = logged_in? ? ConferenceRegistration.find_by(:user_id => current_user.id, :conference_id => @this_conference.id) : nil
@is_host = @this_conference.host?(current_user) end
if @registration || @is_host
@submenu = { def set_or_create_conference_registration
register_path(@this_conference.slug) => 'registration.Registration', set_conference_registration
workshops_path(@this_conference.slug) => 'registration.Workshops' @registration ||= ConferenceRegistration.new(
} conference: @this_conference,
@submenu[schedule_path(@this_conference.slug)] = 'registration.Schedule' if @this_conference.workshop_schedule_published || @is_host user_id: current_user.id,
if @is_host steps_completed: []
@submenu[edit_conference_path(@this_conference.slug)] = 'registration.Edit' )
@submenu[stats_path(@this_conference.slug)] = 'registration.Stats'
@submenu[broadcast_path(@this_conference.slug)] = 'registration.Broadcast'
end
end
end end
# Only allow a trusted parameter "white list" through. # Only allow a trusted parameter "white list" through.
def conference_params def conference_params
params.require(:conference).permit(:title, :slug, :start_date, :end_date, :info, :poster, :cover, :workshop_schedule_published, :registration_open, :meals_provided, :meal_info, :travel_info, :conference_type_id, conference_types: [:id]) params.require(:conference).permit(:title, :slug, :start_date, :end_date, :info, :poster, :cover, :workshop_schedule_published, :registration_status, :meals_provided, :meal_info, :travel_info, :conference_type_id, conference_types: [:id])
end end
def update_field_position(field_id, position) def update_field_position(field_id, position)

39
app/controllers/oauths_controller.rb

@ -4,24 +4,37 @@ class OauthsController < ApplicationController
# sends the user on a trip to the provider, # sends the user on a trip to the provider,
# and after authorizing there back to the callback url. # and after authorizing there back to the callback url.
def oauth def oauth
session[:oauth_last_url] = request.referer
login_at(auth_params[:provider]) login_at(auth_params[:provider])
end end
def callback def callback
provider = auth_params[:provider] user_info = (sorcery_fetch_user_hash auth_params[:provider] || {})[:user_info]
if @user = login_from(provider) user = User.find_by_email(user_info['email'])
redirect_to root_path, :notice => "Logged in with #{provider.titleize}!"
else # create the user if the email is not recognized
begin unless user
@user = create_from(auth_params[:provider]) user = User.new(email: user_info['email'], firstname: user_info['name'])
user.save!
reset_session
auto_login(@user)
redirect_to root_path, :notice => "Signed up with #{provider.titleize}!"
rescue
redirect_to root_path, :alert => "Failed to login with #{provider.titleize}!"
end
end end
# log in the user
auto_login(user) if user
redirect_to (session[:oauth_last_url] || home_path)
#, :notice => "Logged in with #{provider.titleize}!"
# if @user = login_from(provider)
# else
# begin
# @user = create_from(auth_params[:provider])
# reset_session
# auto_login(@user)
# redirect_to redirect_url, :notice => "Signed up with #{provider.titleize}!"
# rescue
# redirect_to redirect_url, :alert => "Failed to login with #{provider.titleize}!"
# end
# end
end end
private private

71
app/helpers/application_helper.rb

@ -98,6 +98,35 @@ module ApplicationHelper
content_for(:banner) { ('<div class="row"><h1>' + banner_title.to_s + '</h1></div>').html_safe } content_for(:banner) { ('<div class="row"><h1>' + banner_title.to_s + '</h1></div>').html_safe }
end end
def add_stylesheet(sheet)
@stylesheets ||= []
@stylesheets << 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 ||= []
@_inline_scripts << Rails.application.assets.find_asset("#{script.to_s}.js").to_s
end
def inline_scripts
return '' unless @_inline_scripts.present?
"<script>#{@_inline_scripts.join("\n")}</script>".html_safe
end
def banner_attribution def banner_attribution
if @@banner_image && @@banner_attribution_details if @@banner_image && @@banner_attribution_details
src = @@banner_attribution_details[:src] src = @@banner_attribution_details[:src]
@ -185,12 +214,17 @@ module ApplicationHelper
false false
end end
def off_screen(text)
"<span class=\"screen-reader-text\">#{text}</span>".html_safe
end
def url_for_locale(locale) def url_for_locale(locale)
url_for(params new_params = params.merge({action: (params[:_original_action] || params[:action])})
.merge({action: (params[:_original_action] || params[:action])} new_params.delete(:_original_action)
.merge(url_params(locale)))
.delete(:_original_action) return url_for(new_params.merge({lang: locale.to_s})) if Rails.env.development? || Rails.env.test?
) return "https://preview-#{locale.to_s}.bikebike.org#{url_for(new_params)}" if Rails.env.preview?
"https://#{locale.to_s}.bikebike.org#{url_for(new_params)}"
end end
def registration_steps(conference = @conference) def registration_steps(conference = @conference)
@ -550,6 +584,18 @@ module ApplicationHelper
country = location.country country = location.country
region = location.territory region = location.territory
city = location.city 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 else
country = location.data['country_code'] country = location.data['country_code']
region = location.data['region_code'] region = location.data['region_code']
@ -565,6 +611,14 @@ module ApplicationHelper
return hash.length > 1 ? _("geography.formats.#{hash.keys.join('_')}", vars: hash) : hash.values.first return hash.length > 1 ? _("geography.formats.#{hash.keys.join('_')}", vars: hash) : hash.values.first
end end
def show_errors(field)
return '' unless @errors && @errors[field].present?
error_txt = _"errors.messages.fields.#{field.to_s}.#{@errors[field]}", :s
"<div class=\"field-error\">#{error_txt}</div>".html_safe
end
def nav_link(link, title = nil, class_name = nil) def nav_link(link, title = nil, class_name = nil)
if title.nil? && link.is_a?(Symbol) if title.nil? && link.is_a?(Symbol)
title = link title = link
@ -572,12 +626,13 @@ module ApplicationHelper
end end
if class_name.nil? && title.is_a?(Symbol) if class_name.nil? && title.is_a?(Symbol)
class_name = title class_name = title
title = _"page_titles.#{title.to_s.titlecase}"
end end
title = _"page_titles.#{title.to_s.titlecase.gsub(/\s/, '_')}"
classes = [] classes = []
classes << class_name if class_name.present? classes << class_name if class_name.present?
classes << 'current' if current_page?(link.gsub(/(^.*)\/$/, '\1')) classes << "strlen-#{strip_tags(title).length}"
link_to title.html_safe, link, :class => classes classes << 'current' if request.fullpath.start_with?(link.gsub(/^(.*?)\/$/, '\1'))
link_to "<span class=\"title\">#{title}</span>".html_safe, link, :class => classes
end end
def date(date, format = :long) def date(date, format = :long)

97
app/mailers/user_mailer.rb

@ -1,3 +1,5 @@
require 'diffy'
class UserMailer < ActionMailer::Base class UserMailer < ActionMailer::Base
add_template_helper(ApplicationHelper) add_template_helper(ApplicationHelper)
include LinguaFrancaHelper include LinguaFrancaHelper
@ -26,10 +28,6 @@ class UserMailer < ActionMailer::Base
mail to: "to@example.org" mail to: "to@example.org"
end end
def test_email
mail to: 'goodgodwin@hotmail.com', subject: 'This is a test', from: 'info@preview.bikebike.org'
end
def conference_registration_email(conference, data, conference_registration) def conference_registration_email(conference, data, conference_registration)
@data = data @data = data
@conference = conference @conference = conference
@ -49,8 +47,8 @@ class UserMailer < ActionMailer::Base
def email_confirmation(confirmation) def email_confirmation(confirmation)
@confirmation = confirmation @confirmation = confirmation
@host = UserMailer.default_url_options[:host] @host = UserMailer.default_url_options[:host]
mail to: confirmation.user.email, @subject = _'email.subject.confirm_email','Please confirm your email address'
subject: (_'email.subject.confirm_email','Please confirm your email address') mail to: confirmation.user.email, subject: @subject
end end
def registration_confirmation(registration) def registration_confirmation(registration)
@ -58,19 +56,27 @@ class UserMailer < ActionMailer::Base
@registration = registration @registration = registration
@conference = Conference.find(@registration.conference_id) @conference = Conference.find(@registration.conference_id)
@user = User.find(@registration.user_id) @user = User.find(@registration.user_id)
mail to: @user.email, @subject = @conference.registration_status.to_sym == :pre ?
subject: _('email.subject.registration_confirmed', _(
"Thank you for registering for #{@conference.title}", 'email.subject.pre_registration_confirmed',
:vars => {:conference_title => @conference.title}) "Thank you for pre-registering for #{@conference.title}",
:vars => {:conference_title => @conference.title}
) : _(
'email.subject.registration_confirmed',
"Thank you for registering for #{@conference.title}",
:vars => {:conference_title => @conference.title}
)
mail to: @user.email, subject: @subject
end end
def broadcast(host, subject, content, user, conference) def broadcast(host, subject, content, user, conference)
@host = host @host = host
@content = content @content = content
@banner = nil#(@host || 'http://localhost/') + (conference ? (conference.poster.preview.url || '') : image_url('logo.png')) @banner = nil#(@host || 'http://localhost/') + (conference ? (conference.poster.preview.url || '') : image_url('logo.png'))
@subject = "[#{conference ? conference.title : 'Bike!Bike!'}] #{subject}"
if user && user.email if user && user.email
email = user.email email = user.email
mail to: email, subject: "[#{conference ? conference.title : 'Bike!Bike!'}] #{subject}" mail to: email, subject: @subject
end end
end end
@ -84,11 +90,10 @@ class UserMailer < ActionMailer::Base
end end
@message = message @message = message
@conference = Conference.find(@workshop.conference_id) @conference = Conference.find(@workshop.conference_id)
mail to: addresses, @subject = _('email.subject.workshop_facilitator_request',
from: @requester.email, "Request to facilitate #{@workshop.title} from #{@requester.name}",
subject: _('email.subject.workshop_facilitator_request',
"Request to facilitate #{@workshop.title} from #{@requester.firstname}",
:vars => {:workshop_title => @workshop.title, :requester_name => @requester.firstname}) :vars => {:workshop_title => @workshop.title, :requester_name => @requester.firstname})
mail to: addresses, from: @requester.email, subject: @subject
end end
def workshop_facilitator_request_approved(workshop, user) def workshop_facilitator_request_approved(workshop, user)
@ -96,10 +101,10 @@ class UserMailer < ActionMailer::Base
@workshop = workshop @workshop = workshop
@conference = Conference.find(@workshop.conference_id) @conference = Conference.find(@workshop.conference_id)
@user = user @user = user
mail to: user.email, @subject = (_'email.subject.workshop_request_approved',
subject: (_'email.subject.workshop_request_approved', "You have been added as a facilitator of #{@workshop.title}",
"You have been added as a facilitator of #{@workshop.title}", :vars => {:workshop_title => @workshop.title})
:vars => {:workshop_title => @workshop.title}) mail to: user.email, subject: @subject
end end
def workshop_facilitator_request_denied(workshop, user) def workshop_facilitator_request_denied(workshop, user)
@ -107,10 +112,56 @@ class UserMailer < ActionMailer::Base
@workshop = workshop @workshop = workshop
@conference = Conference.find(@workshop.conference_id) @conference = Conference.find(@workshop.conference_id)
@user = user @user = user
mail to: user.email, @subject = (_'email.subject.workshop_request_denied',
subject: (_'email.subject.workshop_request_denied', "Your request to facilitate #{@workshop.title} has been denied",
"Your request to facilitate #{@workshop.title} has been denied", :vars => {:workshop_title => @workshop.title})
:vars => {:workshop_title => @workshop.title}) mail to: user.email, subject: @subject
end
def workshop_translated(workshop, data, locale, user, translator)
@host = UserMailer.default_url_options[:host]
@workshop = workshop
@data = data
@locale = locale
@locale_name = language_name(locale)
@user = user
@translator = translator
@subject = (_'email.subject.workshop_translated',
"The #{@locale_name} translation for #{@workshop.title} has been modified",
vars: {language: @language_name, workshop_title: @workshop.title})
@data.each do |field, values|
diff = Diffy::Diff.new(values[:old], values[:new])
@data[field][:diff] = {
text: diff.to_s(:text),
html: diff.to_s(:html)
}
end
@wrapper_id = :full_width
mail to: user.email, subject: @subject
end
def workshop_original_content_changed(workshop, data, user, translator)
@host = UserMailer.default_url_options[:host]
@workshop = workshop
@data = data
@user = user
@translator = translator
@subject = (_'email.subject.workshop_original_content_changed',
"Original content for #{@workshop.title} has been modified",
vars: {workshop_title: @workshop.title})
@data.each do |field, values|
diff = Diffy::Diff.new(values[:old], values[:new])
@data[field][:diff] = {
text: diff.to_s(:text),
html: diff.to_s(:html)
}
end
@wrapper_id = :full_width
mail to: user.email, subject: @subject
end end
end end

19
app/models/conference.rb

@ -42,8 +42,23 @@ class Conference < ActiveRecord::Base
def registered?(user) def registered?(user)
registration = ConferenceRegistration.find_by(:user_id => user.id, :conference_id => id) registration = ConferenceRegistration.find_by(:user_id => user.id, :conference_id => id)
return false unless registration return registration ? registration.is_attending : false
return registration.is_attending 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 registration_status
read_attribute(:registration_status).to_sym
end
def registration_status=(new_registration_status)
write_attribute :registration_status, new_registration_status.to_s
end end
end end

6
app/models/conference_registration.rb

@ -1,5 +1,11 @@
class ConferenceRegistration < ActiveRecord::Base class ConferenceRegistration < ActiveRecord::Base
belongs_to :conference
belongs_to :user
has_many :conference_registration_responses has_many :conference_registration_responses
AttendingOptions = [:yes, :no] AttendingOptions = [:yes, :no]
def languages
user.languages
end
end end

14
app/models/user.rb

@ -21,8 +21,18 @@ class User < ActiveRecord::Base
has_many :authentications, :dependent => :destroy has_many :authentications, :dependent => :destroy
accepts_nested_attributes_for :authentications accepts_nested_attributes_for :authentications
def can_translate? def can_translate?(to_locale = nil, from_locale = nil)
is_translator 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 end
end end

32
app/models/workshop.rb

@ -18,7 +18,7 @@ class Workshop < ActiveRecord::Base
return nil unless user return nil unless user
workshop_facilitators.each do |u| workshop_facilitators.each do |u|
if u.user_id == user.id if u.user_id == user.id
return conference.registered?(user) ? u.role.to_sym : :unregistered return conference.registration_exists?(user) ? u.role.to_sym : :unregistered
end end
end end
return nil return nil
@ -31,7 +31,7 @@ class Workshop < ActiveRecord::Base
def active_facilitators def active_facilitators
users = [] users = []
workshop_facilitators.each do |u| workshop_facilitators.each do |u|
users << User.find(u.user_id) if u.role.to_sym != :request users << User.find(u.user_id) unless u.role.to_sym == :requested
end end
return users return users
end end
@ -75,16 +75,18 @@ class Workshop < ActiveRecord::Base
end end
def interested_count def interested_count
return 0 unless id
collaborators = [] collaborators = []
workshop_facilitators.each do |f| workshop_facilitators.each do |f|
collaborators << f.user_id unless f.role.to_sym == :requested collaborators << f.user_id unless f.role.to_sym == :requested
end end
interested = WorkshopInterest.where("workshop_id=#{id} AND user_id NOT IN (#{collaborators.join ','})") return 10 unless collaborators.present?
interested = WorkshopInterest.where("workshop_id=#{id} AND user_id NOT IN (#{collaborators.join ','})") || []
interested ? interested.size : 0 interested ? interested.size : 0
end end
def can_translate?(user, lang) def can_translate?(user, lang)
(user.can_translate? && lang.to_sym != locale.to_sym) || can_edit?(user) user.can_translate?(lang, locale) || (can_edit?(user) && lang.to_s != locale.to_s)
end end
def conference_day def conference_day
@ -100,6 +102,28 @@ class Workshop < ActiveRecord::Base
((end_time - start_time) / 60).to_i ((end_time - start_time) / 60).to_i
end end
def self.all_themes
[:race_gender, :mechanics, :funding, :organization, :community]
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
private private
def make_slug def make_slug
if !self.slug if !self.slug

3
app/views/application/404.html.haml

@ -5,5 +5,4 @@
= columns(medium: 12) do = columns(medium: 12) do
%h1 %h1
=_'error.404.title','This page does not exist!' =_'error.404.title','This page does not exist!'
%p =paragraph(_'error.404.description', :p)
=p(_'error.404.description', :p)

13
app/views/application/_header.html.haml

@ -5,13 +5,14 @@
- figure = nil - figure = nil
- if capable_of(:css_mixblendmode) - if capable_of(:css_mixblendmode)
- cover = "<div class=\"cover\" style=\"background-image: url(#{image})\"></div>" - cover = "<div class=\"cover\" style=\"background-image: url(#{image})\"></div>"
- elsif capable_of(:svg) && Rails.env != 'test'
- banner_image = 'application/banner_image.svg'
- else - else
- style = "background-image: url(#{image})" - style = "background-image: url(#{image})"
#header-title.short{style: style} #header-title.short{style: style}
= (render banner_image, {:image => image}) if banner_image = (render banner_image, {:image => image}) if defined?(banner_image) == "local-variable"
= cover.html_safe if cover = cover.html_safe if cover
= row do - if @page_title.present? || defined?(page_group)
= columns do - content_for :title do
%h1=_(@page_title || "page_titles.#{page_group.to_s}.#{page_key.to_s}") =@page_title.present? ? I18n.t(@page_title, @page_title_vars) : I18n.t("page_titles.#{page_group.to_s}.#{page_key.to_s}")
= row do
= columns do
%h1=(_"page_titles.#{page_group.to_s}.#{page_key.to_s}")

2
app/views/application/_login_confirmation_sent.html.haml

@ -2,4 +2,4 @@
= columns(medium: 12) do = columns(medium: 12) do
%h2=_'articles.permission_denied.headings.confirmation_sent','Confirmation Sent' %h2=_'articles.permission_denied.headings.confirmation_sent','Confirmation Sent'
= columns(medium: 6) do = columns(medium: 6) do
=m('articles.permission_denied.paragraphs.confirmation_sent', :p) %p='articles.permission_denied.paragraphs.confirmation_sent', :p

26
app/views/application/home.html.haml

@ -9,16 +9,16 @@
- if @schedule - if @schedule
%h3=_'articles.workshops.headings.Schedule' %h3=_'articles.workshops.headings.Schedule'
= render 'schedule/programme', :schedule => @schedule, :conference => @conference, :workshops => @workshops, :events => @events, :locations => @locations, :show_interest => true, :day_parts => @day_parts, :show_previews => true = render 'schedule/programme', :schedule => @schedule, :conference => @conference, :workshops => @workshops, :events => @events, :locations => @locations, :show_interest => true, :day_parts => @day_parts, :show_previews => true
- elsif @conference.registration_open - if @conference.registration_status == :open
%h3=_'articles.workshops.headings.Proposed_Workshops' %h3=_'articles.workshops.headings.Proposed_Workshops'
%p=_'articles.workshops.paragraphs.Proposed_Workshops', "Would you like to facilitate your own workshop? Simply register and visit the workshops page. If you have already registered you can access the page by restarting the registration process." %p=_'articles.workshops.paragraphs.Proposed_Workshops', "Would you like to facilitate your own workshop? Simply register and visit the workshops page. If you have already registered you can access the page by restarting the registration process."
%ul.workshop-list %ul.workshop-list
- @conference.workshops.sort_by{ |w| w.title.downcase }.each do |w| - @conference.workshops.sort_by{ |w| w.title.downcase }.each do |w|
%li %li
%h4=w.title %h4=w.title
.workshop-interest .workshop-interest
- if w.interested?(current_user) - if w.interested?(current_user)
=_'articles.workshops.info.you_are_interested_count', "You and #{w.interested_count - 1} others are interested in this workshop", :vars => {:count => (w.interested_count - 1)} =_'articles.workshops.info.you_are_interested_count', "You and #{w.interested_count - 1} others are interested in this workshop", :vars => {:count => (w.interested_count - 1)}
- elsif w.interested_count > 0 - elsif w.interested_count > 0
=_'articles.workshops.info.interested_count', "#{w.interested_count} people are interested in this workshop", :vars => {:count => w.interested_count} =_'articles.workshops.info.interested_count', "#{w.interested_count} people are interested in this workshop", :vars => {:count => w.interested_count}
.workshop-description=markdown w.info .workshop-description=markdown w.info

8
app/views/application/permission_denied.html.haml

@ -1,7 +1,9 @@
= render :partial => 'application/header', :locals => {:image_file => @banner_image || '403.jpg'} = render :partial => 'application/header', :locals => {:image_file => @banner_image || '403.jpg'}
%article %article
- if @template - if @template.present?
=render @template =render @template
- else - else
%h2=_'articles.permission_denied.headings.main','Sorry, you currently don\'t have access to this page' = row do
%p=_'articles.permission_denied.paragraphs.main', :p = columns do
%h2=_'articles.permission_denied.headings.main','Sorry, you currently don\'t have access to this page'
%p=_'articles.permission_denied.paragraphs.main', :p

15
app/views/conferences/_confirm_email.html.haml

@ -1,6 +1,15 @@
%article %article
= columns(medium: 12) do = columns(medium: 12) do
%h2=_'articles.conference_registration.headings.Confirm_You_Email_Address','Confirm Email Address' %h2=_'articles.conference_registration.headings.Pre_Registration_Details','Pre-Registration is now open!'
= columns(medium: 6, large: 5) do %p=_'articles.conference_registration.paragraphs.Pre_Registration_Details', 'By completing the pre-registration process you are letting us know that you are interested in coming, it allows us to contact you when we have news, and lets us better plan by knowing who might be coming. Once registration is fully open, we will need to ask a few more questions and you can confirm whether or not you will actually be coming.'
= form_tag register_path(@this_conference.slug) do %h3=_'articles.conference_registration.headings.Verify_Account','Verify your account'
%p=_'articles.conference_registration.paragraphs.Verify_Account', 'In order to confirm that you are a real person and that we will be able to contact you later, please verify your email address. Before continuing with pre-registration, we will send you an email that will allow you to continue with the process.'
= columns(medium: 12) do
= 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 = button_tag :continue, :value => :confirm_email
= columns(medium: 12, class: 'flex-column') do
%p.stretch-item=_'articles.conference_registration.paragraphs.facebook_sign_in','Alternatively you can sign in using your Facebook account and skip waiting for us to send you an email.'
= link_to (_'forms.actions.generic.facebook_sign_in','Facebook Sign In'), auth_at_provider_path(:provider => :facebook), class: [:button, :facebook]

26
app/views/conferences/_contact_info.html.haml

@ -0,0 +1,26 @@
= columns(medium: 12) do
%h2=_(@page_title)
%p=_'articles.conference_registration.paragraphs.Contact_Info', :s, 2
= columns(medium: 12) do
= form_tag register_path(@this_conference.slug) do
%h3=_'articles.conference_registration.headings.name','What is your name?'
.text-field.input-field{class: @errors[:name].present? ? 'has-error' : nil}
= show_errors :name
= label_tag :name
= text_field_tag :name, @name, required: true
%h3=_'articles.conference_registration.headings.location','Where are you coming from?'
.text-field.input-field{class: @errors[:location].present? ? 'has-error' : nil}
= show_errors :location
= label_tag :location
= text_field_tag :location, @registration.city || location(lookup_ip_location), required: true
%h3=_'articles.conference_registration.headings.languages','Which languages do you speak?'
- puts @errors.to_json.to_s
.check-box-field.input-field{class: @errors[:languages].present? ? 'has-error' : nil}
- [:en, :es, :fr].each do |language|
= check_box_tag "languages[#{language}]", 1, (@registration.languages || [I18n.locale.to_s]).include?(language.to_s)
= label_tag "languages_#{language}" do
= _"languages.#{language}"
= show_errors :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

2
app/views/conferences/_email_confirm.html.haml

@ -3,4 +3,4 @@
= columns(medium: 12) do = columns(medium: 12) do
%h2=_'articles.conference_registration.headings.email_confirm','Please confirm your email address' %h2=_'articles.conference_registration.headings.email_confirm','Please confirm your email address'
= columns(medium: 12) do = columns(medium: 12) do
=m('articles.conference_registration.paragraphs.email_confirm', :p) %p=_'articles.conference_registration.paragraphs.email_confirm', :p

4
app/views/conferences/_header.html.haml

@ -2,7 +2,7 @@
.title .title
%h1=_!@conference.title %h1=_!@conference.title
.details .details
%h3.primary=location(@conference.organizations.first.locations.first) %h2.primary=location(@conference.organizations.first.locations.first)
.secondary .secondary
= date_span(@conference.start_date.to_date, @conference.end_date.to_date) = date_span(@conference.start_date.to_date, @conference.end_date.to_date)
%img{src: @conference.poster.full.url || image_path('default_poster.jpg')} %img{src: @conference.poster.full.url || image_path('default_poster.jpg'), role: :presentation, alt: ''}

19
app/views/conferences/_policy.html.haml

@ -1,10 +1,9 @@
%article = columns(medium: 12) do
= columns(medium: 12) do %h2=_@page_title
%h2=_'articles.conference_registration.headings.Policy_Agreement' %p=_'articles.conference_registration.paragraphs.Policy_Agreement', :s, 2
%p=_'articles.conference_registration.paragraphs.Policy_Agreement', :s, 2 = columns(medium: 10, push: 1) do
= columns(medium: 10, push: 1) do = render 'application/policy'
= render 'application/policy' = columns(medium: 12) do
= columns(medium: 12) do %p=_'articles.conference_registration.paragraphs.Confirm_Agreement', :p
%p=_'articles.conference_registration.paragraphs.Confirm_Agreement', :p = form_tag register_path(@this_conference.slug) do
= form_tag register_path(@this_conference.slug) do .actions= button_tag :agree, :value => :policy
.actions= button_tag :agree, :value => :policy

58
app/views/conferences/_workshops.html.haml

@ -1,42 +1,20 @@
= columns(medium: 12) do = columns(medium: 12) do
%h2=_'articles.conference_registration.headings.Workshops','Workshops' %h2=_@page_title
= columns(medium: 6) do
%h3=_'articles.conference_registration.headings.Add_Workshop'
= form_tag register_path(@this_conference.slug) do
.text-field.input-field
= text_field_tag :title, nil, required: true
= label_tag :title
= text_area_tag :info, nil, required: true
= button_tag :add
= columns(medium: 6) do
%h3=_'articles.conference_registration.headings.Your_Workshops'
%ul.workshops.my-workshops
- @my_workshops.each do |workshop|
%li
%h4=workshop[:title]
.info
%h5{:contenteditable => true}=workshop[:title]
%p{:contenteditable => true}=workshop[:info]
= form_tag register_path(@this_conference.slug) do
= button_tag :save, :value => :save
= button_tag :delete, :value => :delete
= button_tag :cancel, :value => :cancel
= columns(medium: 12) do = columns(medium: 12) do
= form_tag register_path(@this_conference.slug) do - if @my_workshops.present?
= button_tag :previous, :value => :registration %h3=_'articles.conference_registration.headings.Your_Workshops'
= button_tag :next, :value => :workshops %p=_'articles.conference_registration.paragraphs.Your_Workshops', :p
:javascript = render 'workshops/workshop_previews', :workshops => @my_workshops
function makeWorkshopsClickable() { - else
var workshops = document.querySelectorAll('ul.workshops h4'); %p=_'articles.conference_registration.paragraphs.Create_Workshop', :p
for (var i = 0; i < workshops.length; i++) { = link_to (_'articles.conference_registration.headings.Add_Workshop'), create_workshop_path(@this_conference.slug), class: :button
workshops[i].addEventListener('click', function(e) { - if @workshops_in_need.present?
var workshop = e.target.parentElement; = columns(medium: 12) do
workshop.className = 'view'; %h3=_'articles.conference_registration.headings.Workshops_Looking_For_Facilitators'
workshop.querySelector('form').onsubmit = function() { %p=_'articles.conference_registration.paragraphs.Workshops_Looking_For_Facilitators', :p
workshop.removeAttribute('class'); = render 'workshops/workshop_previews', :workshops => @workshops_in_need
return false; - if @workshops.present?
} = columns(medium: 12) do
}, false); %h3=_'articles.conference_registration.headings.All_Workshops'
} -#%p=_'articles.conference_registration.paragraphs.All_Workshops', :p
} = render 'workshops/workshop_previews', :workshops => @workshops
makeWorkshopsClickable();

30
app/views/conferences/edit.html.haml

@ -13,18 +13,18 @@
= button_tag :save, :value => :save = button_tag :save, :value => :save
:javascript / :javascript
window.jQuery || document.write('<script src="https://code.jquery.com/jquery-2.1.3.min.js"><\/script>') / window.jQuery || document.write('<script src="https://code.jquery.com/jquery-2.1.3.min.js"><\/script>')
= javascript_include_tag "froala_editor.min.js" -#= javascript_include_tag "froala_editor.min.js"
= stylesheet_link_tag "froala_editor.min.css" -#= stylesheet_link_tag "froala_editor.min.css"
= stylesheet_link_tag "font-awesome.min.css" -#= stylesheet_link_tag "font-awesome.min.css"
:javascript / :javascript
$(function() { / $(function() {
$('.text-editor textarea').editable({ / $('.text-editor textarea').editable({
language: '<% I18n.locale.to_s %>', / language: '<% I18n.locale.to_s %>',
inlineMode: false, / inlineMode: false,
blockTags: ["n", "p", "h2", "blockquote", "pre"], / blockTags: ["n", "p", "h2", "blockquote", "pre"],
buttons: ["formatBlock", "bold", "italic", "underline", "insertOrderedList", "insertUnorderedList", "sep", "createLink", "html", "undo", "redo"], / buttons: ["formatBlock", "bold", "italic", "underline", "insertOrderedList", "insertUnorderedList", "sep", "createLink", "html", "undo", "redo"],
colors: ['#00ADEF', '#DD57EF', '#E6C74B', 'REMOVE'] / colors: ['#00ADEF', '#DD57EF', '#E6C74B', 'REMOVE']
}); / });
}); / });

18
app/views/conferences/register.html.haml

@ -1,4 +1,20 @@
= render :partial => 'page_header', :locals => {:page_key => 'Conference_Registration'} = render :partial => 'page_header', :locals => {:page_key => 'Conference_Registration'}
- if (steps = current_registration_steps)
= row id: 'registration-steps', class: 'flow-steps' do
= columns do
%ul
- current_registration_steps.each do | step |
- text = _"articles.conference_registration.headings.#{step[:name].to_s}"
%li{class: [step[:enabled] ? :enabled : nil, @register_template == step[:name] ? :current : nil]}
- if step[:enabled]
.step= link_to text, register_step_path(@this_conference.slug, step[:name])
- else
.step= text
- if @warnings.present?
= row class: 'warnings', tag: :ul do
- @warnings.each do | warning |
= columns tag: :li do
= warning
%article %article
= row do = row do
= render (@register_template || :register).to_s = render @register_template.to_s

26
app/views/layouts/application.html.haml

@ -3,19 +3,17 @@
%head %head
%meta{ charset: 'utf-8' } %meta{ charset: 'utf-8' }
%meta{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0' } %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0' }
%title= (yield :title) + (content_for?(:title) ? (_!' | ') : '') + (_!'Bike!Bike!') -#%title= (yield :title) + (content_for?(:title) ? (_!' | ') : '') + (_!'Bike!Bike!')
%title=_!('Bike!Bike!' + (content_for?(:title) ? " - #{yield :title}" : ''))
%meta{ name: 'description', content: (yield_or_default :description, I18n.t('page_descriptions.home')) } %meta{ name: 'description', content: (yield_or_default :description, I18n.t('page_descriptions.home')) }
= csrf_meta_tags = csrf_meta_tags
= inject_css! = stylesheets
- (@stylesheets || []).each do |css|
= inject_css! css
= stylesheet_link_tag 'i18n-debug' if request.params['i18nDebug']
%link{ href: asset_path('favicon.ico'), rel: 'shortcut icon', type: 'image/x-icon' } %link{ href: asset_path('favicon.ico'), rel: 'shortcut icon', type: 'image/x-icon' }
%link{ href: asset_path('favicon.ico'), rel: 'icon', type: 'image/x-icon' } %link{ href: asset_path('favicon.ico'), rel: 'icon', type: 'image/x-icon' }
%link{ href: asset_path('apple-touch-icon.png'), rel: 'apple-touch-icon' } -#%link{ href: asset_path('apple-touch-icon.png'), rel: 'apple-touch-icon' }
%link{ href: asset_path('apple-touch-icon-72x72.png'), rel: 'apple-touch-icon', sizes: '72x72' } -#%link{ href: asset_path('apple-touch-icon-72x72.png'), rel: 'apple-touch-icon', sizes: '72x72' }
%link{ href: asset_path('apple-touch-icon-114x114.png'), rel: 'apple-touch-icon', sizes: '114x114' } -#%link{ href: asset_path('apple-touch-icon-114x114.png'), rel: 'apple-touch-icon', sizes: '114x114' }
%link{ href: asset_path('apple-touch-icon-144x144.png'), rel: 'apple-touch-icon', sizes: '144x144' } -#%link{ href: asset_path('apple-touch-icon-144x144.png'), rel: 'apple-touch-icon', sizes: '144x144' }
= yield :head = yield :head
%body{ class: page_style } %body{ class: page_style }
@ -24,8 +22,8 @@
- if content_for?(:side_bar) - if content_for?(:side_bar)
%nav#side-bar %nav#side-bar
= yield :side_bar = yield :side_bar
- flash.each do |key, msg| -# - flash.each do |key, msg|
= content_tag :div, msg, :id => key -# = content_tag :div, msg, :id => key
%header#banner=yield :banner %header#banner=yield :banner
- if @submenu - if @submenu
=row do =row do
@ -39,5 +37,7 @@
= yield = yield
#footer #footer
%footer= render 'shared/footer' %footer= render 'shared/footer'
- if content_for?(:footer_scripts)
= yield :footer_scripts = yield :footer_scripts if content_for?(:footer_scripts)
- add_inline_script :main
= inline_scripts

70
app/views/layouts/user_mailer.html.haml

@ -3,74 +3,10 @@
%head %head
%meta{:content => "text/html; charset=utf-8", "http-equiv" => "Content-Type"}/ %meta{:content => "text/html; charset=utf-8", "http-equiv" => "Content-Type"}/
%meta{:content => "width=device-width, initial-scale=1.0", :name => "viewport"}/ %meta{:content => "width=device-width, initial-scale=1.0", :name => "viewport"}/
%title Your Message Subject or Title %title=@subject
:css %style{type: 'text/css'}=Rails.application.assets.find_asset("user-mailer.css").to_s
#outlook a {padding:0;}
body{width:100% !important; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0;}
.ExternalClass {width:100%;}
.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;}
#backgroundTable {margin:0; padding:0; width:100% !important; line-height: 100% !important;}
img {outline:none; text-decoration:none; -ms-interpolation-mode: bicubic;}
a img {border:none;}
.image_fix {display:block;max-width:100%}
p {margin: 1em;color:#333 !important;}
h1, h2, h3, h4, h5, h6 {color:#333 !important;}
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color: #00ADEF !important;}
h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active { color: #D89E59 !important; }
h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited { color: #DD57EF !important; }
table td {border-collapse: collapse;}
table { border-collapse:collapse; mso-table-lspace:0pt; mso-table-rspace:0pt; max-width:512px;min-width:280px;}
a {color: #00ADEF;}
a:link { color: #00ADEF; }
a:visited { color: #00ADEF; }
a:hover { color: #02CA9E; }
@media only screen and (max-device-width: 480px) {
a[href^="tel"], a[href^="sms"] {
text-decoration: none;
color: #333;
pointer-events: none;
cursor: default;
}
.mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
text-decoration: default;
color: #00ADEF !important;
pointer-events: auto;
cursor: default;
}
}
h3 b {
padding: 10px 20px;
line-height: 50px;
}
h3 b a,
h3 b a:visited {
color: #FFF !important;
background-color: #02CA9E;
text-decoration: none !important;
border-radius: 4px;
padding: 10px 15px;
margin-left: 20px;
border-bottom: 3px solid #00AF88;
}
h3 b a:hover {
background-color: #00AF88;
}
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) {
a[href^="tel"], a[href^="sms"] {
text-decoration: none;
color: #00ADEF;
pointer-events: none;
cursor: default;
}
.mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
text-decoration: default;
color: #00ADEF !important;
pointer-events: auto;
cursor: default;
}
}
%body %body
%table#backgroundTable{:border => "0", :cellpadding => "0", :cellspacing => "0"} %table{:border => "0", :cellpadding => "0", :cellspacing => "0", id: @wrapper_id.present? ? "bb_#{@wrapper_id.to_s}" : 'backgroundTable'}
%tr %tr
%td %td
%table{:align => "center", :border => "0", :cellpadding => "0", :cellspacing => "0"} %table{:align => "center", :border => "0", :cellpadding => "0", :cellspacing => "0"}

3
app/views/shared/_donate_button.html.haml

@ -5,4 +5,5 @@
%input{type: 'hidden', name: 'item_name', value: 'Bike!Bike!'} %input{type: 'hidden', name: 'item_name', value: 'Bike!Bike!'}
%input{type: 'hidden', name: 'no_note', value: '0'} %input{type: 'hidden', name: 'no_note', value: '0'}
%input{type: 'hidden', name: 'currency_code', value: 'USD'} %input{type: 'hidden', name: 'currency_code', value: 'USD'}
%button{type: 'submit', value: 'PP-DonationsBF:paypal_logo.png:NonHostedGuest'}=_'donate.button_label','Donate' %button{type: 'submit', value: 'PP-DonationsBF:paypal_logo.png:NonHostedGuest'}
%span.title=_'donate.button_label','Donate'

21
app/views/shared/_navbar.html.haml

@ -1,14 +1,23 @@
%nav %nav
#main-nav #main-nav
= row(class: 'inner-nav') do = row(class: 'inner-nav') do
- if logged_in?
= columns(medium: 12, class: 'user-nav') do
.user-nav-bar
=link_to (_!current_user.name), register_step_path(@conference.slug, :contact_info), class: 'my-account'
=link_to (_'forms.actions.generic.Log_out'), logout_path
= columns(medium: 4, class: 'inner-nav') do = columns(medium: 4, class: 'inner-nav') do
= link_to '/', :class => 'logo' do = link_to home_path, :class => 'logo' do
= svg_sprite('icons', "bb-icon-logo", "Logo") = svg_sprite('icons', "bb-icon-logo", "Logo")
= svg_sprite('icons', "bb-icon-logo-text", "Logo Text") = svg_sprite('icons', "bb-icon-logo-text", "Logo Text")
= off_screen _!'Bike Bike'
= columns(medium: 8, class: 'nav') do = columns(medium: 8, class: 'nav') do
= nav_link about_path, (_'page_titles.About'), 'about' = nav_link :about
= nav_link policy_path, (_'page_titles.Policy'), 'policy' = nav_link :policy
- if @conference && @conference.registration_open - case (@conference ? @conference.registration_status.to_sym : :closed)
= nav_link register_path(@conference.slug), (_'page_titles.Register') - when :pre
= nav_link register_path(@conference.slug), :pre_register, :register
- when :open
= nav_link register_path(@conference.slug), :register
- else - else
= render 'shared/donate_button', :email_address => (@conference.paypal_email_address || @conference.email_address) = render 'shared/donate_button', :email_address => (@conference.paypal_email_address || @conference.email_address || @conference.organizations.first.email_address)

8
app/views/user_mailer/registration_confirmation.html.haml

@ -1,4 +1,8 @@
%p=_'email.general.paragraph.thank_you', "Thank you #{@user.firstname},", :vars => {:name => @user.firstname} %p=_'email.general.paragraph.thank_you', "Thank you #{@user.firstname},", :vars => {:name => @user.firstname}
%p=_'email.registration.paragraph.confirmed', "You have successfully registered for #{@conference.title}. You can modify your registration details, pay, or add workshops at any time by restarting the registration process. If you have yet to pay or add your workshops and plan to do so, we ask that you take care of it as soon as possible to help us prepare in advance of your arrival.", :vars => {:conference_title => @conference.title}
%p=_'email.general.paragraph.see_you', "See you in #{@conference.location.city}!", :vars => {:conference_location => @conference.location.city} - if @conference.registration_status.to_sym == :pre
%p=_'email.general.paragraph.pre_registered', "You have successfully pre-registered for #{@conference.title}. We will let you know when registration is fully open and if there is any important news about the conference that you should know about. We encourage you to create or volunteer to facilitate workshops soon. You can do that, or change your registration details at any time by clicking on the pre-register link again.", :vars => {:conference_title => @conference.title}
- else
%p=_'email.registration.paragraph.confirmed', :vars => {:conference_title => @conference.title}
%p=_'email.general.paragraph.see_you', :vars => {:conference_location => @conference.location.city}

7
app/views/user_mailer/registration_confirmation.text.haml

@ -1,5 +1,8 @@
=_'email.general.paragraph.thank_you', "Thank you #{@user.firstname},", :vars => {:name => @user.firstname} =_'email.general.paragraph.thank_you', "Thank you #{@user.firstname},", :vars => {:name => @user.firstname}
=_'email.registration.paragraph.confirmed', "You have successfully registered for #{@conference.title}. You can modify your registration details, pay, or add workshops at any time by restarting the registration process. If you have yet to pay or add your workshops and plan to do so, we ask that you take care of it as soon as possible to help us prepare in advance of your arrival.", :vars => {:conference_title => @conference.title} - if @conference.registration_status.to_sym == :pre
=_'email.general.paragraph.pre_registered', "You have successfully pre-registered for #{@conference.title}. We will let you know when registration is fully open and if there is any important news about the conference that you should know about. We encourage you to create or volunteer to facilitate workshops soon. You can do that, or change your registration details at any time by clicking on the pre-register link again.", :vars => {:conference_title => @conference.title}
- else
=_'email.registration.paragraph.confirmed', :vars => {:conference_title => @conference.title}
=_'email.general.paragraph.see_you', "See you in #{@conference.location.city}!", :vars => {:conference_location => @conference.location.city} =_'email.general.paragraph.see_you', :vars => {:conference_location => @conference.location.city}

1
app/views/user_mailer/test_email.text.haml

@ -1 +0,0 @@
Hi, this is a test. Did you get it?

15
app/views/user_mailer/workshop_original_content_changed.html.haml

@ -0,0 +1,15 @@
%p=_('email.translations.paragraph.workshop_original_content_changed', "#{@translator.name} has modified the original content for #{@workshop.title}. Translations may need to be updated.", vars: {user_name: @translator.firstname, workshop_title: @workshop.title})
- @data.each do |field, values|
%h2=_"forms.labels.generic.#{field.to_s}"
%b=_'email.translations.headings.new_value'
%blockquote=_!values[:new].html_safe
%b=_'email.translations.headings.old_value'
%blockquote=_!values[:old].html_safe
%b=_'email.translations.headings.diff'
=_!(values[:diff][:html].html_safe)
%p
=_'email.workshop.paragraph.view_workshop',"You can view the workshop here: "
- workshop_link = view_workshop_url(@workshop.conference.slug, @workshop.id)
%a{href: workshop_link}=workshop_link

25
app/views/user_mailer/workshop_original_content_changed.text.haml

@ -0,0 +1,25 @@
=_('email.translations.paragraph.workshop_translated', "#{@translator.name} has modified the #{@locale_name} translation for #{@workshop.title}.", vars: {user_name: @translator.firstname, language: @locale_name, workshop_title: @workshop.title})
- @data.each do |field, values|
=(_!'** ') + (_"forms.labels.generic.#{field.to_s}")
=(_!' - ') + (_'email.translations.headings.new_value')
=(_!' ------------------------------ ')
=_!values[:new]
=(_!' ------------------------------ ')
=(_!' - ') + (_'email.translations.headings.old_value')
=(_!' ------------------------------ ')
=_!values[:old]
=(_!' ------------------------------ ')
=(_!' - ') + (_'email.translations.headings.diff')
=(_!' ------------------------------ ')
=(_!values[:diff][:text])
=(_!' ------------------------------ ')
=_'email.workshop.paragraph.view_workshop',"You can view the workshop here: "
= view_workshop_url(@workshop.conference.slug, @workshop.id)

15
app/views/user_mailer/workshop_translated.html.haml

@ -0,0 +1,15 @@
%p=_('email.translations.paragraph.workshop_translated', "#{@translator.name} has modified the #{@locale_name} translation for #{@workshop.title}.", vars: {user_name: @translator.firstname, language: @locale_name, workshop_title: @workshop.title})
- @data.each do |field, values|
%h2=_"forms.labels.generic.#{field.to_s}"
%b=_'email.translations.headings.new_value'
%blockquote=_!values[:new].html_safe
%b=_'email.translations.headings.old_value'
%blockquote=_!values[:old].html_safe
%b=_'email.translations.headings.diff'
=_!(values[:diff][:html].html_safe)
%p
=_'email.workshop.paragraph.view_workshop',"You can view the workshop here: "
- workshop_link = view_workshop_url(@workshop.conference.slug, @workshop.id)
%a{href: workshop_link}=workshop_link

25
app/views/user_mailer/workshop_translated.text.haml

@ -0,0 +1,25 @@
=_('email.translations.paragraph.workshop_translated', "#{@translator.firstname} has modified the #{@locale_name} translation for #{@workshop.title}.", vars: {user_name: @translator.firstname, language: @locale_name, workshop_title: @workshop.title})
- @data.each do |field, values|
=(_!'** ') + (_"forms.labels.generic.#{field.to_s}")
=(_!' - ') + (_'email.translations.headings.new_value')
=(_!' ------------------------------ ')
=_!values[:new]
=(_!' ------------------------------ ')
=(_!' - ') + (_'email.translations.headings.old_value')
=(_!' ------------------------------ ')
=_!values[:old]
=(_!' ------------------------------ ')
=(_!' - ') + (_'email.translations.headings.diff')
=(_!' ------------------------------ ')
=(_!values[:diff][:text])
=(_!' ------------------------------ ')
=_'email.workshop.paragraph.view_workshop',"You can view the workshop here: "
= view_workshop_url(@workshop.conference.slug, @workshop.id)

50
app/views/workshops/_show.html.haml

@ -6,14 +6,14 @@
=_'articles.workshops.info.you_are_interested_count', "You and #{workshop.interested_count - 1} others are interested in this workshop", :vars => {:count => (workshop.interested_count - 1)} =_'articles.workshops.info.you_are_interested_count', "You and #{workshop.interested_count - 1} others are interested in this workshop", :vars => {:count => (workshop.interested_count - 1)}
- else - else
=_'articles.workshops.info.interested_count', "#{workshop.interested_count} people are interested in this workshop", :vars => {:count => workshop.interested_count} =_'articles.workshops.info.interested_count', "#{workshop.interested_count} people are interested in this workshop", :vars => {:count => workshop.interested_count}
- if !preview && workshop.can_show_interest?(current_user) - if preview.blank? && workshop.can_show_interest?(current_user)
= form_tag toggle_workshop_interest_path(workshop.conference.slug, workshop.id) do = form_tag toggle_workshop_interest_path(workshop.conference.slug, workshop.id) do
= button_tag (workshop.interested?(current_user) ? :remove_interest : :show_interest), :value => :toggle_interest, :class => (workshop.interested?(current_user) ? 'delete' : 'add') = button_tag (workshop.interested?(current_user) ? :remove_interest : :show_interest), :value => :toggle_interest, :class => (workshop.interested?(current_user) ? :delete : :add)
=markdown _!(workshop.info) || '' =_!(workshop.info).html_safe if workshop.info.present?
- if !preview && logged_in? && current_user.can_translate? - if preview.blank? && translations_available_for_editing
.actions .actions
- I18n.backend.enabled_locales.each do |locale| - translations_available_for_editing.each do |locale|
= (link_to (_'actions.workshops.Translate', "Translate into #{language_name(locale)}", :vars => {:language => language_name(locale)}), edit_workshop_url(workshop.conference.slug, workshop.id, url_params(locale)), :class => 'button translate') if workshop.can_translate?(current_user, locale) = link_to (_'actions.workshops.Translate', "Translate into #{language_name(locale)}", :vars => {:language => language_name(locale)}), translate_workshop_url(workshop.conference.slug, workshop.id, locale), :class => [:button, :translate]
= columns(medium: 6) do = columns(medium: 6) do
%h3=_'articles.workshops.headings.facilitators' %h3=_'articles.workshops.headings.facilitators'
.facilitators .facilitators
@ -21,38 +21,40 @@
- u = User.find(f.user_id) - u = User.find(f.user_id)
- if logged_in? && (workshop.public_facilitator?(u) || f.user_id == current_user.id || workshop.active_facilitator?(current_user)) - if logged_in? && (workshop.public_facilitator?(u) || f.user_id == current_user.id || workshop.active_facilitator?(current_user))
.facilitator .facilitator
.name=_!(u.firstname || u.username || u.email) .name=_!u.name
.role .role
=_"roles.workshops.facilitator.#{workshop.role(u).to_s}" =_"roles.workshops.facilitator.#{workshop.role(u).to_s}"
- if !preview && f.role.to_sym == :requested && workshop.active_facilitator?(current_user) - if preview.blank? && f.role.to_sym == :requested && workshop.active_facilitator?(current_user)
=(link_to (_'actions.workshops.Approve'), approve_facilitate_workshop_request_path(workshop.conference.slug, workshop.id, f.user_id, 'approve'), :class => 'button modify') =(link_to (_'actions.workshops.Approve'), approve_facilitate_workshop_request_path(workshop.conference.slug, workshop.id, f.user_id, 'approve'), :class => [:button, :modify])
=(link_to (_'actions.workshops.Deny'), approve_facilitate_workshop_request_path(workshop.conference.slug, workshop.id, f.user_id, 'deny'), :class => 'button delete') =(link_to (_'actions.workshops.Deny'), approve_facilitate_workshop_request_path(workshop.conference.slug, workshop.id, f.user_id, 'deny'), :class => [:button, :delete])
- elsif !preview && (f.user_id == current_user.id && f.role.to_sym != :creator) || (!workshop.conference.registered?(u) && workshop.active_facilitator?(current_user)) - elsif preview.blank? && (f.user_id == current_user.id && f.role.to_sym != :creator) || (!workshop.conference.registered?(u) && workshop.active_facilitator?(current_user))
=(link_to (_'actions.workshops.Remove'), approve_facilitate_workshop_request_path(workshop.conference.slug, workshop.id, f.user_id, 'remove'), :class => 'button delete') =(link_to (_'actions.workshops.Remove'), approve_facilitate_workshop_request_path(workshop.conference.slug, workshop.id, f.user_id, 'remove'), :class => [:button, :delete])
- if !preview - unless preview.present?
.actions .actions
=(link_to (_'actions.workshops.Facilitate'), facilitate_workshop_path(workshop.conference.slug, workshop.id), :class => 'button modify') if !workshop.facilitator?(current_user) =(link_to (_'actions.workshops.Facilitate'), facilitate_workshop_path(workshop.conference.slug, workshop.id), :class => [:button, workshop.needs_facilitators ? :accented : :subdued]) unless workshop.facilitator?(current_user)
- if workshop.active_facilitator?(current_user) - if workshop.active_facilitator?(current_user)
= form_tag workshop_add_facilitator_path(workshop.conference.slug, workshop.id), :class => 'add-facilitator' do %h4=_'articles.workshops.headings.add_facilitator','Add a facilitator'
%h4='Add a facilitator' = form_tag workshop_add_facilitator_path(workshop.conference.slug, workshop.id), :class => 'add-facilitator flex-form' do
.email-field.input-field .email-field.input-field
= email_field_tag :email, nil, required: true = email_field_tag :email, nil, required: true
= label_tag :email = label_tag :email
= button_tag :add = button_tag :add
- if workshop.languages - languages = JSON.parse(workshop.languages || '[]')
- if languages.present?
= columns(medium: 6) do = columns(medium: 6) do
%h3=_'articles.workshops.headings.languages','Workshop Language' %h3=_'articles.workshops.headings.languages','Workshop Language'
%p= _!((JSON.parse(workshop.languages || '[]').map { |x| _"languages.#{x}" }).join(', ').to_s.html_safe) %p= _!((languages.map { |x| _"languages.#{x}" }).join(', ').to_s.html_safe)
- if workshop.theme - if workshop.theme.present?
= columns(medium: 6) do = columns(medium: 6) do
%h3=_'articles.workshops.headings.theme','Theme' %h3=_'articles.workshops.headings.theme','Theme'
%p= [:race_gender, :mechanics, :funding, :organization, :community].include?((workshop.theme || '').to_sym) ? (_"workshop.options.theme.#{workshop.theme}") : workshop.theme %p= Workshop.all_themes.include?((workshop.theme || '').to_sym) ? (_"workshop.options.theme.#{workshop.theme}") : workshop.theme
- if workshop.active_facilitator?(current_user) || workshop.conference.host?(current_user) - if workshop.active_facilitator?(current_user) || workshop.conference.host?(current_user)
- if workshop.needs - needs = JSON.parse(workshop.needs || '[]')
- if needs.present?
= columns(medium: 6) do = columns(medium: 6) do
%h3=_'articles.workshops.headings.needs','What do you need?' %h3=_'articles.workshops.headings.needs','What do you need?'
%p= _!((JSON.parse(workshop.needs || '[]').map { |x| _"workshop.options.needs.#{x}" }).join(', ').to_s.html_safe) %p= _!((needs.map { |x| _"workshop.options.needs.#{x}" }).join(', ').to_s.html_safe)
- if workshop.notes - if workshop.notes.present?
= columns(medium: 12) do = columns(medium: 12) do
%h3=_'articles.workshops.headings.notes','Notes' %h3=_'articles.workshops.headings.notes','Notes'
=markdown _!(workshop.notes) =_!(workshop.notes).html_safe

18
app/views/workshops/_workshop_previews.html.haml

@ -1,8 +1,12 @@
- # encoding: utf-8 %ul.workshop-list
%ul.workshop-previews
- workshops.sort_by{ |w| w.title.downcase }.each do |w| - workshops.sort_by{ |w| w.title.downcase }.each do |w|
- if w.title - is_interested = w.interested?(current_user)
%li %li{class: [is_interested ? :interested : nil]}
=link_to view_workshop_path(@this_conference.slug, w.id) do = link_to view_workshop_path(w.conference.slug, w.id) do
%h4=_!(w.title) %h4.title=w.title
%p=_!(w.info || '').gsub(/\n\#+(.*?)\r/, "\n<strong>\\1</strong>\r").gsub(/[\t ]*\r\n[\t ]*/, '<br>').html_safe .workshop-interest
- if is_interested
=_'articles.workshops.info.you_are_interested_count', "You and #{w.interested_count - 1} others are interested in this workshop", :vars => {:count => (w.interested_count - 1)}
- elsif w.interested_count > 0
=_'articles.workshops.info.interested_count', "#{w.interested_count} people are interested in this workshop", :vars => {:count => w.interested_count}
.workshop-description=w.info.html_safe

6
app/views/workshops/facilitate_request_sent.html.haml

@ -1,9 +1,11 @@
= render 'conferences/page_header', :page_key => 'Facilitate_Workshop' = render 'conferences/page_header', :page_key => 'Conference_Registration'
%article %article
= row do = row do
%h2=_'page_titles.conferences.Facilitate_Workshop'
= columns(medium: 12) do = columns(medium: 12) do
=m('articles.workshops.paragraphs.facilitate_request_sent','Your request has been sent. You will receive an email once your request is approved or denied or if the current facilitators have any questions.') =m('articles.workshops.paragraphs.facilitate_request_sent','Your request has been sent. You will receive an email once your request is approved or denied or if the current facilitators have any questions.')
= columns(medium: 12) do = columns(medium: 12) do
.actions .actions
= link_to (_'actions.workshops.View'), view_workshop_url(@this_conference.slug, @workshop.id), :class => 'button' = link_to (_'actions.workshops.View'), view_workshop_url(@this_conference.slug, @workshop.id), :class => 'button'
= link_to (_'actions.workshops.View_All'), workshops_url(@this_conference.slug), :class => 'button' = link_to (_'actions.workshops.View_All'), register_step_path(@this_conference.slug, :wokshops), :class => 'button'

11
app/views/workshops/index.html.haml

@ -1,13 +1,18 @@
= render 'conferences/page_header', :page_key => 'Workshops' = render 'conferences/page_header', :page_key => 'Workshops'
%article %article
= row do = row do
= columns(medium: 12) do = columns(medium: 12) do
%h2=_'articles.workshops.headings.Workshops' %h2=_'articles.workshops.headings.Workshops'
= columns(medium: 6) do = row do
= columns(medium: 12) do
%p=_'articles.workshops.paragraphs.Workshops', :p %p=_'articles.workshops.paragraphs.Workshops', :p
= columns(medium: 6) do = row do
= columns(medium: 12) do
%h3=_'articles.workshops.headings.Your_Workshops' %h3=_'articles.workshops.headings.Your_Workshops'
= render 'workshops/workshop_previews', :workshops => @my_workshops = render 'workshops/workshop_previews', :workshops => @my_workshops
= link_to (_'actions.workshops.create','New Workshop'), create_workshop_path(@this_conference.slug), class: [:button, :modify] if @conference.registration_open = link_to (_'actions.workshops.create','New Workshop'), create_workshop_path(@this_conference.slug), class: [:button, :modify] if @conference.registration_open
= row do = row do
= render 'workshops/workshop_previews', :workshops => @workshops = columns(medium: 12) do
%h3=_'articles.workshops.headings.All_Workshops'
= render 'workshops/workshop_previews', :workshops => @workshops

94
app/views/workshops/new.html.haml

@ -1,69 +1,91 @@
= render 'conferences/page_header', :page_key => (@workshop ? 'Edit_Workshop' : 'Create_Workshop') - add_stylesheet :editor
- add_inline_script :pen
- add_inline_script :markdown
- add_inline_script :editor
-#= render 'conferences/page_header', :page_key => (@workshop.id.present? ? 'Edit_Workshop' : 'Create_Workshop')
= render 'conferences/page_header', :page_key => 'Conference_Registration'
%article %article
= row do = row do
= form_tag save_workshop_path(@this_conference.slug), class: 'composition' do = form_tag save_workshop_path(@this_conference.slug), class: 'composition' do
= (hidden_field_tag :workshop_id, @workshop.id) if @workshop
= columns(medium: 12) do = columns(medium: 12) do
=m('articles.workshops.paragraphs.new_workshop','Please accurately describe your workshop in detail. This will help hosts decide if they wish to add it to the schedule and when it should best be scheduled. Enter normal text but if you want to get fancy you can use [Markdown](http://daringfireball.net/projects/markdown/basics).') - if @workshop.id.present?
= hidden_field_tag :workshop_id, @workshop.id
- if @is_translating
%h2=_(@page_title, vars: @page_title_vars)
= hidden_field_tag :translation, @translation
- else
%h2=_@page_title, :t
- else
%h2=_@page_title, :t
.text-field.input-field.big .text-field.input-field.big
= label_tag :title = label_tag :title
= text_field_tag :title, @title, :required => true = text_field_tag :title, @title, required: true, lang: @translation
- if @workshop && I18n.locale.to_s != @workshop.locale.to_s - if @is_translating
.original-text .original-text
%h4=_'translate.content.Translation_of' %h4=_'translate.content.Translation_of'
.value=@workshop.title! .value=@workshop.title!
.text-area-field.input-field .text-area-field.input-field
= label_tag :info = label_tag :info
= text_area_tag :info, @info, :required => true .input-field-help=_'articles.workshops.paragraphs.info', :s, 2
- if @workshop && I18n.locale.to_s != @workshop.locale.to_s .textarea#info{lang: @translation, data: {name: :info}}=(@info || '').html_safe
- if @is_translating
.original-text .original-text
%h4=_'translate.content.Translation_of' %h4=_'translate.content.Translation_of'
.value=markdown @workshop.info! .value=(@workshop.info! || '').html_safe
- if !@workshop || @can_edit - if !@is_translating && (@workshop.id.blank? || @can_edit)
= columns(medium: 6) do = columns(medium: 6) do
%h3=_'articles.workshops.headings.languages','Workshop Language' %h3=_'articles.workshops.headings.languages','Workshop Language'
- [:en, :es, :fr].each do |language| .input-field-help=_'articles.workshops.paragraphs.languages', :s, 2
.single-check-box-field.input-field .check-box-field.vertical.input-field
- [:en, :es, :fr].each do |language|
= check_box_tag "languages[#{language}]", 1, @languages && @languages.include?(language)
= label_tag "languages_#{language}" do = label_tag "languages_#{language}" do
= check_box_tag "languages[#{language}]", 1, @languages && @languages.include?(language)
= _"languages.#{language}" = _"languages.#{language}"
%h3=_'articles.workshops.headings.theme','Theme' %h3=_'articles.workshops.headings.theme','Theme'
.input-field-help=_'articles.workshops.paragraphs.theme', :p .input-field-help=_'articles.workshops.paragraphs.theme', :s, 2
- theme_found = false - theme_found = false
- [:race_gender, :mechanics, :funding, :organization, :community].each do |theme| .check-box-field.vertical.input-field
.single-radio-button-field.input-field - Workshop.all_themes.each do |theme|
- is_selected = (@workshop.theme.to_s == theme.to_s)
- theme_found ||= is_selected
= radio_button_tag :theme, theme, is_selected
= label_tag "theme_#{theme}" do = label_tag "theme_#{theme}" do
- is_selected = (@theme == theme)
- theme_found ||= is_selected
= radio_button_tag :theme, theme, is_selected
= _"workshop.options.theme.#{theme}" = _"workshop.options.theme.#{theme}"
.single-radio-button-field.other-field.input-field - is_other = (@workshop.theme.present? && !theme_found)
= radio_button_tag :theme, :other, is_other
= label_tag "theme_other" do = label_tag "theme_other" do
= radio_button_tag :theme, :other, (@theme && !theme_found) .other
%div = text_field_tag :other_theme, (is_other ? @workshop.theme : nil), placeholder: (_"workshop.options.theme.other"), required: is_other
= _"workshop.options.theme.other"
= text_field_tag :other_theme, (@theme && !theme_found ? @theme : nil)
= columns(medium: 6) do = columns(medium: 6) do
%h3=_'articles.workshops.headings.needs','What do you need?' %h3=_'articles.workshops.headings.needs','What do you need?'
- [:sound, :projector, :tools].each do |need| .input-field-help=_'articles.workshops.paragraphs.needs', :s, 2
.single-check-box-field.input-field .check-box-field.vertical.input-field
- [:sound, :projector, :tools].each do |need|
= check_box_tag "needs[#{need.to_s}]", 1, JSON.parse(@workshop.needs || '[]').include?(need.to_s)
= label_tag "needs_#{need}" do = label_tag "needs_#{need}" do
= check_box_tag "needs[#{need}]", 1, @needs.include?(need) = _"workshop.options.needs.#{need.to_s}"
= _"workshop.options.needs.#{need}"
%h3=_'articles.workshops.headings.space','Type of space' %h3=_'articles.workshops.headings.space','Type of space'
.input-field-help=_'articles.workshops.paragraphs.space', :p .input-field-help=_'articles.workshops.paragraphs.space', :s, 2
- [:meeting_room, :workshop, :outdoor_meeting].each do |space| .check-box-field.vertical.input-field
.single-radio-button-field.input-field - [:meeting_room, :workshop, :outdoor_meeting].each do |space|
= radio_button_tag :space, space, @workshop.space == space
= label_tag "space_#{space}" do = label_tag "space_#{space}" do
= radio_button_tag :space, space, @space == space
= _"workshop.options.space.#{space}" = _"workshop.options.space.#{space}"
%h3=_'articles.workshops.headings.needs_facilitators','Looking for help?'
.input-field-help=_'articles.workshops.paragraphs.needs_facilitators', :s, 2
.check-box-field.vertical.input-field
= check_box_tag :needs_facilitators, 1, @workshop.needs_facilitators
= label_tag :needs_facilitators do
= _'workshop.options.needs_facilitators', 'Needs Additional Facilitators'
= columns(medium: 12) do = columns(medium: 12) do
%h3=_'articles.workshops.headings.notes','Notes for Conference Organizers and Workshop Facilitators'
%p=_'articles.workshops.paragraphs.notes','Notes are only viewable by conference hosts and workshop facilitators'
.text-area-field.input-field .text-area-field.input-field
= label_tag :notes = label_tag :notes
= text_area_tag :notes, @notes .warning-info=_'articles.workshops.paragraphs.notes','Notes are only viewable by conference hosts and workshop facilitators'
.textarea#notes{data: {name: :notes}}=(@workshop.notes || '').html_safe
= columns(medium: 12) do = columns(medium: 12) do
.actions.right .actions.next-prev
= button_tag :save, :value => :save = button_tag :save, value: :save
= button_tag :cancel, value: :cancel, class: :subdued, formnovalidate: true

18
app/views/workshops/show.html.haml

@ -1,8 +1,20 @@
= render 'conferences/page_header', :page_key => 'View_Workshop' = render 'conferences/page_header', :page_key => 'Conference_Registration'
= row id: 'registration-steps', class: 'flow-steps' do
= columns do
%ul
- current_registration_steps.each do | step |
- text = _"articles.conference_registration.headings.#{step[:name].to_s}"
%li{class: [step[:enabled] ? :enabled : nil, :workshops == step[:name] ? :current : nil]}
- if step[:enabled]
.step= link_to text, register_step_path(@this_conference.slug, step[:name])
- else
.step= text
%article %article
= render 'workshops/show', :workshop => @workshop, :preview => false = render 'workshops/show', :workshop => @workshop, :translations_available_for_editing => @translations_available_for_editing, :preview => false
= row do = row do
= columns(medium: 12) do = columns(medium: 12) do
.actions.right .actions.right
= (link_to (_'actions.workshops.Edit'), edit_workshop_path(@this_conference.slug, @workshop.id), :class => 'button modify') if @workshop.can_edit?(current_user) = (link_to (_'actions.workshops.Edit'), edit_workshop_path(@this_conference.slug, @workshop.id), :class => [:button, :modify]) if @workshop.can_edit?(current_user)
= (link_to (_'actions.workshops.Delete'), delete_workshop_path(@this_conference.slug, @workshop.id), :class => 'button delete') if @workshop.can_delete?(current_user) = (link_to (_'actions.workshops.Delete'), delete_workshop_path(@this_conference.slug, @workshop.id), :class => 'button delete') if @workshop.can_delete?(current_user)

49
config/deploy.rb

@ -0,0 +1,49 @@
# config valid only for current version of Capistrano
lock '3.5.0'
set :application, 'my_app_name'
set :repo_url, 'git@example.com:me/my_repo.git'
# Default branch is :master
# ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp
# Default deploy_to directory is /var/www/my_app_name
# set :deploy_to, '/var/www/my_app_name'
# Default value for :scm is :git
# set :scm, :git
# Default value for :format is :airbrussh.
# set :format, :airbrussh
# You can configure the Airbrussh format using :format_options.
# These are the defaults.
# set :format_options, command_output: true, log_file: 'log/capistrano.log', color: :auto, truncate: :auto
# Default value for :pty is false
# set :pty, true
# Default value for :linked_files is []
# set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')
# Default value for linked_dirs is []
# set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system')
# Default value for default_env is {}
# set :default_env, { path: "/opt/ruby/bin:$PATH" }
# Default value for keep_releases is 5
# set :keep_releases, 5
namespace :deploy do
after :restart, :clear_cache do
on roles(:web), in: :groups, limit: 3, wait: 10 do
# Here we can do anything such as:
# within release_path do
# execute :rake, 'cache:clear'
# end
end
end
end

61
config/deploy/production.rb

@ -0,0 +1,61 @@
# server-based syntax
# ======================
# Defines a single server with a list of roles and multiple properties.
# You can define all roles on a single server, or split them:
# server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value
# server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value
# server 'db.example.com', user: 'deploy', roles: %w{db}
# role-based syntax
# ==================
# Defines a role with one or multiple servers. The primary server in each
# group is considered to be the first unless any hosts have the primary
# property set. Specify the username and a domain or IP for the server.
# Don't use `:all`, it's a meta role.
# role :app, %w{deploy@example.com}, my_property: :my_value
# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value
# role :db, %w{deploy@example.com}
# Configuration
# =============
# You can set any configuration variable like in config/deploy.rb
# These variables are then only loaded and set in this stage.
# For available Capistrano configuration variables see the documentation page.
# http://capistranorb.com/documentation/getting-started/configuration/
# Feel free to add new variables to customise your setup.
# Custom SSH Options
# ==================
# You may pass any option but keep in mind that net/ssh understands a
# limited set of options, consult the Net::SSH documentation.
# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
#
# Global options
# --------------
# set :ssh_options, {
# keys: %w(/home/rlisowski/.ssh/id_rsa),
# forward_agent: false,
# auth_methods: %w(password)
# }
#
# The server-based syntax can be used to override options:
# ------------------------------------
# server 'example.com',
# user: 'user_name',
# roles: %w{web app},
# ssh_options: {
# user: 'user_name', # overrides user setting above
# keys: %w(/home/user_name/.ssh/id_rsa),
# forward_agent: false,
# auth_methods: %w(publickey password)
# # password: 'please use keys'
# }

61
config/deploy/staging.rb

@ -0,0 +1,61 @@
# server-based syntax
# ======================
# Defines a single server with a list of roles and multiple properties.
# You can define all roles on a single server, or split them:
# server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value
# server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value
# server 'db.example.com', user: 'deploy', roles: %w{db}
# role-based syntax
# ==================
# Defines a role with one or multiple servers. The primary server in each
# group is considered to be the first unless any hosts have the primary
# property set. Specify the username and a domain or IP for the server.
# Don't use `:all`, it's a meta role.
# role :app, %w{deploy@example.com}, my_property: :my_value
# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value
# role :db, %w{deploy@example.com}
# Configuration
# =============
# You can set any configuration variable like in config/deploy.rb
# These variables are then only loaded and set in this stage.
# For available Capistrano configuration variables see the documentation page.
# http://capistranorb.com/documentation/getting-started/configuration/
# Feel free to add new variables to customise your setup.
# Custom SSH Options
# ==================
# You may pass any option but keep in mind that net/ssh understands a
# limited set of options, consult the Net::SSH documentation.
# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
#
# Global options
# --------------
# set :ssh_options, {
# keys: %w(/home/rlisowski/.ssh/id_rsa),
# forward_agent: false,
# auth_methods: %w(password)
# }
#
# The server-based syntax can be used to override options:
# ------------------------------------
# server 'example.com',
# user: 'user_name',
# roles: %w{web app},
# ssh_options: {
# user: 'user_name', # overrides user setting above
# keys: %w(/home/user_name/.ssh/id_rsa),
# forward_agent: false,
# auth_methods: %w(publickey password)
# # password: 'please use keys'
# }

12
config/environments/development.rb

@ -30,10 +30,11 @@ BikeBike::Application.configure do
# This option may cause significant delays in view rendering with a large # This option may cause significant delays in view rendering with a large
# number of complex assets. # number of complex assets.
config.assets.debug = false config.assets.debug = false
config.assets.digest = false
config.assets.compile = true
config.action_mailer.delivery_method = :smtp config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = { config.action_mailer.smtp_settings = {
:enable_starttls_auto => true,
:address => 'mail.bikebike.org', :address => 'mail.bikebike.org',
:domain => 'preview.bikebike.org', :domain => 'preview.bikebike.org',
:port => 587, :port => 587,
@ -45,15 +46,10 @@ BikeBike::Application.configure do
} }
config.action_mailer.raise_delivery_errors = true config.action_mailer.raise_delivery_errors = true
config.action_mailer.perform_deliveries = true config.action_mailer.perform_deliveries = true
#config.force_ssl = true
#config.action_mailer.default_charset = 'utf-8'
#Carmen.i18n_backend.locale_paths = ''
#puts "CARMEN\t" + Carmen.i18n_backend.locale_paths
#PerfTools::CpuProfiler.start('/tmp/dev_prof')
config.serve_static_files = true config.serve_static_files = true
#config.assets.precompile = false # config.action_controller.perform_caching = true
Paypal.sandbox! Paypal.sandbox!
#Paypal.sandbox = false
end end

1
config/environments/preview.rb

@ -87,7 +87,6 @@ BikeBike::Application.configure do
config.action_mailer.delivery_method = :smtp config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = { config.action_mailer.smtp_settings = {
:enable_starttls_auto => true,
:address => 'mail.bikebike.org', :address => 'mail.bikebike.org',
:domain => 'preview.bikebike.org', :domain => 'preview.bikebike.org',
:port => 587, :port => 587,

1
config/environments/production.rb

@ -88,7 +88,6 @@ BikeBike::Application.configure do
config.action_mailer.delivery_method = :smtp config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = { config.action_mailer.smtp_settings = {
:enable_starttls_auto => true,
:address => 'mail.bikebike.org', :address => 'mail.bikebike.org',
:domain => 'preview.bikebike.org', :domain => 'preview.bikebike.org',
:port => 587, :port => 587,

11
config/initializers/assets.rb

@ -0,0 +1,11 @@
# Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path
# Rails.application.config.assets.paths << Emoji.images_path
# 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( map.js pen.js editor.js markdown.js main.js favicon.ico )

24
config/initializers/sorcery.rb

@ -3,7 +3,19 @@
# Available submodules are: :user_activation, :http_basic_auth, :remember_me, # Available submodules are: :user_activation, :http_basic_auth, :remember_me,
# :reset_password, :session_timeout, :brute_force_protection, :activity_logging, :external # :reset_password, :session_timeout, :brute_force_protection, :activity_logging, :external
# Rails.application.config.sorcery.submodules = [:remember_me, :reset_password, :user_activation, :brute_force_protection, :external] # Rails.application.config.sorcery.submodules = [:remember_me, :reset_password, :user_activation, :brute_force_protection, :external]
Rails.application.config.sorcery.submodules = [:remember_me, :reset_password, :brute_force_protection, :external] require 'sorcery'
require 'openssl'
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE if Rails.env.development?
ENV['bb_host'] = {
test: 'localhost:3000',
development: 'development.bikebike.org:3000',
preview: 'preview.bikebike.org',
production: 'bikebike.org',
}[Rails.env]
Rails.application.config.sorcery.submodules = [:external]
# Here you can configure each submodule's features. # Here you can configure each submodule's features.
Rails.application.config.sorcery.configure do |config| Rails.application.config.sorcery.configure do |config|
@ -112,10 +124,12 @@ Rails.application.config.sorcery.configure do |config|
config.facebook.key = "257350517701074" config.facebook.key = "257350517701074"
config.facebook.secret = "2f6ab1fd7eeff9aee73140991fc68314" config.facebook.secret = "2f6ab1fd7eeff9aee73140991fc68314"
config.facebook.callback_url = "http://dev.bikebike.org/oauth/callback?provider=facebook" config.facebook.callback_url = "#{ENV['bb_host']}/oauth/callback?provider=facebook"
config.facebook.user_info_mapping = {:email => "email", :username => "username", :avatar => "picture/data/url"} config.facebook.user_info_mapping = {:email => "email", :username => "username"}
config.facebook.scope = "email" config.facebook.scope = "email"
config.facebook.display = "popup" config.facebook.display = "popup"
config.facebook.api_version = "v2.5"
config.facebook.user_info_path = "me?fields=email,name"
# config.github.key = "" # config.github.key = ""
# config.github.secret = "" # config.github.secret = ""
@ -306,7 +320,7 @@ Rails.application.config.sorcery.configure do |config|
# mailer class. Needed. # mailer class. Needed.
# Default: `nil` # Default: `nil`
# #
user.reset_password_mailer = UserMailer # user.reset_password_mailer = UserMailer
# reset password email method on your mailer class. # reset password email method on your mailer class.
@ -378,7 +392,7 @@ Rails.application.config.sorcery.configure do |config|
# Unlock token mailer class # Unlock token mailer class
# Default: `nil` # Default: `nil`
# #
user.unlock_token_mailer = UserMailer # user.unlock_token_mailer = UserMailer
# -- activity logging -- # -- activity logging --
# Last login attribute name. # Last login attribute name.

45
config/locales/en.yml

@ -5212,6 +5212,7 @@ en:
un_translated: Untranslated content un_translated: Untranslated content
outdated_translation: Outdated content outdated_translation: Outdated content
not_translation: Not a translation not_translation: Not a translation
Translation_of: Translation of
volunteer: volunteer:
become_a_volunteer: Become a volunteer translator become_a_volunteer: Become a volunteer translator
volunteer: Can you help us translate it? volunteer: Can you help us translate it?
@ -5294,6 +5295,7 @@ en:
conference_registration: conference_registration:
headings: headings:
Policy_Agreement: Safer Space Agreement Policy_Agreement: Safer Space Agreement
policy: Policy
arrival_and_departure: For what days will you need housing? (If you don't arrival_and_departure: For what days will you need housing? (If you don't
need housing, just tell us how long you plan to hang out with Bike!Bike!) need housing, just tell us how long you plan to hang out with Bike!Bike!)
other: Is there anything else you'd like to tell us? other: Is there anything else you'd like to tell us?
@ -5316,6 +5318,7 @@ en:
Allergies: Allergies Allergies: Allergies
Stats: Stats Stats: Stats
Workshops: Workshops Workshops: Workshops
workshops: Workshops
Your_Workshops: Your Workshops Your_Workshops: Your Workshops
payment_confirm: Please confirm your payment payment_confirm: Please confirm your payment
Preview: Preview Preview: Preview
@ -5323,8 +5326,13 @@ en:
date: Date date: Date
email: Email email: Email
fees_paid: Fees Paid fees_paid: Fees Paid
Contact_Info: Contact Info
contact_info: Contact Info
Add_Workshop: Propose a Workshop
Workshops_Looking_For_Facilitators: Workshops Looking for Facilitators
All_Workshops: All Workshops
paragraphs: paragraphs:
Policy_Agreement: Safer Space Agreement Policy_Agreement: Ensuring that all attendees feel welcome, safe, and respected at all times is especially important to us all. Please ensure that you have fully read and understand our safer spaces policy below, if you have any questions or concerns you can reach out to the organizers at any time.
Confirm_Agreement: By clicking the "I Agree" button, you are pledging to do Confirm_Agreement: By clicking the "I Agree" button, you are pledging to do
your best to uphold Bike!Bike!'s safer space agreement. Thank you! your best to uphold Bike!Bike!'s safer space agreement. Thank you!
Registration_Info: Please fill in this registration form to help us prepare Registration_Info: Please fill in this registration form to help us prepare
@ -5361,6 +5369,10 @@ en:
participants_emailed: Your email has been sent to all participants of %%{conference_title}. participants_emailed: Your email has been sent to all participants of %%{conference_title}.
workshops: You can now take a look at proposed workshops and even propose workshops: You can now take a look at proposed workshops and even propose
one yourself if you like. one yourself if you like.
Contact_Info: Please let us know a little bit about you.
Create_Workshop: At Bike!Bike! anyone can lead a workshop or just propose and idea that someone else can volunteer to lead. If, where, and when the workshop will be scheduled will ultimately be decided by the conference organizers.
Workshops_Looking_For_Facilitators: Would you like to lend a hand facilitating a workshop proposed by someone else? Below is a list of workshops that are actively looking for volunteers, if you are interested in helping out you can make a facilitation request.
Your_Workshops: The following is a list of all the workshops that you have created or have requested to facilitate.
questions: questions:
bike: bike:
large: Large large: Large
@ -5462,13 +5474,13 @@ en:
Workshops: Workshops Workshops: Workshops
Your_Workshops: Your Workshops Your_Workshops: Your Workshops
facilitate: Request to Facilitate ‘%{workshop_title}’ facilitate: Request to Facilitate ‘%{workshop_title}’
add_facilitator: Add a facilitator
needs_facilitators: Looking for help?
paragraphs: paragraphs:
Proposed_Workshops: Would you like to facilitate your own workshop? Simply Proposed_Workshops: Would you like to facilitate your own workshop? Simply
register and visit the workshops page. If you have already registered you register and visit the workshops page. If you have already registered you
can access the page by restarting the registration process. can access the page by restarting the registration process.
info: Please accurately describe your workshop in detail. This will help hosts info: Describe your workshop in detail; highlighting text will reveal formatting controls.
decide if they wish to add it to the schedule and when it should best be
scheduled. Enter normal text but if you want to get fancy you can use [Markdown](http://daringfireball.net/projects/markdown/basics)
space: What kind of space do you need for your workshop? space: What kind of space do you need for your workshop?
theme: Which of the themes below best match your workshop? This will help theme: Which of the themes below best match your workshop? This will help
hosts to avoid scheduling conflicts. Select other if none of the options hosts to avoid scheduling conflicts. Select other if none of the options
@ -5495,6 +5507,9 @@ en:
facilitate_request_sent: Your request has been sent. You will receive an email facilitate_request_sent: Your request has been sent. You will receive an email
once your request is approved or denied or if the current facilitators have once your request is approved or denied or if the current facilitators have
any questions. any questions.
languages: What language will the workshop be presented in? Will there be translations or translators available?
needs: If you need any of the following the conference organizers will do their best to make sure you have them. If you have any other requests, you can include them in the notes section.
needs_facilitators: Are you actively looking for help running this workshop? Anyone who is registered for the conference can request to facilitate at any time but checking this box will improve your chances of finding collaborators.
info: info:
interested_count: interested_count:
one: One person is interested in this workshop one: One person is interested in this workshop
@ -5522,7 +5537,7 @@ en:
name: Name name: Name
subject: Subject subject: Subject
title: Title title: Title
info: Info info: Description
content: Content content: Content
notes: Notes notes: Notes
message: 'Your Message:' message: 'Your Message:'
@ -5546,6 +5561,8 @@ en:
remove_interest: "-1" remove_interest: "-1"
show_interest: "+1" show_interest: "+1"
add: "+" add: "+"
previous: Previous
next: Next
page_titles: page_titles:
'403': '403':
Please_Confirm_Email: Please confirm your email Please_Confirm_Email: Please confirm your email
@ -5562,8 +5579,9 @@ en:
Registration_Stats: Registration Stats Registration_Stats: Registration Stats
View_Workshop: View Workshop View_Workshop: View Workshop
Workshops: Workshops Workshops: Workshops
Delete_Workshop: Create Workshop Delete_Workshop: Delete Workshop
Edit_Workshop: Edit Workshop Edit_Workshop: Edit Workshop
Translate_Workshop: Edit %{language} Workshop Translation
Edit: Edit Conference Edit: Edit Conference
Facilitate_Workshop: Workshop Facilitation Request Facilitate_Workshop: Workshop Facilitation Request
policy: policy:
@ -5572,6 +5590,8 @@ en:
Page_Not_Found: Page Not Found Page_Not_Found: Page Not Found
Policy: 'Policy' Policy: 'Policy'
About: 'About' About: 'About'
Register: 'Register'
Pre_Register: 'Pre-Register'
links: links:
footer: footer:
help_text: help_text:
@ -5640,6 +5660,7 @@ en:
organization: Organizational Concerns organization: Organizational Concerns
other: Other other: Other
race_gender: Race, Gender, or Class Politics race_gender: Race, Gender, or Class Politics
needs_facilitators: Needs Additional Facilitators
email: email:
confirmation: confirmation:
paragraph: paragraph:
@ -5650,10 +5671,13 @@ en:
subject: subject:
confirm_email: Confirmation Email confirm_email: Confirmation Email
registration_confirmed: Thank you for registering for %{conference_title} registration_confirmed: Thank you for registering for %{conference_title}
pre_registration_confirmed: Thank you for pre-registering for %{conference_title}
workshop_facilitator_request: Request to facilitate %{workshop_title} from %{requester_name} workshop_facilitator_request: Request to facilitate %{workshop_title} from %{requester_name}
workshop_request_approved: You have been added as a facilitator of %{workshop_title} workshop_request_approved: You have been added as a facilitator of %{workshop_title}
workshop_request_denied: Your request to facilitate %{workshop_title} has been workshop_request_denied: Your request to facilitate %{workshop_title} has been
denied denied
workshop_translated: The %{language} translation for %{workshop_title} has been modified
workshop_original_content_changed: Original content for %{workshop_title} has been modified
general: general:
paragraph: paragraph:
see_you: See you in %{conference_location}! see_you: See you in %{conference_location}!
@ -5665,7 +5689,14 @@ en:
the registration process. If you have yet to pay or add your workshops and the registration process. If you have yet to pay or add your workshops and
plan to do so, we ask that you take care of it as soon as possible to help plan to do so, we ask that you take care of it as soon as possible to help
us prepare in advance of your arrival. us prepare in advance of your arrival.
workshop: translations:
headings:
new_value: 'New Value: '
old_value: 'Old Value: '
diff: 'Difference: '
paragraph:
workshop_translated: '%{user_name} has modified the %{language} translation for %{workshop_title}.'
workshop_original_content_changed: '%{user_name} has modified the original content for %{workshop_title}. Translations may need to be updated.'
paragraph: paragraph:
request_approved: You have been added as a facilitator of %{workshop_title}. request_approved: You have been added as a facilitator of %{workshop_title}.
request_denied: Your request to become a facilitator of %{workshop_title} request_denied: Your request to become a facilitator of %{workshop_title}

7
config/locales/es.yml

@ -702,7 +702,7 @@ es:
actions: actions:
Register: Registrate Register: Registrate
donate: donate:
button_label: donar button_label: Donar
articles: articles:
conference_registration: conference_registration:
headings: headings:
@ -744,7 +744,6 @@ es:
en: bikebike@gdlenbici.org en: bikebike@gdlenbici.org
Workshops: 'Título de la actividad: Que necesitas? Tipo de espacio? Tema? Workshops: 'Título de la actividad: Que necesitas? Tipo de espacio? Tema?
Quién es responsable? Descripción? Tiempo?' Quién es responsable? Descripción? Tiempo?'
Policy_Agreement: Acuerdo de Espacios Más Seguros
Confirm_Agreement: Al hacer click en el botón "Acepto", estás comprometiéndote Confirm_Agreement: Al hacer click en el botón "Acepto", estás comprometiéndote
a cumplir el Acuerdo de Espacios Más Seguros de Bike!Bike!. ¡Gracias! 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. Email_Participants: Esta página se usa para contactar a todxs lxs participantes.
@ -851,8 +850,6 @@ es:
Proposed_Workshops: ¿Te gustaría facilitar tu propio taller? Simplemente regístrate 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 y visita la página de Talleres. Si ya te registraste puedes acceder a la
página reiniciando el proceso de registro. página reiniciando el proceso de registro.
info: 'Título de la actividad: Que necesitas? Tipo de espacio? Tema? Quién
es responsable? Descripción? Tiempo?'
Workshops: ¿Tienes alguna habilidad emocionante que quieres compartir con Workshops: ¿Tienes alguna habilidad emocionante que quieres compartir con
nosotros? ¿Quieres charlar sobre crear espacios comunitarios seguros? ¿Quieres nosotros? ¿Quieres charlar sobre crear espacios comunitarios seguros? ¿Quieres
asegurarte de que hagamos un buen paseo el fin de semana? ¡Propón un taller! asegurarte de que hagamos un buen paseo el fin de semana? ¡Propón un taller!
@ -1012,6 +1009,8 @@ es:
Page_Not_Found: Crear un taller Page_Not_Found: Crear un taller
Policy: 'Política' Policy: 'Política'
About: 'Acerca de' About: 'Acerca de'
Register: 'Registrate'
Pre_Register: 'Pre-Registrate'
actions: actions:
workshops: workshops:
create: Crear create: Crear

8
config/routes.rb

@ -6,6 +6,7 @@ BikeBike::Application.routes.draw do
post '/conferences/:slug/save' => 'conferences#save', :as => :save_conference post '/conferences/:slug/save' => 'conferences#save', :as => :save_conference
match '/conferences/:slug/register' => 'conferences#register', :as => :register, via: [:get, :post] match '/conferences/:slug/register' => 'conferences#register', :as => :register, via: [:get, :post]
get '/conferences/:slug/register/:step' => 'conferences#register', :as => :register_step
match '/conferences/:slug/broadcast' => 'conferences#broadcast', :as => :broadcast, via: [:get, :post] match '/conferences/:slug/broadcast' => 'conferences#broadcast', :as => :broadcast, via: [:get, :post]
get '/conferences/:slug/stats' => 'conferences#stats', :as => :stats get '/conferences/:slug/stats' => 'conferences#stats', :as => :stats
get '/conferences/:slug/register/:button/:confirmation_token' => 'conferences#register', :as => :register_paypal_confirm get '/conferences/:slug/register/:button/:confirmation_token' => 'conferences#register', :as => :register_paypal_confirm
@ -24,12 +25,13 @@ BikeBike::Application.routes.draw do
get '/conferences/:slug/schedule/event/:id' => 'conferences#view_event', :as => :view_event get '/conferences/:slug/schedule/event/:id' => 'conferences#view_event', :as => :view_event
get '/conferences/:slug/schedule/event/:id/edit' => 'conferences#edit_event', :as => :edit_event get '/conferences/:slug/schedule/event/:id/edit' => 'conferences#edit_event', :as => :edit_event
get '/conferences/:slug/workshops' => 'conferences#workshops', :as => :workshops # get '/conferences/:slug/workshops' => 'conferences#workshops', :as => :workshops
match '/conferences/:slug/workshops/create' => 'conferences#create_workshop', :as => :create_workshop, via: [:get, :post] match '/conferences/:slug/workshops/create' => 'conferences#create_workshop', :as => :create_workshop, via: [:get, :post]
post '/conferences/:slug/workshops/save' => 'conferences#save_workshop', :as => :save_workshop post '/conferences/:slug/workshops/save' => 'conferences#save_workshop', :as => :save_workshop
get '/conferences/:slug/workshops/:workshop_id' => 'conferences#view_workshop', :as => :view_workshop get '/conferences/:slug/workshops/:workshop_id' => 'conferences#view_workshop', :as => :view_workshop
post '/conferences/:slug/workshops/:workshop_id/toggle-interest' => 'conferences#toggle_workshop_interest', :as => :toggle_workshop_interest post '/conferences/:slug/workshops/:workshop_id/toggle-interest' => 'conferences#toggle_workshop_interest', :as => :toggle_workshop_interest
match '/conferences/:slug/workshops/:workshop_id/edit' => 'conferences#edit_workshop', :as => :edit_workshop, via: [:get, :post] match '/conferences/:slug/workshops/:workshop_id/edit' => 'conferences#edit_workshop', :as => :edit_workshop, via: [:get, :post]
match '/conferences/:slug/workshops/:workshop_id/translate/:locale' => 'conferences#translate_workshop', :as => :translate_workshop, via: [:get, :post]
match '/conferences/:slug/workshops/:workshop_id/delete' => 'conferences#delete_workshop', :as => :delete_workshop, via: [:get, :post] match '/conferences/:slug/workshops/:workshop_id/delete' => 'conferences#delete_workshop', :as => :delete_workshop, via: [:get, :post]
get '/conferences/:slug/workshops/:workshop_id/facilitate' => 'conferences#facilitate_workshop', :as => :facilitate_workshop get '/conferences/:slug/workshops/:workshop_id/facilitate' => 'conferences#facilitate_workshop', :as => :facilitate_workshop
post '/conferences/:slug/workshops/:workshop_id/facilitate_request' => 'conferences#facilitate_request', :as => :facilitate_workshop_request post '/conferences/:slug/workshops/:workshop_id/facilitate_request' => 'conferences#facilitate_request', :as => :facilitate_workshop_request
@ -43,7 +45,9 @@ BikeBike::Application.routes.draw do
get '/confirm/:token' => 'application#confirm', :as => :confirm get '/confirm/:token' => 'application#confirm', :as => :confirm
match '/doconfirm' => 'application#do_confirm', :as => :do_confirm, via: [:get, :post] match '/doconfirm' => 'application#do_confirm', :as => :do_confirm, via: [:get, :post]
#post '/doconfirm' => 'application#do_confirm', :as => :do_confirm #post '/doconfirm' => 'application#do_confirm', :as => :do_confirm
post '/logout' => 'application#user_logout', :as => :logout match '/logout' => 'application#user_logout', :as => :logout, :via => [:get, :post]
match '/oauth/callback' => 'oauths#callback', :via => [:get, :post]
get '/oauth/:provider' => 'oauths#oauth', :as => :auth_at_provider
post '/translator-request' => 'application#translator_request', :as => :translator_request post '/translator-request' => 'application#translator_request', :as => :translator_request
get '/error_404' => 'application#error_404' get '/error_404' => 'application#error_404'

9
db/migrate/20160515210708_add_registration_status_to_conferences.rb

@ -0,0 +1,9 @@
class AddRegistrationStatusToConferences < ActiveRecord::Migration
def change
add_column :conferences, :registration_status, :string
Conference.find_each do |conference|
conference.registration_status = conference.registration_open ? :open : :closed
conference.save!
end
end
end

5
db/migrate/20160521230653_add_highest_step_to_conference_registrations.rb

@ -0,0 +1,5 @@
class AddHighestStepToConferenceRegistrations < ActiveRecord::Migration
def change
add_column :conference_registrations, :highest_step, :string
end
end

5
db/migrate/20160522022034_add_steps_completed_to_conference_registrations.rb

@ -0,0 +1,5 @@
class AddStepsCompletedToConferenceRegistrations < ActiveRecord::Migration
def change
add_column :conference_registrations, :steps_completed, :json
end
end

5
db/migrate/20160526044849_add_needs_facilitators_to_workshops.rb

@ -0,0 +1,5 @@
class AddNeedsFacilitatorsToWorkshops < ActiveRecord::Migration
def change
add_column :workshops, :needs_facilitators, :boolean
end
end

5
db/migrate/20160529225253_add_languages_to_users.rb

@ -0,0 +1,5 @@
class AddLanguagesToUsers < ActiveRecord::Migration
def change
add_column :users, :languages, :json
end
end

5
db/migrate/20160530175805_change_date_format_in_dynamic_translation_records.rb

@ -0,0 +1,5 @@
class ChangeDateFormatInDynamicTranslationRecords < ActiveRecord::Migration
def change
change_column :dynamic_translation_records, :created_at, :timestamp
end
end

23
db/schema.rb

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160304021005) do ActiveRecord::Schema.define(version: 20160530175805) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -81,6 +81,8 @@ ActiveRecord::Schema.define(version: 20160304021005) do
t.string "allergies" t.string "allergies"
t.string "languages" t.string "languages"
t.string "food" t.string "food"
t.string "highest_step"
t.json "steps_completed"
end end
create_table "conference_types", force: :cascade do |t| create_table "conference_types", force: :cascade do |t|
@ -121,6 +123,7 @@ ActiveRecord::Schema.define(version: 20160304021005) do
t.string "paypal_password" t.string "paypal_password"
t.string "paypal_signature" t.string "paypal_signature"
t.string "day_parts" t.string "day_parts"
t.string "registration_status"
end end
create_table "delayed_jobs", force: :cascade do |t| create_table "delayed_jobs", force: :cascade do |t|
@ -140,13 +143,13 @@ ActiveRecord::Schema.define(version: 20160304021005) do
add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree
create_table "dynamic_translation_records", force: :cascade do |t| create_table "dynamic_translation_records", force: :cascade do |t|
t.string "locale" t.string "locale"
t.integer "translator_id" t.integer "translator_id"
t.string "model_type" t.string "model_type"
t.integer "model_id" t.integer "model_id"
t.string "column" t.string "column"
t.text "value" t.text "value"
t.date "created_at" t.datetime "created_at"
end end
create_table "email_confirmations", force: :cascade do |t| create_table "email_confirmations", force: :cascade do |t|
@ -188,7 +191,7 @@ ActiveRecord::Schema.define(version: 20160304021005) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "event_location_id" t.integer "event_location_id"
t.string "type" t.string "event_type"
end end
create_table "locations", force: :cascade do |t| create_table "locations", force: :cascade do |t|
@ -317,6 +320,7 @@ ActiveRecord::Schema.define(version: 20160304021005) do
t.string "firstname" t.string "firstname"
t.string "lastname" t.string "lastname"
t.boolean "is_translator" t.boolean "is_translator"
t.json "languages"
end end
add_index "users", ["activation_token"], name: "index_users_on_activation_token", using: :btree add_index "users", ["activation_token"], name: "index_users_on_activation_token", using: :btree
@ -403,6 +407,7 @@ ActiveRecord::Schema.define(version: 20160304021005) do
t.text "notes" t.text "notes"
t.string "locale" t.string "locale"
t.integer "event_location_id" t.integer "event_location_id"
t.boolean "needs_facilitators"
end end
end end

78
vendor/assets/javascripts/markdown.js

@ -0,0 +1,78 @@
/*! Licensed under MIT, https://github.com/sofish/pen */
(function(root) {
// only works with Pen
if(!root.Pen) return;
// markdown covertor obj
var covertor = {
keymap: { '96': '`', '62': '>', '49': '1', '46': '.', '45': '-', '42': '*', '35': '#'},
stack : []
};
// return valid markdown syntax
covertor.valid = function(str) {
var len = str.length;
if(str.match(/[#]{1,6}/)) {
return ['h' + len, len];
} else if(str === '```') {
return ['pre', len];
} else if(str === '>') {
return ['blockquote', len];
} else if(str === '1.') {
return ['insertorderedlist', len];
} else if(str === '-' || str === '*') {
return ['insertunorderedlist', len];
} else if(str.match(/(?:\.|\*|\-){3,}/)) {
return ['inserthorizontalrule', len];
}
};
// parse command
covertor.parse = function(e) {
var code = e.keyCode || e.which;
// when `space` is pressed
if (code === 32) {
var markdownSyntax = this.stack.join('');
// reset stack
this.stack = [];
var cmd = this.valid(markdownSyntax);
if (cmd) {
// prevents leading space after executing command
e.preventDefault();
return cmd;
}
}
// make cmd
if(this.keymap[code]) this.stack.push(this.keymap[code]);
return false;
};
// exec command
covertor.action = function(pen, cmd) {
// only apply effect at line start
if(pen.selection.focusOffset > cmd[1]) return;
var node = pen.selection.focusNode;
node.textContent = node.textContent.slice(cmd[1]);
pen.execCommand(cmd[0]);
};
// init covertor
covertor.init = function(pen) {
pen.on('keypress', function(e) {
var cmd = covertor.parse(e);
if(cmd) return covertor.action(pen, cmd);
});
};
// append to Pen
root.Pen.prototype.markdown = covertor;
}(window));

835
vendor/assets/javascripts/pen.js

@ -0,0 +1,835 @@
/*! Licensed under MIT, https://github.com/sofish/pen */
(function(root, doc) {
var Pen, debugMode, selection, utils = {};
var toString = Object.prototype.toString;
var slice = Array.prototype.slice;
// allow command list
var commandsReg = {
block: /^(?:p|h[1-6]|blockquote|pre)$/,
inline: /^(?:bold|italic|underline|insertorderedlist|insertunorderedlist|indent|outdent|strikethrough)$/,
source: /^(?:createlink|unlink)$/,
insert: /^(?:inserthorizontalrule|insertimage|insert)$/,
wrap: /^(?:code)$/
};
var lineBreakReg = /^(?:blockquote|pre|div)$/i;
var effectNodeReg = /(?:[pubia]|h[1-6]|blockquote|[uo]l|li)/i;
var strReg = {
whiteSpace: /(^\s+)|(\s+$)/g,
mailTo: /^(?!mailto:|.+\/|.+#|.+\?)(.*@.*\..+)$/,
http: /^(?!\w+?:\/\/|mailto:|\/|\.\/|\?|#)(.*)$/
};
var autoLinkReg = {
url: /((https?|ftp):\/\/|www\.)[^\s<]{3,}/gi,
prefix: /^(?:https?|ftp):\/\//i,
notLink: /^(?:img|a|input|audio|video|source|code|pre|script|head|title|style)$/i,
maxLength: 100
};
// type detect
utils.is = function(obj, type) {
return toString.call(obj).slice(8, -1) === type;
};
utils.forEach = function(obj, iterator, arrayLike) {
if (!obj) return;
if (arrayLike == null) arrayLike = utils.is(obj, 'Array');
if (arrayLike) {
for (var i = 0, l = obj.length; i < l; i++) iterator(obj[i], i, obj);
} else {
for (var key in obj) {
if (obj.hasOwnProperty(key)) iterator(obj[key], key, obj);
}
}
};
// copy props from a obj
utils.copy = function(defaults, source) {
utils.forEach(source, function (value, key) {
defaults[key] = utils.is(value, 'Object') ? utils.copy({}, value) :
utils.is(value, 'Array') ? utils.copy([], value) : value;
});
return defaults;
};
// log
utils.log = function(message, force) {
if (debugMode || force)
console.log('%cPEN DEBUGGER: %c' + message, 'font-family:arial,sans-serif;color:#1abf89;line-height:2em;', 'font-family:cursor,monospace;color:#333;');
};
utils.delayExec = function (fn) {
var timer = null;
return function (delay) {
clearTimeout(timer);
timer = setTimeout(function() {
fn();
}, delay || 1);
};
};
// merge: make it easy to have a fallback
utils.merge = function(config) {
// default settings
var defaults = {
class: 'pen',
debug: false,
toolbar: null, // custom toolbar
stay: config.stay || !config.debug,
stayMsg: 'Are you going to leave here?',
textarea: '<textarea name="content"></textarea>',
list: [
'blockquote', 'h2', 'h3', 'p', 'code', 'insertorderedlist', 'insertunorderedlist', 'inserthorizontalrule',
'indent', 'outdent', 'bold', 'italic', 'underline', 'createlink', 'insertimage'
],
titles: {},
cleanAttrs: ['id', 'class', 'style', 'name'],
cleanTags: ['script']
};
// user-friendly config
if (config.nodeType === 1) {
defaults.editor = config;
} else if (config.match && config.match(/^#[\S]+$/)) {
defaults.editor = doc.getElementById(config.slice(1));
} else {
defaults = utils.copy(defaults, config);
}
return defaults;
};
function commandOverall(ctx, cmd, val) {
var message = ' to exec 「' + cmd + '」 command' + (val ? (' with value: ' + val) : '');
try {
doc.execCommand(cmd, false, val);
} catch(err) {
// TODO: there's an error when insert a image to document, bug not a bug
return utils.log('fail' + message, true);
}
utils.log('success' + message);
}
function commandInsert(ctx, name, val) {
var node = getNode(ctx);
if (!node) return;
ctx._range.selectNode(node);
ctx._range.collapse(false);
// hide menu when a image was inserted
if(name === 'insertimage' && ctx._menu) toggleNode(ctx._menu, true);
return commandOverall(ctx, name, val);
}
function commandBlock(ctx, name) {
var list = effectNode(ctx, getNode(ctx), true);
if (list.indexOf(name) !== -1) name = 'p';
return commandOverall(ctx, 'formatblock', name);
}
function commandWrap(ctx, tag, value) {
value = '<' + tag + '>' + (value||selection.toString()) + '</' + tag + '>';
return commandOverall(ctx, 'insertHTML', value);
}
function initToolbar(ctx) {
var icons = '', inputStr = '<input class="pen-input" placeholder="http://" />';
ctx._toolbar = ctx.config.toolbar;
if (!ctx._toolbar) {
var toolList = ctx.config.list;
utils.forEach(toolList, function (name) {
var klass = 'pen-icon icon-' + name;
var title = ctx.config.titles[name] || '';
icons += '<i class="' + klass + '" data-action="' + name + '" title="' + title + '"></i>';
}, true);
if (toolList.indexOf('createlink') >= 0 || toolList.indexOf('insertimage') >= 0)
icons += inputStr;
} else if (ctx._toolbar.querySelectorAll('[data-action=createlink]').length ||
ctx._toolbar.querySelectorAll('[data-action=insertimage]').length) {
icons += inputStr;
}
if (icons) {
ctx._menu = doc.createElement('div');
ctx._menu.setAttribute('class', ctx.config.class + '-menu pen-menu');
ctx._menu.innerHTML = icons;
ctx._inputBar = ctx._menu.querySelector('input');
toggleNode(ctx._menu, true);
doc.body.appendChild(ctx._menu);
}
if (ctx._toolbar && ctx._inputBar) toggleNode(ctx._inputBar);
}
function initEvents(ctx) {
var toolbar = ctx._toolbar || ctx._menu, editor = ctx.config.editor;
var toggleMenu = utils.delayExec(function() {
ctx.highlight().menu();
});
var outsideClick = function() {};
function updateStatus(delay) {
ctx._range = ctx.getRange();
toggleMenu(delay);
}
if (ctx._menu) {
var setpos = function() {
if (ctx._menu.style.display === 'block') ctx.menu();
};
// change menu offset when window resize / scroll
addListener(ctx, root, 'resize', setpos);
addListener(ctx, root, 'scroll', setpos);
// toggle toolbar on mouse select
var selecting = false;
addListener(ctx, editor, 'mousedown', function() {
selecting = true;
});
addListener(ctx, editor, 'mouseleave', function() {
if (selecting) updateStatus(800);
selecting = false;
});
addListener(ctx, editor, 'mouseup', function() {
if (selecting) updateStatus(100);
selecting = false;
});
// Hide menu when focusing outside of editor
outsideClick = function(e) {
if (ctx._menu && !containsNode(editor, e.target) && !containsNode(ctx._menu, e.target)) {
removeListener(ctx, doc, 'click', outsideClick);
toggleMenu(100);
}
};
} else {
addListener(ctx, editor, 'click', function() {
updateStatus(0);
});
}
addListener(ctx, editor, 'keyup', function(e) {
if (e.which === 8 && ctx.isEmpty()) return lineBreak(ctx, true);
// toggle toolbar on key select
if (e.which !== 13 || e.shiftKey) return updateStatus(400);
var node = getNode(ctx, true);
if (!node || !node.nextSibling || !lineBreakReg.test(node.nodeName)) return;
if (node.nodeName !== node.nextSibling.nodeName) return;
// hack for webkit, make 'enter' behavior like as firefox.
if (node.lastChild.nodeName !== 'BR') node.appendChild(doc.createElement('br'));
utils.forEach(node.nextSibling.childNodes, function(child) {
if (child) node.appendChild(child);
}, true);
node.parentNode.removeChild(node.nextSibling);
focusNode(ctx, node.lastChild, ctx.getRange());
});
// check line break
addListener(ctx, editor, 'keydown', function(e) {
editor.classList.remove('pen-placeholder');
if (e.which !== 13 || e.shiftKey) return;
var node = getNode(ctx, true);
if (!node || !lineBreakReg.test(node.nodeName)) return;
var lastChild = node.lastChild;
if (!lastChild || !lastChild.previousSibling) return;
if (lastChild.previousSibling.textContent || lastChild.textContent) return;
// quit block mode for 2 'enter'
e.preventDefault();
var p = doc.createElement('p');
p.innerHTML = '<br>';
node.removeChild(lastChild);
if (!node.nextSibling) node.parentNode.appendChild(p);
else node.parentNode.insertBefore(p, node.nextSibling);
focusNode(ctx, p, ctx.getRange());
});
var menuApply = function(action, value) {
ctx.execCommand(action, value);
ctx._range = ctx.getRange();
ctx.highlight().menu();
};
// toggle toolbar on key select
addListener(ctx, toolbar, 'click', function(e) {
var node = e.target, action;
while (node !== toolbar && !(action = node.getAttribute('data-action'))) {
node = node.parentNode;
}
if (!action) return;
if (!/(?:createlink)|(?:insertimage)/.test(action)) return menuApply(action);
if (!ctx._inputBar) return;
// create link
var input = ctx._inputBar;
if (toolbar === ctx._menu) toggleNode(input);
else {
ctx._inputActive = true;
ctx.menu();
}
if (ctx._menu.style.display === 'none') return;
setTimeout(function() { input.focus(); }, 400);
var createlink = function() {
var inputValue = input.value;
if (!inputValue) action = 'unlink';
else {
inputValue = input.value
.replace(strReg.whiteSpace, '')
.replace(strReg.mailTo, 'mailto:$1')
.replace(strReg.http, 'http://$1');
}
menuApply(action, inputValue);
if (toolbar === ctx._menu) toggleNode(input, false);
else toggleNode(ctx._menu, true);
};
input.onkeypress = function(e) {
if (e.which === 13) return createlink();
};
});
// listen for placeholder
addListener(ctx, editor, 'focus', function() {
if (ctx.isEmpty()) lineBreak(ctx, true);
addListener(ctx, doc, 'click', outsideClick);
});
addListener(ctx, editor, 'blur', function() {
checkPlaceholder(ctx);
ctx.checkContentChange();
});
// listen for paste and clear style
addListener(ctx, editor, 'paste', function() {
setTimeout(function() {
ctx.cleanContent();
});
});
}
function addListener(ctx, target, type, listener) {
if (ctx._events.hasOwnProperty(type)) {
ctx._events[type].push(listener);
} else {
ctx._eventTargets = ctx._eventTargets || [];
ctx._eventsCache = ctx._eventsCache || [];
var index = ctx._eventTargets.indexOf(target);
if (index < 0) index = ctx._eventTargets.push(target) - 1;
ctx._eventsCache[index] = ctx._eventsCache[index] || {};
ctx._eventsCache[index][type] = ctx._eventsCache[index][type] || [];
ctx._eventsCache[index][type].push(listener);
target.addEventListener(type, listener, false);
}
return ctx;
}
// trigger local events
function triggerListener(ctx, type) {
if (!ctx._events.hasOwnProperty(type)) return;
var args = slice.call(arguments, 2);
utils.forEach(ctx._events[type], function (listener) {
listener.apply(ctx, args);
});
}
function removeListener(ctx, target, type, listener) {
var events = ctx._events[type];
if (!events) {
var _index = ctx._eventTargets.indexOf(target);
if (_index >= 0) events = ctx._eventsCache[_index][type];
}
if (!events) return ctx;
var index = events.indexOf(listener);
if (index >= 0) events.splice(index, 1);
target.removeEventListener(type, listener, false);
return ctx;
}
function removeAllListeners(ctx) {
utils.forEach(this._events, function (events) {
events.length = 0;
}, false);
if (!ctx._eventsCache) return ctx;
utils.forEach(ctx._eventsCache, function (events, index) {
var target = ctx._eventTargets[index];
utils.forEach(events, function (listeners, type) {
utils.forEach(listeners, function (listener) {
target.removeEventListener(type, listener, false);
}, true);
}, false);
}, true);
ctx._eventTargets = [];
ctx._eventsCache = [];
return ctx;
}
function checkPlaceholder(ctx) {
ctx.config.editor.classList[ctx.isEmpty() ? 'add' : 'remove']('pen-placeholder');
}
function trim(str) {
return (str || '').replace(/^\s+|\s+$/g, '');
}
// node.contains is not implemented in IE10/IE11
function containsNode(parent, child) {
if (parent === child) return true;
child = child.parentNode;
while (child) {
if (child === parent) return true;
child = child.parentNode;
}
return false;
}
function getNode(ctx, byRoot) {
var node, root = ctx.config.editor;
ctx._range = ctx._range || ctx.getRange();
node = ctx._range.commonAncestorContainer;
if (!node || node === root) return null;
while (node && (node.nodeType !== 1) && (node.parentNode !== root)) node = node.parentNode;
while (node && byRoot && (node.parentNode !== root)) node = node.parentNode;
return containsNode(root, node) ? node : null;
}
// node effects
function effectNode(ctx, el, returnAsNodeName) {
var nodes = [];
el = el || ctx.config.editor;
while (el && el !== ctx.config.editor) {
if (el.nodeName.match(effectNodeReg)) {
nodes.push(returnAsNodeName ? el.nodeName.toLowerCase() : el);
}
el = el.parentNode;
}
return nodes;
}
// breakout from node
function lineBreak(ctx, empty) {
var range = ctx._range = ctx.getRange(), node = doc.createElement('p');
if (empty) ctx.config.editor.innerHTML = '';
node.innerHTML = '<br>';
range.insertNode(node);
focusNode(ctx, node.childNodes[0], range);
}
function focusNode(ctx, node, range) {
range.setStartAfter(node);
range.setEndBefore(node);
range.collapse(false);
ctx.setRange(range);
}
function autoLink(node) {
if (node.nodeType === 1) {
if (autoLinkReg.notLink.test(node.tagName)) return;
utils.forEach(node.childNodes, function (child) {
autoLink(child);
}, true);
} else if (node.nodeType === 3) {
var result = urlToLink(node.nodeValue || '');
if (!result.links) return;
var frag = doc.createDocumentFragment(),
div = doc.createElement('div');
div.innerHTML = result.text;
while (div.childNodes.length) frag.appendChild(div.childNodes[0]);
node.parentNode.replaceChild(frag, node);
}
}
function urlToLink(str) {
var count = 0;
str = str.replace(autoLinkReg.url, function(url) {
var realUrl = url, displayUrl = url;
count++;
if (url.length > autoLinkReg.maxLength) displayUrl = url.slice(0, autoLinkReg.maxLength) + '...';
// Add http prefix if necessary
if (!autoLinkReg.prefix.test(realUrl)) realUrl = 'http://' + realUrl;
return '<a href="' + realUrl + '">' + displayUrl + '</a>';
});
return {links: count, text: str};
}
function toggleNode(node, hide) {
node.style.display = hide ? 'none' : 'block';
}
Pen = function(config) {
if (!config) throw new Error('Can\'t find config');
debugMode = config.debug;
// merge user config
var defaults = utils.merge(config);
var editor = defaults.editor;
if (!editor || editor.nodeType !== 1) throw new Error('Can\'t find editor');
// set default class
editor.classList.add(defaults.class);
// set contenteditable
editor.setAttribute('contenteditable', 'true');
// assign config
this.config = defaults;
// set placeholder
if (defaults.placeholder) editor.setAttribute('data-placeholder', defaults.placeholder);
checkPlaceholder(this);
// save the selection obj
this.selection = selection;
// define local events
this._events = {change: []};
// enable toolbar
initToolbar(this);
// init events
initEvents(this);
// to check content change
this._prevContent = this.getContent();
// enable markdown covert
if (this.markdown) this.markdown.init(this);
// stay on the page
if (this.config.stay) this.stay(this.config);
};
Pen.prototype.on = function(type, listener) {
addListener(this, this.config.editor, type, listener);
return this;
};
Pen.prototype.isEmpty = function(node) {
node = node || this.config.editor;
return !(node.querySelector('img')) && !(node.querySelector('blockquote')) &&
!(node.querySelector('li')) && !trim(node.textContent);
};
Pen.prototype.getContent = function() {
return this.isEmpty() ? '' : trim(this.config.editor.innerHTML);
};
Pen.prototype.setContent = function(html) {
this.config.editor.innerHTML = html;
this.cleanContent();
return this;
};
Pen.prototype.checkContentChange = function () {
var prevContent = this._prevContent, currentContent = this.getContent();
if (prevContent === currentContent) return;
this._prevContent = currentContent;
triggerListener(this, 'change', currentContent, prevContent);
};
Pen.prototype.getRange = function() {
var editor = this.config.editor, range = selection.rangeCount && selection.getRangeAt(0);
if (!range) range = doc.createRange();
if (!containsNode(editor, range.commonAncestorContainer)) {
range.selectNodeContents(editor);
range.collapse(false);
}
return range;
};
Pen.prototype.setRange = function(range) {
range = range || this._range;
if (!range) {
range = this.getRange();
range.collapse(false); // set to end
}
try {
selection.removeAllRanges();
selection.addRange(range);
} catch (e) {/* IE throws error sometimes*/}
return this;
};
Pen.prototype.focus = function(focusStart) {
if (!focusStart) this.setRange();
this.config.editor.focus();
return this;
};
Pen.prototype.execCommand = function(name, value) {
name = name.toLowerCase();
this.setRange();
if (commandsReg.block.test(name)) {
commandBlock(this, name);
} else if (commandsReg.inline.test(name) || commandsReg.source.test(name)) {
commandOverall(this, name, value);
} else if (commandsReg.insert.test(name)) {
commandInsert(this, name, value);
} else if (commandsReg.wrap.test(name)) {
commandWrap(this, name, value);
} else {
utils.log('can not find command function for name: ' + name + (value ? (', value: ' + value) : ''), true);
}
if (name === 'indent') this.checkContentChange();
else this.cleanContent({cleanAttrs: ['style']});
};
// remove attrs and tags
// pen.cleanContent({cleanAttrs: ['style'], cleanTags: ['id']})
Pen.prototype.cleanContent = function(options) {
var editor = this.config.editor;
if (!options) options = this.config;
utils.forEach(options.cleanAttrs, function (attr) {
utils.forEach(editor.querySelectorAll('[' + attr + ']'), function(item) {
item.removeAttribute(attr);
}, true);
}, true);
utils.forEach(options.cleanTags, function (tag) {
utils.forEach(editor.querySelectorAll(tag), function(item) {
item.parentNode.removeChild(item);
}, true);
}, true);
checkPlaceholder(this);
this.checkContentChange();
return this;
};
// auto link content, return content
Pen.prototype.autoLink = function() {
autoLink(this.config.editor);
return this.getContent();
};
// highlight menu
Pen.prototype.highlight = function() {
var toolbar = this._toolbar || this._menu
, node = getNode(this);
// remove all highlights
utils.forEach(toolbar.querySelectorAll('.active'), function(el) {
el.classList.remove('active');
}, true);
if (!node) return this;
var effects = effectNode(this, node)
, inputBar = this._inputBar
, highlight;
if (inputBar && toolbar === this._menu) {
// display link input if createlink enabled
inputBar.style.display = 'none';
// reset link input value
inputBar.value = '';
}
highlight = function(str) {
if (!str) return;
var el = toolbar.querySelector('[data-action=' + str + ']');
return el && el.classList.add('active');
};
utils.forEach(effects, function(item) {
var tag = item.nodeName.toLowerCase();
switch(tag) {
case 'a':
if (inputBar) inputBar.value = item.getAttribute('href');
tag = 'createlink';
break;
case 'img':
if (inputBar) inputBar.value = item.getAttribute('src');
tag = 'insertimage';
break;
case 'i':
tag = 'italic';
break;
case 'u':
tag = 'underline';
break;
case 'strike':
tag = 'strikethrough';
break;
case 'b':
tag = 'bold';
break;
case 'pre':
case 'code':
tag = 'code';
break;
case 'ul':
tag = 'insertunorderedlist';
break;
case 'ol':
tag = 'insertorderedlist';
break;
case 'li':
tag = 'indent';
break;
}
highlight(tag);
}, true);
return this;
};
// show menu
Pen.prototype.menu = function() {
if (!this._menu) return this;
if (selection.isCollapsed) {
this._menu.style.display = 'none'; //hide menu
this._inputActive = false;
return this;
}
if (this._toolbar) {
if (!this._inputBar || !this._inputActive) return this;
}
var offset = this._range.getBoundingClientRect()
, menuPadding = 10
, top = offset.top - menuPadding
, left = offset.left + (offset.width / 2)
, menu = this._menu
, menuOffset = {x: 0, y: 0}
, stylesheet = this._stylesheet;
// fixes some browser double click visual discontinuity
// if the offset has no width or height it should not be used
if (offset.width === 0 && offset.height === 0) return this;
// store the stylesheet used for positioning the menu horizontally
if (this._stylesheet === undefined) {
var style = document.createElement("style");
document.head.appendChild(style);
this._stylesheet = stylesheet = style.sheet;
}
// display block to caculate its width & height
menu.style.display = 'block';
menuOffset.x = left - (menu.clientWidth / 2);
menuOffset.y = top - menu.clientHeight;
// check to see if menu has over-extended its bounding box. if it has,
// 1) apply a new class if overflowed on top;
// 2) apply a new rule if overflowed on the left
if (stylesheet.cssRules.length > 0) {
stylesheet.deleteRule(0);
}
if (menuOffset.x < 0) {
menuOffset.x = 0;
stylesheet.insertRule('.pen-menu:after {left: ' + left + 'px;}', 0);
} else {
stylesheet.insertRule('.pen-menu:after {left: 50%; }', 0);
}
if (menuOffset.y < 0) {
menu.classList.add('pen-menu-below');
menuOffset.y = offset.top + offset.height + menuPadding;
} else {
menu.classList.remove('pen-menu-below');
}
menu.style.top = menuOffset.y + 'px';
menu.style.left = menuOffset.x + 'px';
return this;
};
Pen.prototype.stay = function(config) {
var ctx = this;
if (!window.onbeforeunload) {
window.onbeforeunload = function() {
if (!ctx._isDestroyed) return config.stayMsg;
};
}
};
Pen.prototype.destroy = function(isAJoke) {
var destroy = isAJoke ? false : true
, attr = isAJoke ? 'setAttribute' : 'removeAttribute';
if (!isAJoke) {
removeAllListeners(this);
try {
selection.removeAllRanges();
if (this._menu) this._menu.parentNode.removeChild(this._menu);
} catch (e) {/* IE throws error sometimes*/}
} else {
initToolbar(this);
initEvents(this);
}
this._isDestroyed = destroy;
this.config.editor[attr]('contenteditable', '');
return this;
};
Pen.prototype.rebuild = function() {
return this.destroy('it\'s a joke');
};
// a fallback for old browers
root.Pen = function(config) {
if (!config) return utils.log('can\'t find config', true);
var defaults = utils.merge(config)
, klass = defaults.editor.getAttribute('class');
klass = klass ? klass.replace(/\bpen\b/g, '') + ' pen-textarea ' + defaults.class : 'pen pen-textarea';
defaults.editor.setAttribute('class', klass);
defaults.editor.innerHTML = defaults.textarea;
return defaults.editor;
};
// export content as markdown
var regs = {
a: [/<a\b[^>]*href=["']([^"]+|[^']+)\b[^>]*>(.*?)<\/a>/ig, '[$2]($1)'],
img: [/<img\b[^>]*src=["']([^\"+|[^']+)[^>]*>/ig, '![]($1)'],
b: [/<b\b[^>]*>(.*?)<\/b>/ig, '**$1**'],
i: [/<i\b[^>]*>(.*?)<\/i>/ig, '***$1***'],
h: [/<h([1-6])\b[^>]*>(.*?)<\/h\1>/ig, function(a, b, c) {
return '\n' + ('######'.slice(0, b)) + ' ' + c + '\n';
}],
li: [/<(li)\b[^>]*>(.*?)<\/\1>/ig, '* $2\n'],
blockquote: [/<(blockquote)\b[^>]*>(.*?)<\/\1>/ig, '\n> $2\n'],
pre: [/<pre\b[^>]*>(.*?)<\/pre>/ig, '\n```\n$1\n```\n'],
p: [/<p\b[^>]*>(.*?)<\/p>/ig, '\n$1\n'],
hr: [/<hr\b[^>]*>/ig, '\n---\n']
};
Pen.prototype.toMd = function() {
var html = this.getContent()
.replace(/\n+/g, '') // remove line break
.replace(/<([uo])l\b[^>]*>(.*?)<\/\1l>/ig, '$2'); // remove ul/ol
for(var p in regs) {
if (regs.hasOwnProperty(p))
html = html.replace.apply(html, regs[p]);
}
return html.replace(/\*{5}/g, '**');
};
// make it accessible
if (doc.getSelection) {
selection = doc.getSelection();
root.Pen = Pen;
}
}(window, document));

1675
vendor/assets/javascripts/world-110m.json

File diff suppressed because it is too large

1675
vendor/assets/javascripts/world-50m.json

File diff suppressed because it is too large

159
vendor/assets/stylesheets/pen.css

@ -0,0 +1,159 @@
/*! Licensed under MIT, https://github.com/sofish/pen */
/* basic reset */
.pen:focus{outline:none;}
/*.pen, .pen-menu, .pen-input, .pen textarea{font:400 1.16em/1.45 Palatino, Optima, Georgia, serif;color:#331;}
.pen fieldset, img {border: 0;}
.pen blockquote{padding-left:10px;margin-left:-14px;border-left:4px solid #1abf89;}
.pen a{color:#1abf89;}
.pen del{text-decoration:line-through;}
.pen sub, .pen sup {font-size:75%;position:relative;vertical-align:text-top;}
:root .pen sub, :root .pen sup{vertical-align:baseline; /* for ie9 and other mordern browsers *//*}
.pen sup {top:-0.5em;}
.pen sub {bottom:-0.25em;}
.pen hr{border:none;border-bottom:1px solid #cfcfcf;margin-bottom:25px;*color:pink;*filter:chroma(color=pink);height:10px;*margin:-7px 0 15px;}
.pen small{font-size:0.8em;color:#888;}
.pen em, .pen b, .pen strong{font-weight:700;}
.pen pre{white-space:pre-wrap;padding:0.85em;background:#f8f8f8;}
*/
/* block-level element margin */
/*.pen p, .pen pre, .pen ul, .pen ol, .pen dl, .pen form, .pen table, .pen blockquote{margin-bottom:16px;}
*/
/* headers */
/*.pen h1, .pen h2, .pen h3, .pen h4, .pen h5, .pen h6{margin-bottom:16px;font-weight:700;line-height:1.2;}
.pen h1{font-size:2em;}
.pen h2{font-size:1.8em;}
.pen h3{font-size:1.6em;}
.pen h4{font-size:1.4em;}
.pen h5, .pen h6{font-size:1.2em;}*/
/* list */
/*.pen ul, .pen ol{margin-left:1.2em;}
.pen ul, .pen-ul{list-style:disc;}
.pen ol, .pen-ol{list-style:decimal;}
.pen li ul, .pen li ol, .pen-ul ul, .pen-ul ol, .pen-ol ul, .pen-ol ol{margin:0 2em 0 1.2em;}
.pen li ul, .pen-ul ul, .pen-ol ul{list-style: circle;}*/
/* pen menu */
.pen-menu [class^="icon-"], .pen-menu [class*=" icon-"] { /* reset to avoid conflicts with Bootstrap */
background: transparent;
background-image: none;
}
.pen-menu { min-width: 320px; }
.pen-menu, .pen-input{font-size:14px;line-height:1;}
.pen-menu{white-space:nowrap;box-shadow:1px 2px 3px -2px #222;background:#333;background-image:linear-gradient(to bottom, #222, #333);opacity:0.9;position:fixed;height:36px;border:1px solid #333;border-radius:3px;display:none;z-index:1000;}
.pen-menu:after {top:100%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none;}
.pen-menu:after {border-color:rgba(51, 51, 51, 0);border-top-color:#333;border-width:6px;left:50%;margin-left:-6px;}
.pen-menu-below:after {top: -11px; display:block; -moz-transform: rotate(180deg); -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); -o-transform: rotate(180deg); transform: rotate(180deg);}
.pen-icon{
font-size: 16px;
line-height: 40px;;min-width:20px;display:inline-block;padding:0 10px;height:36px;overflow:hidden;color:#fff;text-align:center;cursor:pointer;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;}
.pen-icon:first-of-type{border-top-left-radius:3px;border-bottom-left-radius:3px;}
.pen-icon:last-of-type{border-top-right-radius:3px;border-bottom-right-radius:3px;}
.pen-icon:hover{background:#000;}
.pen-icon.active{color:#1abf89;background:#000;box-shadow:inset 2px 2px 4px #000;}
.pen-input{position:absolute;width:100%;left:0;top:0;height:36px;line-height:20px;background:#333;color:#fff;border:none;text-align:center;display:none;font-family:arial, sans-serif;}
.pen-input:focus{outline:none;}
.pen-textarea{display:block;background:#f8f8f8;padding:20px;}
.pen textarea{font-size:14px;border:none;background:none;width:100%;_height:200px;min-height:200px;resize:none;}
/*@font-face {
font-family: 'pen';
src: url('font/fontello.eot?370dad08');
src: url('font/fontello.eot?370dad08#iefix') format('embedded-opentype'),
url('font/fontello.woff?370dad08') format('woff'),
url('font/fontello.ttf?370dad08') format('truetype'),
url('font/fontello.svg?370dad08#fontello') format('svg');
font-weight: normal;
font-style: normal;
}*/
.pen-menu [class^="icon-"]:before, .pen-menu [class*=" icon-"]:before {
speak: none;
display: inline-block;
width: 1em;
margin-right: .2em;
text-align: center;
font-variant: normal;
text-transform: none;
line-height: 1em;
margin-left: .2em;
}
.pen-menu .icon-location:before { content: '\e815'; } /* '' */
.pen-menu .icon-fit:before { content: '\e80f'; } /* '' */
.pen-menu .icon-justifyleft:before { content: '\e80a'; } /* '' */
.pen-menu .icon-justifycenter:before { content: '\e80b'; } /* '' */
.pen-menu .icon-justifyright:before { content: '\e80c'; } /* '' */
.pen-menu .icon-justifyfull:before { content: '\e80d'; } /* '' */
.pen-menu .icon-outdent:before { content: '\e800'; } /* '' */
.pen-menu .icon-indent:before { content: '\e801'; } /* '' */
.pen-menu .icon-mode:before { content: '\e813'; } /* '' */
.pen-menu .icon-fullscreen:before { content: '\e80e'; } /* '' */
.pen-menu .icon-undo:before { content: '\e817'; } /* '' */
.pen-menu .icon-code:before { content: '\e816'; } /* '' */
.pen-menu .icon-pre:before { content: '\e816'; } /* '' */
.pen-menu .icon-unlink:before { content: '\e811'; } /* '' */
.pen-menu .icon-superscript:before { content: '\e808'; } /* '' */
.pen-menu .icon-subscript:before { content: '\e809'; } /* '' */
.pen-menu .icon-inserthorizontalrule:before { content: '\e818'; } /* '' */
.pen-menu .icon-pin:before { content: '\e812'; } /* '' */
.pen {
position: relative;
}
/*.pen.hinted h1:before,
.pen.hinted h2:before,
.pen.hinted h3:before,
.pen.hinted h4:before,
.pen.hinted h5:before,
.pen.hinted h6:before,
.pen.hinted blockquote:before,
.pen.hinted hr:before {
color: #eee;
position: absolute;
right: 100%;
white-space: nowrap;
padding-right: 10px;
}
.pen.hinted blockquote { border-left: 0; margin-left: 0; padding-left: 0; }
.pen.hinted blockquote:before {
color: #1abf89;
content: ">";
font-weight: bold;
vertical-align: center;
}
.pen.hinted h1:before { content: "#";}
.pen.hinted h2:before { content: "##";}
.pen.hinted h3:before { content: "###";}
.pen.hinted h4:before { content: "####";}
.pen.hinted h5:before { content: "#####";}
.pen.hinted h6:before { content: "######";}
.pen.hinted hr:before { content: "﹘﹘﹘"; line-height: 1.2; vertical-align: bottom; }
.pen.hinted pre:before, .pen.hinted pre:after {
content: "```";
display: block;
color: #ccc;
}
.pen.hinted ul { list-style: none; }
.pen.hinted ul li:before {
content: "*";
color: #999;
line-height: 1;
vertical-align: bottom;
margin-left: -1.2em;
display: inline-block;
width: 1.2em;
}
.pen.hinted b:before, .pen.hinted b:after { content: "**"; color: #eee; font-weight: normal; }
.pen.hinted i:before, .pen.hinted i:after { content: "*"; color: #eee; }
.pen.hinted a { text-decoration: none; }
.pen.hinted a:before {content: "["; color: #ddd; }
.pen.hinted a:after { content: "](" attr(href) ")"; color: #ddd; }
.pen-placeholder:after { position: absolute; top: 0; left: 0; content: attr(data-placeholder); color: #999; cursor: text; }
*/
Loading…
Cancel
Save