Browse Source

Added conference list page, administration tools, and re-designed conference administration

development
Godwin 8 years ago
parent
commit
1dc608bf58
  1. 17
      Rakefile
  2. 84
      app/assets/images/Facebook_icon_2013.svg
  3. BIN
      app/assets/images/admin.jpg
  4. 1
      app/assets/images/admin/administrators.svg
  5. BIN
      app/assets/images/admin/broadcast.png
  6. 1
      app/assets/images/admin/broadcast.svg
  7. BIN
      app/assets/images/admin/dates.png
  8. 1
      app/assets/images/admin/dates.svg
  9. BIN
      app/assets/images/admin/description.png
  10. 1
      app/assets/images/admin/description.svg
  11. BIN
      app/assets/images/admin/events.png
  12. 1
      app/assets/images/admin/events.svg
  13. BIN
      app/assets/images/admin/housing.png
  14. 1
      app/assets/images/admin/housing.svg
  15. BIN
      app/assets/images/admin/locations.png
  16. 1
      app/assets/images/admin/locations.svg
  17. BIN
      app/assets/images/admin/meals.png
  18. 1
      app/assets/images/admin/meals.svg
  19. BIN
      app/assets/images/admin/payment.png
  20. 1
      app/assets/images/admin/payment.svg
  21. 1
      app/assets/images/admin/payment_message.svg
  22. 1
      app/assets/images/admin/paypal.svg
  23. BIN
      app/assets/images/admin/poster.png
  24. 1
      app/assets/images/admin/poster.svg
  25. BIN
      app/assets/images/admin/providers.png
  26. 1
      app/assets/images/admin/providers.svg
  27. 1
      app/assets/images/admin/publish_schedule.svg
  28. 1
      app/assets/images/admin/registration_status.svg
  29. BIN
      app/assets/images/admin/registrations.png
  30. 1
      app/assets/images/admin/registrations.svg
  31. BIN
      app/assets/images/admin/schedule.png
  32. 1
      app/assets/images/admin/schedule.svg
  33. BIN
      app/assets/images/admin/stats.png
  34. 1
      app/assets/images/admin/stats.svg
  35. 1
      app/assets/images/admin/suggested_amounts.svg
  36. BIN
      app/assets/images/admin/workshop_times.png
  37. 1
      app/assets/images/admin/workshop_times.svg
  38. BIN
      app/assets/images/conferences.jpg
  39. 0
      app/assets/images/facebook.png
  40. 1
      app/assets/images/facebook.svg
  41. 0
      app/assets/images/github.png
  42. 1
      app/assets/images/github.svg
  43. 2
      app/assets/images/logo-text.svg
  44. 2
      app/assets/images/logo.svg
  45. 3
      app/assets/images/octicat.svg
  46. 231
      app/assets/javascripts/editor.js
  47. 31
      app/assets/javascripts/filefield.js
  48. 4
      app/assets/javascripts/jquery.min.js
  49. 11
      app/assets/javascripts/main.js
  50. 117
      app/assets/javascripts/map.js
  51. 26
      app/assets/javascripts/userfield.js
  52. 8428
      app/assets/stylesheets/_application.scss
  53. 287
      app/assets/stylesheets/_settings.scss
  54. 4
      app/assets/stylesheets/bumbleberry-settings.json
  55. 36
      app/controllers/admin_controller.rb
  56. 1375
      app/controllers/application_controller.rb
  57. 1208
      app/controllers/conference_administration_controller.rb
  58. 2229
      app/controllers/conferences_controller.rb
  59. 355
      app/controllers/workshops_controller.rb
  60. 4178
      app/helpers/application_helper.rb
  61. 352
      app/mailers/user_mailer.rb
  62. 77
      app/models/city.rb
  63. 9
      app/models/city_cache.rb
  64. 40
      app/models/comment.rb
  65. 201
      app/models/conference.rb
  66. 4
      app/models/conference_administrator.rb
  67. 112
      app/models/conference_registration.rb
  68. 26
      app/models/event.rb
  69. 6
      app/models/locations_organization.rb
  70. 120
      app/models/organization.rb
  71. 91
      app/models/user.rb
  72. 12
      app/models/user_organization_relationship.rb
  73. 192
      app/uploaders/avatar_uploader.rb
  74. 102
      app/uploaders/poster_uploader.rb
  75. 24
      app/views/admin/new.html.haml
  76. 38
      app/views/application/_header.html.haml
  77. 22
      app/views/application/home.html.haml
  78. 44
      app/views/application/user_settings.html.haml
  79. 41
      app/views/conference_administration/_administrators.html.haml
  80. 23
      app/views/conference_administration/_broadcast.html.haml
  81. 0
      app/views/conference_administration/_broadcast_sent.html.haml
  82. 17
      app/views/conference_administration/_dates.html.haml
  83. 5
      app/views/conference_administration/_description.html.haml
  84. 41
      app/views/conference_administration/_events.html.haml
  85. 11
      app/views/conference_administration/_hosts_table.html.haml
  86. 10
      app/views/conference_administration/_housing.html.haml
  87. 42
      app/views/conference_administration/_locations.html.haml
  88. 35
      app/views/conference_administration/_meals.html.haml
  89. 6
      app/views/conference_administration/_payment_message.html.haml
  90. 8
      app/views/conference_administration/_paypal.html.haml
  91. 5
      app/views/conference_administration/_poster.html.haml
  92. 2
      app/views/conference_administration/_providers.html.haml
  93. 8
      app/views/conference_administration/_publish_schedule.html.haml
  94. 5
      app/views/conference_administration/_registration_status.html.haml
  95. 17
      app/views/conference_administration/_registrations.html.haml
  96. 85
      app/views/conference_administration/_schedule.html.haml
  97. 46
      app/views/conference_administration/_select_guest_table.html.haml
  98. 0
      app/views/conference_administration/_select_workshop_table.html.haml
  99. 21
      app/views/conference_administration/_stats.html.haml
  100. 8
      app/views/conference_administration/_suggested_amounts.html.haml

17
Rakefile

@ -4,3 +4,20 @@
require File.expand_path('../config/application', __FILE__) require File.expand_path('../config/application', __FILE__)
BikeBike::Application.load_tasks BikeBike::Application.load_tasks
task regenerate_images: :environment do
{
conference: [ :cover, :poster ],
organization: [ :logo, :avatar, :cover ]
}.each do | model_class, attributes |
Object.const_get(model_class.to_s.titlecase).all.each do | model |
attributes.each do | attribute |
uploader = model.send(attribute)
if uploader.present?
puts "Regenerating #{model_class}.#{attribute} = #{uploader.url}"
uploader.recreate_versions!
end
end
end
end
end

84
app/assets/images/Facebook_icon_2013.svg

@ -1,84 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1000"
height="1000"
id="svg2"
version="1.1"
inkscape:version="0.48.2 r9819"
sodipodi:docname="New document 1">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49497475"
inkscape:cx="1268.2181"
inkscape:cy="-8.1858472"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-left="0.5"
fit-margin-bottom="0.5"
fit-margin-top="0.5"
fit-margin-right="0.5"
inkscape:window-width="1920"
inkscape:window-height="1028"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid2996"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-39.499999,287.13782)">
<g
id="g3005"
transform="matrix(1.7404181,0,0,1.7404181,-29.616725,-527.44586)">
<path
sodipodi:nodetypes="ssssscccssss"
inkscape:connector-curvature="0"
id="rect3000"
d="m 71,138.36218 512,0 c 17.174,0 31,13.826 31,31 l 0,512 c 0,17.174 -13.826,31 -31,31 l -146.5,0 -45.81978,-37.88194 -44.18022,37.88194 -275.5,0 c -17.174,0 -31,-13.826 -31,-31 l 0,-512 c 0,-17.174 13.826,-31 31,-31 z"
style="fill:#3b579d;fill-opacity:1;stroke:none" />
<path
sodipodi:nodetypes="ccccccccccccccccc"
inkscape:connector-curvature="0"
id="path3002"
d="m 346.5,712.36218 0,-222.5 -75,0 0,-86.5 75,0 0,-66 c 0,-69.18633 42.89263,-112.18866 110.45353,-112.18866 40.67053,0 68.04647,3.18866 68.04647,3.18866 l 0,78 -45.82143,0 c -29.03699,0 -42.67857,10.21289 -42.67857,41 l 0,56 85.5,0 -11,86.5 -74.5,0 0,222.5 z"
style="fill:#ffffff;stroke:none" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

BIN
app/assets/images/admin.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

1
app/assets/images/admin/administrators.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 100 125"><path d="M32.5 30.1a4.63 4.63 0 1 1-4.62-4.6 4.62 4.62 0 0 1 4.62 4.6zm58.17 42.73V91H71.3V81h-9.9V70.64h-9.88v-6a29.28 29.28 0 1 1 14.7-16.25zm-4 1.66L61.6 49.23l.4-1.15a25.4 25.4 0 1 0-9.6 11.46l3.1-2v9.08h9.88V77h9.88v10h11.4z"/></svg>

After

Width:  |  Height:  |  Size: 320 B

BIN
app/assets/images/admin/broadcast.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

1
app/assets/images/admin/broadcast.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 40"><path d="M0 25.5h7.7v5.7l7-5.7H32V.8H0v24.7zM2 2.8h28v20.7H14l-.5.4-3.8 3v-3.7H2V2.8z"/><path d="M8.8 4.7H17v2H8.8zm0 3.7h11.8v2H8.8zm0 3.6h14.5v2H8.8zm0 3.8h11.8v2H8.8zm0 3.6h8.6v2H8.8z"/></svg>

After

Width:  |  Height:  |  Size: 255 B

BIN
app/assets/images/admin/dates.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

1
app/assets/images/admin/dates.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1" viewBox="0 0 62 72.5"><path d="M0 0v58h62V0H0zm12 56H2V46h10v10zm12 0H14V46h10v10zm12 0H26V46h10v10zm12 0H38V46h10v10zm12 0H50V46h10v10zm0-36v2H50v10h10v12H50V34h-2v10H38V34h-2v10H26V34h-2v10H14V34h-2v10H2V32h10V22H2V10h10v10h2V10h10v10h2V10h10v10h2V10h10v10h2V10h10v10z"/><path d="M14 22h10v10H14zm12 0h10v10H26zm12 0h10v10H38z"/></svg>

After

Width:  |  Height:  |  Size: 389 B

BIN
app/assets/images/admin/description.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

1
app/assets/images/admin/description.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M76 24.6l-8-8H24v67h52v-59zM68.5 20l4 4h-4v-4zM26 81.4v-63h40.5V26H74v55.5H26z"/><path d="M30 24h20v2H30zm0 5h40v2H30zm0 5h25v2H30zm27.5 0H70v2H57.5zM30 39h40v2H30zm0 5h15v2H30zm17.5 0H70v2H47.5zM30 49h40v2H30zm0 5h17.5v2H30zm20 0h20v2H50zm-20 5h40v2H30zm0 5h7.5v2H30zm10 0h30v2H40zm-10 5h40v2H30zm0 5h40v2H30z"/></svg>

After

Width:  |  Height:  |  Size: 390 B

BIN
app/assets/images/admin/events.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

1
app/assets/images/admin/events.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 640"><path d="M510 209.6c0-68.5-47.8-124.2-106.6-124.2-16 0-31.8 4.3-46.3 12.5-19-42-57-70-101-70-43 0-81 28-101 70-14-9-30-13-46-13C50 85 2 141 2 209c0 59 36.8 123.2 88.4 136L65 380.4h35.6l-7.6 15c-8.2 16.4-3 36.4 12.3 46.5 5 3 8.2 8 8.8 14 1 6-1 12-5 16l11 12c8-8 12-19 11-30s-7-21-16-27c-8-5.6-11-16.5-7-25.5l11-22.4h27l-24-34c30-5 57.6-28 75.5-62.7 13 10.5 27.6 18.2 43.5 21.3L216 338h35l-7.6 15c-8.2 16.5-3 36.5 12.3 46.6 5 3.2 8.2 8.5 8.8 14.5.6 6-1.5 12-5.7 16l-8.5 9 11.7 11 8-8.5c7.8-8 11.6-18.5 10.6-29.3-1.2-11-7-20.5-16-26.5-8.4-5.5-11.3-16.4-7-25.4l11.4-22h27.5l-25-34c16-3 30.6-11 43.5-21.5 17.3 33.7 43.8 56 72.6 62.2l-25 34.5h35.7l-7.6 15c-8.3 16.5-3 36.5 12.2 46.6 5 3 8 8.5 8 14.5s-2 12-6 16l12 12c7-7.8 11-18.5 10-29-1-11-7-21-16-27-8.6-5.3-11-16.2-7-25.2l11-22.8h28l-24-33c53-11 91-76.6 91-136zM105.5 352l8.5 12H97l8.5-12zm3-21c-51.5 0-90-64-90-121.4 0-59.5 40.4-108 90-108 14 0 27.8 4 40.3 11.6-5 15-7.5 30.7-7.5 46.8 0 41 16.4 83.2 43 112-17.3 36.5-46 59-75.7 59zM256 311l8.5 12h-17l8.5-12zm0-21c-56.3 0-98.3-68.6-98.3-130 0-63.8 44-115.7 98.3-115.7s98.3 52 98.3 115.6c0 61-42 130-98.3 130zm139 74l8.5-12 8.5 12h-17zm8.4-33c-29.7 0-58.4-22.4-75.8-59 26.7-28.8 43-71 43-112 0-16-2.4-31.8-7.4-46.8 12.5-7.5 26.3-11.4 40.2-11.4 49.7 0 90.2 48.4 90.2 108 0 57-38.6 121.3-90.2 121.3z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
app/assets/images/admin/housing.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

1
app/assets/images/admin/housing.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 125"><path d="M67.5 20c0-5.5-4.5-10-10-10h-15c-5.5 0-10 4.5-10 10v2.5H5V90h90V22.5H67.5V20zm-30 0c0-2.8 2.2-5 5-5h15c2.8 0 5 2.2 5 5v2.5h-25V20zm36.3 7.5V85H26.3V27.5h47.5zm-63.8 0h13.8V85H10V27.5zM90 85H76.3V27.5H90V85z"/></svg>

After

Width:  |  Height:  |  Size: 286 B

BIN
app/assets/images/admin/locations.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

1
app/assets/images/admin/locations.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 56.25"><path d="M11 37.5l.8.4V17l-.7-.5M22 13v21l.6-.3h.4V13l-.3-.3M34.2 16l-1 .6V38l1-.7"/><path fill="none" d="M25 13l-2.5-1-10.7 4.3-7.4-4v22.2l7.5 4 10-4.2 2.5 1 8 3.3 7.4-4V12.2l-7.4 4-2.4-1"/><path d="M33 14l-1.8-.6-.7 2 2.6 1 8-4v22l-8 4.3-8-3.3-2.3-1h-.2L12 38.6l-7.6-4V12.2l7.4 4L22.5 12l2.6 1-1-2.6-1-.6L12 14 2.3 9v26.7l9.4 5 10.7-4.2L33 40.8l9.6-5v-27"/><path d="M28.2 3.4c-2.3 0-4 2-4 4.2v1.2l4 10 4-10.3c.2-.3.2-.6.2-1 0-2.2-2-4-4.2-4zm0 6.2c-1 0-1.7-.8-1.7-1.8S27.3 6 28.2 6s1.8.8 1.8 1.8-.8 1.8-1.8 1.8z"/></svg>

After

Width:  |  Height:  |  Size: 584 B

BIN
app/assets/images/admin/meals.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

1
app/assets/images/admin/meals.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" class="stroked"><circle cx="386.5" cy="16.5" r="3.6"/><path stroke-width="2" d="M392 16.5c0 2.4-3.5 7.2-4.9 9.1-.3.4-.9.4-1.2 0-1.4-2-4.9-6.7-4.9-9.1 0-3 2.5-5.5 5.5-5.5s5.5 2.4 5.5 5.5z"/><circle cx="404.1" cy="45.6" r="3.6"/><path stroke-width="2" d="M409.6 45.6c0 2.4-3.5 7.2-4.9 9.1-.3.4-.9.4-1.2 0-1.4-2-4.9-6.7-4.9-9.1 0-3 2.5-5.5 5.5-5.5 3.1.1 5.5 2.5 5.5 5.5z"/><path stroke-width="2" d="M381.2.9h27.9c4.5 0 8.1 3.7 8.1 8.1v54.6c0 4.5-3.7 8.1-8.1 8.1h-27.9c-4.5 0-8.1-3.7-8.1-8.1V9.1c0-4.5 3.6-8.2 8.1-8.2z"/><path d="M382 6.1h26.4c2.4 0 4.4 2 4.4 4.4v47.7c0 2.4-2 4.4-4.4 4.4H382c-2.4 0-4.4-2-4.4-4.4V10.5c-.1-2.4 1.9-4.4 4.4-4.4z"/><circle cx="395.2" cy="67.2" r="1.9"/><line x1="388.8" y1="3.6" x2="395.8" y2="3.6"/><line x1="398.7" y1="3.6" x2="402.2" y2="3.6"/><path stroke-width="2" d="M386.5 29.3h14.3c2 0 3.7 1.7 3.7 3.7s-1.7 3.7-3.7 3.7H386c-2.1 0-3.7 1.7-3.7 3.7v14.4c0 2.1 1.7 3.7 3.7 3.7h18.5"/><polygon stroke-width="2" points="23.7,80.7 78.4,80.7 24.7,27"/><polyline stroke-width="2" points="24.7,27 27.3,24.5 80.9,78.1 78.4,80.7"/><path stroke-width="0.9766" d="M30.5 22.5h.1c2.8 0 5.1 2.3 5.1 5.1v.2c0 2.8 2.3 5.1 5.1 5.1h.4c2.8 0 5.1 2.3 5.1 5.1v.1c0 2.8 2.3 5.1 5.1 5.1h.2c2.8 0 5.1 2.3 5.1 5.1v.3c0 2.8 2.3 5.1 5.1 5.1h.3c2.8 0 5.1 2.3 5.1 5.1v.3c0 2.8 2.3 5.1 5.1 5.1h.4c2.8 0 5.1 2.3 5.1 5.1v.3c0 2.8 2.3 5.1 5.1 5.1h.2"/><path d="M68.3 57.6v.2c0 2.8 2.3 5.1 5.1 5.1h.4M57.7 47.1v.2c0 2.8 2.3 5.1 5.1 5.1h.2M47.4 36.8c0 2.8 2.3 5.1 5.1 5.1h.1M42.1 31.6h-.2c-1.7 0-3.2-.8-4.1-2.1m-.9-3.2v.2c0 .5.1 1 .2 1.4"/><rect x="22.9" y="42.6" transform="rotate(-135 60.783 44.59)" stroke-width="2" width="75.9" height="3.9"/><line stroke-width="2" x1="32.5" y1="19.3" x2="32.5" y2="20.3"/><line stroke-width="2" x1="32.6" y1="25.3" x2="32.6" y2="29.4"/><line stroke-width="2" x1="86.2" y1="72.8" x2="83" y2="72.8"/><line stroke-width="2" x1="76.6" y1="72.8" x2="75.8" y2="72.8"/></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
app/assets/images/admin/payment.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

