Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Marlena Compton
2014-11-23 14:36:21 -08:00
272 changed files with 4375 additions and 1813 deletions

View File

@@ -1 +1 @@
2.1.1
2.1.2

View File

@@ -1,9 +1,10 @@
---
language: ruby
env: GROWSTUFF_SITE_NAME="Growstuff (travis)"
bundler_args: --without development assets production staging
env: GROWSTUFF_SITE_NAME="Growstuff (travis)" RAILS_SECRET_TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
bundler_args: --without development production staging
rvm:
- 2.1.1
- 2.1.2
before_script:
- psql -c 'create database growstuff_test;' -U postgres
script:

View File

@@ -1,35 +1,13 @@
Thanks for contributing to Growstuff! We have different contribution
guidelines depending on whether your change is a small one (a one-line
bugfix or similar) or a larger one.
Thanks for contributing to Growstuff!
## Small changes
Please include the following information in your pull request:
Send us a pull request! We will get one of our pairs of coders to
review it.
If you are interested in becoming a more regular contributor or working
on larger features, please see the [Growstuff
wiki](http://wiki.growstuff.org/) for more information.
## Larger changes
Growstuff does pair programming (two coders working together) for all
new features and other significant changes. This means that if you
submit a pull request and weren't working in a pair, we're unlikely to
merge it into the project as-is.
If you would like to work on any larger change, we would appreciate it
if you would get in touch with us, preferably via our [mailing
list](http://lists.growstuff.org/mailman/listinfo/discuss), and talk to
us about it first. We'll try and hook you up with a partner so you can
work as a pair, either in person or remotely depending on where you are.
The [Growstuff wiki](http://wiki.growstuff.org/) has lots more
information on our dev process, to get you started if you would like to
join us.
If you submit a larger change without working in a pair, we will treat
your work as an experimental "spike" and get one of our pairs of
programmers to look over it and maybe use what you've done as the basis
for re-implementing it using our processes. **We'd much rather work
with you, so please talk to us first!**
* A link to the [Pivotal Tracker](http://tracker.growstuff.org/) task to which it relates.
* Your code should follow our [Coding style guide](http://wiki.growstuff.org/index.php/Coding_style_guide)
* Make sure you have automated tests for your work, where possible.
* Add your name (and that of your pair partner, if any) to [CONTRIBUTORS.md](CONTRIBUTORS.md).
If you would like to discuss your work before submitting a pull request,
please join any of our [Discussion
forums](http://wiki.growstuff.org/index.php/Discussion_forums), where
our dev team will be happy to help you.

View File

@@ -1,5 +1,5 @@
This is a list of contributors to Growstuff's codebase. We maintain
this list because we work in pairs, but Github only knows about the
this list because we often work in pairs, but Github only knows about the
person who actually does the commits. This gives credit to both members
of the pair.
@@ -42,3 +42,10 @@ submit the change with your pull request.
- Amelia Greenhall / [ameliagreenhall](https://github.com/ameliagreenhall)
- Barb Natali / [barbnatali](https://github.com/barbnatali)
- Taylor Griffin / [tygriffin](https://github.com/tygriffin)
- Marlena Compton / [Marlena](https://github.com/marlena)
- Elizabeth A. Kari / [catfriend](https://github.com/catfriend)
- Cheri Allen / [cherimarie](https://github.com/cherimarie)
- Maki Sugita / [macckii](https:://github.com/macckii)
- Shiho Takagi / [oshiho3](https://github.com/oshiho3)
- Emma Winston / [emmawinston](https://github.com/emmawinston)
- Kevin Rio / [krio](https://github.com/krio)

28
Gemfile
View File

@@ -1,6 +1,6 @@
source 'https://rubygems.org'
ruby "2.1.1"
ruby "2.1.2"
gem 'bundler', '>=1.1.5'
@@ -54,12 +54,10 @@ group :assets do
gem 'libv8', '3.16.14.3'
# Another CSS preprocessor, used for Bootstrap overrides
gem "less", '~>2.3.2'
gem "less-rails", '~> 2.3.3'
gem "less", '~>2.5.0'
gem "less-rails", '~> 2.5.0'
# CSS framework
gem "twitter-bootstrap-rails",
:git => 'https://github.com/seyhunak/twitter-bootstrap-rails.git',
:ref => '2c7c52'
gem "less-rails-bootstrap", '~> 3.2.0'
gem 'uglifier', '>= 1.0.3' # JavaScript compressor
@@ -67,6 +65,8 @@ group :assets do
end
gem 'jquery-rails'
gem 'jquery-ui-rails'
gem 'js-routes' # provides access to Rails routes in Javascript
gem 'flickraw'
# To use ActiveModel has_secure_password
@@ -78,11 +78,11 @@ gem 'flickraw'
# Use unicorn as the app server
# gem 'unicorn'
# To use debugger
group :development do
# Installation of the debugger gem fails on Travis CI,
# so we don't use it in the test environment
gem 'debugger'
# A debugger and irb alternative. Pry doesn't play nice
# with unicorn, so start a Webrick server when debugging
# with Pry
gem 'pry'
gem 'better_errors'
gem 'binding_of_caller'
gem 'letter_opener'
@@ -95,7 +95,7 @@ gem 'bluecloth'
gem 'will_paginate', '~> 3.0'
# user signup/login/etc
gem 'devise', '~> 3.0.0'
gem 'devise', '~> 3.2.0'
# nicely formatted URLs
gem 'friendly_id', '~> 4.0.10'
@@ -119,9 +119,15 @@ gem 'omniauth-flickr', '>= 0.0.15'
gem 'rake', '>= 10.0.0'
group :development, :test do
gem 'byebug' # debugging
gem 'haml-rails' # HTML templating language
gem 'rspec-rails', '~> 2.12.1' # unit testing framework
gem 'database_cleaner', '~> 1.3.0'
gem 'webrat' # provides HTML matchers for view tests
gem 'factory_girl_rails', '~> 4.0' # for creating test data
gem 'coveralls', require: false # coverage analysis
gem 'capybara' # integration tests
gem 'capybara-email' # integration tests for email
gem 'poltergeist', '~> 1.5.1' # for headless JS testing
gem 'i18n-tasks' # adds tests for finding missing and unused translations
end

View File

@@ -5,17 +5,6 @@ GIT
specs:
geocoder (1.1.8)
GIT
remote: https://github.com/seyhunak/twitter-bootstrap-rails.git
revision: 2c7c527c354d9068ce49346d4fd8389328d32ce6
ref: 2c7c52
specs:
twitter-bootstrap-rails (2.2.7)
actionpack (>= 3.1)
execjs
rails (>= 3.1)
railties (>= 3.1)
PATH
remote: vendor/gems/active_utils-1.0.5
specs:
@@ -60,74 +49,87 @@ GEM
multi_json (~> 1.0)
addressable (2.3.6)
arel (3.0.3)
bcrypt (3.1.7)
bcrypt-ruby (3.1.5)
bcrypt (>= 3.1.3)
better_errors (1.1.0)
bcrypt (3.1.9)
better_errors (2.0.0)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
rack (>= 0.9.0)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
bluecloth (2.2.0)
bootstrap-datepicker-rails (1.3.0.2)
railties (>= 3.0)
builder (3.0.4)
byebug (3.5.1)
columnize (~> 0.8)
debugger-linecache (~> 1.2)
slop (~> 3.6)
cancan (1.6.10)
chunky_png (1.3.1)
capybara (2.4.4)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
capybara-email (2.4.0)
capybara (~> 2.4)
mail
chunky_png (1.3.3)
cliver (0.3.2)
coderay (1.1.0)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
coffee-script (2.2.0)
coffee-script (2.3.0)
coffee-script-source
execjs
coffee-script-source (1.7.0)
coffee-script-source (1.8.0)
columnize (0.8.9)
commonjs (0.2.7)
compass (0.12.6)
compass (0.12.7)
chunky_png (~> 1.2)
fssm (>= 0.2.7)
sass (~> 3.2.19)
compass-rails (1.0.3)
compass (>= 0.12.2, < 0.14)
coveralls (0.7.0)
coveralls (0.7.1)
multi_json (~> 1.3)
rest-client
simplecov (>= 0.7)
term-ansicolor
thor
csv_shaper (1.0.0)
csv_shaper (1.1.1)
activesupport (>= 3.0.0)
dalli (2.7.0)
dalli (2.7.2)
database_cleaner (1.3.0)
debug_inspector (0.0.2)
debugger (1.6.6)
columnize (>= 0.3.1)
debugger-linecache (~> 1.2.0)
debugger-ruby_core_source (~> 1.3.2)
debugger-linecache (1.2.0)
debugger-ruby_core_source (1.3.2)
devise (3.0.4)
bcrypt-ruby (~> 3.0)
devise (3.2.4)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5)
thread_safe (~> 0.1)
warden (~> 1.2.3)
diff-lcs (1.1.3)
docile (1.1.3)
docile (1.1.5)
easy_translate (0.5.0)
json
thread
thread_safe
erubis (2.7.0)
execjs (2.0.2)
factory_girl (4.4.0)
execjs (2.2.2)
factory_girl (4.5.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.4.1)
factory_girl (~> 4.4.0)
factory_girl_rails (4.5.0)
factory_girl (~> 4.5.0)
railties (>= 3.0.0)
figaro (0.7.0)
bundler (~> 1.0)
rails (>= 3, < 5)
figaro (1.0.0)
thor (~> 0.14)
flickraw (0.9.8)
friendly_id (4.0.10.1)
activerecord (>= 3.0, < 4.0)
fssm (0.2.10)
gibbon (1.1.2)
gibbon (1.1.4)
httparty
multi_json (>= 1.3.4)
gravatar-ultimate (2.0.0)
@@ -140,28 +142,45 @@ GEM
activesupport (>= 3.1, < 4.1)
haml (>= 3.1, < 4.1)
railties (>= 3.1, < 4.1)
hashie (2.1.1)
hashie (3.3.1)
highline (1.6.21)
hike (1.2.3)
httparty (0.11.0)
multi_json (~> 1.0)
multi_xml (>= 0.5.2)
i18n (0.6.1)
i18n-tasks (0.7.8)
activesupport
easy_translate (>= 0.5.0)
erubis
highline
i18n
slop (>= 3.5.0)
term-ansicolor
terminal-table
journey (1.0.4)
jquery-rails (3.1.0)
jquery-rails (3.1.2)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
jquery-ui-rails (4.1.2)
railties (>= 3.1.0)
js-routes (0.9.9)
railties (>= 3.2)
sprockets-rails
json (1.7.7)
kgio (2.9.2)
launchy (2.4.2)
launchy (2.4.3)
addressable (~> 2.3)
leaflet-markercluster-rails (0.7.0)
railties (>= 3.1)
leaflet-rails (0.7.2)
less (2.3.3)
commonjs (~> 0.2.6)
less-rails (2.3.3)
leaflet-rails (0.7.4)
less (2.5.1)
commonjs (~> 0.2.7)
less-rails (2.5.0)
actionpack (>= 3.1)
less (~> 2.3.1)
less (~> 2.5.0)
less-rails-bootstrap (3.2.0)
less-rails (~> 2.5.0)
letter_opener (1.2.0)
launchy (~> 2.2)
libv8 (3.16.14.3)
@@ -169,28 +188,39 @@ GEM
mime-types (~> 1.16)
treetop (~> 1.4.8)
memcachier (0.0.2)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.5.3)
multi_json (1.9.3)
mini_portile (0.6.1)
multi_json (1.10.1)
multi_xml (0.5.5)
newrelic_rpm (3.8.0.218)
nokogiri (1.6.1)
mini_portile (~> 0.5.0)
netrc (0.8.0)
newrelic_rpm (3.9.6.257)
nokogiri (1.6.4.1)
mini_portile (~> 0.6.0)
oauth (0.4.7)
omniauth (1.2.1)
hashie (>= 1.2, < 3)
omniauth (1.2.2)
hashie (>= 1.2, < 4)
rack (~> 1.0)
omniauth-flickr (0.0.15)
omniauth-oauth (~> 1.0)
omniauth-oauth (1.0.1)
oauth
omniauth (~> 1.0)
omniauth-twitter (1.0.1)
omniauth-twitter (1.1.0)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
orm_adapter (0.5.0)
pg (0.17.1)
polyglot (0.3.4)
poltergeist (1.5.1)
capybara (~> 2.1)
cliver (~> 0.3.1)
multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
polyglot (0.3.5)
pry (0.10.1)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
rack (1.4.5)
rack-cache (1.2)
rack (>= 0.4)
@@ -206,7 +236,7 @@ GEM
activesupport (= 3.2.13)
bundler (~> 1.0)
railties (= 3.2.13)
rails_12factor (0.0.2)
rails_12factor (0.0.3)
rails_serve_static_assets
rails_stdout_logging
rails_serve_static_assets (0.0.2)
@@ -219,12 +249,13 @@ GEM
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
raindrops (0.13.0)
rake (10.3.1)
rake (10.3.2)
rdoc (3.12.2)
json (~> 1.4)
ref (1.0.5)
rest-client (1.6.7)
mime-types (>= 1.16)
rest-client (1.7.2)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rspec-core (2.12.2)
rspec-expectations (2.12.1)
diff-lcs (~> 1.1.3)
@@ -241,32 +272,38 @@ GEM
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
simplecov (0.8.2)
simplecov (0.9.1)
docile (~> 1.1.0)
multi_json
multi_json (~> 1.0)
simplecov-html (~> 0.8.0)
simplecov-html (0.8.0)
sprockets (2.2.2)
slop (3.6.0)
sprockets (2.2.3)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sprockets-rails (0.0.1)
sprockets (>= 1.0.2)
term-ansicolor (1.3.0)
tins (~> 1.0)
terminal-table (1.4.5)
therubyracer (0.12.1)
libv8 (~> 3.16.14.0)
ref
thor (0.19.1)
thread (0.1.4)
thread_safe (0.3.4)
tilt (1.4.1)
tins (1.1.0)
tins (1.3.3)
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.39)
tzinfo (0.3.42)
uglifier (2.2.1)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
unicorn (4.8.2)
unicorn (4.8.3)
kgio (~> 2.6)
rack
raindrops (~> 0.7)
@@ -276,7 +313,10 @@ GEM
nokogiri (>= 1.2.0)
rack (>= 1.0)
rack-test (>= 0.5.3)
will_paginate (3.0.5)
websocket-driver (0.4.0)
will_paginate (3.0.7)
xpath (2.0.0)
nokogiri (~> 1.3)
PLATFORMS
ruby
@@ -289,14 +329,17 @@ DEPENDENCIES
bluecloth
bootstrap-datepicker-rails
bundler (>= 1.1.5)
byebug
cancan
capybara
capybara-email
coffee-rails (~> 3.2.1)
compass-rails (~> 1.0.3)
coveralls
csv_shaper
dalli
debugger
devise (~> 3.0.0)
database_cleaner (~> 1.3.0)
devise (~> 3.2.0)
factory_girl_rails (~> 4.0)
figaro
flickraw
@@ -306,12 +349,16 @@ DEPENDENCIES
gravatar-ultimate
haml
haml-rails
i18n-tasks
jquery-rails
jquery-ui-rails
js-routes
json (~> 1.7.7)
leaflet-markercluster-rails
leaflet-rails
less (~> 2.3.2)
less-rails (~> 2.3.3)
less (~> 2.5.0)
less-rails (~> 2.5.0)
less-rails-bootstrap (~> 3.2.0)
letter_opener
libv8 (= 3.16.14.3)
memcachier
@@ -320,6 +367,8 @@ DEPENDENCIES
omniauth-flickr (>= 0.0.15)
omniauth-twitter
pg
poltergeist (~> 1.5.1)
pry
rack (~> 1.4.5)
rails (= 3.2.13)
rails_12factor
@@ -327,7 +376,6 @@ DEPENDENCIES
rspec-rails (~> 2.12.1)
sass-rails (~> 3.2.3)
therubyracer (~> 0.12)
twitter-bootstrap-rails!
uglifier (>= 1.0.3)
unicorn
webrat

View File

@@ -5,9 +5,32 @@
Welcome to the Growstuff project.
Growstuff is an open source/open data project to create a website for food gardeners.
Growstuff is an open source/open data project to create a website for
food gardeners. We crowdsource information on what our members are
growing and harvesting, aggregate it, and make it available as open data
via our API.
You can find most of our documentation at: http://wiki.growstuff.org/
Growstuff was founded in 2012 and has been built by dozens of
[contributors](CONTRIBUTORS.md). We are an inclusive, welcoming project, and
encourage participation from people of all backgrounds and skill levels.
Our development uses Extreme Programming practices, which means we pair on all our work. Feel free to fork our code and explore, but if you would like to contribute back to us,
please read http://wiki.growstuff.org/index.php/Development_process_overview before sending us a pull request.
## Important links
* [Wiki](http://wiki.growstuff.org/) (general documentation)
* [Task tracker](http://tracker.growstuff.org/) (recent and upcoming work, bugs, etc)
* [Discussion forums](http://wiki.growstuff.org/index.php/Discussion_forums) (mailing lists, IRC, etc)
## For developers
* Start by looking at our [task tracker](http://tracker.growstuff.org/) for something to work on.
* Drop in to one of our [discussion forums](http://wiki.growstuff.org/index.php/Discussion_forums) to talk to our team about it.
* To set up your development environment, see [Getting started](http://wiki.growstuff.org/index.php/Development/Getting_Started).
* We encourage [pair programming](http://wiki.growstuff.org/index.php/Pairing), especially for newer developers.
* You may also be interested in our [API](http://wiki.growstuff.org/index.php/API).
## Contact
For more information about this project, contact [info@growstuff.org](mailto:info@growstuff.org).
You can also contact us on [Twitter](http://twitter.com/growstufforg/) or
[Facebook](https://www.facebook.com/pages/Growstuff/1531133417099494).

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -0,0 +1,38 @@
# Displays datepicker to finished at date
# when marking a planting finished using a
# button. The button must have class 'append-date'.
jQuery ->
el = $('.append-date')
el.datepicker({'format': 'yyyy-mm-dd'})
href = el.attr('href')
originalText = el.text()
el.click (e) ->
e.stopPropagation()
e.preventDefault()
$(this).text('Confirm without date')
$(this).bind('click.confirm', (e) ->
link = $("<a href='#{href}' data-method='put'></a>")
$('body').append(link)
$(link).click()
)
$(this).blur (e) ->
$(this).text(originalText)
$(this).unbind('click.confirm')
el.one 'changeDate', ->
date = $(this).datepicker('getDate')
url = "#{href}&planting[finished_at]=#{date}"
link = $("<a href='#{url}' data-method='put'></a>")
$('body').append(link)
$(link).click()

View File

@@ -12,8 +12,11 @@
//
//= require leaflet
//= require leaflet.markercluster
//= require js-routes
//= require jquery
//= require jquery_ujs
//= require jquery.ui.autocomplete
//= require twitter/bootstrap
//= require_tree .
//= require bootstrap-datepicker

View File

@@ -0,0 +1,37 @@
# Uses JQuery's autocomplete to make a suggestion in lieu of a
# preposterously long select dropdown. To implement add code to
# the view like this:
#
# = auto_suggest @resource, :auto_suggest_source
#
# You must also add a search method to the resource's controller.
jQuery ->
if el = $( '.auto-suggest' )
id = $( '.auto-suggest-id' )
el.autocomplete
minLength: 1,
source: el.attr( 'data-source-url' ),
focus: ( event, ui ) ->
el.val( ui.item.name )
id.val( ui.item.id )
false
select: ( event, ui ) ->
el.val( ui.item.name )
id.val( ui.item.id )
false
response: ( event, ui ) ->
id.val( "" )
for item in ui.content
if item.name == el.val()
id.val( item.id )
if el.data( 'uiAutocomplete' )
el.data( 'uiAutocomplete' )._renderItem = ( ul, item ) ->
$( '<li></li>' )
.data( 'item.autocomplete', item )
.append( "<a>#{item.name}</a>" )
.appendTo( ul )

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,5 +1,5 @@
if (document.getElementById("cropmap") !== null) {
mapbox_map_id = "<%= Growstuff::Application.config.mapbox_map_id %>";
mapbox_map_id = "<%= Rails.env == 'test' ? 0 : Growstuff::Application.config.mapbox_map_id %>";
mapbox_base_url = "https://c.tiles.mapbox.com/v3/" + mapbox_map_id + "/{z}/{x}/{y}.png";
L.Icon.Default.imagePath = '/assets'

View File

@@ -0,0 +1,19 @@
# Clears the finished at date field when
# a planting is marked unfinished, and
# repopulates the field with a cached value
# marking unfinished is undone.
jQuery ->
previousValue = ''
$('#planting_finished').on('click', ->
finished = $('#planting_finished_at')
if @checked
if previousValue.length
date = previousValue
finished.val(date)
else
finished.trigger('focus')
else
previousValue = finished.val()
finished.val('')
)

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -0,0 +1,28 @@
if (document.getElementById("membermap") !== null) {
mapbox_map_id = "<%= Rails.env == 'test' ? 0 : Growstuff::Application.config.mapbox_map_id %>";
mapbox_base_url = "https://c.tiles.mapbox.com/v3/" + mapbox_map_id + "/{z}/{x}/{y}.png";
L.Icon.Default.imagePath = '/assets'
$.getJSON(location.pathname + '.json', function(member) {
if (member.latitude && member.longitude) {
membermap = L.map('membermap').setView([member.latitude, member.longitude], 4);
L.tileLayer(mapbox_base_url, {
attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors under <a href="http://www.openstreetmap.org/copyright">ODbL</a> | Map imagery &copy; <a href="http://mapbox.com">Mapbox</a>',
maxZoom: 18
}).addTo(membermap);
marker = new L.Marker(new L.LatLng(member.latitude, member.longitude));
member_url = "/members/" + member.slug;
member_link = "<a href='" + member_url + "'>" + member.login_name + "</a>";
where = "<p><i>" + member.location + "</i></p>";
marker.bindPopup(member_link + where).openPopup();
marker.addTo(membermap);
}
});
}

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,9 +1,9 @@
if (document.getElementById("placesmap") !== null) {
places_base_path = "/places";
mapbox_map_id = "<%= Growstuff::Application.config.mapbox_map_id %>";
mapbox_map_id = "<%= Rails.env == 'test' ? 0 : Growstuff::Application.config.mapbox_map_id %>";
mapbox_base_url = "https://c.tiles.mapbox.com/v3/" + mapbox_map_id + "/{z}/{x}/{y}.png";
nominatim_base_url = 'http://nominatim.openstreetmap.org/search/';
nominatim_user_agent_email = "<%= Growstuff::Application.config.user_agent_email %>";
nominatim_user_agent_email = "<%= Rails.env == 'test' ? 0 : Growstuff::Application.config.user_agent_email %>";
L.Icon.Default.imagePath = '/assets'

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -1,3 +0,0 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@@ -3,11 +3,12 @@
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
*= require_self
*= require jquery.ui.autocomplete
*= require bootstrap-datepicker
*= require leaflet
*= require leaflet.markercluster
*= require leaflet.markercluster.default
*= require custom_bootstrap/custom_bootstrap
*= require overrides.css
*= require_tree .
*/

View File

@@ -0,0 +1,56 @@
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!! AUTOMATICALLY GENERATED FILE. DO NOT MODIFY !!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Core variables and mixins
@import "twitter/bootstrap/variables.less";
@import "custom_bootstrap/variables.less"; // Modify this for custom colors, font-sizes, etc
@import "twitter/bootstrap/mixins.less";
@import "custom_bootstrap/mixins.less"; // Modify this for custom mixins
// Reset and dependencies
@import "twitter/bootstrap/normalize.less";
@import "twitter/bootstrap/print.less";
@import "twitter/bootstrap/glyphicons.less";
// Core CSS
@import "twitter/bootstrap/scaffolding.less";
@import "twitter/bootstrap/type.less";
@import "twitter/bootstrap/code.less";
@import "twitter/bootstrap/grid.less";
@import "twitter/bootstrap/tables.less";
@import "twitter/bootstrap/forms.less";
@import "twitter/bootstrap/buttons.less";
// Components
@import "twitter/bootstrap/component-animations.less";
@import "twitter/bootstrap/dropdowns.less";
@import "twitter/bootstrap/button-groups.less";
@import "twitter/bootstrap/input-groups.less";
@import "twitter/bootstrap/navs.less";
@import "twitter/bootstrap/navbar.less";
@import "twitter/bootstrap/breadcrumbs.less";
@import "twitter/bootstrap/pagination.less";
@import "twitter/bootstrap/pager.less";
@import "twitter/bootstrap/labels.less";
@import "twitter/bootstrap/badges.less";
@import "twitter/bootstrap/jumbotron.less";
@import "twitter/bootstrap/thumbnails.less";
@import "twitter/bootstrap/alerts.less";
@import "twitter/bootstrap/progress-bars.less";
@import "twitter/bootstrap/media.less";
@import "twitter/bootstrap/list-group.less";
@import "twitter/bootstrap/panels.less";
@import "twitter/bootstrap/responsive-embed.less";
@import "twitter/bootstrap/wells.less";
@import "twitter/bootstrap/close.less";
// Components w/ JavaScript
@import "twitter/bootstrap/modals.less";
@import "twitter/bootstrap/tooltip.less";
@import "twitter/bootstrap/popovers.less";
@import "twitter/bootstrap/carousel.less";
// Utility classes
@import "twitter/bootstrap/utilities.less";
@import "twitter/bootstrap/responsive-utilities.less";

View File

@@ -0,0 +1 @@
// Use this file to override Twitter Bootstrap mixins or define own mixins.

View File

@@ -0,0 +1,54 @@
// Use this file to override Twitter Bootstrap variables or define own variables.
// Import original variables so they can be used in overrides
@import "twitter/bootstrap/variables.less";
// Base colours
@beige: #f3f1ee;
@brown: #413f3b;
@green: #5f8e43;
@blue: #2f4365;
@red: #8e4d43;
@orange: #b2685c;
@yellow: #b2935c;
@body-bg: @beige;
@text-color: @brown;
@link-color: @green;
@brand-primary: @green;
@font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
@font-family-serif: Georgia, "Times New Roman", Times, serif;
@font-family-mono: Monaco, Menlo, Consolas, "Courier New", monospace;
@font-size-base: 14px;
@font-family-base: @font-family-sans-serif;
@line-height-base: 1.5;
@alt-font-family: @font-family-serif;
@headings-font-family: @font-family-sans-serif;
@headings-font-weight: bold; // instead of browser default, bold
@headings-color: inherit; // empty to use BS default, @textColor
// Hero unit
@jumbotron-bg: darken(@body-bg, 10%);
// Nav bar
@navbar-default-bg: @brown;
@navbar-default-bg-highlight: @brown;
@navbar-default-color: @beige;
@navbar-default-link-color: darken(@beige, 20%);
@navbar-default-link-hover-color: @beige;
@navbar-default-link-active-color: @beige;
@navbar-default-brand-color: lighten(@green, 20%);
// Top nav collapse threshold
@grid-float-breakpoint: @screen-md-min;
@dropdown-bg: lighten(@beige, 10%);
@dropdown-link-color: @brown;
@dropdown-link-hover-color: @brown;
@dropdown-link-hover-bg: lighten(@green, 50%);

View File

@@ -1,11 +1,14 @@
@import "twitter/bootstrap/bootstrap";
@import "custom_bootstrap/variables";
// this padding needs to be done before the responsive stuff is imported
body {
padding-top: @navbarHeight + 15px;
// modifying this for our promotional banner. can be replaced after if
// needed.
// padding-top: @navbar-height + 15px;
padding-top: @navbar-height;
}
@import "twitter/bootstrap/responsive";
//@import "twitter/bootstrap/responsive";
// Set the correct sprite paths
@iconSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings");
@@ -19,72 +22,13 @@ body {
@fontAwesomeSvgPath: asset-url("fontawesome-webfont.svg#fontawesomeregular");
// Font Awesome
@import "fontawesome/font-awesome";
//@import "fontawesome/font-awesome";
// Glyphicons
//@import "twitter/bootstrap/sprites.less";
// Your custom LESS stylesheets goes here
//
// Since bootstrap was imported above you have access to its mixins which
// you may use and inherit here
//
// If you'd like to override bootstrap's own variables, you can do so here as well
// See http://twitter.github.com/bootstrap/customize.html#variables for their names and documentation
//
// Example:
// @linkColor: #ff0000;
// Base colours
@beige: #f3f1ee;
@brown: #413f3b;
@green: #5f8e43;
@blue: #2f4365;
@red: #8e4d43;
@orange: #b2685c;
@yellow: #b2935c;
@bodyBackground: @beige;
@textColor: @brown;
@linkColor: @green;
// Typography (with help from bootswatch.com's "readable" theme)
@import url('//fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic');
@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif;
@serifFontFamily: Georgia, "Times New Roman", Times, serif;
@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace;
@baseFontSize: 15px;
@baseFontFamily: @serifFontFamily;
@baseLineHeight: @baseFontSize * 1.5;
@altFontFamily: @sansFontFamily;
@headingsFontFamily: "Lora", Georgia, "Times New Roman", Times, serif;
@headingsFontWeight: bold; // instead of browser default, bold
@headingsColor: inherit; // empty to use BS default, @textColor
// Hero unit
@heroUnitBackground: darken(@bodyBackground, 10%);
// Nav bar
@navbarBackground: @brown;
@navbarBackgroundHighlight: @brown;
@navbarText: @beige;
@navbarLinkColor: darken(@beige, 20%);
@navbarLinkColorHover: @beige;
@navbarLinkColorActive: @beige;
@navbarBrandColor: lighten(@green, 20%);
@dropdownBackground: lighten(@beige, 10%);
@dropdownLinkColor: @brown;
@dropdownLinkColorHover: @brown;
@dropdownLinkBackgroundHover: lighten(@green, 50%);
ul.inline > li.first {
.list-inline > li.first {
padding-left: 0px;
}
@@ -92,6 +36,17 @@ h2 {
font-size: 150%;
}
/*
#subtitle {
color: lighten(@brown, 30%);
margin-top: 0px;
padding-top: 0px;
padding-left: 1em;
font-style: italic;
font-weight: normal;
}
*/
h3 {
font-size: 120%;
}
@@ -117,24 +72,24 @@ h3 {
}
// let's condense the hero unit a little
.hero-unit {
.jumbotron {
padding-top: 30px;
padding-bottom: 30px;
}
// info under the main heading on homepage
.hero-unit .info {
.jumbotron .info {
padding-top: 15px;
}
// signup widget on homepage
.hero-unit .signup {
.jumbotron .signup {
background-color: lighten(@green, 40%);
border: 1px solid lighten(@green, 20%);
border-radius: 6px;
padding: 15px;
text-align: center;
line-height: 110%;
line-height: 200%;
}
// stats shown on homepage. eg. "999 members..."
@@ -154,6 +109,10 @@ p.stats {
height: 500px;
}
#membermap {
height: 250px;
}
.member-location {
font-size: small;
font-style: italic;
@@ -190,13 +149,25 @@ p.stats {
}
}
.member-thumbnail {
img {
height: 85px;
width: 85px;
max-width: 85px
}
}
.thumbnail {
margin-bottom: 1.5em;
}
li.crop-hierarchy {
list-style-type: disc;
}
.navbar-inner {
border-width: 0px !important;
.navbar-brand {
margin: 0px;
padding: 0px;
}
.navbar-bottom {
@@ -223,24 +194,36 @@ footer .navbar .nav {
}
}
.navbar-inner {
border: none !important;
background-image: none !important;
-webkit-border-radius: 0px;
-moz-border-radius: 0px;
border-radius: 0px;
}
.navbar-bottom .navbar-inner {
.navbar-bottom.navbar {
text-align: center;
border-radius: 0;
}
.navbar-bottom .container{
padding: 20px 0px 40px 0px;
.crop-image, .member-image {
width: 100%;
height: 100%;
}
.navbar-search{
padding-bottom: 7px;
// Autosuggest
.ui-autocomplete {
z-index: @zindex-tooltip;
}
// Crowdfunding campaign, Sep-Oct 2014
.crowdfunding-banner {
text-align: center;
font-weight: bold;
background-color: lighten(@green, 30%);
margin-top: 0px;
margin-bottom: 5px;
padding: 15px;
}
.crowdfunding-banner a {
color: @brown;
text-decoration: underline;
}
// Overrides applying only to mobile view. This must be at the end of the overrides file.

View File

@@ -64,7 +64,7 @@ class AccountTypesController < ApplicationController
@account_type.destroy
respond_to do |format|
format.html { redirect_to account_types_url }
format.html { redirect_to account_types_url, notice: 'Account type was successfully deleted.' }
end
end
end

View File

@@ -0,0 +1,90 @@
class AlternateNamesController < ApplicationController
load_and_authorize_resource
# GET /alternate_names
# GET /alternate_names.json
def index
@alternate_names = AlternateName.all
respond_to do |format|
format.html # index.html.haml
format.json { render json: @alternate_names }
end
end
# GET /alternate_names/1
# GET /alternate_names/1.json
def show
@alternate_name = AlternateName.find(params[:id])
respond_to do |format|
format.html # show.html.haml
format.json { render json: @alternate_name }
end
end
# GET /alternate_names/new
# GET /alternate_names/new.json
def new
@alternate_name = AlternateName.new
@crop = Crop.find_by_id(params[:crop_id]) || Crop.new
respond_to do |format|
format.html # new.html.haml
format.json { render json: @alternate_name }
end
end
# GET /alternate_names/1/edit
def edit
@alternate_name = AlternateName.find(params[:id])
end
# POST /alternate_names
# POST /alternate_names.json
def create
params[:alternate_name][:creator_id] = current_member.id
@alternate_name = AlternateName.new(params[:alternate_name])
respond_to do |format|
if @alternate_name.save
format.html { redirect_to @alternate_name.crop, notice: 'Alternate name was successfully created.' }
format.json { render json: @alternate_name, status: :created, location: @alternate_name }
else
format.html { render action: "new" }
format.json { render json: @alternate_name.errors, status: :unprocessable_entity }
end
end
end
# PUT /alternate_names/1
# PUT /alternate_names/1.json
def update
@alternate_name = AlternateName.find(params[:id])
respond_to do |format|
if @alternate_name.update_attributes(params[:alternate_name])
format.html { redirect_to @alternate_name.crop, notice: 'Alternate name was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: @alternate_name.errors, status: :unprocessable_entity }
end
end
end
# DELETE /alternate_names/1
# DELETE /alternate_names/1.json
def destroy
@alternate_name = AlternateName.find(params[:id])
@crop = @alternate_name.crop
@alternate_name.destroy
respond_to do |format|
format.html {
redirect_to @crop, notice: 'Alternate name was successfully deleted.'
}
format.json { head :no_content }
end
end
end

View File

@@ -4,17 +4,22 @@ class ApplicationController < ActionController::Base
include ApplicationHelper
after_filter :store_location
before_filter :set_locale
def store_location
# store last url - this is needed for post-login redirect to whatever the user last visited.
if (request.fullpath != new_member_session_path && \
!request.xhr?) # don't store ajax calls
session[:previous_url] = request.fullpath
if (request.path != "/members/sign_in" &&
request.path != "/members/sign_up" &&
request.path != "/members/password/new" &&
request.path != "/members/password/edit" &&
request.path != "/members/confirmation" &&
request.path != "/members/sign_out" &&
!request.xhr?)
store_location_for(:member, request.fullpath)
end
end
def after_sign_in_path_for(resource)
session[:previous_url] || root_path
stored_location_for(:member) || root_path
end
# tweak CanCan defaults because we don't have a "current_user" method
@@ -28,5 +33,14 @@ class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied do |exception|
redirect_to request.referer || root_url, :alert => exception.message
end
def set_locale
I18n.locale = params[:locale] || extract_locale_from_subdomain || I18n.default_locale
end
def extract_locale_from_subdomain
parsed_locale = request.subdomains.first
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
end

View File

@@ -34,7 +34,7 @@ class CropsController < ApplicationController
# GET /crops/wrangle
def wrangle
@crops = Crop.recent.paginate(:page => params[:page])
@crop_wranglers = Role.crop_wranglers
respond_to do |format|
format.html
end
@@ -57,8 +57,11 @@ class CropsController < ApplicationController
# exclude exact match from partial match list
@partial_matches.reject!{ |r| @exact_match && r.eql?(@exact_match) }
@fuzzy = Crop.search(params[:term])
respond_to do |format|
format.html
format.json { render :json => @fuzzy }
end
end
@@ -66,6 +69,7 @@ class CropsController < ApplicationController
# GET /crops/1.json
def show
@crop = Crop.includes(:scientific_names, {:plantings => :photos}).find(params[:id])
@posts = @crop.posts.paginate(:page => params[:page])
respond_to do |format|
format.html # show.html.haml
@@ -101,7 +105,6 @@ class CropsController < ApplicationController
params[:crop][:creator_id] = current_member.id
@crop = Crop.new(params[:crop])
respond_to do |format|
if @crop.save
format.html { redirect_to @crop, notice: 'Crop was successfully created.' }

View File

@@ -80,7 +80,7 @@ class ForumsController < ApplicationController
@forum.destroy
respond_to do |format|
format.html { redirect_to forums_url }
format.html { redirect_to forums_url, notice: 'Forum was successfully deleted' }
format.json { head :no_content }
end
end

View File

@@ -85,7 +85,7 @@ class GardensController < ApplicationController
@garden.destroy
respond_to do |format|
format.html { redirect_to @garden.owner, notice: 'Garden was successfully deleted.' }
format.html { redirect_to gardens_by_owner_path(:owner => @garden.owner), notice: 'Garden was successfully deleted.' }
format.json { head :no_content }
end
end

View File

@@ -6,23 +6,21 @@ class HarvestsController < ApplicationController
# GET /harvests.json
def index
@owner = Member.find_by_slug(params[:owner])
@crop = Crop.find_by_slug(params[:crop])
if @owner
@harvests = @owner.harvests.includes(:owner, :crop).paginate(:page => params[:page])
@harvests = @owner.harvests.includes(:owner, :crop)
elsif @crop
@harvests = @crop.harvests.includes(:owner, :crop)
else
@harvests = Harvest.includes(:owner, :crop).paginate(:page => params[:page])
@harvests = Harvest.includes(:owner, :crop)
end
respond_to do |format|
format.html # index.html.erb
format.html { @harvests = @harvests.paginate(:page => params[:page]) }
format.json { render json: @harvests }
format.csv do
if @owner
@filename = "Growstuff-#{@owner}-Harvests-#{Time.zone.now.to_s(:number)}.csv"
@harvests = @owner.harvests.includes(:owner, :crop)
else
@filename = "Growstuff-Harvests-#{Time.zone.now.to_s(:number)}.csv"
@harvests = Harvest.includes(:owner, :crop)
end
specifics = (@owner ? "#{@owner.name}-" : @crop ? "#{@crop.name}-" : nil)
@filename = "Growstuff-#{specifics}Harvests-#{Time.zone.now.to_s(:number)}.csv"
render :csv => @harvests
end
end

View File

@@ -10,6 +10,7 @@ class MembersController < ApplicationController
respond_to do |format|
format.html # index.html.haml
format.json { render :json => @members.to_json(:only => [:id, :login_name, :slug, :bio, :created_at, :location, :latitude, :longitude]) }
end
end
@@ -25,6 +26,7 @@ class MembersController < ApplicationController
respond_to do |format|
format.html # show.html.haml
format.json { render :json => @member.to_json(:only => [:id, :login_name, :bio, :created_at, :slug, :location, :latitude, :longitude]) }
format.rss { render(
:layout => false,
:locals => { :member => @member }

View File

@@ -29,7 +29,8 @@ class PhotosController < ApplicationController
# GET /photos/new.json
def new
@photo = Photo.new
@planting_id = params[:planting_id]
@type = params[:type]
@id = params[:id]
page = params[:page] || 1
@@ -63,19 +64,35 @@ class PhotosController < ApplicationController
@photo.owner_id = current_member.id
@photo.set_flickr_metadata
if params[:planting_id]
planting = Planting.find_by_id(params[:planting_id])
if planting
if planting.owner.id == current_member.id
@photo.plantings << planting unless @photo.plantings.include?(planting)
# several models can have photos. we need to know what model and the id
# for the entry to attach the photo to
valid_models = ["planting", "harvest"]
if params[:type]
if valid_models.include?(params[:type])
if params[:id]
item = params[:type].camelcase.constantize.find_by_id(params[:id])
if item
if item.owner.id == current_member.id
# This syntax is weird, so just know that it means this:
# @photo.harvests << item unless @photo.harvests.include?(item)
# but with the correct many-to-many relationship automatically referenced
(@photo.send "#{params[:type]}s") << item unless (@photo.send "#{params[:type]}s").include?(item)
else
flash[:alert] = "You must own both the #{params[:type]} and the photo."
end
else
flash[:alert] = "Couldn't find #{params[:type]} to connect to photo."
end
else
flash[:alert] = "You must own both the planting and the photo."
flash[:alert] = "Missing id parameter"
end
else
flash[:alert] = "Couldn't find planting to connect to photo."
flash[:alert] = "Cannot attach photos to #{params[:type]}"
end
else
flash[:alert] = "Missing type parameter"
end
respond_to do |format|
if @photo.save
format.html { redirect_to @photo, notice: 'Photo was successfully added.' }

View File

@@ -7,24 +7,22 @@ class PlantingsController < ApplicationController
# GET /plantings.json
def index
@owner = Member.find_by_slug(params[:owner])
@crop = Crop.find_by_slug(params[:crop])
if @owner
@plantings = @owner.plantings.includes(:owner, :crop, :garden).paginate(:page => params[:page])
elsif @crop
@plantings = @crop.plantings.includes(:owner, :crop, :garden).paginate(:page => params[:page])
else
@plantings = Planting.includes(:owner, :crop, :garden).paginate(:page => params[:page])
end
respond_to do |format|
format.html # index.html.erb
format.html { @plantings = @plantings.paginate(:page => params[:page]) }
format.json { render json: @plantings }
format.rss { render :layout => false } #index.rss.builder
format.csv do
if @owner
@filename = "Growstuff-#{@owner}-Plantings-#{Time.zone.now.to_s(:number)}.csv"
@plantings = @owner.plantings.includes(:owner, :crop, :garden)
else
@filename = "Growstuff-Plantings-#{Time.zone.now.to_s(:number)}.csv"
@plantings = Planting.includes(:owner, :crop, :garden)
end
specifics = (@owner ? "#{@owner.name}-" : @crop ? "#{@crop.name}-" : nil)
@filename = "Growstuff-#{specifics}Plantings-#{Time.zone.now.to_s(:number)}.csv"
render :csv => @plantings
end
end

View File

@@ -10,26 +10,37 @@ class RegistrationsController < Devise::RegistrationsController
# we need this subclassed method so that Devise doesn't force people to
# change their password every time they want to edit their settings.
# we also check that they give their current password to change their password.
# Code copied from
# https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password
def update
# required for settings form to submit when password is left blank
if params[:member][:password].blank?
params[:member].delete("password")
params[:member].delete("password_confirmation")
params[:member].delete("current_password")
end
@member = Member.find(current_member.id)
if @member.update_attributes(params[:member])
successfully_updated = if needs_password?(@member, params)
@member.update_with_password(devise_parameter_sanitizer.sanitize(:account_update))
else
# remove the virtual current_password attribute
# update_without_password doesn't know how to ignore it
params[:member].delete(:current_password)
@member.update_without_password(devise_parameter_sanitizer.sanitize(:account_update))
end
if successfully_updated
set_flash_message :notice, :updated
# Sign in the member bypassing validation in case his password changed
# Sign in the member bypassing validation in case their password changed
sign_in @member, :bypass => true
redirect_to edit_member_registration_path
else
render "edit"
end
end
end
# check if we need the current password to update fields
def needs_password?(member, params)
params[:member][:password].present? ||
params[:member][:password_confirmation].present?
end

View File

@@ -83,7 +83,9 @@ class ScientificNamesController < ApplicationController
@scientific_name.destroy
respond_to do |format|
format.html { redirect_to @crop }
format.html {
redirect_to @crop, notice: 'Scientific name was successfully deleted.'
}
format.json { head :no_content }
end
end

View File

@@ -7,8 +7,11 @@ class SeedsController < ApplicationController
# GET /seeds.json
def index
@owner = Member.find_by_slug(params[:owner])
@crop = Crop.find_by_slug(params[:crop])
if @owner
@seeds = @owner.seeds.includes(:owner, :crop).paginate(:page => params[:page])
elsif @crop
@seeds = @crop.seeds.includes(:owner, :crop).paginate(:page => params[:page])
else
@seeds = Seed.includes(:owner, :crop).paginate(:page => params[:page])
end

View File

@@ -0,0 +1,22 @@
module AutoSuggestHelper
def auto_suggest(resource, source, options={})
if options[:default] && !options[:default].new_record?
default = options[:default]
default_id = options[:default].try(:id)
else
default = resource.send(source)
default_id = default.try(:id)
end
resource = resource.class.name.downcase
source_path = Rails.application.routes.url_helpers.send("#{source}s_search_path")
%Q{
<input id="#{source}" class="auto-suggest #{options[:class]}" type="text" value="#{default}" data-source-url="#{source_path}", placeholder="e.g. lettuce">
<noscript class="text-warning">Warning: Javascript must be available to search and match crops</noscript>
<input id="#{resource}_#{source}_id" class="auto-suggest-id" type="hidden" name="#{resource}[#{source}_id]" value="#{default_id}">
}.html_safe
end
end

View File

@@ -1,2 +0,0 @@
module PlantPartsHelper
end

View File

@@ -1,2 +0,0 @@
module SeedsHelper
end

View File

@@ -9,4 +9,17 @@ class Notifier < ActionMailer::Base
mail(:to => @notification.recipient.email,
:subject => @notification.subject)
end
def planting_reminder(member)
@member = member
@plantings = @member.plantings.reorder.last(5)
@harvests = @member.harvests.reorder.last(5)
if @member.send_planting_reminder
mail(:to => @member.email,
:subject => "What have you planted lately?")
end
end
end

View File

@@ -40,6 +40,7 @@ class Ability
can :wrangle, Crop
can :manage, Crop
can :manage, ScientificName
can :manage, AlternateName
end
# can create & destroy their own authentications against other sites.

View File

@@ -0,0 +1,5 @@
class AlternateName < ActiveRecord::Base
attr_accessible :crop_id, :name, :creator_id
belongs_to :crop
belongs_to :creator, :class_name => 'Member'
end

View File

@@ -8,6 +8,7 @@ class Crop < ActiveRecord::Base
:allow_destroy => true,
:reject_if => :all_blank
has_many :alternate_names
has_many :plantings
has_many :photos, :through => :plantings
has_many :seeds
@@ -17,6 +18,9 @@ class Crop < ActiveRecord::Base
belongs_to :parent, :class_name => 'Crop'
has_many :varieties, :class_name => 'Crop', :foreign_key => 'parent_id'
has_and_belongs_to_many :posts
before_destroy {|crop| crop.posts.clear}
default_scope order("lower(name) asc")
scope :recent, reorder("created_at desc")
@@ -30,11 +34,6 @@ class Crop < ActiveRecord::Base
:message => 'is not a valid English Wikipedia URL'
}
def Crop.random
@crop = Crop.offset(rand(Crop.count)).first
return @crop
end
def to_s
return name
end
@@ -127,52 +126,55 @@ class Crop < ActiveRecord::Base
# - en_wikipedia_url (required)
# - parent (name, optional)
def Crop.create_from_csv(row, definitely_new=false)
def Crop.create_from_csv(row)
name,scientific_name,en_wikipedia_url,parent = row
@cropbot = Member.find_by_login_name('cropbot')
raise "cropbot account not found: run rake db:seed" unless @cropbot
cropbot = Member.find_by_login_name('cropbot')
raise "cropbot account not found: run rake db:seed" unless cropbot
crop = Crop.find_or_create_by_name(name)
crop.update_attributes(
:en_wikipedia_url => en_wikipedia_url,
:creator_id => cropbot.id
)
if definitely_new then
@crop = Crop.create(
:name => name,
:en_wikipedia_url => en_wikipedia_url,
:creator_id => @cropbot.id
)
else
@crop = Crop.find_or_create_by_name(name)
@crop.update_attributes(
:en_wikipedia_url => en_wikipedia_url,
:creator_id => @cropbot.id
)
end
if parent
@parent = Crop.find_by_name(parent)
if @parent
@crop.update_attributes(:parent_id => @parent.id)
parent = Crop.find_by_name(parent)
if parent
crop.update_attributes(:parent_id => parent.id)
else
logger.warn("Warning: parent crop #{parent} not found")
end
end
unless @crop.scientific_names.exists?(:scientific_name => scientific_name)
@sn = ''
if scientific_name
@sn = scientific_name
elsif @crop.parent
@sn = @crop.parent.scientific_names.first.scientific_name
end
crop.add_scientific_name_from_csv(scientific_name)
if @sn
@crop.scientific_names.create(
:scientific_name => @sn,
:creator_id => @cropbot.id
)
else
logger.warn("Warning: no scientific name (not even on parent crop) for #{@crop}")
end
end
def add_scientific_name_from_csv(scientific_name)
name_to_add = nil
if ! scientific_name.blank? # i.e. we actually passed one in, which isn't a given
name_to_add = scientific_name
elsif parent && parent.default_scientific_name
name_to_add = parent.default_scientific_name
else
logger.warn("Warning: no scientific name (not even on parent crop) for #{self}")
end
if name_to_add
if scientific_names.exists?(:scientific_name => name_to_add)
logger.warn("Warning: skipping duplicate scientific name #{name_to_add} for #{self}")
else
cropbot = Member.find_by_login_name('cropbot')
raise "cropbot account not found: run rake db:seed" unless cropbot
scientific_names.create(
:scientific_name => name_to_add,
:creator_id => cropbot.id
)
end
end
end
# Crop.search(string)

View File

@@ -14,6 +14,7 @@ class Garden < ActiveRecord::Base
geocoded_by :location
after_validation :geocode
after_validation :empty_unwanted_geocodes
after_save :mark_inactive_garden_plantings_as_finished
default_scope order("lower(name) asc")
scope :active, where(:active => true)
@@ -74,4 +75,15 @@ class Garden < ActiveRecord::Base
name
end
# When you mark a garden as inactive, all the plantings in it should be
# marked as finished. This automates that.
def mark_inactive_garden_plantings_as_finished
if (active == false)
plantings.current.each do |p|
p.finished = true
p.save
end
end
end
end

View File

@@ -1,4 +1,5 @@
class Harvest < ActiveRecord::Base
include ActionView::Helpers::NumberHelper
extend FriendlyId
friendly_id :harvest_slug, use: :slugged
@@ -9,8 +10,21 @@ class Harvest < ActiveRecord::Base
belongs_to :owner, :class_name => 'Member'
belongs_to :plant_part
has_and_belongs_to_many :photos
before_destroy do |harvest|
photolist = harvest.photos.to_a # save a temp copy of the photo list
harvest.photos.clear # clear relationship b/w harvest and photo
photolist.each do |photo|
photo.destroy_if_unused
end
end
default_scope order('created_at DESC')
validates :crop, :presence => {:message => "must be present and exist in our database"}
validates :quantity,
:numericality => { :only_integer => false },
:allow_nil => true
@@ -70,4 +84,41 @@ class Harvest < ActiveRecord::Base
"#{owner.login_name}-#{crop}".downcase.gsub(' ', '-')
end
# stringify as "beet in Skud's backyard" or similar
def to_s
# 50 individual apples, weighing 3lb
# 2 buckets of apricots, weighing 10kg
string = ''
if self.quantity
string += "#{number_to_human(self.quantity.to_s, :strip_insignificant_zeros => true)} "
if self.unit == 'individual'
string += 'individual '
else
if self.quantity == 1
string += "#{self.unit} of "
else
string += "#{self.unit.pluralize} of "
end
end
end
if self.unit != 'individual' # buckets of apricot*s*
string += "#{self.crop.name.pluralize}"
elsif self.quantity == 1
string += "#{self.crop.name}"
else
string += "#{self.crop.name.pluralize}"
end
if self.weight_quantity
string += " weighing #{number_to_human(self.weight_quantity, :strip_insignificant_zeros => true)} #{self.weight_unit}"
end
return string
end
def default_photo
return photos.first
end
end

View File

@@ -42,8 +42,13 @@ class Member < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :login_name, :email, :password, :password_confirmation,
:remember_me, :login, :tos_agreement, :show_email, :newsletter,
:location, :latitude, :longitude, :send_notification_email, :bio
:remember_me, :login,
# terms of service
:tos_agreement,
# profile stuff
:bio, :location, :latitude, :longitude,
# email settings
:show_email, :newsletter, :send_notification_email, :send_planting_reminder
# set up geocoding
geocoded_by :location

View File

@@ -4,10 +4,21 @@ class Photo < ActiveRecord::Base
belongs_to :owner, :class_name => 'Member'
has_and_belongs_to_many :plantings
before_destroy {|photo| photo.plantings.clear}
has_and_belongs_to_many :harvests
before_destroy do |photo|
photo.plantings.clear
photo.harvests.clear
end
default_scope order("created_at desc")
# remove photos that aren't used by anything
def destroy_if_unused
unless plantings.size > 0 or harvests.size > 0
self.destroy
end
end
# This is split into a side-effect free method and a side-effecting method
# for easier stubbing and testing.
def flickr_metadata

View File

@@ -3,16 +3,27 @@ class Planting < ActiveRecord::Base
friendly_id :planting_slug, use: :slugged
attr_accessible :crop_id, :description, :garden_id, :planted_at,
:quantity, :sunniness, :planted_from, :owner_id
:quantity, :sunniness, :planted_from, :owner_id, :finished,
:finished_at
belongs_to :garden
belongs_to :owner, :class_name => 'Member', :counter_cache => true
belongs_to :crop, :counter_cache => true
has_and_belongs_to_many :photos
before_destroy {|planting| planting.photos.clear}
before_destroy do |planting|
photolist = planting.photos.to_a # save a temp copy of the photo list
planting.photos.clear # clear relationship b/w planting and photo
photolist.each do |photo|
photo.destroy_if_unused
end
end
default_scope order("created_at desc")
scope :finished, where(:finished => true)
scope :current, where(:finished => false)
delegate :name,
:en_wikipedia_url,
@@ -23,6 +34,8 @@ class Planting < ActiveRecord::Base
default_scope order("created_at desc")
validates :crop_id, :presence => {:message => "must be present and exist in our database"}
validates :quantity,
:numericality => { :only_integer => true },
:allow_nil => true
@@ -51,6 +64,14 @@ class Planting < ActiveRecord::Base
:allow_nil => true,
:allow_blank => true
validate :finished_must_be_after_planted
# check that any finished_at date occurs after planted_at
def finished_must_be_after_planted
return unless planted_at and finished_at # only check if we have both
errors.add(:finished_at, "must be after the planting date") unless planted_at < finished_at
end
def planting_slug
"#{owner.login_name}-#{garden}-#{crop}".downcase.gsub(' ', '-')
end
@@ -76,15 +97,15 @@ class Planting < ActiveRecord::Base
# return a list of interesting plantings, for the homepage etc.
# we can't do this via a scope (as far as we know) so sadly we have to
# do it this way.
def Planting.interesting
howmany = 12 # max amount to collect
def Planting.interesting(howmany=12, require_photo=true)
interesting_plantings = Array.new
seen_owners = Hash.new(false) # keep track of which owners we've seen already
Planting.all.each do |p|
break if interesting_plantings.count == howmany # got enough yet?
next unless p.interesting? # skip those that don't have photos
if require_photo
next unless p.photos.present? # skip those without photos, if required
end
next if seen_owners[p.owner] # skip if we already have one from this owner
seen_owners[p.owner] = true # we've seen this owner
interesting_plantings.push(p)

View File

@@ -5,6 +5,9 @@ class Post < ActiveRecord::Base
belongs_to :author, :class_name => 'Member'
belongs_to :forum
has_many :comments, :dependent => :destroy
has_and_belongs_to_many :crops
before_destroy {|post| post.crops.clear}
after_save :update_crops_posts_association
# also has_many notifications, but kinda meaningless to get at them
# from this direction, so we won't set up an association for now.
@@ -39,4 +42,15 @@ class Post < ActiveRecord::Base
end
end
private
def update_crops_posts_association
self.crops.destroy_all
# look for crops mentioned in the post. eg. [tomato](crop)
self.body.scan(Haml::Filters::GrowstuffMarkdown::CROP_REGEX) do |m|
# find crop case-insensitively
crop = Crop.where('lower(name) = ?', $1.downcase).first
# create association
self.crops << crop if crop and not self.crops.include?(crop)
end
end
end

View File

@@ -3,4 +3,13 @@ class Role < ActiveRecord::Base
friendly_id :name, use: :slugged
attr_accessible :description, :name, :members, :slug
has_and_belongs_to_many :members
class << self
[:crop_wranglers, :admins].each do |method|
define_method method do
slug = method.to_s.singularize.dasherize
Role.where(slug: slug).try(:first).try(:members)
end
end
end
end

View File

@@ -2,7 +2,7 @@ class Seed < ActiveRecord::Base
extend FriendlyId
friendly_id :seed_slug, use: :slugged
attr_accessible :owner_id, :crop_id, :description, :quantity, :plant_before,
attr_accessible :owner_id, :crop_id, :description, :quantity, :plant_before,
:tradable_to, :slug
belongs_to :crop
@@ -10,6 +10,7 @@ class Seed < ActiveRecord::Base
default_scope order("created_at desc")
validates :crop, :presence => {:message => "must be present and exist in our database"}
validates :quantity,
:numericality => { :only_integer => true },
:allow_nil => true
@@ -55,6 +56,6 @@ class Seed < ActiveRecord::Base
end
def seed_slug
"#{owner.login_name}-#{crop.name}".downcase.gsub(' ', '-')
"#{owner.login_name}-#{crop}".downcase.gsub(' ', '-')
end
end

View File

@@ -14,4 +14,4 @@
%dd= link_to 'media@growstuff.org', 'mailto:media@growstuff.org'
%dl
%dt Twitter
%dd= link_to '@Growstuff', 'http:/twitter.com/Growstuff'
%dd= link_to '@growstufforg', 'http://twitter.com/growstufforg'

View File

@@ -1,4 +1,4 @@
%h1 Editing account_type
-content_for :title, "Editing account type"
= render 'form'

View File

@@ -1,4 +1,4 @@
%h1 Listing account_types
- content_for :title, "Listing account types"
%table
%tr

View File

@@ -1,4 +1,4 @@
%h1 New account_type
- content_for :title, "New account type"
= render 'form'

View File

@@ -12,4 +12,6 @@
= link_to 'Edit', edit_account_type_path(@account_type)
\|
= link_to 'Delete', @account_type, :method => :delete, :data => { :confirm => 'Are you sure?' }
\|
= link_to 'Back', account_types_path

View File

@@ -1,4 +1,4 @@
%h1 Editing account
- content_for :title, "Editing account"
= render 'form'

View File

@@ -1,4 +1,4 @@
%h1 Listing accounts
- content_for :title, "Listing accounts"
%table
%tr

View File

@@ -1,4 +1,4 @@
%h1 New account
- content_for :title, "New account"
= render 'form'

View File

@@ -2,7 +2,7 @@
%h2 Manage
%ul
%ul#admin_links
%li= link_to "Account types", account_types_path
%li= link_to "Products", products_path
%li= link_to "Roles", roles_path

View File

@@ -36,4 +36,4 @@
@
= price_with_currency(o.price)
%br/
%td= link_to 'Details', order, :class => 'btn btn-mini'
%td= link_to 'Details', order, :class => 'btn btn-default btn-xs'

View File

@@ -0,0 +1,25 @@
= form_for @alternate_name, :html => {:class => 'form-horizontal', :role => "form"} do |f|
- if @alternate_name.errors.any?
#error_explanation
%h2= "#{pluralize(@alternate_name.errors.count, "error")} prohibited this alternate_name from being saved:"
%ul
- @alternate_name.errors.full_messages.each do |msg|
%li= msg
%p
%span.help-block
For detailed crop wrangling guidelines, please consult the
=link_to "crop wrangling guide", "http://wiki.growstuff.org/index.php/Crop_wrangling"
on the Growstuff wiki.
.form-group
= f.label :crop_id, :class => 'control-label col-md-2'
.col-md-8
= collection_select(:alternate_name, :crop_id, Crop.all, :id, :name, { :selected => @alternate_name.crop_id || @crop.id }, :class => 'form-control')
.form-group
= f.label :name, :class => 'control-label col-md-2'
.col-md-8
= f.text_field :name, :class => 'form-control'
.form-group
.form-actions.col-md-offset-2.col-md-8
= f.submit 'Save', :class => 'btn btn-primary'

View File

@@ -0,0 +1,9 @@
- content_for :title, "Edit alternate name"
%p
Added by
= @alternate_name.creator
= distance_of_time_in_words(@alternate_name.created_at, Time.zone.now)
ago.
= render 'form'

View File

@@ -0,0 +1,23 @@
- content_for :title, "Listing alternate names"
- if can? :create, AlternateName
%p= link_to 'New alternate name', new_alternate_name_path, :class => 'btn btn-primary'
%table
%tr
%th Alternate name
%th Crop
%th
%th
- @alternate_names.each do |alternate_name|
%tr
%td= link_to alternate_name.name, alternate_name
%td= alternate_name.crop
%td= link_to 'Show', alternate_name
%td
- if can? :edit, alternate_name
= link_to 'Edit', edit_alternate_name_path(alternate_name), :class => 'btn btn-default btn-xs'
%td
- if can? :destroy, alternate_name
= link_to 'Delete', alternate_name, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs'

View File

@@ -0,0 +1,3 @@
- content_for :title, "New alternate name"
= render 'form'

View File

@@ -0,0 +1,13 @@
%p#notice= notice
%p
%b Alternate name:
= @alternate_name.name
%p
%b Crop:
= link_to @alternate_name.crop, @alternate_name.crop
- if can? :edit, @alternate_name
= link_to 'Edit', edit_alternate_name_path(@alternate_name), :class => 'btn btn-default btn-xs'
\|
= link_to 'Back', alternate_names_path

View File

@@ -1,4 +1,4 @@
%h1 Linked accounts on other sites
- content_for :title, "Linked accounts on other sites"
- if @authentications
- unless @authentications.empty?

View File

@@ -1,4 +1,4 @@
= form_for @comment do |f|
= form_for(@comment, :html => {:class => "form-horizontal", :role => "form"}) do |f|
- if @comment.errors.any?
#error_explanation
%h2= "#{pluralize(@comment.errors.count, "error")} prohibited this comment from being saved:"
@@ -6,8 +6,9 @@
- @comment.errors.full_messages.each do |msg|
%li= msg
.field
= f.text_area :body, :rows => 6, :class => 'input-block-level'
.form-group
= f.label :body, "Your comment:"
= f.text_area :body, :rows => 6, :class => 'form-control'
%span.help-block
= render :partial => "shared/markdown_help"
.actions

View File

@@ -1,9 +1,9 @@
.well
.comment
.row-fluid
.span1
.row
.col-md-1
= render :partial => "members/avatar", :locals => { :member => comment.author }
.span11
.col-md-11
.comment-meta
Posted by
= link_to comment.author.login_name, member_path(comment.author)
@@ -17,8 +17,8 @@
- if can? :edit, comment or can? :destroy, comment
.comment-actions
- if can? :edit, comment
= link_to 'Edit', edit_comment_path(comment), :class => 'btn btn-mini'
= link_to 'Edit', edit_comment_path(comment), :class => 'btn btn-default btn-xs'
- if can? :destroy, comment
= link_to 'Delete', comment, method: :delete, |
data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini'
data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs'

View File

@@ -4,6 +4,4 @@
= render :partial => "posts/comments", :locals => {:post => @post || @comment.post}
%p Your comment:
= render 'form'

View File

@@ -8,7 +8,9 @@
%li
= link_to "#{seed.owner} will trade #{seed.tradable_to}.", seed_path(seed)
= render :partial => 'members/location', :locals => { :member => seed.owner }
%p
= link_to "View all #{crop.name} seeds", seeds_by_crop_path(crop)
- if current_member
= link_to "List your seeds to trade.", new_seed_path()
= link_to "List achiote seeds to trade", new_seed_path(:crop_id => crop.id)
- else
= render :partial => 'shared/signin_signup', :locals => { :to => 'list your seeds to trade' }

View File

@@ -1,4 +1,4 @@
= form_for @crop, :html => {:class => 'form-horizontal'} do |f|
= form_for @crop, :html => {:class => 'form-horizontal', :role => "form"} do |f|
- if @crop.errors.any?
#error_explanation
%h3= "#{pluralize(@crop.errors.count, "error")} prohibited this crop from being saved:"
@@ -12,32 +12,34 @@
=link_to "crop wrangling guide", "http://wiki.growstuff.org/index.php/Crop_wrangling"
on the Growstuff wiki.
.control-group
= f.label :name, :class => 'control-label'
.controls
= f.text_field :name
%span.help-inline Name in US English; singular; capitalize proper nouns only.
.control-group
= f.label :en_wikipedia_url, 'Wikipedia URL', :class => 'control-label'
.controls
= f.text_field :en_wikipedia_url
%span.help-inline Link to this crop's page on the English language Wikipedia.
.control-group
= f.label :parent_id, 'Parent crop', :class => 'control-label'
.controls
= collection_select(:crop, :parent_id, Crop.all, :id, :name, {:include_blank => true})
%span.help-inline Optional. For setting up crop hierarchies for varieties etc.
.form-group
= f.label :name, :class => 'control-label col-md-2'
.col-md-8
= f.text_field :name, :class => 'form-control'
%span.help-block Name in US English; singular; capitalize proper nouns only.
.form-group
= f.label :en_wikipedia_url, 'Wikipedia URL', :class => 'control-label col-md-2'
.col-md-8
= f.text_field :en_wikipedia_url, :class => 'form-control'
%span.help-block Link to this crop's page on the English language Wikipedia.
.form-group
= f.label :parent_id, 'Parent crop', :class => 'control-label col-md-2'
.col-md-8
= collection_select(:crop, :parent_id, Crop.all, :id, :name, {:include_blank => true}, :class => 'form-control')
%span.help-block Optional. For setting up crop hierarchies for varieties etc.
%p
%span.help-block
You may enter up to 3 scientific names for a crop. Most crops will have only one.
= f.fields_for :scientific_names do |sn|
.control-group
= sn.label :scientific_name, "Scientific name", :class => 'control-label'
.controls
= sn.text_field :scientific_name
.form-group
= sn.label :scientific_name, "Scientific name", :class => 'control-label col-md-2'
.col-md-8
= sn.text_field :scientific_name, :class => 'form-control'
.col-md-2
- if sn.object && sn.object.persisted?
%label.checkbox
= sn.check_box :_destroy
= sn.label :_destroy, "Delete"
.form-actions
= f.submit 'Save', :class => 'btn btn-primary'
.form-group
.form-actions.col-md-offset-2.col-md-8
= f.submit 'Save', :class => 'btn btn-primary'

View File

@@ -4,16 +4,18 @@
Nobody has harvested this crop yet.
- else
%ul
- crop.harvests.each do |harvest|
- crop.harvests.take(3).each do |harvest|
%li
= link_to "#{harvest.owner} harvested #{display_quantity(harvest)}.", harvest_path(harvest)
= render :partial => 'members/location', :locals => { :member => harvest.owner }
%small
= distance_of_time_in_words(harvest.created_at, Time.zone.now)
ago.
%p
= link_to "View all #{crop.name} harvests", harvests_by_crop_path(crop)
- if current_member
= link_to "Track your #{crop.name} harvests.", new_harvest_path()
%p
= link_to "Harvest #{crop.name}", new_harvest_path(:crop_id => crop.id)
- else
= render :partial => 'shared/signin_signup', :locals => { :to => "track your #{crop.name} harvests" }

View File

@@ -2,11 +2,12 @@
= link_to |
image_tag( |
crop.default_photo ? crop.default_photo.thumbnail_url : 'placeholder_150.png', |
:alt => crop.name |
:alt => crop.name, |
:class => 'image-responsive crop-image' |
), |
crop, |
:rel => "popover", |
'data-trigger' => 'hover', |
'data-title' => crop.name, |
'data-content' => "#{ render :partial => 'crops/popover', :locals => { :crop => crop } }", |
'data-html' => true
'data-html' => true |

View File

@@ -1,8 +1,8 @@
.well
.row-fluid
.span4
= link_to image_tag((crop.default_photo ? crop.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded'), crop
.span8
.row
.col-md-4
= link_to image_tag((crop.default_photo ? crop.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded crop-image'), crop
.col-md-8
%h3{:style => 'padding-top: 0px; margin-top: 0px'}
= link_to crop, crop

View File

@@ -1,6 +1,5 @@
.row-fluid
- if !crop.photos.empty?
%ul.thumbnails
.row
- if !crop.photos.empty?
- crop.photos.first(3).each do |p|
%li.span4
.col-md-4
= render :partial => "photos/thumbnail", :locals => { :photo => p }

View File

@@ -0,0 +1,21 @@
%h4 Plantings
- if crop.plantings.empty?
%p
Nobody has planted this crop yet.
- else
%ul
- crop.plantings.take(3).each do |planting|
%li
= link_to "#{planting.owner} planted #{planting.quantity} #{planting.planted_from}.", planting_path(planting)
= render :partial => 'members/location', :locals => { :member => planting.owner }
%small
= distance_of_time_in_words(planting.created_at, Time.zone.now)
ago.
%p
= link_to "View all #{crop.name} plantings", plantings_by_crop_path(crop)
- if current_member
%p
= link_to "Plant #{crop.name}", new_planting_path(:crop_id => crop.id)
- else
= render :partial => 'shared/signin_signup', :locals => { :to => "track your #{crop.name} plantings" }

View File

@@ -5,18 +5,19 @@
View any crop page to see which of our members have planted it and find
information on how to grow it yourself.
= form_tag(crops_path, :method => :get, :class => 'form-inline') do
= label_tag :sort, "Sort by:", :class => 'control-label'
= select_tag "sort", options_for_select({"Popularity" => 'popular', "Alphabetical" => 'alpha'}, @sort || 'popular')
= form_tag(crops_path, :method => :get, :class => 'form-inline', :role => 'form') do
.form-group
= label_tag :sort, "Sort by:", :class => 'sr-only'
= select_tag "sort", options_for_select({"Sort by popularity" => 'popular', "Sort alphabetically" => 'alpha'}, @sort || 'popular'), :class => 'form-control'
= submit_tag "Show", :class => 'btn btn-primary'
%div.pagination
= page_entries_info @crops, :model => "crops"
= will_paginate @crops
%ul.thumbnails
.row
- @crops.each do |crop|
%li.span2.six-across
.col-md-2.six-across
= render :partial => "thumbnail", :locals => { :crop => crop }
- if can? :create, Crop
@@ -28,7 +29,7 @@
= will_paginate @crops
%ul.inline
%ul.list-inline
%li The data on this page is available in the following formats:
%li= link_to "CSV", crops_path(:format => 'csv')
%li= link_to "JSON", crops_path(:format => 'json')

View File

@@ -13,14 +13,14 @@
%div#exact_match
%h2 Exact match
%ul.thumbnails
%li.span2.six-across
.row
.col-md-2.six-across
= render :partial => "thumbnail", :locals => { :crop => @exact_match }
- if ! @partial_matches.empty?
%div#partial_matches
%h2 Partial matches
%ul.thumbnails
.row
- @partial_matches.each do |c|
%li.span2.six-across
.col-md-2.six-across
= render :partial => "thumbnail", :locals => { :crop => c }

View File

@@ -1,18 +1,18 @@
- content_for :title, @crop.name
- content_for :subtitle, @crop.default_scientific_name
- content_for :buttonbar do
- if can? :create, Planting
= link_to "Plant this", new_planting_path(:crop_id => @crop.id), :class => 'btn btn-default'
.row-fluid
.span9
%p
- if can? :create, Planting
= link_to "Plant this", new_planting_path(:crop_id => @crop.id), :class => 'btn btn-primary'
- else
= render :partial => 'shared/signin_signup', :locals => { :to => 'plant this crop' }
- if can? :create, Harvest
= link_to "Harvest this", new_harvest_path(:crop_id => @crop.id), :class => 'btn btn-default'
- if can? :create, Harvest
= link_to "Harvest this", new_harvest_path(:crop_id => @crop.id), :class => 'btn btn-primary'
- if can? :create, Seed
= link_to 'Add seeds to stash', new_seed_path(:params => { :crop_id => @crop.id }), :class => 'btn btn-default'
- if can? :create, Seed
= link_to 'Add seeds to stash', new_seed_path(:params => { :crop_id => @crop.id }), :class => 'btn btn-primary'
.row
.col-md-9
= render :partial => 'photos', :locals => { :crop => @crop }
@@ -33,14 +33,20 @@
%div#cropmap
- if @crop.plantings.size > 0
%a{:name => 'posts'}
%div.pagination
= page_entries_info @posts, :model => "posts"
= will_paginate @posts, :params => {:anchor => "posts"}
%h2 All plantings
- unless @posts.empty?
- @posts.each do |post|
= render :partial => "posts/single", :locals => { :post => post, :subject => true }
- @crop.plantings.each do |p|
= render :partial => "plantings/thumbnail", :locals => { :planting => p, :title => 'owner' }
%div.pagination
= page_entries_info @posts, :model => "posts"
= will_paginate @posts, :params => {:anchor => "posts"}
.span3
.col-md-3
- if can? :edit, @crop or can? :destroy, @crop
%h4 Crop wrangling
%p
@@ -49,22 +55,37 @@
%strong CROP WRANGLER
%p
- if can? :edit, @crop
= link_to 'Edit crop', edit_crop_path(@crop), { :class => 'btn btn-mini' }
= link_to 'Edit crop', edit_crop_path(@crop), { :class => 'btn btn-default btn-xs' }
- if can? :destroy, @crop
= link_to 'Delete crop', @crop, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini'
= link_to 'Delete crop', @crop, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs'
%h4 Scientific names
%ul
- @crop.scientific_names.each do |sn|
%li
= sn.scientific_name
- if can? :edit, sn
= link_to 'Edit', edit_scientific_name_path(sn), { :class => 'btn btn-mini' }
- if can? :destroy, sn
= link_to 'Delete', sn, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini'
%p
- if can? :edit, @crop
= link_to 'Add', new_scientific_name_path( :crop_id => @crop.id ), { :class => 'btn btn-mini' }
.scientific_names
%h4 Scientific names
%ul
- @crop.scientific_names.each do |sn|
%li
= sn.scientific_name
- if can? :edit, sn
= link_to 'Edit', edit_scientific_name_path(sn), { :class => 'btn btn-default btn-xs' }
- if can? :destroy, sn
= link_to 'Delete', sn, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs'
%p
- if can? :edit, @crop
= link_to 'Add', new_scientific_name_path( :crop_id => @crop.id ), { :class => 'btn btn-default btn-xs' }
.alternate_names
%h4 Alternate names
%ul
- @crop.alternate_names.each do |an|
%li
= an.name
- if can? :edit, an
= link_to 'Edit', edit_alternate_name_path(an), { :class => 'btn btn-default btn-xs' }
- if can? :destroy, an
= link_to 'Delete', an, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-default btn-xs'
%p
- if can? :edit, @crop
= link_to 'Add', new_alternate_name_path( :crop_id => @crop.id ), { :class => 'btn btn-default btn-xs' }
= render :partial => 'varieties', :locals => { :crop => @crop }
@@ -76,5 +97,6 @@
%ul
%li= link_to 'Wikipedia (English)', @crop.en_wikipedia_url
= render :partial => 'plantings', :locals => { :crop => @crop }
= render :partial => 'harvests', :locals => { :crop => @crop }
= render :partial => 'find_seeds', :locals => { :crop => @crop }

View File

@@ -8,6 +8,13 @@
%li= link_to "Full crop hierarchy", crops_hierarchy_path
%li= link_to "Add Crop", new_crop_path
%div.crop_wranglers
%h2 Crop Wranglers:
%ul
- @crop_wranglers.each do |crop_wrangler|
%li.crop_wrangler
= link_to crop_wrangler.login_name, crop_wrangler
%h2 Recently added crops
%div.pagination

View File

@@ -1,12 +1,16 @@
- content_for :title, "Resend confirmation instructions"
= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f|
= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post, :class => 'form-horizontal', :role => 'form' }) do |f|
= devise_error_messages!
%p Enter either your login name or your email address to resend the confirmation email.
= f.label :login
= f.text_field :login
= f.submit "Resend confirmation instructions"
.form-group
= f.label :login, :class => 'control-label col-md-2'
.col-md-8
= f.text_field :login, :class => 'form-control'
.form-group
.form-actions.col-md-offset-2.col-md-8
= f.submit "Resend confirmation instructions", :class => 'btn btn-primary'
= render "devise/shared/links"

View File

@@ -5,7 +5,7 @@
Your account on #{site_name} has been created. You just need to confirm
your email address through the link below:
%p= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token)
%p= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @token)
%p
Once you're confirmed, you can sign in with your login name

View File

@@ -5,7 +5,7 @@
Someone has requested a link to reset your password on #{site_name}.
We presume this was you, in which case you can do so through this link:
%p= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token)
%p= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @token)
%p
If it wasn't you, then someone's made a typo or has been messing

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