1
app/assets/images/admin/payment.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 640"><path d="M92.1 227.3c-13.6 0-24.6 11-24.6 24.6 0 13.6 11 24.6 24.6 24.6s24.6-11 24.6-24.6c0-13.5-11-24.6-24.6-24.6zm0 32.8c-4.5 0-8.2-3.7-8.2-8.2 0-4.5 3.7-8.2 8.2-8.2 4.5 0 8.2 3.7 8.2 8.2 0 4.5-3.7 8.2-8.2 8.2z"/><path d="M359.9 133.8c1.7-6.9 2.6-14 2.6-21.2 0-49.7-40.4-90.1-90.1-90.1-49.7 0-90.1 40.4-90.1 90.1 0 5.9.6 11.7 1.7 17.3-7.3 1.7-14.6 3.5-21.5 5.6-16.7-24.4-44.6-39.3-74.3-39.3-26.1 0-52.1 13.5-52.1 13.5l20.5 7.7c20.7 7.8 36.8 24.3 43.9 45-29.8 17.8-52.7 40.1-66.6 65H2V342h31.8c17.5 31.3 49.5 58.6 91.1 77.7v69.8h98.3v-43.3c25 2.8 49.1 3.1 73.7.9v42.4h98.3v-66.1c72-30.1 114.8-81.6 114.8-138.7 0-66.8-58.7-125.3-150.1-150.9zm-87.5-94.9c40.7 0 73.7 33.1 73.7 73.7 0 18.3-6.6 35.7-18.8 49.2h-46.7v-9.7c9.5-3.4 16.4-12.4 16.4-23.1 0-13.6-11-24.6-24.6-24.6-4.5 0-8.2-3.7-8.2-8.2 0-4.5 3.7-8.2 8.2-8.2 4.5 0 8.2 3.7 8.2 8.2H297c0-10.7-6.9-19.7-16.4-23.1v-9.7h-16.4v9.7c-9.5 3.4-16.4 12.4-16.4 23.1 0 13.6 11 24.6 24.6 24.6 4.5 0 8.2 3.7 8.2 8.2 0 4.5-3.7 8.2-8.2 8.2-4.5 0-8.2-3.7-8.2-8.2h-16.4c0 10.7 6.9 19.7 16.4 23.1v9.7h-46.8c-12.1-13.5-18.8-30.8-18.8-49.2 0-40.7 33.1-73.7 73.8-73.7zm111.7 371.4l-5.2 2.1v60.8h-65.5v-44.3l-9.1 1c-29.3 3.3-58.1 3.1-88.1-1.1l-9.3-1.3v45.6h-65.5V409l-4.9-2.1c-42.5-18.5-74.6-45.7-90.3-76.7l-2.3-4.5H18.4v-81.9h25.4s19-45.2 70.3-70.3l5.5-3.1-1.5-6.1c-5.3-21-18-39.2-35.4-51.4 27.8-2 55 12.3 69.1 36.5l3.3 5.7s24.1-7.2 33.5-9.4c2.2 5.6 5 11 8.3 16.1h-14.6v16.4h180.3v-16.4H348c2.6-4 4.8-8.1 6.7-12.4 83.4 23.2 139 76.9 139 135.4-.1 51.3-41 98.3-109.6 125.5z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

1
app/assets/images/admin/payment_message.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 60"><path d="M8.5 46.7V35.3h-8v-34h47v34H19.9L8.5 46.7zm-6-13.4h8v8.6l8.6-8.6h26.4v-30h-43v30z"/><path d="M23.5 28.6v-2c-1-.1-1.8-.4-2.5-.7-.6-.3-1.2-.9-1.7-1.6-.5-.7-.7-1.6-.8-2.7l2-.4c.2 1.1.4 1.9.8 2.4.6.7 1.3 1.1 2.1 1.2v-6.5c-.8-.2-1.7-.5-2.6-1-.6-.4-1.1-.9-1.5-1.5-.4-.6-.5-1.4-.5-2.2 0-1.4.5-2.6 1.5-3.5.7-.6 1.7-1 3.1-1.1V8h1.2v1c1.2.1 2.1.5 2.8 1 .9.7 1.4 1.8 1.6 3l-2 .4c-.1-.8-.4-1.4-.8-1.8-.4-.4-.9-.7-1.6-.8v5.9l2 .6c.6.3 1.2.6 1.6 1 .4.4.7.9.9 1.4.2.6.3 1.1.3 1.8 0 1.4-.4 2.6-1.3 3.5-.9.9-2.1 1.4-3.5 1.5v2.1h-1.1zm0-17.9c-.8.1-1.4.4-1.9 1-.5.5-.7 1.1-.7 1.8s.2 1.3.6 1.7c.4.5 1 .8 2 1.1v-5.6zm1.2 14.1c.8-.1 1.4-.4 2-1 .5-.6.8-1.3.8-2.2 0-.7-.2-1.3-.5-1.8-.4-.4-1.1-.9-2.2-1.2v6.2z"/></svg>

After

Width:  |  Height:  |  Size: 762 B

1
app/assets/images/admin/paypal.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 640"><path d="M92.1 227.3c-13.6 0-24.6 11-24.6 24.6 0 13.6 11 24.6 24.6 24.6s24.6-11 24.6-24.6c0-13.5-11-24.6-24.6-24.6zm0 32.8c-4.5 0-8.2-3.7-8.2-8.2 0-4.5 3.7-8.2 8.2-8.2 4.5 0 8.2 3.7 8.2 8.2 0 4.5-3.7 8.2-8.2 8.2z"/><path d="M359.9 133.8c1.7-6.9 2.6-14 2.6-21.2 0-49.7-40.4-90.1-90.1-90.1-49.7 0-90.1 40.4-90.1 90.1 0 5.9.6 11.7 1.7 17.3-7.3 1.7-14.6 3.5-21.5 5.6-16.7-24.4-44.6-39.3-74.3-39.3-26.1 0-52.1 13.5-52.1 13.5l20.5 7.7c20.7 7.8 36.8 24.3 43.9 45-29.8 17.8-52.7 40.1-66.6 65H2V342h31.8c17.5 31.3 49.5 58.6 91.1 77.7v69.8h98.3v-43.3c25 2.8 49.1 3.1 73.7.9v42.4h98.3v-66.1c72-30.1 114.8-81.6 114.8-138.7 0-66.8-58.7-125.3-150.1-150.9zm-87.5-94.9c40.7 0 73.7 33.1 73.7 73.7 0 18.3-6.6 35.7-18.8 49.2h-46.7v-9.7c9.5-3.4 16.4-12.4 16.4-23.1 0-13.6-11-24.6-24.6-24.6-4.5 0-8.2-3.7-8.2-8.2 0-4.5 3.7-8.2 8.2-8.2 4.5 0 8.2 3.7 8.2 8.2H297c0-10.7-6.9-19.7-16.4-23.1v-9.7h-16.4v9.7c-9.5 3.4-16.4 12.4-16.4 23.1 0 13.6 11 24.6 24.6 24.6 4.5 0 8.2 3.7 8.2 8.2 0 4.5-3.7 8.2-8.2 8.2-4.5 0-8.2-3.7-8.2-8.2h-16.4c0 10.7 6.9 19.7 16.4 23.1v9.7h-46.8c-12.1-13.5-18.8-30.8-18.8-49.2 0-40.7 33.1-73.7 73.8-73.7zm111.7 371.4l-5.2 2.1v60.8h-65.5v-44.3l-9.1 1c-29.3 3.3-58.1 3.1-88.1-1.1l-9.3-1.3v45.6h-65.5V409l-4.9-2.1c-42.5-18.5-74.6-45.7-90.3-76.7l-2.3-4.5H18.4v-81.9h25.4s19-45.2 70.3-70.3l5.5-3.1-1.5-6.1c-5.3-21-18-39.2-35.4-51.4 27.8-2 55 12.3 69.1 36.5l3.3 5.7s24.1-7.2 33.5-9.4c2.2 5.6 5 11 8.3 16.1h-14.6v16.4h180.3v-16.4H348c2.6-4 4.8-8.1 6.7-12.4 83.4 23.2 139 76.9 139 135.4-.1 51.3-41 98.3-109.6 125.5z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
app/assets/images/admin/poster.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

1
app/assets/images/admin/poster.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 640"><path d="M233.8 277.6l-25.8-43-64 106.7h224L282.7 192l-49 85.6zm-52 42.4l26.2-44 13.6 22.8-12 21.2h-28zm52.3 0l48.8-85 48.5 85h-97z"/><path d="M416 85.3h-21.3V64h-21.4V0H352v64h-21.3v21.3H181.3V64H160V0h-21.3v64h-21.4v21.3H74.7V512h362.6V85.3H416zm-64 0h21.3v21.4H352V85.3zm-213.3 0H160v21.4h-21.3V85.3zM416 490.7H96v-384h21.3V128h64v-21.3h149.4V128h64v-21.3H416v384z"/><path d="M298.7 426.7h-85.4c-6 0-10.6 4.7-10.6 10.6s4.7 10.7 10.6 10.7h85.4c6 0 10.6-4.8 10.6-10.7s-4.7-10.6-10.6-10.6zm42.6-42.7H170.7c-6 0-10.7 4.8-10.7 10.7 0 6 4.8 10.6 10.7 10.6h170.6c6 0 10.7-4.7 10.7-10.6 0-6-4.8-10.7-10.7-10.7z"/></svg>

After

Width:  |  Height:  |  Size: 676 B

BIN
app/assets/images/admin/providers.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

1
app/assets/images/admin/providers.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 80"><path d="M2.2 46.5V64h23V46.5L14 38.3 2.2 46.5zm21 15.5h-19V47.5l9.6-6.8 9.5 6.8V62z"/><path d="M0 42.4v3.3l13.8-10 13.7 10v-3.3l-13.7-9.8m25 14V64h23V46.5L50 38.3l-11.5 8.2zm21 15.4h-19V47.5l9.4-6.8 9.6 6.8V62z"/><path d="M36.5 42.4v3.3l13.7-10 13.8 10v-3.3l-13.8-9.8M20.4 14v17.4h23.2V14L32 5.6 20.4 14zm21.2 15.4H22.4V15L32 8l9.6 7v14.4z"/><path d="M45.8 10L32 0 18.2 10v3L32 3.4 45.8 13"/></svg>

After

Width:  |  Height:  |  Size: 459 B

1
app/assets/images/admin/publish_schedule.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 125"><path d="M71.8 33.2c7.2 0 13-5.8 13-13s-5.8-13-13-13-13 5.8-13 13c0 1.5.3 2.9.7 4.2L36.9 41c-2.3-2.3-5.6-3.7-9.1-3.7-7.2 0-13 5.8-13 13s5.8 13 13 13c3.5 0 6.8-1.4 9.1-3.7l22.6 16.6c-.4 1.3-.7 2.7-.7 4.2 0 7.2 5.8 13 13 13s13-5.8 13-13-5.8-13-13-13c-3.5 0-6.8 1.4-9.1 3.7L40.1 54.4c.4-1.3.7-2.7.7-4.2s-.3-2.9-.7-4.2l22.6-16.6c2.3 2.4 5.5 3.8 9.1 3.8zm0-20c3.9 0 7 3.1 7 7s-3.1 7-7 7-7-3.1-7-7 3.1-7 7-7zm-44 44c-3.9 0-7-3.1-7-7s3.1-7 7-7 7 3.1 7 7-3.2 7-7 7zm44 16c3.9 0 7 3.1 7 7s-3.1 7-7 7-7-3.1-7-7 3.1-7 7-7z"/></svg>

After

Width:  |  Height:  |  Size: 582 B

1
app/assets/images/admin/registration_status.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 125"><path d="M38.7 72.4l34.9-22.6-34.9-22.6v45.2zm4-37.8l23.5 15.2L42.7 65V34.6z"/><path d="M50 0C22.4 0 0 22.4 0 50s22.4 50 50 50 50-22.4 50-50S77.6 0 50 0zm0 96C24.6 96 4 75.4 4 50S24.6 4 50 4s46 20.6 46 46-20.6 46-46 46z"/></svg>

After

Width:  |  Height:  |  Size: 290 B

BIN
app/assets/images/admin/registrations.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

1
app/assets/images/admin/registrations.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 26.25"><path d="M0 21h24V0H0v21zM19 1h4v3h-4V1zm0 4h4v3h-4V5zm0 4h4v3h-4V9zm0 4h4v3h-4v-3zm0 4h4v3h-4v-3zM14 1h4v3h-4V1zm0 4h4v3h-4V5zm0 4h4v3h-4V9zm0 4h4v3h-4v-3zm0 4h4v3h-4v-3zM9 1h4v3H9V1zm0 4h4v3H9V5zm0 4h4v3H9V9zm0 4h4v3H9v-3zm0 4h4v3H9v-3zM1 1h7v3H1V1zm0 4h7v3H1V5zm0 4h7v3H1V9zm0 4h7v3H1v-3zm0 4h7v3H1v-3z"/></svg>

After

Width:  |  Height:  |  Size: 377 B

BIN
app/assets/images/admin/schedule.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

1
app/assets/images/admin/schedule.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 67.5" class="stroked"><path stroke-width="2.5" d="M32.2 8v5.8H41v-6z"/><path d="M37.2 13.7v10m0 6.3v10m0 5.6v6.2h34v-50h-34v6m-1 6v9.8m0 6v10m0 6.4v5.8h-34v-50h34v6" stroke-width="3.5"/><path stroke-width="2.5" d="M32.2 24v5.8H41v-6zm0 16v5.8H41v-6zM8.5 13.8h18m-18 6.7h18m-18 6.3h18m-18 6.4h18m-18 6.6h18m20-26h18m-18 6.7h18m-18 6.3h18m-18 6.4h18m-18 6.6h18"/></svg>

After

Width:  |  Height:  |  Size: 423 B

BIN
app/assets/images/admin/stats.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

1
app/assets/images/admin/stats.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 112.5"><path d="M74.8 80V41.8H61.6V80H58V26.8H45V80h-3.2V42H28.6v38h-3V57.6H12.3V80h-3V10h-4v74.2H86V80H74.8zm-9-34h4.7v33.5H66V46zM49.4 31H54v48.7h-4.7V31zM33 46.3h4.5v33.5H33V46.3zM16.5 80V61.8H21V80h-4.5z"/></svg>

After

Width:  |  Height:  |  Size: 272 B

1
app/assets/images/admin/suggested_amounts.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 125"><switch><foreignObject requiredExtensions="http://ns.adobe.com/AdobeIllustrator/10.0/" x="0" y="0" width="1" height="1"/><g><g fill="none" stroke="#000" stroke-width="3" stroke-miterlimit="10"><line x1="19.3" y1="50" x2="19.3" y2="57.1"/><line x1="36.8" y1="50" x2="36.8" y2="57.1"/><line x1="19.3" y1="56" x2="19.3" y2="63.1"/><line x1="36.8" y1="56" x2="36.8" y2="63.1"/><line x1="19.3" y1="62" x2="19.3" y2="69.1"/><line x1="36.8" y1="62" x2="36.8" y2="69.1"/><ellipse cx="28" cy="50" rx="8.8" ry="4.9"/><path d="M36.8 56c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5m17.6 6c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5m17.6 6c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5m17.6 6c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5"/><line x1="19.3" y1="68" x2="19.3" y2="75.1"/><line x1="36.8" y1="68" x2="36.8" y2="75.1"/><line x1="41.3" y1="38" x2="41.3" y2="45.1"/><line x1="58.8" y1="38" x2="58.8" y2="45.1"/><line x1="41.3" y1="44" x2="41.3" y2="51.1"/><line x1="58.8" y1="44" x2="58.8" y2="51.1"/><line x1="41.3" y1="50" x2="41.3" y2="57.1"/><line x1="58.8" y1="50" x2="58.8" y2="57.1"/><line x1="41.3" y1="56" x2="41.3" y2="63.1"/><line x1="58.8" y1="56" x2="58.8" y2="63.1"/><line x1="41.3" y1="62" x2="41.3" y2="69.1"/><line x1="58.8" y1="62" x2="58.8" y2="69.1"/><ellipse cx="50" cy="38" rx="8.8" ry="4.9"/><path d="M58.8 44c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5m17.6 6c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5m17.6 6c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5m17.6 6c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5m17.6 6c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5m17.6 6c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5"/><line x1="41.3" y1="68" x2="41.3" y2="75.1"/><line x1="58.8" y1="68" x2="58.8" y2="75.1"/><line x1="63.3" y1="26" x2="63.3" y2="33.1"/><line x1="63.3" y1="32" x2="63.3" y2="39.1"/><line x1="63.3" y1="38" x2="63.3" y2="45.1"/><line x1="63.3" y1="44" x2="63.3" y2="51.1"/><line x1="63.3" y1="50" x2="63.3" y2="57.1"/><line x1="63.3" y1="56" x2="63.3" y2="63.1"/><line x1="63.3" y1="61.1" x2="63.3" y2="68.2"/><line x1="63.3" y1="67" x2="63.3" y2="74.1"/><ellipse cx="72" cy="26" rx="8.8" ry="4.9"/><path d="M80.8 32c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5"/><line x1="80.8" y1="26" x2="80.8" y2="33.1"/><path d="M80.8 38c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5"/><line x1="80.8" y1="32" x2="80.8" y2="39.1"/><path d="M80.8 44c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5"/><line x1="80.8" y1="38" x2="80.8" y2="45.1"/><path d="M80.8 50c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5"/><line x1="80.8" y1="44" x2="80.8" y2="51.1"/><path d="M80.8 56c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5"/><line x1="80.8" y1="50" x2="80.8" y2="57.1"/><path d="M80.8 62c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5"/><line x1="80.8" y1="56" x2="80.8" y2="63.1"/><path d="M80.8 68c0 2.8-4 5-8.8 5s-8.8-2.2-8.8-5"/><line x1="80.8" y1="61.1" x2="80.8" y2="68.2"/><path d="M80.8 74c0 2.7-4 5-8.8 5s-8.8-2.3-8.8-5"/><line x1="80.8" y1="67" x2="80.8" y2="74.1"/></g></g></switch></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
app/assets/images/admin/workshop_times.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

1
app/assets/images/admin/workshop_times.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 91 113.75"><path d="M45.2 18.6C30 18.6 17.6 31 17.6 46.2 17.6 61.5 30 74 45.2 74c15.2 0 27.6-12.6 27.6-28 0-15.2-12.4-27.6-27.6-27.6zm0 52C31.8 70.6 21 59.6 21 46c0-13 10.8-24 24.2-24 13.4 0 24.2 11 24.2 24.2 0 13.4-10.8 24.3-24.2 24.3z"/><path d="M47 26.3h-3.4V48h11v-3.5H47m-3.4 17.2H47V66h-3.4zM25.2 44.5h4.4V48h-4.4zm35.6 0h4.4V48h-4.4zm-29.2-13l3.2 3-2.3 2.4-3.2-3zM58.8 61l-3.2-3 2.3-2.6 3 3zM33 62l-2.5-2 3-3.3L36 59zm24.4-31.7l2.5 2.3-3 3.2-2.8-2.3z"/></svg>

After

Width:  |  Height:  |  Size: 519 B

BIN
app/assets/images/conferences.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 KiB

0
app/assets/images/bb-icon-fb.png → app/assets/images/facebook.png

Before

Width:  |  Height:  |  Size: 308 B

After

Width:  |  Height:  |  Size: 308 B

1
app/assets/images/facebook.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-723 0 1000 1000" class="icons"><path d="M222.5,0.5h-891.1c-29.9,0-54,24.1-54,54v891.1c0,29.9,24.1,54,54,54h479.5V612.3h-130.5V461.7h130.5V346.8 c0-120.4,74.7-195.3,192.2-195.3c70.8,0,118.4,5.5,118.4,5.5v135.8H41.9c-50.5,0-74.3,17.8-74.3,71.4v97.5h148.8L97.2,612.3H-32.4 v387.2h255c29.9,0,54-24.1,54-54V54.5C276.5,24.6,252.4,0.5,222.5,0.5z"/></svg>

After

Width:  |  Height:  |  Size: 397 B

0
app/assets/images/bb-icon-github.png → app/assets/images/github.png

Before

Width:  |  Height:  |  Size: 470 B

After

Width:  |  Height:  |  Size: 470 B

1
app/assets/images/github.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" class="icons"><path xmlns="http://www.w3.org/2000/svg" d="M512 0C229.25 0 0 229.2 0 512c0 226.2 146.7 418.1 350.2 485.8 25.6 4.7 34.938-11.125 34.938-24.625 0-12.188-0.469-52.562-0.719-95.312C242 908.8 211.9 817.5 211.9 817.5c-23.312-59.125-56.844-74.875-56.844-74.875-46.531-31.75 3.53-31.125 3.53-31.125 51.4 3.6 78.5 52.8 78.5 52.8 45.7 78.2 119.9 55.6 149 42.5 4.654-33 17.904-55.625 32.5-68.375C304.906 725.4 185.3 681.5 185.3 485.312c0-55.938 19.969-101.562 52.656-137.406-5.219-13-22.844-65.094 5.062-135.562 0 0 42.938-13.75 140.8 52.5 40.812-11.406 84.594-17.031 128.125-17.219 43.5 0.2 87.3 5.9 128.2 17.3 97.688-66.312 140.688-52.5 140.688-52.5 28 70.5 10.4 122.6 5.1 135.5 32.8 35.8 52.6 81.5 52.6 137.4 0 196.688-119.75 240-233.812 252.7 18.4 15.9 34.8 47 34.8 94.8 0 68.438-0.688 123.625-0.688 140.5 0 13.6 9.3 29.6 35.2 24.562C877.438 930 1024 738.1 1024 512 1024 229.2 794.8 0 512 0z"/></svg>

After

Width:  |  Height:  |  Size: 972 B

2
app/assets/images/logo-text.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

2
app/assets/images/logo.svg

@ -1,2 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 291 288.5" id="bb-icon-logo"><path d="m290.7 136.1c-8.8.9-16.9-5.1-18.4-14-1.5-8.8 4-17.3 12.6-19.4-1.6-5.4-3.5-10.7-5.7-15.8-8 3.9-17.6 1-22.1-6.8-4.5-7.8-2.1-17.6 5.2-22.5-3.3-4.5-7-8.8-10.8-12.9-6.2 6.3-16.2 6.9-23.1 1.2-6.9-5.8-8-15.8-2.8-23-4.7-3.1-9.5-5.9-14.5-8.4-3.6 8.1-12.9 12.1-21.3 9-8.4-3.1-12.9-12.1-10.5-20.6-5.4-1.3-10.9-2.2-16.5-2.9-.6 8.8-8 15.8-16.9 15.8-9 0-16.3-7-16.9-15.8-5.5.6-10.9 1.6-16.2 2.8 2.4 8.5-2.1 17.5-10.6 20.6-8.4 3-17.7-1-21.3-9.1-5 2.5-9.9 5.3-14.5 8.4 5.2 7.2 4 17.2-2.9 23-6.9 5.7-17 5.1-23.1-1.2-3.8 4.1-7.5 8.3-10.8 12.8 7.3 5 9.6 14.8 5.1 22.6-4.5 7.8-14.2 10.6-22.1 6.7-2.2 5.1-4.1 10.4-5.8 15.8 8.6 2.2 14.1 10.6 12.6 19.4-1.6 8.8-9.7 14.8-18.5 13.9-.2 2.8-.3 5.7-.3 8.6 0 2.8.1 5.5.2 8.2 8.8-.9 16.9 5.1 18.4 14 1.5 8.8-4 17.3-12.6 19.4 1.6 5.4 3.5 10.7 5.7 15.8 8-3.9 17.6-1 22.1 6.8 4.5 7.8 2.1 17.6-5.2 22.5 3.3 4.5 7 8.8 10.8 12.9 6.2-6.3 16.2-6.9 23.1-1.2 6.9 5.8 8 15.8 2.8 23 4.7 3.1 9.5 5.9 14.5 8.4 3.6-8.1 12.9-12.1 21.3-9 8.4 3.1 12.9 12.1 10.5 20.6 5.4 1.3 10.9 2.2 16.5 2.9.6-8.8 8-15.8 16.9-15.8 9 0 16.3 7 16.9 15.8 5.5-.6 10.9-1.6 16.1-2.8-2.4-8.5 2.1-17.5 10.6-20.6 8.4-3 17.7 1 21.3 9.1 5-2.5 9.9-5.3 14.6-8.4-5.2-7.2-4-17.2 2.9-23 6.9-5.7 17-5.1 23.1 1.2 3.8-4.1 7.5-8.3 10.8-12.8-7.3-5-9.6-14.8-5.1-22.6 4.5-7.8 14.2-10.6 22.2-6.7 2.2-5.1 4.1-10.4 5.8-15.8-8.6-2.2-14.1-10.6-12.6-19.4 1.6-8.8 9.7-14.8 18.5-13.9.2-2.8.3-5.7.3-8.6-.1-2.8-.1-5.5-.3-8.2m-117.5 55.4c-.6 1.2-1.2 2.4-1.8 3.5-.6 1.1-1.2 2.1-1.9 3.1-4.5 5.8-10.2 10-17 12.6-2.6 1-5.4 1.7-8.2 2.1-2.8.4-5.8.7-8.9.9-1.6.1-3.2.1-4.8-.1-1.6-.2-3.2-.4-4.7-.5-.7 0-1.3-.1-1.9-.3-.6-.2-1.2-.3-1.9-.3-.6 0-1.1-.2-1.7-.4-.6-.3-1.2-.4-1.9-.3-.6 0-1.1-.1-1.7-.3-.6-.2-1.2-.4-1.8-.6l-57-15.5c-2.2-.6-4-1.2-5.5-1.8-1.4-.6-2.3-1.8-2.5-3.5-.1-1.1.2-2.8.8-4.9.6-2.1 1.2-3.9 1.5-5.4l30.8-114.8c.4-1.6.8-3.2 1.2-5 .4-1.8 1.1-3.1 2-4 .8-.8 1.9-1.2 3.5-1.2.3-.1.6 0 .8.1.2.1.5.2.8.1l58.3 15.6c.9.2 1.8.5 2.8.6.9.2 1.8.4 2.5.8.6.4 1.2.7 1.9.7.7 0 1.3.2 2 .5l1.2.3c.7.5 1.5.8 2.4.9.9.2 1.7.5 2.6 1 1 .5 1.9 1 2.9 1.4.9.4 1.9.9 2.9 1.4 2.2 1.6 4.3 3.1 6.3 4.5 2 1.4 3.7 3.1 5.2 4.8 2.1 2.5 3.7 5.4 4.6 8.6 1 3.2 1.5 6.7 1.6 10.5 0 1-.1 1.9-.2 2.9-.2.9-.3 2-.3 3.1 0 .6-.1 1.1-.3 1.6-.2.5-.3 1-.3 1.6 0 .4-.1.9-.3 1.5-.2.6-.4 1-.6 1.2l-.5 1.8c-.1.4-.3.8-.5 1.3-.2.5-.3.9-.4 1.1-.5.8-.9 1.6-1.1 2.3-.3.7-.6 1.3-1 1.9-3 4.2-6.2 7.5-9.7 9.8-1.2.7-2.3 1.2-3.6 1.6-1.2.4-2.4 1-3.4 1.8-.3.2-.7.5-1.2.8-.5.4-.6.9-.5 1.6 0 1.1.6 2.2 1.8 3.3 1.2 1.1 2.3 2.1 3.2 3 2.9 3.3 5 6.8 6.4 10.5 1.3 3.6 2 7.5 2.1 11.9.1 1.2.1 2.3-.1 3.4-.2 1.1-.3 2.3-.3 3.4l-.6 2.2c-.1 1.5-.6 3.4-1.4 5.7-1.2 2.6-1.9 4.3-2.6 5.6m38.9 17.5c-.2 1.2-.5 2.5-.9 4l-3.8 14.1c-.4 1.4-.8 2.7-1.2 3.9-.4 1.1-1.1 1.9-1.9 2.4-1.1.4-2.3.5-3.6.2-1.3-.3-2.7-.6-4.1-1l-13.1-3.5c-1.4-.4-2.8-.8-4.1-1.2-1.3-.4-2.2-1.1-2.6-2.1-.4-1-.6-2.1-.4-3.3.2-1.3.5-2.6.9-4.1l3.3-12.3c.3-1.3.7-2.6 1-3.8.3-1.2.9-2.2 1.6-2.8.7-.6 1.9-1 3.5-1.2.3-.1.6 0 .9.1.3.2.6.2.9.1l15.4 4.1c1.6.4 3 .8 4.4 1.3 1.4.4 2.4 1.1 3.1 2 .7.9.9 1.9.7 3.1m28.4-108.9c-.3 1.2-.6 2.4-1 3.7l-5.8 21.7c-.2.8-.4 1.7-.5 2.7-.1 1-.4 1.8-.9 2.4l-.6 2.2c-.5.8-.9 1.8-1.1 2.8-.2 1-.5 1.8-1 2.6-.4.6-.7 1.2-.8 1.8-.1.6-.4 1.2-.8 1.8-.6 1.1-1 2.2-1.3 3.3-.3 1.1-.7 2.2-1.3 3.1-.3.5-.5 1-.6 1.4-.1.5-.3.9-.6 1.4-.7 1.6-1.4 3.3-2 5.1-.6 1.8-1.4 3.5-2.2 5.1-.5.8-.8 1.7-1 2.5-.1.8-.5 1.7-1 2.7-1.2 2.2-2.1 4.6-2.8 7.1-.7 2.5-1.8 4.9-3 7-.9 1.7-1.6 3.5-2.2 5.4-.6 1.9-1.6 3.1-3.2 3.7-.5.2-1 .2-1.5.2-.6 0-1.2 0-1.9-.1l-2.7-.7c-1.3-.3-2.4-.8-3.3-1.3-.9-.5-1.5-1.2-1.9-2-.4-1-.5-1.9-.3-2.8.2-.9.4-1.9.4-3 0-1.5.2-3.1.5-4.7.4-1.6.5-3.1.5-4.7.3-1 .4-1.8.4-2.2.1-.8.2-1.6.4-2.4.2-.8.4-1.6.4-2.4 0-2 .2-4 .7-6 .5-2 .8-4 .9-6 0-.7.1-1.3.3-1.9.2-.6.3-1.2.3-1.9.1-1.2.2-2.5.5-3.8.3-1.3.5-2.7.6-4 .1-.3.1-.5.1-.8 0-.3 0-.5.1-.8 0-1.1.2-2.2.4-3.1.3-1 .4-2 .4-3.1.1-3 .7-6.2 1.6-9.6 1-3.4 1.9-6.8 2.8-10.1l4-15.1c.4-1.4.8-2.8 1.1-4.1.3-1.3 1-2.3 1.9-3.1.9-.6 1.9-.9 3.2-.8.3-.1.6 0 .8.1.2.1.5.2.8.1l15.1 4c1.6.4 3 .9 4.3 1.4 1.3.5 2.2 1.3 2.6 2.4.5.6.5 1.6.2 2.8"/> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 291 288.5" id="bb-icon-logo" class="icons bb-icon-logo"><path d="m290.7 136.1c-8.8.9-16.9-5.1-18.4-14-1.5-8.8 4-17.3 12.6-19.4-1.6-5.4-3.5-10.7-5.7-15.8-8 3.9-17.6 1-22.1-6.8-4.5-7.8-2.1-17.6 5.2-22.5-3.3-4.5-7-8.8-10.8-12.9-6.2 6.3-16.2 6.9-23.1 1.2-6.9-5.8-8-15.8-2.8-23-4.7-3.1-9.5-5.9-14.5-8.4-3.6 8.1-12.9 12.1-21.3 9-8.4-3.1-12.9-12.1-10.5-20.6-5.4-1.3-10.9-2.2-16.5-2.9-.6 8.8-8 15.8-16.9 15.8-9 0-16.3-7-16.9-15.8-5.5.6-10.9 1.6-16.2 2.8 2.4 8.5-2.1 17.5-10.6 20.6-8.4 3-17.7-1-21.3-9.1-5 2.5-9.9 5.3-14.5 8.4 5.2 7.2 4 17.2-2.9 23-6.9 5.7-17 5.1-23.1-1.2-3.8 4.1-7.5 8.3-10.8 12.8 7.3 5 9.6 14.8 5.1 22.6-4.5 7.8-14.2 10.6-22.1 6.7-2.2 5.1-4.1 10.4-5.8 15.8 8.6 2.2 14.1 10.6 12.6 19.4-1.6 8.8-9.7 14.8-18.5 13.9-.2 2.8-.3 5.7-.3 8.6 0 2.8.1 5.5.2 8.2 8.8-.9 16.9 5.1 18.4 14 1.5 8.8-4 17.3-12.6 19.4 1.6 5.4 3.5 10.7 5.7 15.8 8-3.9 17.6-1 22.1 6.8 4.5 7.8 2.1 17.6-5.2 22.5 3.3 4.5 7 8.8 10.8 12.9 6.2-6.3 16.2-6.9 23.1-1.2 6.9 5.8 8 15.8 2.8 23 4.7 3.1 9.5 5.9 14.5 8.4 3.6-8.1 12.9-12.1 21.3-9 8.4 3.1 12.9 12.1 10.5 20.6 5.4 1.3 10.9 2.2 16.5 2.9.6-8.8 8-15.8 16.9-15.8 9 0 16.3 7 16.9 15.8 5.5-.6 10.9-1.6 16.1-2.8-2.4-8.5 2.1-17.5 10.6-20.6 8.4-3 17.7 1 21.3 9.1 5-2.5 9.9-5.3 14.6-8.4-5.2-7.2-4-17.2 2.9-23 6.9-5.7 17-5.1 23.1 1.2 3.8-4.1 7.5-8.3 10.8-12.8-7.3-5-9.6-14.8-5.1-22.6 4.5-7.8 14.2-10.6 22.2-6.7 2.2-5.1 4.1-10.4 5.8-15.8-8.6-2.2-14.1-10.6-12.6-19.4 1.6-8.8 9.7-14.8 18.5-13.9.2-2.8.3-5.7.3-8.6-.1-2.8-.1-5.5-.3-8.2m-117.5 55.4c-.6 1.2-1.2 2.4-1.8 3.5-.6 1.1-1.2 2.1-1.9 3.1-4.5 5.8-10.2 10-17 12.6-2.6 1-5.4 1.7-8.2 2.1-2.8.4-5.8.7-8.9.9-1.6.1-3.2.1-4.8-.1-1.6-.2-3.2-.4-4.7-.5-.7 0-1.3-.1-1.9-.3-.6-.2-1.2-.3-1.9-.3-.6 0-1.1-.2-1.7-.4-.6-.3-1.2-.4-1.9-.3-.6 0-1.1-.1-1.7-.3-.6-.2-1.2-.4-1.8-.6l-57-15.5c-2.2-.6-4-1.2-5.5-1.8-1.4-.6-2.3-1.8-2.5-3.5-.1-1.1.2-2.8.8-4.9.6-2.1 1.2-3.9 1.5-5.4l30.8-114.8c.4-1.6.8-3.2 1.2-5 .4-1.8 1.1-3.1 2-4 .8-.8 1.9-1.2 3.5-1.2.3-.1.6 0 .8.1.2.1.5.2.8.1l58.3 15.6c.9.2 1.8.5 2.8.6.9.2 1.8.4 2.5.8.6.4 1.2.7 1.9.7.7 0 1.3.2 2 .5l1.2.3c.7.5 1.5.8 2.4.9.9.2 1.7.5 2.6 1 1 .5 1.9 1 2.9 1.4.9.4 1.9.9 2.9 1.4 2.2 1.6 4.3 3.1 6.3 4.5 2 1.4 3.7 3.1 5.2 4.8 2.1 2.5 3.7 5.4 4.6 8.6 1 3.2 1.5 6.7 1.6 10.5 0 1-.1 1.9-.2 2.9-.2.9-.3 2-.3 3.1 0 .6-.1 1.1-.3 1.6-.2.5-.3 1-.3 1.6 0 .4-.1.9-.3 1.5-.2.6-.4 1-.6 1.2l-.5 1.8c-.1.4-.3.8-.5 1.3-.2.5-.3.9-.4 1.1-.5.8-.9 1.6-1.1 2.3-.3.7-.6 1.3-1 1.9-3 4.2-6.2 7.5-9.7 9.8-1.2.7-2.3 1.2-3.6 1.6-1.2.4-2.4 1-3.4 1.8-.3.2-.7.5-1.2.8-.5.4-.6.9-.5 1.6 0 1.1.6 2.2 1.8 3.3 1.2 1.1 2.3 2.1 3.2 3 2.9 3.3 5 6.8 6.4 10.5 1.3 3.6 2 7.5 2.1 11.9.1 1.2.1 2.3-.1 3.4-.2 1.1-.3 2.3-.3 3.4l-.6 2.2c-.1 1.5-.6 3.4-1.4 5.7-1.2 2.6-1.9 4.3-2.6 5.6m38.9 17.5c-.2 1.2-.5 2.5-.9 4l-3.8 14.1c-.4 1.4-.8 2.7-1.2 3.9-.4 1.1-1.1 1.9-1.9 2.4-1.1.4-2.3.5-3.6.2-1.3-.3-2.7-.6-4.1-1l-13.1-3.5c-1.4-.4-2.8-.8-4.1-1.2-1.3-.4-2.2-1.1-2.6-2.1-.4-1-.6-2.1-.4-3.3.2-1.3.5-2.6.9-4.1l3.3-12.3c.3-1.3.7-2.6 1-3.8.3-1.2.9-2.2 1.6-2.8.7-.6 1.9-1 3.5-1.2.3-.1.6 0 .9.1.3.2.6.2.9.1l15.4 4.1c1.6.4 3 .8 4.4 1.3 1.4.4 2.4 1.1 3.1 2 .7.9.9 1.9.7 3.1m28.4-108.9c-.3 1.2-.6 2.4-1 3.7l-5.8 21.7c-.2.8-.4 1.7-.5 2.7-.1 1-.4 1.8-.9 2.4l-.6 2.2c-.5.8-.9 1.8-1.1 2.8-.2 1-.5 1.8-1 2.6-.4.6-.7 1.2-.8 1.8-.1.6-.4 1.2-.8 1.8-.6 1.1-1 2.2-1.3 3.3-.3 1.1-.7 2.2-1.3 3.1-.3.5-.5 1-.6 1.4-.1.5-.3.9-.6 1.4-.7 1.6-1.4 3.3-2 5.1-.6 1.8-1.4 3.5-2.2 5.1-.5.8-.8 1.7-1 2.5-.1.8-.5 1.7-1 2.7-1.2 2.2-2.1 4.6-2.8 7.1-.7 2.5-1.8 4.9-3 7-.9 1.7-1.6 3.5-2.2 5.4-.6 1.9-1.6 3.1-3.2 3.7-.5.2-1 .2-1.5.2-.6 0-1.2 0-1.9-.1l-2.7-.7c-1.3-.3-2.4-.8-3.3-1.3-.9-.5-1.5-1.2-1.9-2-.4-1-.5-1.9-.3-2.8.2-.9.4-1.9.4-3 0-1.5.2-3.1.5-4.7.4-1.6.5-3.1.5-4.7.3-1 .4-1.8.4-2.2.1-.8.2-1.6.4-2.4.2-.8.4-1.6.4-2.4 0-2 .2-4 .7-6 .5-2 .8-4 .9-6 0-.7.1-1.3.3-1.9.2-.6.3-1.2.3-1.9.1-1.2.2-2.5.5-3.8.3-1.3.5-2.7.6-4 .1-.3.1-.5.1-.8 0-.3 0-.5.1-.8 0-1.1.2-2.2.4-3.1.3-1 .4-2 .4-3.1.1-3 .7-6.2 1.6-9.6 1-3.4 1.9-6.8 2.8-10.1l4-15.1c.4-1.4.8-2.8 1.1-4.1.3-1.3 1-2.3 1.9-3.1.9-.6 1.9-.9 3.2-.8.3-.1.6 0 .8.1.2.1.5.2.8.1l15.1 4c1.6.4 3 .9 4.3 1.4 1.3.5 2.2 1.3 2.6 2.4.5.6.5 1.6.2 2.8"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

3
app/assets/images/octicat.svg

@ -1,3 +0,0 @@
<svg height="1024" width="1024" xmlns="http://www.w3.org/2000/svg">
<path d="M512 0C229.25 0 0 229.25 0 512c0 226.25 146.688 418.125 350.156 485.812 25.594 4.688 34.938-11.125 34.938-24.625 0-12.188-0.469-52.562-0.719-95.312C242 908.812 211.906 817.5 211.906 817.5c-23.312-59.125-56.844-74.875-56.844-74.875-46.531-31.75 3.53-31.125 3.53-31.125 51.406 3.562 78.47 52.75 78.47 52.75 45.688 78.25 119.875 55.625 149 42.5 4.654-33 17.904-55.625 32.5-68.375C304.906 725.438 185.344 681.5 185.344 485.312c0-55.938 19.969-101.562 52.656-137.406-5.219-13-22.844-65.094 5.062-135.562 0 0 42.938-13.75 140.812 52.5 40.812-11.406 84.594-17.031 128.125-17.219 43.5 0.188 87.312 5.875 128.188 17.281 97.688-66.312 140.688-52.5 140.688-52.5 28 70.531 10.375 122.562 5.125 135.5 32.812 35.844 52.625 81.469 52.625 137.406 0 196.688-119.75 240-233.812 252.688 18.438 15.875 34.75 47 34.75 94.75 0 68.438-0.688 123.625-0.688 140.5 0 13.625 9.312 29.562 35.25 24.562C877.438 930 1024 738.125 1024 512 1024 229.25 794.75 0 512 0z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

231
app/assets/javascripts/editor.js

@ -1,115 +1,130 @@
(function() { (function() {
var pens = {}; var pens = {};
Array.prototype.forEach.call(document.querySelectorAll('.textarea'), function(editor) { Array.prototype.forEach.call(document.querySelectorAll('.textarea'), function(editor) {
var event= editor.dataset.editOn; var event= editor.dataset.editOn;
if (event == 'load') { if (event == 'load') {
startEditing(editor); startEditing(editor);
} else { } else {
editor.addEventListener(event, function() { editor.addEventListener(event, function() {
if (editor.getAttribute('contenteditable') !== 'true') { if (editor.getAttribute('contenteditable') !== 'true') {
startEditing(editor); startEditing(editor);
// for content editable, we need to refocus to show the caret // for content editable, we need to refocus to show the caret
editor.blur(); editor.blur();
editor.focus(); editor.focus();
} }
}); });
} }
}); });
function startEditing(editor) { function startEditing(editor) {
var name = editor.dataset.name; var name = editor.dataset.name;
pens[name] = new Pen({ pens[name] = new Pen({
editor: editor, editor: editor,
class: 'pen', class: 'pen',
textarea: '<textarea name="' + name + '"></textarea>', textarea: '<textarea name="' + name + '"></textarea>',
list: ['p', 'h1', 'h2', 'blockquote', 'insertorderedlist', 'insertunorderedlist', 'bold', 'italic', 'underline', 'strikethrough', 'createlink', 'insertimage'], list: ['p', 'h1', 'h2', 'blockquote', 'insertorderedlist', 'insertunorderedlist', 'bold', 'italic', 'underline', 'strikethrough', 'createlink', 'insertimage'],
title: { title: {
'p': 'Paragraph', 'p': 'Paragraph',
'h1': 'Major Heading', 'h1': 'Major Heading',
'h2': 'Minor Heading', 'h2': 'Minor Heading',
'blockquote': 'Quotation', 'blockquote': 'Quotation',
'insertorderedlist': 'Ordered List', 'insertorderedlist': 'Ordered List',
'insertunorderedlist': 'Unordered List', 'insertunorderedlist': 'Unordered List',
'bold': 'Bold', 'bold': 'Bold',
'italic': 'Italic', 'italic': 'Italic',
'underline': 'Underline', 'underline': 'Underline',
'strikethrough': 'Strikethrough', 'strikethrough': 'Strikethrough',
'createlink': 'Link', 'createlink': 'Link',
'insertimage': 'Image' 'insertimage': 'Image'
} }
}); });
return pens[name]; return pens[name];
} }
Array.prototype.forEach.call(document.querySelectorAll('form'), function(form) { Array.prototype.forEach.call(document.querySelectorAll('form'), function(form) {
var shouldAllowAlert = false; var shouldAllowAlert = false;
form.addEventListener('submit', function() { form.addEventListener('submit', function() {
if (shouldAllowAlert) { if (shouldAllowAlert) {
return; return;
} }
Array.prototype.forEach.call(document.querySelectorAll('.textarea'), function(editor) { Array.prototype.forEach.call(document.querySelectorAll('.textarea'), function(editor) {
var name = editor.dataset.name; var name = editor.dataset.name;
var textarea = document.querySelector('textarea[name="' + name + '"]'); var textarea = document.querySelector('textarea[name="' + name + '"]');
if (!textarea) { if (!textarea) {
textarea = document.createElement('textarea'); textarea = document.createElement('textarea');
textarea.name = name; textarea.name = name;
textarea.style.display = 'none'; textarea.style.display = 'none';
form.appendChild(textarea); form.appendChild(textarea);
} }
textarea.value = editor.innerHTML; textarea.value = editor.innerHTML;
if (pens[name]) { if (pens[name]) {
pens[name].destroy(); pens[name].destroy();
} }
}); });
}, false); }, false);
Array.prototype.forEach.call(form.querySelectorAll('button'), function(button) { Array.prototype.forEach.call(form.querySelectorAll('button'), function(button) {
form.addEventListener('click', function(event) { form.addEventListener('click', function(event) {
shouldAllowAlert = (event.target.value === 'cancel'); shouldAllowAlert = (event.target.value === 'cancel');
}); });
}); });
}); });
Array.prototype.forEach.call(document.querySelectorAll('.check-box-field .other input'), function(input) { Array.prototype.forEach.call(document.querySelectorAll('.check-box-field .other input'), function(input) {
var checkbox = document.getElementById(input.parentElement.parentElement.attributes.for.value); var checkbox = document.getElementById(input.parentElement.parentElement.attributes.for.value);
input.addEventListener('keyup', function(event) { input.addEventListener('keyup', function(event) {
if (event.target.value) { if (event.target.value) {
checkbox.checked = true; checkbox.checked = true;
} }
}); });
input.addEventListener('click', function(event) { input.addEventListener('click', function(event) {
checkbox.checked = true; checkbox.checked = true;
}); });
var setRequired = function() { var setRequired = function() {
if (checkbox.checked) { if (checkbox.checked) {
input.setAttribute('required', 'required'); input.setAttribute('required', 'required');
} else { } else {
input.removeAttribute('required'); input.removeAttribute('required');
} }
}; };
Array.prototype.forEach.call(document.querySelectorAll('.check-box-field input'), function(_input) { Array.prototype.forEach.call(document.querySelectorAll('.check-box-field input'), function(_input) {
_input.addEventListener('change', function(event) { setRequired(); }); _input.addEventListener('change', function(event) { setRequired(); });
}); });
}); });
Array.prototype.forEach.call(document.querySelectorAll('[data-toggles]'), function(checkbox) { Array.prototype.forEach.call(document.querySelectorAll('[data-toggles]'), function(checkbox) {
var toggles = document.getElementById(checkbox.dataset.toggles); var toggles = document.getElementById(checkbox.dataset.toggles);
toggles.classList.add('toggleable'); toggles.classList.add('toggleable');
var form = checkbox.parentNode; var form = checkbox.parentNode;
while (form && form.nodeName != 'FORM') { while (form && form.nodeName != 'FORM') {
form = form.parentNode; form = form.parentNode;
} }
var toggle = function() { var toggle = function() {
toggles.classList[checkbox.checked ? 'add' : 'remove']('open'); toggles.classList[checkbox.checked ? 'add' : 'remove']('open');
if (form) { if (form) {
if (checkbox.checked) { if (checkbox.checked) {
form.removeAttribute('novalidate'); form.removeAttribute('novalidate');
} else { } else {
form.setAttribute('novalidate', 'novalidate'); form.setAttribute('novalidate', 'novalidate');
} }
} }
}; };
toggle(); toggle();
checkbox.addEventListener('change', function(event) { toggle(); }); checkbox.addEventListener('change', function(event) { toggle(); });
}); });
Array.prototype.forEach.call(document.querySelectorAll('fieldset.translator'), function(translator) {
Array.prototype.forEach.call(translator.querySelectorAll('.locale-select a'), function(selector) {
selector.addEventListener('click', function(event) {
event.preventDefault();
var locale = event.target.parentElement.getAttribute('data-locale');
Array.prototype.forEach.call(translator.querySelectorAll('.locale-select li'), function(_selector) {
_selector.className = _selector.getAttribute('data-locale') == locale ? 'selected' : '';
});
Array.prototype.forEach.call(translator.querySelectorAll('.text-editors li'), function(editor) {
editor.className = editor.getAttribute('data-locale') == locale ? 'selected' : '';
});
});
});
});
})(); })();

31
app/assets/javascripts/filefield.js

@ -0,0 +1,31 @@
(function() {
document.addEventListener('DOMContentLoaded', function() {
var fields = document.getElementsByClassName('file-field');
for (var i = 0; i < fields.length; i++) {
var field = fields[i];
var input = field.getElementsByTagName('input')[0];
var image = field.getElementsByTagName('img')[0];
var state = field.getElementsByClassName('file-field-name')[0];
input.onchange = function() {
state.className = 'file-field-name selected';
state.innerHTML = this.value.split(/[\/\\]/).reverse()[0];
var uploadButton = this.form.querySelector('[value="upload"]');
if (uploadButton) {
uploadButton.setAttribute('data-enabled', '1');
}
if (this.files && this.files[0] && typeof FileReader !== "undefined") {
var reader = new FileReader();
reader.onload = function (e) {
image.className = 'changed';
image.src = e.target.result;
};
reader.readAsDataURL(this.files[0]);
}
}
}
}, false);
})();

4
app/assets/javascripts/jquery.min.js

File diff suppressed because one or more lines are too long

11
app/assets/javascripts/main.js

@ -230,4 +230,15 @@
}); });
}; };
initNode(); initNode();
document.addEventListener('DOMContentLoaded', function() {
var errors = document.getElementsByClassName('has-error');
if (errors.length <= 0) {
errors = document.getElementsByClassName('info-message');
}
if (errors.length > 0) {
errors[0].scrollIntoView();
}
});
})(); })();

117
app/assets/javascripts/map.js

@ -0,0 +1,117 @@
(function() {
function loadMap() {
var projection = d3.geo.mercator();
var path = d3.geo.path().projection(projection);
var tooltip = document.getElementById('tooltip');
var initialScale = 3.5;
var initialPosition = [-100, -175];
var zoom = d3.behavior.zoom()
.scaleExtent([1, 20])
.scale(initialScale).translate(initialPosition)
.on("zoom", function() {
setZoom(d3.event.translate, d3.event.scale);
});
function setZoom(translate, scale) {
container.attr("transform", "translate(" + translate + ") scale(" + scale + ")").attr('data-scale', scale);
}
var container = d3.select('#map').call(zoom).insert('g', ':first-child').attr('class', 'map');
setZoom(initialPosition, initialScale);
container.append("path")
.datum(d3.geo.graticule())
.attr("class", "graticule")
.attr("d", path);
d3.selection.prototype.moveToFront = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
d3.json('/assets/world-110m.json', function(error, world) {
if (error) {
throw error;
}
container.selectAll('path')
.data(topojson.feature(world, world.objects.countries).features)
.enter()
.append('path')
.attr('d', path)
.attr('class', 'country');
var year = (new Date()).getFullYear();
var conferences = document.querySelectorAll('#conferences .conference');
var conference_path = []
for (var i = 0; i < conferences.length; i++) {
var d = conferences[i];
if (d.getAttribute('data-t') === 'annual') {
var coords = projection([d.getAttribute('data-o'), d.getAttribute('data-a')]);
if (conference_path.length) {
conference_path[conference_path.length - 1].x2 = coords[0];
conference_path[conference_path.length - 1].y2 = coords[1];
}
conference_path.push({
x1: coords[0],
y1: coords[1]
});
}
}
container.append('defs').html('<filter id="svg-gooey-filter"><feGaussianBlur in="SourceGraphic" stdDeviation="3" result="blur"></feGaussianBlur><feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7" result="svg-gooey-filter"></feColorMatrix><feComposite in="SourceGraphic" in2="svg-gooey-filter" operator="atop"></feComposite></filter>')
var regionalConferences = container.append('g').attr('filter', 'url(#svg-gooey-filter)').attr('class', 'cities regional-conferences');
var annualConferences = container.append('g').attr('filter', 'url(#svg-gooey-filter)').attr('class', 'cities annual-conferences');
function mouseover(e) {
c = document.getElementById('conference-' + event.target.getAttribute('data-c'));
tooltip.innerHTML = '<h3>' + c.querySelector('.title').innerHTML + '</h3>' +
'<div class="conference-details">' + c.querySelector('.conference-details').innerHTML + '</div>';
tooltip.className = 'open';
}
function mouseout(e) {
tooltip.className = '';
}
function click(e) {
l = document.querySelector('#conference-' + event.target.getAttribute('data-c') + ' .conference-link');
window.location.href = l.getAttribute('href');
}
for (var i = conferences.length - 1; i >= 0; i--) {
var c = conferences[i];
var type = c.getAttribute('data-t');
var coords = projection([c.getAttribute('data-o'), c.getAttribute('data-a')]);
(type === 'annual' ? annualConferences : regionalConferences)
.append('circle')
.attr('class', 'city type-' + type)
.attr('data-c', c.id.replace(/^conference\-/, ''))
.attr('cx', function(d) { return coords[0]; })
.attr('cy', function(d) { return coords[1]; })
.attr('r', Math.max(3,
(15 - (
(year - parseInt(c.getAttribute('data-y'))) * 2.5)) * 1.125)
)
.on('mouseover', mouseover)
.on('mouseout', mouseout)
.on('click', click);
}
});
d3.select("#map").attr('class', 'loaded');
}
document.onreadystatechange = function () {
if (document.readyState == 'complete') {
loadMap();
}
};
})();

26
app/assets/javascripts/userfield.js

@ -0,0 +1,26 @@
(function() {
function find_user(email, f) {
var request = new XMLHttpRequest();
request.open('POST', '/user/find', true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
request.setRequestHeader('X-CSRF-Token', encodeURI(document.querySelector('meta[name="csrf-token"]').getAttribute('content')));
request.send('e=' + encodeURI(email));
request.onreadystatechange = function() {
if (request.readyState == 4) {
if (request.status == 200) {
f(JSON.parse(request.responseText));
} else {
f({error: request.status});
}
}
}
}
document.addEventListener('DOMContentLoaded', function() {
var fields = document.getElementsByClassName('user-field');
for (var i = 0; i < fields.length; i++) {
var field = fields[i];
var input = field.getElementsByTagName('input')[0];
var name = field.getElementsByClassName('user-name')[0];
}
}, false);
})();

8428
app/assets/stylesheets/_application.scss

File diff suppressed because it is too large

287
app/assets/stylesheets/_settings.scss

@ -14,156 +14,165 @@ $colour-5: #02CA9E; // green
$white: #FFFEFE; $white: #FFFEFE;
$black: #333; $black: #333;
$gray: #E8E8E8;
$light-gray: #EEE;
$mid-gray: #888;
// $red: #B24C63;
$red: #FF5A5F;
$link-colour: darken($colour-1, 13%); $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) {
$offset: 0.2em; $offset: 0.2em;
@if $direction == right or $direction == bottom { @if $direction == right or $direction == bottom {
$offset: -$offset; $offset: -$offset;
} }
@if $direction == left or $direction == right { @if $direction == left or $direction == right {
$offset: '#{$offset} 0'; $offset: '#{$offset} 0';
} @else { } @else {
$offset: '0 #{$offset}'; $offset: '0 #{$offset}';
} }
@if $inset { @if $inset {
$offset: 'inset #{$offset}'; $offset: 'inset #{$offset}';
} }
@if $additional-shadow { @if $additional-shadow {
$additional-shadow: ', #{$additional-shadow}'; $additional-shadow: ', #{$additional-shadow}';
} @else { } @else {
$additional-shadow: ''; $additional-shadow: '';
} }
@include _(box-shadow, #{$offset} 0.8em #{-0.2em * $distance} #000#{$additional-shadow}); @include _(box-shadow, #{$offset} 0.8em #{-0.2em * $distance} #000#{$additional-shadow});
} }
} }
@mixin monospace-font { @mixin monospace-font {
@include font(monospace); @include font(monospace);
@include font(monospace-bold); @include font(monospace-bold);
} }
@mixin translation-pointer { @mixin translation-pointer {
@include keyframes(bouncy) { @include keyframes(bouncy) {
from { transform: translateY(-0.25em); } from { transform: translateY(-0.25em); }
to { transform: translateY(0.25em); } to { transform: translateY(0.25em); }
} }
#lingua-franca-pointer { #lingua-franca-pointer {
$colour: $colour-5; $colour: $colour-5;
width: 0.6667em; width: 0.6667em;
height: 1.25em; height: 1.25em;
background-color: $colour; background-color: $colour;
z-index: 1000; z-index: 1000;
margin-left: -0.5em; margin-left: -0.5em;
margin-top: -2em; margin-top: -2em;
@include _(mix-blend-mode, exclusion); @include _(mix-blend-mode, exclusion);
@include default-box-shadow(top, 2); @include default-box-shadow(top, 2);
@include _(animation, bouncy 1s infinite alternate); @include _(animation, bouncy 1s infinite alternate);
$twidth: 0.8em; $twidth: 0.8em;
@include after { @include after {
content: ''; content: '';
width: 0; width: 0;
height: 0; height: 0;
position: absolute; position: absolute;
border-style: solid; border-style: solid;
border-color: $colour transparent transparent; border-color: $colour transparent transparent;
border-width: $twidth $twidth 0; border-width: $twidth $twidth 0;
top: 100%; top: 100%;
left: -.4em; left: -.4em;
} }
&.up { &.up {
margin-top: 2em; margin-top: 2em;
@include after { @include after {
top: auto; top: auto;
bottom: 100%; bottom: 100%;
border-color: transparent transparent $colour; border-color: transparent transparent $colour;
border-width: 0 $twidth $twidth; border-width: 0 $twidth $twidth;
} }
} }
} }
}
@mixin text-stroke {
@include _(text-stroke, 1px rgba(0, 0, 0, 0.25));
} }
@mixin button { @mixin button {
position: relative; position: relative;
display: inline-block; display: inline-block;
color: $white; color: $white;
background-color: $colour-1; background-color: $colour-1;
border: 0; border: 0;
padding: 0.5em 1em; padding: 0.5em 1em;
font-size: 1.25em; font-size: 1.25em;
outline: 0; outline: 0;
border-bottom: 0.125em solid rgba(0, 0, 0, 0.15); border-bottom: 0.125em solid rgba(0, 0, 0, 0.15);
@include _(border-radius, 0.15em); @include _(border-radius, 0.15em);
@include default-box-shadow(top, 2); @include default-box-shadow(top, 2);
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
@include _(text-stroke, 1px rgba(0, 0, 0, 0.25)); @include text-stroke;
@include before { @include before {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
background-color: rgba(0,0,0,0); background-color: rgba(0,0,0,0);
@include _(transition, background-color 150ms ease-in-out); @include _(transition, background-color 150ms ease-in-out);
} }
/*@include before-and-after { /*@include before-and-after {
content: ''; content: '';
position: absolute; position: absolute;
display: block; display: block;
top: 0; top: 0;
right: 100%; right: 100%;
font-size: 1.15em; font-size: 1.15em;
background-color: rgba(0, 0, 0, 0); background-color: rgba(0, 0, 0, 0);
border-color: transparent; border-color: transparent;
@include _(transition, all 250ms ease-in-out); @include _(transition, all 250ms ease-in-out);
} }
@include before { @include before {
height: 100%; height: 100%;
width: 100%; width: 100%;
margin-right: 1em; margin-right: 1em;
} }
@include after { @include after {
border-style: solid; border-style: solid;
border-width: 1em 0 1em 1em; border-width: 1em 0 1em 1em;
}*/ }*/
/*&:hover, /*&:hover,
&:focus { &:focus {
@include before-and-after { @include before-and-after {
right: -1em; right: -1em;
border-left-color: rgba(0, 0, 0, 0.15); border-left-color: rgba(0, 0, 0, 0.15);
} }
}*/ }*/
&:hover, &:hover,
&:focus { &:focus {
@include before { @include before {
background-color: rgba(0, 0, 0, 0.15); background-color: rgba(0, 0, 0, 0.15);
} }
} }
&:active { &:active {
@include _(transform, scale(0.95)); @include _(transform, scale(0.95));
@include after { @include after {
left: 120%; left: 120%;
} }
} }
&:disabled { &:disabled {
@include _(opacity, 0.5); @include _(opacity, 0.5);
cursor: inherit; cursor: inherit;
@include before-and-after { @include before-and-after {
display: none; display: none;
} }
} }
} }

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

@ -5,8 +5,8 @@
"chrome": ["51"] "chrome": ["51"]
}, },
"development": { "development": {
"and_chr": ["53"], "and_chr": ["54"],
"chrome": ["53"], "chrome": ["54"],
"edge": ["13"], "edge": ["13"],
"firefox": ["44"], "firefox": ["44"],
"ie": ["11"], "ie": ["11"],

36
app/controllers/admin_controller.rb

@ -0,0 +1,36 @@
require 'geocoder/calculations'
class AdminController < ApplicationController
def new
return do_404 unless logged_in? && current_user.administrator?
@this_conference = Conference.new
@page_title = 'articles.conferences.headings.new'
end
def edit
return do_404 unless logged_in? && current_user.administrator?
@this_conference = Conference.find_by!(slug: params[:slug])
@page_title = 'articles.conferences.headings.edit'
render 'new'
end
def save
conference = params[:id].present? ? Conference.find_by!(id: params[:id]) : Conference.new
if params[:button] == 'save'
city = City.search(params[:city])
conference.city_id = city.id
conference.conferencetype = params[:type]
conference.year = params[:year].to_i
conference.is_public = params[:is_public].present?
conference.is_featured = params[:is_featured].present?
conference.make_slug(true)
conference.save!
elsif params[:button] == 'delete'
conference.destroy
return redirect_to conferences_url
end
redirect_to conference_url(conference.slug)
end
end

1375
app/controllers/application_controller.rb

File diff suppressed because it is too large

1208
app/controllers/conference_administration_controller.rb

File diff suppressed because it is too large

2229
app/controllers/conferences_controller.rb

File diff suppressed because it is too large

355
app/controllers/workshops_controller.rb

@ -0,0 +1,355 @@
class WorkshopsController < ApplicationController
def workshops
set_conference
set_conference_registration!
@workshops = Workshop.where(:conference_id => @this_conference.id)
@my_workshops = Workshop.joins(:workshop_facilitators).where(:workshop_facilitators => {:user_id => current_user.id}, :conference_id => @this_conference.id)
render 'workshops/index'
end
def view_workshop
set_conference
set_conference_registration!
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
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'
@register_template = :workshops
render 'workshops/show'
end
def create_workshop
set_conference
set_conference_registration!
@workshop = Workshop.new
@languages = [I18n.locale.to_sym]
@needs = []
@page_title = 'page_titles.conferences.Create_Workshop'
@register_template = :workshops
render 'workshops/new'
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) }
@register_template = :workshops
edit_workshop
end
def edit_workshop
set_conference
set_conference_registration!
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless @workshop.present?
@page_title ||= 'page_titles.conferences.Edit_Workshop'
@can_edit = @workshop.can_edit?(current_user)
@is_translating ||= false
if @is_translating
return do_404 if @translation.to_s == @workshop.locale.to_s || !I18n.backend.enabled_locales.include?(@translation.to_s)
return do_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
@languages = JSON.parse(@workshop.languages || '[]').map &:to_sym
@space = @workshop.space.to_sym if @workshop.space
@theme = @workshop.theme.to_sym if @workshop.theme
@notes = @workshop.notes
@register_template = :workshops
render 'workshops/new'
end
def delete_workshop
set_conference
set_conference_registration!
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless @workshop.present?
return do_403 unless @workshop.can_delete?(current_user)
if request.post?
if params[:button] == 'confirm'
if @workshop
@workshop.workshop_facilitators.destroy_all
@workshop.destroy
end
return redirect_to register_step_path(@this_conference.slug, 'workshops')
end
return redirect_to view_workshop_url(@this_conference.slug, @workshop.id)
end
@register_template = :workshops
render 'workshops/delete'
end
def save_workshop
set_conference
set_conference_registration!
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])
return do_404 unless workshop.present?
can_edit = workshop.can_edit?(current_user)
else
workshop = Workshop.new(:conference_id => @this_conference.id)
workshop.workshop_facilitators = [WorkshopFacilitator.new(:user_id => current_user.id, :role => :creator)]
can_edit = true
end
title = params[:title]
info = params[:info].gsub(/^\s*(.*?)\s*$/, '\1')
if params[:translation].present? && workshop.can_translate?(current_user, params[:translation])
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
# only save if the text has changed, if we want to make sure only to update the translator id if necessary
workshop.save_translations if do_save
elsif can_edit
workshop.title = title
workshop.info = info
workshop.languages = (params[:languages] || {}).keys.to_json
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
# Rouge nil facilitators have been know to be created, just destroy them here now
WorkshopFacilitator.where(:user_id => nil).destroy_all
else
return do_403
end
redirect_to view_workshop_url(@this_conference.slug, workshop.id)
end
def toggle_workshop_interest
set_conference
set_conference_registration!
workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless workshop
# save the current state
interested = workshop.interested? current_user
# remove all associated fields
WorkshopInterest.delete_all(:workshop_id => workshop.id, :user_id => current_user.id)
# creat the new interest row if we weren't interested before
WorkshopInterest.create(:workshop_id => workshop.id, :user_id => current_user.id) unless interested
if request.xhr?
render json: [
{
selector: '.interest-button',
html: view_context.interest_button(workshop)
},
{
selector: '.interest-text',
html: view_context.interest_text(workshop)
}
]
else
# go back to the workshop
redirect_to view_workshop_url(@this_conference.slug, workshop.id)
end
end
def facilitate_workshop
set_conference
set_conference_registration!
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless @workshop
return do_403 if @workshop.facilitator?(current_user) || !current_user
@register_template = :workshops
render 'workshops/facilitate'
end
def facilitate_request
set_conference
set_conference_registration!
workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless workshop
return do_403 if workshop.facilitator?(current_user) || !current_user
# create the request by making the user a facilitator but making their role 'requested'
WorkshopFacilitator.create(user_id: current_user.id, workshop_id: workshop.id, role: :requested)
UserMailer.send_mail :workshop_facilitator_request do
{
:args => [ workshop, current_user, params[:message] ]
}
end
redirect_to sent_facilitate_workshop_url(@this_conference.slug, workshop.id)
end
def sent_facilitate_request
set_conference
set_conference_registration!
@workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless @workshop
return do_403 unless @workshop.requested_collaborator?(current_user)
@register_template = :workshops
render 'workshops/facilitate_request_sent'
end
def approve_facilitate_request
return do_403 unless logged_in?
set_conference
set_conference_registration!
workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless workshop.present?
user_id = params[:user_id].to_i
action = params[:approve_or_deny].to_sym
user = User.find(user_id)
case action
when :approve
if workshop.active_facilitator?(current_user) && workshop.requested_collaborator?(User.find(user_id))
f = WorkshopFacilitator.find_by_workshop_id_and_user_id(
workshop.id, user_id)
f.role = :collaborator
f.save
UserMailer.send_mail :workshop_facilitator_request_approved, user.locale do
[ workshop, user ]
end
return redirect_to view_workshop_url(@this_conference.slug, workshop.id)
end
when :deny
if workshop.active_facilitator?(current_user) && workshop.requested_collaborator?(User.find(user_id))
WorkshopFacilitator.delete_all(
:workshop_id => workshop.id,
:user_id => user_id)
UserMailer.send_mail :workshop_facilitator_request_denied, user.locale do
[ workshop, user ]
end
return redirect_to view_workshop_url(@this_conference.slug, workshop.id)
end
when :remove
if workshop.can_remove?(current_user, user)
WorkshopFacilitator.delete_all(
:workshop_id => workshop.id,
:user_id => user_id)
return redirect_to view_workshop_url(@this_conference.slug, workshop.id)
end
when :switch_ownership
if workshop.creator?(current_user)
f = WorkshopFacilitator.find_by_workshop_id_and_user_id(
workshop.id, current_user.id)
f.role = :collaborator
f.save
f = WorkshopFacilitator.find_by_workshop_id_and_user_id(
workshop.id, user_id)
f.role = :creator
f.save
return redirect_to view_workshop_url(@this_conference.slug, workshop.id)
end
end
return do_403
end
def add_workshop_facilitator
set_conference
set_conference_registration!
user = User.find_by_email(params[:email])
# create the user if they don't exist and send them a link to register
unless user
user = User.create(email: params[:email])
generate_confirmation(user, register_path(@this_conference.slug))
end
workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless workshop && current_user
unless workshop.facilitator?(user)
WorkshopFacilitator.create(user_id: user.id, workshop_id: workshop.id, role: :collaborator)
UserMailer.send_mail :workshop_facilitator_request_approved, user.locale do
[ workshop, user ]
end
end
return redirect_to view_workshop_url(@this_conference.slug, params[:workshop_id])
end
def add_comment
set_conference
set_conference_registration!
workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id)
return do_404 unless workshop && current_user
if params[:button] == 'reply'
comment = Comment.find_by!(id: params[:comment_id].to_i, model_type: :workshops, model_id: workshop.id)
new_comment = comment.add_comment(current_user, params[:reply])
unless comment.user.id == current_user.id
UserMailer.send_mail :workshop_comment, comment.user.locale do
[ workshop, new_comment, comment.user ]
end
end
elsif params[:button] = 'add_comment'
new_comment = workshop.add_comment(current_user, params[:comment])
workshop.active_facilitators.each do | u |
unless u.id == current_user.id
UserMailer.send_mail :workshop_comment, u.locale do
[ workshop, new_comment, u ]
end
end
end
else
return do_404
end
return redirect_to view_workshop_url(@this_conference.slug, workshop.id, anchor: "comment-#{new_comment.id}")
end
end

4178
app/helpers/application_helper.rb

File diff suppressed because it is too large

352
app/mailers/user_mailer.rb

@ -1,180 +1,180 @@
require 'diffy' require 'diffy'
class UserMailer < ActionMailer::Base class UserMailer < ActionMailer::Base
add_template_helper(ApplicationHelper) add_template_helper(ApplicationHelper)
include LinguaFrancaHelper include LinguaFrancaHelper
before_filter :set_host before_filter :set_host
default from: "Bike!Bike! <noreply@bikebike.org>" default from: "Bike!Bike! <noreply@bikebike.org>"
def email_confirmation(confirmation) def email_confirmation(confirmation)
@confirmation = EmailConfirmation.find(confirmation) if confirmation.present? @confirmation = EmailConfirmation.find(confirmation) if confirmation.present?
I18n.locale = @confirmation.user.locale if @confirmation.user.locale.present? I18n.locale = @confirmation.user.locale if @confirmation.user.locale.present?
@subject = _'email.subject.confirm_email','Please confirm your email address' @subject = _'email.subject.confirm_email','Please confirm your email address'
mail to: @confirmation.user.named_email, subject: @subject mail to: @confirmation.user.named_email, subject: @subject
end end
def registration_confirmation(registration) def registration_confirmation(registration)
@registration = ConferenceRegistration.find(registration) if registration.present? @registration = ConferenceRegistration.find(registration) if registration.present?
@conference = @registration.conference @conference = @registration.conference
@user = @registration.user @user = @registration.user
I18n.locale = @user.locale if @user.locale.present? I18n.locale = @user.locale if @user.locale.present?
@subject = @conference.registration_status.to_sym == :pre ? @subject = @conference.registration_status.to_sym == :pre ?
_( _(
'email.subject.pre_registration_confirmed', 'email.subject.pre_registration_confirmed',
"Thank you for pre-registering for #{@conference.title}", "Thank you for pre-registering for #{@conference.title}",
:vars => {:conference_title => @conference.title} :vars => {:conference_title => @conference.title}
) : _( ) : _(
'email.subject.registration_confirmed', 'email.subject.registration_confirmed',
"Thank you for registering for #{@conference.title}", "Thank you for registering for #{@conference.title}",
:vars => {:conference_title => @conference.title} :vars => {:conference_title => @conference.title}
) )
mail to: @user.named_email, subject: @subject mail to: @user.named_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 @banner = nil
@conference = Conference.find(conference) if conference.present? @conference = Conference.find(conference) if conference.present?
@user = User.find(user) if user.present? @user = User.find(user) if user.present?
@subject = "[#{@conference ? @conference.title : 'Bike!Bike!'}] #{subject}" @subject = "[#{@conference ? @conference.title : 'Bike!Bike!'}] #{subject}"
if @user && @user.named_email if @user && @user.named_email
mail to: @user.named_email, subject: @subject mail to: @user.named_email, subject: @subject
end end
end end
def workshop_facilitator_request(workshop, requester, message) def workshop_facilitator_request(workshop, requester, message)
@workshop = Workshop.find(workshop) if workshop.present? @workshop = Workshop.find(workshop) if workshop.present?
@requester = User.find(requester) if requester.present? @requester = User.find(requester) if requester.present?
addresses = [] addresses = []
I18n.locale = @workshop.active_facilitators.first.locale if @workshop.active_facilitators.first.locale.present? I18n.locale = @workshop.active_facilitators.first.locale if @workshop.active_facilitators.first.locale.present?
@workshop.active_facilitators.each do |f| @workshop.active_facilitators.each do |f|
addresses << f.named_email addresses << f.named_email
end end
@message = message @message = message
@conference = Conference.find(@workshop.conference_id) @conference = Conference.find(@workshop.conference_id)
@subject = _('email.subject.workshop_facilitator_request', @subject = _('email.subject.workshop_facilitator_request',
"Request to facilitate #{@workshop.title} from #{@requester.name}", "Request to facilitate #{@workshop.title} from #{@requester.name}",
:vars => {:workshop_title => @workshop.title, :requester_name => @requester.firstname}) :vars => {:workshop_title => @workshop.title, :requester_name => @requester.firstname})
mail to: addresses, reply_to: addresses + [@requester.named_email], subject: @subject mail to: addresses, reply_to: addresses + [@requester.named_email], subject: @subject
end end
def workshop_facilitator_request_approved(workshop, user) def workshop_facilitator_request_approved(workshop, user)
@workshop = Workshop.find(workshop) if workshop.present? @workshop = Workshop.find(workshop) if workshop.present?
@conference = Conference.find(@workshop.conference_id) @conference = Conference.find(@workshop.conference_id)
@user = User.find(user) if user.present? @user = User.find(user) if user.present?
I18n.locale = @user.locale if @user.locale.present? I18n.locale = @user.locale if @user.locale.present?
@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.named_email, subject: @subject mail to: @user.named_email, subject: @subject
end end
def workshop_facilitator_request_denied(workshop, user) def workshop_facilitator_request_denied(workshop, user)
@workshop = Workshop.find(workshop) if workshop.present? @workshop = Workshop.find(workshop) if workshop.present?
@conference = @workshop.conference @conference = @workshop.conference
@user = User.find(user) if user.present? @user = User.find(user) if user.present?
I18n.locale = @user.locale if @user.present? && @user.locale.present? I18n.locale = @user.locale if @user.present? && @user.locale.present?
@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.named_email, subject: @subject mail to: @user.named_email, subject: @subject
end end
def workshop_translated(workshop, data, locale, user, translator) def workshop_translated(workshop, data, locale, user, translator)
@workshop = Workshop.find(workshop) if workshop.present? @workshop = Workshop.find(workshop) if workshop.present?
@data = data @data = data
@locale = locale @locale = locale
@locale_name = language_name(locale) @locale_name = language_name(locale)
@user = User.find(user) if user.present? @user = User.find(user) if user.present?
I18n.locale = @user.locale if @user.present? && @user.locale.present? I18n.locale = @user.locale if @user.present? && @user.locale.present?
@translator = User.find(translator) if translator.present? @translator = User.find(translator) if translator.present?
@subject = (_'email.subject.workshop_translated', @subject = (_'email.subject.workshop_translated',
"The #{@locale_name} translation for #{@workshop.title} has been modified", "The #{@locale_name} translation for #{@workshop.title} has been modified",
vars: {language: @language_name, workshop_title: @workshop.title}) vars: {language: @language_name, workshop_title: @workshop.title})
@wrapper_id = :full_width @wrapper_id = :full_width
mail to: @user.named_email, subject: @subject mail to: @user.named_email, subject: @subject
end end
def workshop_original_content_changed(workshop, data, user, translator) def workshop_original_content_changed(workshop, data, user, translator)
@workshop = Workshop.find(workshop) if workshop.present? @workshop = Workshop.find(workshop) if workshop.present?
@data = data @data = data
@user = User.find(user) if user.present? @user = User.find(user) if user.present?
I18n.locale = @user.locale if @user.present? && @user.locale.present? I18n.locale = @user.locale if @user.present? && @user.locale.present?
@translator = User.find(translator) if translator.present? @translator = User.find(translator) if translator.present?
@subject = (_'email.subject.workshop_original_content_changed', @subject = (_'email.subject.workshop_original_content_changed',
"Original content for #{@workshop.title} has been modified", "Original content for #{@workshop.title} has been modified",
vars: {workshop_title: @workshop.title}) vars: {workshop_title: @workshop.title})
@data.each do |field, values| @data.each do |field, values|
diff = Diffy::Diff.new(values[:old], values[:new]) diff = Diffy::Diff.new(values[:old], values[:new])
@data[field][:diff] = { @data[field][:diff] = {
text: diff.to_s(:text), text: diff.to_s(:text),
html: diff.to_s(:html) html: diff.to_s(:html)
} }
end end
@wrapper_id = :full_width @wrapper_id = :full_width
mail to: @user.named_email, subject: @subject mail to: @user.named_email, subject: @subject
end end
def workshop_comment(workshop, comment, user) def workshop_comment(workshop, comment, user)
@workshop = Workshop.find(workshop) if workshop.present? @workshop = Workshop.find(workshop) if workshop.present?
@comment = Comment.find(comment) if comment.present? @comment = Comment.find(comment) if comment.present?
@user = User.find(user) if user.present? @user = User.find(user) if user.present?
I18n.locale = @user.locale if @user.present? && @user.locale.present? I18n.locale = @user.locale if @user.present? && @user.locale.present?
if @comment.reply? if @comment.reply?
@subject = (_'email.subject.workshop_comment.reply', vars: { user_name: @comment.user.name }) @subject = (_'email.subject.workshop_comment.reply', vars: { user_name: @comment.user.name })
else else
@subject = (_'email.subject.workshop_comment.comment', vars: { user_name: @comment.user.name, workshop_title: @workshop.title }) @subject = (_'email.subject.workshop_comment.comment', vars: { user_name: @comment.user.name, workshop_title: @workshop.title })
end end
mail to: @user.named_email, subject: @subject mail to: @user.named_email, subject: @subject
end end
def error_report(subject, message, report, exception, request, params, user, time = nil) def error_report(subject, message, report, exception, request, params, user, time = nil)
@subject = subject @subject = subject
@message = message @message = message
@report = report @report = report
@exception = exception @exception = exception
@request = request @request = request
@params = params @params = params
@time = time @time = time
@user = User.find(user) if user.present? @user = User.find(user) if user.present?
mail to: 'goodgodwin@hotmail.com', subject: @subject mail to: 'goodgodwin@hotmail.com', subject: @subject
end end
def contact(from, subject, message, email_list) def contact(from, subject, message, email_list)
@message = message @message = message
@subject = subject @subject = subject
@from = from.is_a?(Integer) ? User.find(from) : from @from = from.is_a?(Integer) ? User.find(from) : from
mail to: email_list.join(', '), subject: @subject, reply_to: @from.is_a?(User) ? @from.named_email : @from mail to: email_list.join(', '), subject: @subject, reply_to: @from.is_a?(User) ? @from.named_email : @from
end end
def contact_details(from, subject, message, request, params) def contact_details(from, subject, message, request, params)
@message = message @message = message
@subject = "Details for: \"#{subject}\"" @subject = "Details for: \"#{subject}\""
@from = from.is_a?(Integer) ? User.find(from) : from @from = from.is_a?(Integer) ? User.find(from) : from
@request = request @request = request
@params = params @params = params
mail to: 'goodgodwin@hotmail.com', subject: @subject mail to: 'goodgodwin@hotmail.com', subject: @subject
end end
private private
def set_host(*args) def set_host(*args)
if Rails.env.production? if Rails.env.production?
@host = "https://#{I18n.locale.to_s}.bikebike.org" @host = "https://#{I18n.locale.to_s}.bikebike.org"
elsif Rails.env.preview? elsif Rails.env.preview?
@host = "https://preview-#{I18n.locale.to_s}.bikebike.org" @host = "https://preview-#{I18n.locale.to_s}.bikebike.org"
else else
@host = UserMailer.default_url_options[:host] @host = UserMailer.default_url_options[:host]
end end
end end
end end

77
app/models/city.rb

@ -0,0 +1,77 @@
require 'geocoder'
require 'geocoder/railtie'
require 'geocoder/calculations'
Geocoder::Railtie.insert
class City < ActiveRecord::Base
geocoded_by :address
translates :city
reverse_geocoded_by :latitude, :longitude, :address => :full_address
after_validation :geocode, if: ->(obj){ obj.country_changed? or obj.territory_changed? or obj.city_changed? or obj.latitude.blank? or obj.longitude.blank? }
def address
([city!, territory, country] - [nil, '']).join(', ')
end
def get_translation(locale)
location = Geocoder.search(address, language: locale.to_s).first
location.data['address_components'].each do | component |
if component['types'].first == 'locality'
return component['short_name']
end
end
return nil
end
def translate_city(locale)
translation = get_translation(locale)
set_column_for_locale(:city, locale, translation)
save!
return translation
end
def self.search(str)
cache = CityCache.search(str)
# return the city if this search is in our cache
return cache.city if cache.present?
# look up the city in the geocoder
location = Geocoder.search(str, language: 'en').first
# see if the city is already present in our database
city = City.find_by_place_id(location.data['place_id'])
# return the city if we found it in the db already
return city if city.present?
# otherwise build a new city
component_alises = {
'locality' => :city,
'administrative_area_level_1' => :territory,
'country' => :country
}
city_data = {
locale: :en,
latitude: location.data['geometry']['location']['lat'],
longitude: location.data['geometry']['location']['lng'],
place_id: location.data['place_id']
}
location.data['address_components'].each do | component |
property = component_alises[component['types'].first]
city_data[property] = component['short_name'] if property.present?
end
# save the new city
city = City.new(city_data)
city.save!
# and return it
return city
end
end

9
app/models/city_cache.rb

@ -0,0 +1,9 @@
class CityCache < ActiveRecord::Base
self.table_name = :city_cache
belongs_to :city
def self.search(str)
CityCache.find_by_search(str.downcase)
end
end

40
app/models/comment.rb

@ -1,27 +1,27 @@
class Comment < ActiveRecord::Base class Comment < ActiveRecord::Base
belongs_to :user belongs_to :user
def comment_object def comment_object
model_type.classify.constantize.find(model_id) model_type.classify.constantize.find(model_id)
end end
def set_model(model) def set_model(model)
model_type = model.class.name.tableize model_type = model.class.name.tableize
model_id = model.id model_id = model.id
end end
def self.for(model) def self.for(model)
where(model_type: model.class.name.tableize, model_id: model.id).order(created_at: :asc) where(model_type: model.class.name.tableize, model_id: model.id).order(created_at: :asc)
end end
def self.create_for(model, user, comment) def self.create_for(model, user, comment)
create( create(
model_type: model.class.name.tableize, model_type: model.class.name.tableize,
model_id: model.id, model_id: model.id,
user_id: user.id, user_id: user.id,
comment: comment comment: comment
) )
end end
def add_comment(user, comment) def add_comment(user, comment)
Comment.create_for(self, user, comment) Comment.create_for(self, user, comment)
@ -32,6 +32,6 @@ class Comment < ActiveRecord::Base
end end
def reply? def reply?
model_type == 'comments' model_type == 'comments'
end end
end end

201
app/models/conference.rb

@ -1,71 +1,136 @@
class Conference < ActiveRecord::Base class Conference < ActiveRecord::Base
translates :info, :title, :payment_message translates :info, :title, :payment_message
mount_uploader :cover, CoverUploader mount_uploader :cover, CoverUploader
mount_uploader :poster, PosterUploader mount_uploader :poster, PosterUploader
belongs_to :conference_type belongs_to :conference_type
belongs_to :city
has_many :conference_host_organizations, :dependent => :destroy
has_many :organizations, :through => :conference_host_organizations has_many :conference_host_organizations, dependent: :destroy
has_many :event_locations has_many :organizations, through: :conference_host_organizations
has_many :conference_administrators, dependent: :destroy
#has_many :conference_registration_form_fields, :order => 'position ASC', :dependent => :destroy#, :class_name => '::ConferenceRegistrationFormField' has_many :administrators, through: :conference_administrators, source: :user
#has_many :registration_form_fields, :through => :conference_registration_form_fields has_many :event_locations
has_many :workshops has_many :workshops
accepts_nested_attributes_for :conference_host_organizations, :reject_if => proc {|u| u[:organization_id].blank?}, :allow_destroy => true accepts_nested_attributes_for :conference_host_organizations, reject_if: proc {|u| u[:organization_id].blank?}, allow_destroy: true
def to_param before_create :make_slug
slug
end def to_param
slug
def host?(user) end
return false unless user.present?
organizations.each do |o| def host_organization?(org)
return true if o.host?(user) return false unless org.present?
end org_id = org.is_a?(Organization) ? org.id : org
return false
end organizations.each do |o|
return true if o.id = org_id
def url(action = :show) end
path(action)
end return false
end
def path(action = :show)
action = action.to_sym def host?(user)
'/conferences/' + conference_type.slug + '/' + slug + (action == :show ? '' : '/' + action.to_s) if user.present?
end return true if user.administrator?
def location conference_administrators.each do |u|
organizations.first.location return true if user.id == u.id
end end
def registered?(user) organizations.each do |o|
registration = ConferenceRegistration.find_by(:user_id => user.id, :conference_id => id) return true if o.host?(user)
return registration ? registration.is_attending : false end
end end
return false
def registration_exists?(user) end
ConferenceRegistration.find_by(:user_id => user.id, :conference_id => id).present?
end def url(action = :show)
path(action)
def registration_open end
registration_status == :open
end def path(action = :show)
action = action.to_sym
def registration_status '/conferences/' + conference_type.slug + '/' + slug + (action == :show ? '' : '/' + action.to_s)
s = read_attribute(:registration_status) end
s.present? ? s.to_sym : nil
end def location
return nil unless organizations.present?
def registration_status=(new_registration_status) organizations.first.location
write_attribute :registration_status, new_registration_status.to_s end
end
def registered?(user)
def self.default_payment_amounts registration = ConferenceRegistration.find_by(:user_id => user.id, :conference_id => id)
[25, 50, 100] return registration ? registration.is_attending : false
end 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
s = read_attribute(:registration_status)
s.present? ? s.to_sym : nil
end
def registration_status=(new_registration_status)
write_attribute :registration_status, new_registration_status.to_s
end
def make_slug(reset = false)
if reset
self.slug = nil
end
self.slug ||= Conference.generate_slug(
conferencetype || :annual,
conference_year,
city_name.gsub(/\s/, '')
)
end
def city_name
return city.city if city.present?
return location.present? ? location.city : nil
end
def conference_year
self.year || (end_date.present? ? end_date.year : nil)
end
def over?
return false unless end_date.present?
return end_date < DateTime.now
end
def self.default_payment_amounts
[25, 50, 100]
end
def self.conference_types
{
annual: '%{city}%{year}',
n: 'North%{year}',
s: 'South%{year}',
e: 'East%{year}',
w: 'West%{year}',
ne: 'Northeast%{year}',
nw: 'Northwest%{year}',
se: 'Southeast%{year}',
sw: 'Southwest%{year}'
}
end
def self.generate_slug(type, year, city)
Conference.conference_types[(type || :annual).to_sym].gsub('%{city}', city).gsub('%{year}', year.to_s)
end
end end

4
app/models/conference_administrator.rb

@ -0,0 +1,4 @@
class ConferenceAdministrator < ActiveRecord::Base
belongs_to :user
belongs_to :conference
end

112
app/models/conference_registration.rb

@ -1,58 +1,58 @@
class ConferenceRegistration < ActiveRecord::Base class ConferenceRegistration < ActiveRecord::Base
belongs_to :conference belongs_to :conference
belongs_to :user belongs_to :user
has_many :conference_registration_responses has_many :conference_registration_responses
AttendingOptions = [:yes, :no] AttendingOptions = [:yes, :no]
def languages def languages
user.languages user.languages
end end
def self.all_housing_options def self.all_housing_options
[:none, :tent, :house] [:none, :tent, :house]
end end
def self.all_spaces def self.all_spaces
[:bed_space, :floor_space, :tent_space] [:bed_space, :floor_space, :tent_space]
end end
def self.all_bike_options def self.all_bike_options
[:yes, :no] [:yes, :no]
end end
def self.all_food_options def self.all_food_options
[:meat, :vegetarian, :vegan] [:meat, :vegetarian, :vegan]
end end
def self.all_considerations def self.all_considerations
[:vegan, :smoking, :pets, :quiet] [:vegan, :smoking, :pets, :quiet]
end end
def status(was = false) def status(was = false)
return :unregistered if user.firstname.blank? || self.send(was ? :city_was : :city).blank? return :unregistered if user.nil? || user.firstname.blank? || self.send(was ? :city_was : :city).blank?
return :registered if self.send(was ? :housing_was : :housing).present? || (self.send(was ? :can_provide_housing_was : :can_provide_housing) && (self.send(was ? :housing_data_was : :housing_data) || {})['availability'].present?) return :registered if self.send(was ? :housing_was : :housing).present? || (self.send(was ? :can_provide_housing_was : :can_provide_housing) && (self.send(was ? :housing_data_was : :housing_data) || {})['availability'].present?)
return :preregistered return :preregistered
end end
around_update :check_status around_update :check_status
def check_status def check_status
yield yield
old_status = status(true) old_status = status(true)
new_status = status new_status = status
if old_status.present? && old_status != new_status if old_status.present? && old_status != new_status
if (conference.registration_status == :pre && new_status == :preregistered) || if (conference.registration_status == :pre && new_status == :preregistered) ||
(conference.registration_status == :open && new_status == :registered) (conference.registration_status == :open && new_status == :registered)
UserMailer.send_mail :registration_confirmation do UserMailer.send_mail :registration_confirmation do
{ {
:args => self :args => self
} }
end end
end end
end end
end end
end end

26
app/models/event.rb

@ -1,20 +1,20 @@
class Event < ActiveRecord::Base class Event < ActiveRecord::Base
translates :info, :title translates :info, :title
belongs_to :conference belongs_to :conference
belongs_to :event_location belongs_to :event_location
def conference_day def conference_day
return nil unless start_time.present? && end_time.present? return nil unless start_time.present? && end_time.present?
start_day = conference.start_date.change(hour: 0, minute: 0, second: 0) start_day = conference.start_date.change(hour: 0, minute: 0, second: 0)
w_start_day = start_time.change(hour: 0, minute: 0, second: 0) w_start_day = start_time.change(hour: 0, minute: 0, second: 0)
return (((w_start_day - start_day) / 86400) + 1).to_i return (((w_start_day - start_day) / 86400) + 1).to_i
end end
def duration def duration
return nil unless start_time.present? && end_time.present? return nil unless start_time.present? && end_time.present?
((end_time - start_time) / 60).to_i ((end_time - start_time) / 60).to_i
end end
end end

6
app/models/locations_organization.rb

@ -1,6 +1,6 @@
class LocationsOrganization < ActiveRecord::Base class LocationsOrganization < ActiveRecord::Base
belongs_to :location belongs_to :location
belongs_to :organization belongs_to :organization
self.primary_key = :location_id self.primary_key = :location_id
end end

120
app/models/organization.rb

@ -1,71 +1,77 @@
class Organization < ActiveRecord::Base class Organization < ActiveRecord::Base
mount_uploader :logo, LogoUploader mount_uploader :logo, LogoUploader
mount_uploader :avatar, AvatarUploader mount_uploader :avatar, AvatarUploader
mount_uploader :cover, CoverUploader mount_uploader :cover, CoverUploader
has_many :locations_organization has_many :locations_organization
has_many :locations, :through => :locations_organization has_many :locations, through: :locations_organization
has_many :user_organization_relationships, :dependent => :destroy has_many :user_organization_relationships, dependent: :destroy
has_many :users, :through => :user_organization_relationships has_many :users, through: :user_organization_relationships
accepts_nested_attributes_for :locations, :reject_if => proc {|l| l[id].blank?} accepts_nested_attributes_for :locations, :reject_if => proc {|l| l[id].blank?}
accepts_nested_attributes_for :user_organization_relationships, :reject_if => proc {|u| u[:user_id].blank?}, :allow_destroy => true accepts_nested_attributes_for :user_organization_relationships, :reject_if => proc {|u| u[:user_id].blank?}, :allow_destroy => true
before_create :make_slug before_create :make_slug
def location def location
locations.first locations.first
end end
def longitude def longitude
location.longitude location.longitude
end end
def latitude def latitude
location.latitude location.latitude
end end
def to_param def to_param
slug slug
end end
def host?(user) def host?(user)
return false unless user.present? return false unless user.present?
return true if user.administrator? return true if user.administrator?
users.each do |u| users.each do |u|
return true if u.id == user.id return true if u.id == user.id
end end
return false return false
end end
def generate_slug(name, location = nil) def generate_slug(name, location = nil)
s = name.gsub(/[^a-z1-9]+/i, '-').chomp('-').gsub(/\-([A-Z])/, '\1') s = name.gsub(/[^a-z1-9]+/i, '-').chomp('-').gsub(/\-([A-Z])/, '\1')
if Organization.find_by(:slug => s).present? && !location.nil? if Organization.find_by(:slug => s).present? && !location.nil?
if location.city.present? if location.city.present?
s += '-' + location.city s += '-' + location.city
end end
if Organization.find_by(:slug => s).present? && location.territory.present? if Organization.find_by(:slug => s).present? && location.territory.present?
s += '-' + location.territory s += '-' + location.territory
end end
if Organization.find_by(:slug => s).present? if Organization.find_by(:slug => s).present?
s += '-' + location.country s += '-' + location.country
end end
end end
attempt = 1 attempt = 1
ss = s ss = s
while Organization.find_by(:slug => s) while Organization.find_by(:slug => s)
attempt += 1 attempt += 1
s = ss + '-' + attempt.to_s s = ss + '-' + attempt.to_s
end end
s s
end end
private def self.find_by_city(city)
def make_slug Organization.joins(:locations).where(locations: {
if !self.slug city_id: city.is_a?(City) ? city.id : city
self.slug = generate_slug(self.name, self.locations && self.locations[0]) })
end end
end
private
def make_slug
if !self.slug
self.slug = generate_slug(self.name, self.locations && self.locations[0])
end
end
end end

91
app/models/user.rb

@ -1,62 +1,67 @@
class User < ActiveRecord::Base class User < ActiveRecord::Base
authenticates_with_sorcery! do |config| authenticates_with_sorcery! do |config|
config.authentications_class = Authentication config.authentications_class = Authentication
end end
validates :email, uniqueness: true validates :email, uniqueness: true
mount_uploader :avatar, AvatarUploader mount_uploader :avatar, AvatarUploader
has_many :user_organization_relationships has_many :user_organization_relationships
has_many :organizations, through: :user_organization_relationships has_many :organizations, through: :user_organization_relationships
has_many :authentications, :dependent => :destroy has_many :conferences, through: :conference_administrators
accepts_nested_attributes_for :authentications has_many :authentications, :dependent => :destroy
accepts_nested_attributes_for :authentications
before_update do |user| before_update do |user|
user.locale ||= I18n.locale user.locale ||= I18n.locale
end end
before_save do |user| before_save do |user|
user.locale ||= I18n.locale user.locale ||= I18n.locale
end end
def can_translate?(to_locale = nil, from_locale = nil) def can_translate?(to_locale = nil, from_locale = nil)
is_translator unless to_locale.present? is_translator unless to_locale.present?
from_locale = I18n.locale unless from_locale.present? from_locale = I18n.locale unless from_locale.present?
return languages.present? && return languages.present? &&
to_locale.to_s != from_locale.to_s && to_locale.to_s != from_locale.to_s &&
languages.include?(to_locale.to_s) && languages.include?(to_locale.to_s) &&
languages.include?(from_locale.to_s) languages.include?(from_locale.to_s)
end end
def name def name
firstname || username || email firstname || username || email
end end
def named_email def named_email
name = firstname || username name = firstname || username
return email unless name return email unless name
return "#{name} <#{email}>" return "#{name} <#{email}>"
end end
def administrator? def administrator?
role == 'administrator' role == 'administrator'
end end
def self.AVAILABLE_LANGUAGES def self.AVAILABLE_LANGUAGES
[:en, :es, :fr, :ar] [:en, :es, :fr, :ar]
end end
def self.get(email) def self.get(email)
user = where(email: email).first user = where(email: email).first
unless user unless user
user = new(email: email) user = new(email: email)
user.save! user.save!
end end
return user
end
return user def self.find_user(email)
end User.where('lower(email) = ?', email.downcase).first
end
end end

12
app/models/user_organization_relationship.rb

@ -1,11 +1,11 @@
class UserOrganizationRelationship < ActiveRecord::Base class UserOrganizationRelationship < ActiveRecord::Base
belongs_to :user belongs_to :user
belongs_to :organization belongs_to :organization
Administrator = 'administrator' Administrator = 'administrator'
Member = 'member' Member = 'member'
DefaultRelationship = Member DefaultRelationship = Member
AllRelationships = [Administrator, Member] AllRelationships = [Administrator, Member]
end end

192
app/uploaders/avatar_uploader.rb

@ -3,102 +3,102 @@ require 'carrierwave/processing/mini_magick'
class AvatarUploader < CarrierWave::Uploader::Base class AvatarUploader < CarrierWave::Uploader::Base
include CarrierWave::ImageOptimizer include CarrierWave::ImageOptimizer
include CarrierWave::MiniMagick include CarrierWave::MiniMagick
# Include RMagick or MiniMagick support: # Include RMagick or MiniMagick support:
# include CarrierWave::RMagick # include CarrierWave::RMagick
# include CarrierWave::MiniMagick # include CarrierWave::MiniMagick
# Choose what kind of storage to use for this uploader: # Choose what kind of storage to use for this uploader:
storage :file storage :file
process :optimize process :optimize
@@sizes = {:thumb => [120, 120], :icon => [48, 48], :preview => [360, 120], :normal => [512, 512]} @@sizes = {:thumb => [120, 120], :icon => [48, 48], :preview => [360, 120], :normal => [512, 512]}
# storage :fog # storage :fog
# Override the directory where uploaded files will be stored. # Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted: # This is a sensible default for uploaders that are meant to be mounted:
def store_dir def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end end
# Provide a default URL as a default if there hasn't been a file uploaded: # Provide a default URL as a default if there hasn't been a file uploaded:
def default_url # def default_url
# # For Rails 3.1+ asset pipeline compatibility: # # For Rails 3.1+ asset pipeline compatibility:
# # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
# #
#"/images/fallback/" + [version_name, "default.png"].compact.join('_') #"/images/fallback/" + [version_name, "default.png"].compact.join('_')
"http://placehold.it/" + (@@sizes[version_name] || [300, 300]).join('x') # "http://placehold.it/" + (@@sizes[version_name] || [300, 300]).join('x')
end #end
# Process files as they are uploaded: # Process files as they are uploaded:
# process :scale => [200, 300] # process :scale => [200, 300]
# #
#def scale(width, height) #def scale(width, height)
#end #end
# Create different versions of your uploaded files: # Create different versions of your uploaded files:
version :thumb do version :thumb do
process :resize_to_fill => @@sizes[:thumb] process :resize_to_fill => @@sizes[:thumb]
end end
version :icon do version :icon do
process :resize_to_fill => @@sizes[:icon] process :resize_to_fill => @@sizes[:icon]
end end
version :preview do version :preview do
process :resize_to_fit => @@sizes[:preview] process :resize_to_fit => @@sizes[:preview]
end end
version :normal do version :normal do
process :resize_to_fit => @@sizes[:normal] process :resize_to_fit => @@sizes[:normal]
end end
# Add a white list of extensions which are allowed to be uploaded. # Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this: # For images you might use something like this:
# def extension_white_list # def extension_white_list
# %w(jpg jpeg gif png) # %w(jpg jpeg gif png)
# end # end
# Override the filename of the uploaded files: # Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details. # Avoid using model.id or version_name here, see uploader/store.rb for details.
# def filename # def filename
# "something.jpg" if original_filename # "something.jpg" if original_filename
# end # end
def image def image
@image ||= MiniMagick::Image.open(file.path) @image ||= MiniMagick::Image.open(file.path)
end end
def is_landscape? def is_landscape?
image['width'] > (image['height'] * 1.25) image['width'] > (image['height'] * 1.25)
end end
#def recreate_versions!(*versions) #def recreate_versions!(*versions)
# if !current_path.nil? # if !current_path.nil?
# current_path = "'" + (current_path || '') + "'" # current_path = "'" + (current_path || '') + "'"
# end # end
# super(*versions) # super(*versions)
#end #end
# def manipulate! # def manipulate!
# cache_stored_file! if !cached? # cache_stored_file! if !cached?
# image = ::MiniMagick::Image.open(current_path) # image = ::MiniMagick::Image.open(current_path)
# #
# begin # begin
# image.format(@format.to_s.downcase) if @format # image.format(@format.to_s.downcase) if @format
# image = yield(image) # image = yield(image)
# image.write(current_path) # image.write(current_path)
# image.run_command("identify", '"' + current_path + '"') # image.run_command("identify", '"' + current_path + '"')
# ensure # ensure
# image.destroy! # image.destroy!
# end # end
# rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e # rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e
# default = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :locale => :en) # default = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :locale => :en)
# message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :default => default) # message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :default => default)
# raise CarrierWave::ProcessingError, message # raise CarrierWave::ProcessingError, message
# end # end
end end

102
app/uploaders/poster_uploader.rb

@ -3,67 +3,67 @@ require 'carrierwave/processing/mini_magick'
class PosterUploader < CarrierWave::Uploader::Base class PosterUploader < CarrierWave::Uploader::Base
include CarrierWave::ImageOptimizer include CarrierWave::ImageOptimizer
include CarrierWave::MiniMagick include CarrierWave::MiniMagick
storage :file storage :file
process :optimize process :optimize
@@sizes = { @@sizes = {
:thumb => [120, 120], :thumb => [120, 120],
:icon => [48, 48], :icon => [48, 48],
:preview => [512, 512], :preview => [512, 512],
:full => [1024, 1024] :full => [1024, 1024]
} }
def store_dir def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end end
version :thumb do version :thumb do
process :resize_to_fill => @@sizes[:thumb] process :resize_to_fill => @@sizes[:thumb]
end end
version :icon do version :icon do
process :resize_to_fill => @@sizes[:icon] process :resize_to_fill => @@sizes[:icon]
end end
version :preview do version :preview do
process :resize_to_fit => @@sizes[:preview] process :resize_to_fit => @@sizes[:preview]
end end
version :full do version :full do
process :resize_to_fit => @@sizes[:full] process :resize_to_fit => @@sizes[:full]
end end
def image def image
@image ||= MiniMagick::Image.open(file.path) @image ||= MiniMagick::Image.open(file.path)
end end
def is_landscape? def is_landscape?
image['width'] > image['height'] image['width'] > image['height']
end end
def manipulate! def manipulate!
cache_stored_file! if !cached? cache_stored_file! if !cached?
image = ::MiniMagick::Image.open(current_path) image = ::MiniMagick::Image.open(current_path)
begin begin
image.format(@format.to_s.downcase) if @format image.format(@format.to_s.downcase) if @format
image = yield(image) image = yield(image)
image.write(current_path) image.write(current_path)
begin begin
image.run_command("identify", current_path) image.run_command("identify", current_path)
rescue rescue
image.run_command("identify", '"' + current_path + '"') image.run_command("identify", '"' + current_path + '"')
end end
ensure ensure
image.destroy! image.destroy!
end end
rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e
default = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :locale => :en) default = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :locale => :en)
message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :default => default) message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :default => default)
raise CarrierWave::ProcessingError, message raise CarrierWave::ProcessingError, message
end end
end end

24
app/views/admin/new.html.haml

@ -0,0 +1,24 @@
- body_class 'banner-bottom'
= render :partial => 'application/header', :locals => {:page_group => :administration, :page_key => 'Administration', :image_file => 'admin.jpg'}
%article
= row do
= form_tag save_conference_path, class: 'composition' do
= columns(medium: 12) do
%h2=_@page_title, :t
= hidden_field_tag :id, @this_conference.id if @this_conference.id.present?
= columns(medium: 12) do
= textfield :city, location(@this_conference.city), required: true, big: true
= columns(medium: 12, class: 'flex-column') do
= selectfield :type, @this_conference.conferencetype, Conference.conference_types.keys.map { |k| [(_"forms.options.conferences.types.#{k}"), k]}, required: true, stretch: true
= numberfield :year, @this_conference.conference_year || Date.today.year, required: true
= columns(medium: 6) do
= checkbox :is_public, @this_conference.is_public != false, 'forms.labels.generic.is_public'
= columns(medium: 6) do
= checkbox :is_featured, @this_conference.is_featured != false, 'forms.labels.generic.is_featured'
= columns(medium: 12) do
.actions.next-prev
= button_tag :save, value: :save
- if @this_conference.id.present?
= button_with_confirmation :delete, value: :delete, class: 'delete'

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

@ -1,20 +1,20 @@
- content_for :banner do - content_for :banner do
- image = image_path(image_file || 'empty-racks.jpg') - image = image_path(image_file || 'empty-racks.jpg')
- style = nil - style = nil
- cover = nil - cover = nil
- 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>"
- 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 defined?(banner_image) == "local-variable" = (render banner_image, {:image => image}) if defined?(banner_image) == "local-variable"
= cover.html_safe if cover = cover.html_safe if cover
- if @page_title.present? || defined?(page_group) - if @page_title.present? || defined?(page_group)
- content_for :title do - content_for :title do
=@page_title.present? ? I18n.t(@page_title, @page_title_vars) : I18n.t("page_titles.#{page_group.to_s}.#{page_key.to_s}") = I18n.t(@page_title || "page_titles.#{page_group.to_s}.#{page_key.to_s}", @page_title_vars)
= row do = row do
= columns do = columns do
%h1=_(@main_title || "page_titles.#{page_group.to_s}.#{page_key.to_s}", :t, @main_title_vars) %h1=_(@main_title || "page_titles.#{page_group.to_s}.#{page_key.to_s}", :t, @main_title_vars || (@page_title_vars.present? && @page_title.blank? ? { vars: @page_title_vars } : nil))
- content_for :og_image do - content_for :og_image do
= image = image

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

@ -1,17 +1,5 @@
- this_is_the_front_page - content_for :og_image do
- if @conference = @conference.poster.full.url || image_path('default_poster.jpg')
= render 'conferences/header' - if @conferences
%article - @conferences.each do | conference |
= row do = render 'conferences/conference', conference: conference, links: [ :read_more, :register ]
= columns(medium: 10, push: {medium: 1}) do
%h2=_!@conference.title
= richtext @conference.info
- if @conference.registration_status == :open
- if @conference.workshop_schedule_published
- add_inline_script :home_schedule
%h3=_'articles.workshops.headings.Schedule'
= render 'conferences/admin/schedule'
- else
%h3=_'articles.workshops.headings.Proposed_Workshops'
%p=_'articles.workshops.paragraphs.Proposed_Workshops'
= render 'workshops/workshop_previews', :workshops => (@conference.workshops.sort { |a, b| a.title.downcase <=> b.title.downcase })

44
app/views/application/user_settings.html.haml

@ -1,25 +1,25 @@
= render :partial => 'application/header', :locals => {:image_file => @banner_image || 'grafitti.jpg'} = render :partial => 'application/header', :locals => {:image_file => @banner_image || 'grafitti.jpg'}
%article %article
= row do = row do
= columns do = columns do
- if logged_in? - if logged_in?
%h2=_'articles.user_settings.headings.Your_Account' %h2=_'articles.user_settings.headings.Your_Account'
- if @conference.present? && (@conference.registration_status == :pre || @conference.registration_status == :open) - if @conference.present? && (@conference.registration_status == :pre || @conference.registration_status == :open)
%p=_'articles.user_settings.paragraphs.conference_registration', :t %p=_'articles.user_settings.paragraphs.conference_registration', :t
= link_to (_'actions.conference.edit_registration'), register_path(@conference.slug), class: :button = link_to (_'actions.conference.edit_registration'), register_path(@conference.slug), class: :button
- if @conferences.present? - if @conferences.present?
%h3=_'articles.user_settings.headings.Your_Conferences' %h3=_'articles.user_settings.headings.Your_Conferences'
.link-dump .link-dump
- @conferences.each do | conference | - @conferences.each do | conference |
= link_to (_!conference.title), administration_step_path(conference.slug, :edit), class: :button = link_to (_!conference.title), administration_step_path(conference.slug, :edit), class: :button
= form_tag update_settings_path do = form_tag update_settings_path do
= textfield :name, current_user.name, required: true, heading: 'articles.conference_registration.headings.name', big: true = textfield :name, current_user.name, required: true, heading: 'articles.conference_registration.headings.name', big: true
= checkboxes :languages, User.AVAILABLE_LANGUAGES, current_user.languages || [I18n.locale], 'languages', heading: 'articles.conference_registration.headings.languages' = checkboxes :languages, User.AVAILABLE_LANGUAGES, current_user.languages || [I18n.locale], 'languages', heading: 'articles.conference_registration.headings.languages'
= radiobuttons :preferred_language, I18n.backend.enabled_locales, current_user.locale || I18n.locale, 'languages', heading: 'articles.conference_registration.headings.preferred_language' = radiobuttons :preferred_language, I18n.backend.enabled_locales, current_user.locale || I18n.locale, 'languages', heading: 'articles.conference_registration.headings.preferred_language'
= checkbox :email_subscribe, current_user.is_subscribed != false, 'articles.user_settings.email_subscribe', heading: 'articles.user_settings.headings.email_subscribe', help: 'articles.user_settings.paragraphs.email_subscribe', inline: true, right_help: true = checkbox :email_subscribe, current_user.is_subscribed != false, 'articles.user_settings.email_subscribe', heading: 'articles.user_settings.headings.email_subscribe', help: 'articles.user_settings.paragraphs.email_subscribe', inline: true, right_help: true
.actions .actions
= button_tag :save, value: :save = button_tag :save, value: :save
- else - else
%h2=_'forms.actions.generic.login' %h2=_'forms.actions.generic.login'
= render 'login' = render 'login'

41
app/views/conference_administration/_administrators.html.haml

@ -0,0 +1,41 @@
= columns(large: 8, push: { large: 2}) do
%h3=_'articles.admin.info.headings.Host_Organizations'
%p=_'articles.admin.info.descriptions.Host_Organizations', vars: { city_name: @this_conference.city.city }
= admin_update_form do
= checkboxes :organizations, (@organizations.map { |org| [org.name, org.id] }), @this_conference.organizations.map(&:id), 'test.test', vertical: true, big: true
.actions.right.small
= button_tag :save, value: :set_organizations
- @this_conference.organizations.each do | organization |
%h4=organization.name
- if organization.users.present?
.details.org-members
- organization.users.each do | user |
= raw_data_set(:h5, user.name) do
= user.email
- unless user.id == current_user.id && !current_user.administrator?
= admin_update_form class: [:inline, :right] do
= hidden_field_tag :user_id, user.id
= hidden_field_tag :org_id, organization.id
= button_tag :remove_member, value: :remove_org_member, class: [:small, :delete]
= admin_update_form class: 'mini-flex-form' do
= hidden_field_tag :org_id, organization.id
= emailfield :email, nil, required: true
= button_tag :add_member, value: :add_org_member, class: :small
%h3=_'articles.admin.info.headings.External_Administrators'
%p=_'articles.admin.info.descriptions.External_Administrators'
- if @this_conference.administrators.present?
.details.org-members
- @this_conference.administrators.each do | user |
= raw_data_set(:h5, user.name) do
= user.email
- unless user.id == current_user.id && !current_user.administrator?
= admin_update_form class: [:inline, :right] do
= hidden_field_tag :user_id, user.id
= button_tag :remove_member, value: :remove_administrator, class: [:small, :delete]
= admin_update_form class: 'mini-flex-form' do
= userfield :email, nil, required: true
-#= emailfield :email, nil, required: true
= button_tag :add_member, value: :add_administrator, class: :small
= columns(large: 2) do
&nbsp;

23
app/views/conference_administration/_broadcast.html.haml

@ -0,0 +1,23 @@
= columns(medium: 12) do
= admin_update_form do
- if @broadcast_step == :preview || @broadcast_step == :test
= hidden_field_tag :subject, @subject
= hidden_field_tag :body, @body
= hidden_field_tag :send_to, @send_to
- if @broadcast_step == :preview
%p= _'articles.conference_registration.paragraphs.admin.broadcast.test', vars: { send_to_count: "<strong>#{(@send_to_count || 0)}</strong>".html_safe }
- else
.warning-info.make-room= _'articles.conference_registration.paragraphs.admin.broadcast.preview', vars: { send_to_count: "<strong>#{(@send_to_count || 0)}</strong>".html_safe }
.test-preview
%h3=@subject
= richtext @body, 4
.actions.right
= button_tag :test, value: :test, class: :secondary if @broadcast_step == :preview
= button_with_confirmation :send, (_'modals.admin.broadcast.confirm', vars: { number: "<strong>#{(@send_to_count || 0)}</strong>".html_safe }), value: :send, class: :delete if @broadcast_step == :test
= button_tag :edit, value: :edit
- else
= selectfield :send_to, nil, broadcast_options, full: true
= textfield :subject, @subject, required: true, big: true
= textarea :body, @body, lang: @this_conference.locale, edit_on: :focus
.actions.right
= button_tag :preview, value: :preview

0
app/views/conferences/admin/_broadcast_sent.html.haml → app/views/conference_administration/_broadcast_sent.html.haml

17
app/views/conference_administration/_dates.html.haml

@ -0,0 +1,17 @@
= admin_update_form do
= row do
= columns(medium: 6, push: { medium: 1 }) do
= fieldset :start_date, inline: true, inline_label: true do
= month_select @start_month, name: :start_month, label: false
= month_day_select @start_day, name: :start_day, label: false
= row do
= columns(medium: 6, push: { medium: 1 }) do
= fieldset :end_date, inline: true, inline_label: true do
= month_select @end_month, name: :end_month, label: false
= month_day_select @end_day, name: :end_day, label: false
= row do
= columns(medium: 6, push: { medium: 1 }) do
.actions
= button_tag :save, value: :save

5
app/views/conference_administration/_description.html.haml

@ -0,0 +1,5 @@
= columns(medium: 12) do
= admin_update_form do
= translate_textarea :info, @this_conference, label: 'articles.conference_registration.headings.admin.edit.info', help: 'articles.conference_registration.paragraphs.admin.edit.info', edit_on: :focus
.actions.right
= button_tag :save, value: :save

41
app/views/conference_administration/_events.html.haml

@ -0,0 +1,41 @@
- if @this_conference.event_locations.blank?
= columns(medium: 12) do
.warning-info=_'articles.admin.events.no_locations_warning'
- else
= columns(medium: 12) do
- if @events.present? && @event.id.blank?
%table.events.admin-edit
%tr
%th=_'forms.labels.generic.title'
%th=_'forms.labels.generic.event_location'
%th=_'forms.labels.generic.day'
%th=_'forms.labels.generic.time'
%th=_'forms.labels.generic.time_span'
%th.form
- @events.each do | event |
%tr
%th=event.title
%td=_!(event.event_location.present? ? event.event_location.title : '')
%td=date(event.start_time.to_date, :weekday)
%td=time(event.start_time, :short)
%td=hours(event.start_time, event.end_time)
%td.form
= admin_update_form do
= hidden_field_tag :id, event.id
= link_to (_'forms.actions.generic.edit'), edit_event_path(@this_conference, event.id), class: [:button, :small, :modify]
= button_with_confirmation :delete, (_'modals.admin.generic.delete.confirm', :p, vars: { title: event.title }), value: :delete, class: [:delete, :small]
= columns(medium: 12) do
%h3=_"articles.admin.locations.headings.#{@event.id.present? ? 'edit' : 'add'}_event", :t
= admin_update_form do
= hidden_field_tag :id, @event.id if @event.id.present?
.flex-inputs
= location_select @event.event_location_id, small: true, stretch: true
= day_select @day, small: true, format: :weekday
= hour_select @time, small: true
= length_select @length, small: true
= translate_fields @event, { title: { type: :textfield, big: true, label: 'forms.labels.generic.title' }, info: { type: :textarea, label: 'forms.labels.generic.info', edit_on: :focus } }
.actions.next-prev
= button_tag :save, value: :save
= button_tag :cancel, value: :cancel, class: :subdued, formnovalidate: true if @event.id.present?

11
app/views/conference_administration/_hosts_table.html.haml

@ -0,0 +1,11 @@
.guests-housed
%h5 Guests Housed:
.data="#{@guests_housed} / #{@guests.size}"
%table.hosts.admin-edit
- @hosts.each do | id, registration |
%tr.host
%th
.name=registration.user.name
.address=registration.housing_data['address']
%td.inner-table{colspan: 2}=host_guests_table(registration)

10
app/views/conference_administration/_housing.html.haml

@ -0,0 +1,10 @@
- add_inline_script :housing
= columns(medium: 12) do
= admin_update_form id: 'housing-table-form' do
#housing-table= render partial: 'hosts_table'
#guest-selector
= admin_update_form class: 'guest-dlg', id: 'guest-list-table' do
%h3 Select a Guest
#table
.actions
= link_to (_'links.download.Excel'), administration_step_path(@this_conference.slug, @admin_step, :format => :xlsx), class: [:button, :download]

42
app/views/conference_administration/_locations.html.haml

@ -0,0 +1,42 @@
- unless @location.present?
- if @locations.present?
= columns(medium: 12) do
%table.locations.admin-edit
%tr
%th=_'forms.labels.generic.title'
%th=_'forms.labels.generic.address'
%th=_'articles.workshops.headings.space'
%th=_'articles.admin.locations.headings.amenities'
%th.form
- @locations.each do | location |
%tr
%th=_!(location.title || '')
%td=location_link location
%td=location.space.present? ? (_"workshop.options.space.#{location.space}") : ''
%td
- amenities = location.amenities.present? ? JSON.parse(location.amenities) : []
=_!(amenities.collect { |amenity| _"workshop.options.needs.#{amenity}" }).join(', ')
%td.form
= admin_update_form do
= hidden_field_tag :id, location.id
= link_to (_'forms.actions.generic.edit'), edit_location_path(@this_conference, location.id), class: [:button, :small, :modify]
= button_with_confirmation :delete, (_'modals.admin.generic.delete.confirm', :p, vars: { title: location.title }), value: :delete, class: [:delete, :small]
= admin_update_form do
= columns(medium: 12) do
%h5=_"articles.admin.locations.headings.#{@location.present? ? 'edit' : 'add'}_location", :t
= hidden_field_tag :id, @location.id if @location.present?
= textfield :title, @location.present? ? @location.title : nil, required: true, big: true, help: 'articles.admin.locations.paragraphs.title'
.flex-column.address-form
= textfield :address, @location.present? ? @location.address : nil, required: true, help: 'articles.admin.locations.paragraphs.address', stretch: true
.city=location(@this_conference.city)
= columns(medium: 6) do
= radiobuttons :space, EventLocation.all_spaces, @space, 'workshop.options.space', vertical: true, heading: 'articles.workshops.headings.space', required: true, help: 'articles.admin.locations.paragraphs.space'
= columns(medium: 6) do
= checkboxes :needs, EventLocation.all_amenities, @amenities || [], 'workshop.options.needs', vertical: true, heading: 'articles.admin.locations.headings.amenities', help: 'articles.admin.locations.paragraphs.amenities'
= columns(medium: 12) do
.actions.next-prev
- if @location.present?
= button_tag :save, value: :save
= button_tag :cancel, value: :cancel, class: :subdued, formnovalidate: true
- else
= button_tag :create, value: :create

35
app/views/conference_administration/_meals.html.haml

@ -0,0 +1,35 @@
= columns(medium: 12) do
- if @this_conference.event_locations.present?
- if @this_conference.meals.present?
%table.meals.admin-edit
%tr
%th=_'forms.labels.generic.title'
%th=_'forms.labels.generic.info'
%th=_'forms.labels.generic.event_location'
%th=_'forms.labels.generic.day'
%th=_'forms.labels.generic.time'
%th.form
- @meals.each do | time, meal |
%tr
%th
=_!(meal['title'] || '')
%td=_!(meal['info'] || '')
%td=_!location_name(meal['location'].to_i)
%td=date(meal['day'], :weekday)
%td=time(meal['time'].to_f)
%td.form
= admin_update_form do
= hidden_field_tag :meal, time
= button_tag :delete, value: :delete, class: [:small, :delete]
= admin_update_form do
%h3=_'articles.admin.locations.headings.add_meal', :t
.flex-inputs
= location_select nil, small: true, stretch: true
= day_select nil, small: true, format: :weekday
= hour_select nil, small: true
= textfield :title, nil, required: true, big: true, help: 'articles.admin.locations.paragraphs.meal_title'
= textfield :info, nil, help: 'articles.admin.locations.paragraphs.meal_info'
.actions.next-prev
= button_tag :add_meal, value: :add_meal
- else
.warning-info=_'articles.admin.meals.no_locations_warning'

6
app/views/conference_administration/_payment_message.html.haml

@ -0,0 +1,6 @@
= columns(medium: 12) do
= form_tag administration_update_path(@this_conference.slug, @admin_step) do
= translate_textarea :payment_message, @this_conference, default: 'articles.conference_registration.paragraphs.Payment', help: 'articles.conference_registration.paragraphs.admin.payment.message', edit_on: :focus, short: true
.actions.right
= button_tag :save, value: :save

8
app/views/conference_administration/_paypal.html.haml

@ -0,0 +1,8 @@
= columns(medium: 12) do
= form_tag administration_update_path(@this_conference.slug, @admin_step) do
= emailfield :paypal_email_address, @this_conference.paypal_email_address || @this_conference.email_address || (@this_conference.organizations.present? && @this_conference.organizations.first.present? ? @this_conference.organizations.first.email_address : nil)
= textfield :paypal_username, @this_conference.paypal_username
= passwordfield :paypal_password, @this_conference.paypal_password
= textfield :paypal_signature, @this_conference.paypal_signature
.actions.right
= button_tag :save, value: :save

5
app/views/conference_administration/_poster.html.haml

@ -0,0 +1,5 @@
= columns(medium: 12) do
= form_tag administration_update_path(@this_conference.slug, :poster), multipart: true do
= filefield :poster, @this_conference.poster, required: true, label: false, preview: true
.actions.left
= button_tag :upload, value: :upload, id: 'upload-file'

2
app/views/conference_administration/_providers.html.haml

@ -0,0 +1,2 @@
= columns(medium: 12) do
TO COME

8
app/views/conference_administration/_publish_schedule.html.haml

@ -0,0 +1,8 @@
= columns(medium: 12) do
= form_tag administration_update_path(@this_conference.slug, @admin_step) do
- if @this_conference.workshop_schedule_published
%p=_'articles.conference_registration.paragraphs.admin.schedule.published', :p
.actions= button_tag :un_publish, value: :publish, class: :delete
- else
%p=_'articles.conference_registration.paragraphs.admin.schedule.un_published', :p
.actions= button_tag :publish, value: :publish

5
app/views/conference_administration/_registration_status.html.haml

@ -0,0 +1,5 @@
= columns(medium: 12) do
= form_tag administration_update_path(@this_conference.slug, @admin_step) do
= selectfield :registration_status, @this_conference.registration_status || 'closed', registration_status_options_list, inline_label: true
.actions.left
= button_tag :save, value: :save

17
app/views/conference_administration/_registrations.html.haml

@ -0,0 +1,17 @@
- add_inline_script :registrations
= columns(medium: 12) do
.goes-fullscreen#registrations-table
.flex-column
= searchfield :search, nil, big: true, stretch: true
%a.button{data: { expands: 'registrations-table' }}='expand'
%a.button.delete{data: { contracts: 'registrations-table' }}='close'
%a.button.modify{data: { 'opens-modal': 'new-registration' }}='+'
.table-scroller
= html_table @excel_data, registrations_table_options
= admin_update_form id: 'new-registration', class: 'modal-edit' do
.modal-edit-overlay{data: { 'closes-modal': 'new-registration' }}
.modal-edit-content
= html_edit_table @excel_data, registrations_edit_table_options
.actions.right
%a.button.subdued{data: { 'closes-modal': 'new-registration' }}='Cancel'
= button_tag :save, value: :save, class: :modify

85
app/views/conference_administration/_schedule.html.haml

@ -0,0 +1,85 @@
= columns(medium: 12) do
- conference = @this_conference || @conference
- if conference.event_locations.blank? && @entire_page
.warning-info=_'articles.admin.schedule.no_locations_warning'
- else
- add_inline_script :schedule if @entire_page
#schedule-preview
- @schedule.each do | day, data |
%h4=date(day, :weekday)
%table.schedule{class: [data[:locations].present? ? 'has-locations' : 'no-locations', "locations-#{data[:num_locations]}"]}
- if data[:locations].present? && data[:locations].values.first != :add
%thead
%tr
%th.corner
- data[:locations].each do | id, location |
%th=location.is_a?(Symbol) ? '' : location.title
%tbody
- data[:times].each do | time, time_data |
%tr
- rowspan = (time_data[:length] * 2).to_i
%th=time(time)
- if time_data[:type] == :workshop
- data[:locations].each do | id, location |
- if time_data[:item][:workshops][id].present?
- workshop = time_data[:item][:workshops][id][:workshop]
- status = time_data[:item][:workshops][id][:status]
- else
- workshop = status = nil
%td{class: [time_data[:type], workshop.present? ? :filled : :open], rowspan: rowspan, data: workshop.present? ? nil : { block: time_data[:item][:block], day: day, location: id }}
- if workshop.present? && workshop.event_location.present?
= link_to view_workshop_path(@conference.slug, workshop.id), class: 'event-detail-link' do
.details
.title=workshop.title
%template.event-details{data: { href: view_workshop_path(@conference.slug, workshop.id) }}
%h1.title=workshop.title
%p.address
= workshop.event_location.title + _!(': ')
= location_link workshop.event_location
.workshop-description= richtext workshop.info, 1
- if @can_edit
= form_tag administration_update_path(conference.slug, @admin_step), class: 'deschedule-workshop' do
.status
.conflict-score
%span.title Conflicts:
%span.value="#{status[:conflict_score]} / #{workshop.interested.size}"
- if status[:errors].present?
.errors
- status[:errors].each do | error |
.error=_"errors.messages.schedule.#{error[:name].to_s}", vars: error[:i18nVars]
= hidden_field_tag :id, workshop.id
= button_tag :deschedule, value: :deschedule_workshop, class: [:delete, :small]
- elsif @can_edit
.title="Block #{time_data[:item][:block] + 1}"
- elsif time_data[:type] != :nil
%td{class: time_data[:type], rowspan: rowspan, colspan: data[:locations].present? ? data[:locations].size : 1}
- case time_data[:type]
- when :meal
- location = EventLocation.where(id: time_data[:item]['location'].to_i).first
- if location.present?
%a.event-detail-link
.details
.title= time_data[:item]['title']
.location= location.title
%template.event-details
%h1.title=time_data[:item]['title']
%p.address
= location.title + _!(': ')
= location_link location
- when :event
- if time_data[:item].event_location.present?
%a.event-detail-link
.details
.title= time_data[:item][:title]
.location= time_data[:item].event_location.title
%template.event-details
%h1.title=time_data[:item][:title]
%p.address
= time_data[:item].event_location.title + _!(': ')
= location_link time_data[:item].event_location
= richtext time_data[:item][:info], 1
- if @entire_page
#workshop-selector
= form_tag administration_update_path(@this_conference.slug, @admin_step), class: 'workshop-dlg', id: 'workshop-table-form' do
%h3 Select a Workshop
#table

46
app/views/conference_administration/_select_guest_table.html.haml

@ -0,0 +1,46 @@
= hidden_field_tag :host, host.id
.host-field
%h4.inline=_'forms.labels.generic.name'
%span.plain-value= host.user.name
.host-field
%h4.inline=_'articles.conference_registration.headings.host.availability'
%span.plain-value= host.housing_data['availability'].present? && host.housing_data['availability'][1].present? ? date_span(host.housing_data['availability'][0].to_date, host.housing_data['availability'][1].to_date) : ''
- if host.housing_data['considerations'].present?
.host-field
%h4.inline=_'articles.conference_registration.headings.host.considerations'
%span.plain-value= (host.housing_data['considerations'].map { | consideration | _"articles.conference_registration.host.considerations.#{consideration}" }).join(', ')
- if sanitize(host.housing_data['notes'], tags: []).present?
.host-field
%h4=_'articles.conference_registration.headings.host.notes'
%blockquote= host.housing_data['notes'].html_safe
%table.guests.admin-edit
%tr
%th.corner
%th=_'forms.labels.generic.city'
%th=_'forms.labels.generic.housing'
%th=_'articles.admin.housing.headings.arrival_departure'
%th=_'articles.conference_registration.headings.companion'
%th=_'forms.labels.generic.food'
%th=_'forms.labels.generic.allergies'
%th=_'forms.labels.generic.other'
- @guests.each do | id, registration |
%tr.selectable{class: get_housing_match(host, registration, space).to_s.gsub('_', '-'), data: {host: host.id, guest: id, space: space}}
%th=registration.user.name
%td=registration.city
%td=registration.housing.present? ? (_"articles.conference_registration.questions.housing.#{registration.housing}") : ''
%td=date_span(registration.arrival.to_date, registration.departure.to_date)
- companion = companion(registration)
%td=companion.present? ? (companion.is_a?(User) ? companion.named_email : (_"articles.conference_registration.terms.registration_status.#{companion}")) : ''
%td=registration.food.present? ? (_"articles.conference_registration.questions.food.#{registration.food}") : ''
%td=registration.allergies
%td
.p=registration.other
.legend
%h4 Legend
%ul
%li.good-match Good Match
%li.bad-match Poor Match
%li.selected-space Also in this space
%li.other-space Also with this host
%li.other-host Already hosted

0
app/views/conferences/admin/_select_workshop_table.html.haml → app/views/conference_administration/_select_workshop_table.html.haml

21
app/views/conference_administration/_stats.html.haml

@ -0,0 +1,21 @@
= columns(medium: 12) do
.details
= data_set(:h3, 'articles.admin.stats.headings.completed_registrations') do
= (@completed_registrations || 0).to_s
= data_set(:h3, 'articles.admin.stats.headings.incomplete_registrations') do
= ((@registration_count - @completed_registrations) || 0).to_s
= data_set(:h3, 'articles.admin.stats.headings.bikes') do
= (@completed_registrations || 0) > 0 ? "#{@bikes} (#{number_to_percentage(@bikes / @completed_registrations.to_f * 100.0)})" : "0"
= data_set(:h3, 'articles.admin.stats.headings.food.meat') do
= (@food[:all] || 0) > 0 ? "#{@food[:meat]} (#{number_to_percentage(@food[:meat] / @food[:all].to_f * 100.0)})" : "0"
= data_set(:h3, 'articles.admin.stats.headings.food.vegetarian') do
= (@food[:all] || 0) > 0 ? "#{@food[:vegetarian]} (#{number_to_percentage(@food[:vegetarian] / @food[:all].to_f * 100.0)})" : "0"
= data_set(:h3, 'articles.admin.stats.headings.food.vegan') do
= (@food[:all] || 0) > 0 ? "#{@food[:vegan]} (#{number_to_percentage(@food[:vegan] / @food[:all].to_f * 100.0)})" : "0"
= data_set(:h3, 'articles.admin.stats.headings.donation_count') do
= (@completed_registrations || 0) > 0 ? "#{@donation_count} (#{number_to_percentage(@donation_count / @completed_registrations.to_f * 100.0)})" : "0"
= data_set(:h3, 'articles.admin.stats.headings.donation_total') do
= "$#{@donations || 0.00}"
.actions
= link_to (_'links.download.Excel'), administration_step_path(@this_conference.slug, :stats, :format => :xlsx), class: [:button, :download]
= link_to (_'links.download.Organizations_Excel'), administration_step_path(@this_conference.slug, :organizations, :format => :xlsx), class: [:button, :download, :subdued]

8
app/views/conference_administration/_suggested_amounts.html.haml

@ -0,0 +1,8 @@
= columns(medium: 12) do
= form_tag administration_update_path(@this_conference.slug, @admin_step) do
= fieldset :payment_amounts, label: false do
- payment_amounts = @this_conference.payment_amounts.present? ? @this_conference.payment_amounts : Conference.default_payment_amounts
- for i in 1..5 do
= numberfield "payment_amounts[#{i - 1}]", payment_amounts[i - 1], step: 0.01, min: 0.00, label: false
.actions.right
= button_tag :save, value: :save

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save