mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-05-25 09:19:15 -04:00
Compare commits
1 Commits
feature/pr
...
feature/bo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
564f5f0dca |
131
Gemfile.lock
131
Gemfile.lock
@@ -33,29 +33,29 @@ GEM
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
actioncable (7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
activejob (= 7.2.2.1)
|
||||
activerecord (= 7.2.2.1)
|
||||
activestorage (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
actionmailbox (7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
activejob (= 7.2.2.2)
|
||||
activerecord (= 7.2.2.2)
|
||||
activestorage (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
actionview (= 7.2.2.1)
|
||||
activejob (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
actionmailer (7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
actionview (= 7.2.2.2)
|
||||
activejob (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (7.2.2.1)
|
||||
actionview (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
actionpack (7.2.2.2)
|
||||
actionview (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
nokogiri (>= 1.8.5)
|
||||
racc
|
||||
rack (>= 2.2.4, < 3.2)
|
||||
@@ -64,15 +64,15 @@ GEM
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
useragent (~> 0.16)
|
||||
actiontext (7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
activerecord (= 7.2.2.1)
|
||||
activestorage (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
actiontext (7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
activerecord (= 7.2.2.2)
|
||||
activestorage (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
actionview (7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
@@ -87,22 +87,22 @@ GEM
|
||||
active_utils (3.5.0)
|
||||
activesupport (>= 4.2)
|
||||
i18n
|
||||
activejob (7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
activejob (7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
activerecord (7.2.2.1)
|
||||
activemodel (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
activemodel (7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
activerecord (7.2.2.2)
|
||||
activemodel (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
activejob (= 7.2.2.1)
|
||||
activerecord (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
activestorage (7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
activejob (= 7.2.2.2)
|
||||
activerecord (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
marcel (~> 1.0)
|
||||
activesupport (7.2.2.1)
|
||||
activesupport (7.2.2.2)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
@@ -251,7 +251,7 @@ GEM
|
||||
elasticsearch-transport (7.0.0)
|
||||
faraday
|
||||
multi_json
|
||||
erb (5.0.1)
|
||||
erb (5.0.2)
|
||||
erubi (1.13.1)
|
||||
erubis (2.7.0)
|
||||
excon (1.2.5)
|
||||
@@ -351,7 +351,7 @@ GEM
|
||||
image_processing (1.12.2)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
io-console (0.8.0)
|
||||
io-console (0.8.1)
|
||||
irb (1.15.2)
|
||||
pp (>= 0.6.0)
|
||||
rdoc (>= 4.0.0)
|
||||
@@ -423,14 +423,14 @@ GEM
|
||||
bigdecimal (~> 3.1)
|
||||
net-http (0.6.0)
|
||||
uri
|
||||
net-imap (0.4.20)
|
||||
net-imap (0.5.9)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
net-protocol
|
||||
net-protocol (0.2.2)
|
||||
timeout
|
||||
net-smtp (0.5.0)
|
||||
net-smtp (0.5.1)
|
||||
net-protocol
|
||||
netrc (0.11.0)
|
||||
nio4r (2.7.4)
|
||||
@@ -499,20 +499,20 @@ GEM
|
||||
rackup (1.0.1)
|
||||
rack (< 3)
|
||||
webrick
|
||||
rails (7.2.2.1)
|
||||
actioncable (= 7.2.2.1)
|
||||
actionmailbox (= 7.2.2.1)
|
||||
actionmailer (= 7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
actiontext (= 7.2.2.1)
|
||||
actionview (= 7.2.2.1)
|
||||
activejob (= 7.2.2.1)
|
||||
activemodel (= 7.2.2.1)
|
||||
activerecord (= 7.2.2.1)
|
||||
activestorage (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
rails (7.2.2.2)
|
||||
actioncable (= 7.2.2.2)
|
||||
actionmailbox (= 7.2.2.2)
|
||||
actionmailer (= 7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
actiontext (= 7.2.2.2)
|
||||
actionview (= 7.2.2.2)
|
||||
activejob (= 7.2.2.2)
|
||||
activemodel (= 7.2.2.2)
|
||||
activerecord (= 7.2.2.2)
|
||||
activestorage (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.2.2.1)
|
||||
railties (= 7.2.2.2)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
@@ -532,9 +532,9 @@ GEM
|
||||
rails_stdout_logging
|
||||
rails_serve_static_assets (0.0.5)
|
||||
rails_stdout_logging (0.0.5)
|
||||
railties (7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
railties (7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
@@ -553,8 +553,8 @@ GEM
|
||||
recaptcha (5.20.1)
|
||||
redis-client (0.23.2)
|
||||
connection_pool
|
||||
regexp_parser (2.11.1)
|
||||
reline (0.6.1)
|
||||
regexp_parser (2.11.2)
|
||||
reline (0.6.2)
|
||||
io-console (~> 0.5)
|
||||
responders (3.1.1)
|
||||
actionpack (>= 5.2)
|
||||
@@ -582,7 +582,7 @@ GEM
|
||||
rspec-mocks (3.13.5)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-rails (8.0.1)
|
||||
rspec-rails (8.0.2)
|
||||
actionpack (>= 7.2)
|
||||
activesupport (>= 7.2)
|
||||
railties (>= 7.2)
|
||||
@@ -628,7 +628,7 @@ GEM
|
||||
rubocop-factory_bot (2.27.1)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-rails (2.32.0)
|
||||
rubocop-rails (2.33.3)
|
||||
activesupport (>= 4.2.0)
|
||||
lint_roller (~> 1.1)
|
||||
rack (>= 1.1)
|
||||
@@ -648,7 +648,7 @@ GEM
|
||||
ruby-units (4.1.0)
|
||||
ruby-vips (2.2.1)
|
||||
ffi (~> 1.12)
|
||||
rubyzip (2.4.1)
|
||||
rubyzip (3.0.1)
|
||||
sass (3.7.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
@@ -668,11 +668,11 @@ GEM
|
||||
activemodel (>= 6.1)
|
||||
hashie
|
||||
securerandom (0.4.1)
|
||||
selenium-webdriver (4.34.0)
|
||||
selenium-webdriver (4.35.0)
|
||||
base64 (~> 0.2)
|
||||
logger (~> 1.4)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
rubyzip (>= 1.2.2, < 4.0)
|
||||
websocket (~> 1.0)
|
||||
sidekiq (7.3.9)
|
||||
base64
|
||||
@@ -730,7 +730,8 @@ GEM
|
||||
rack-test (>= 0.5.3)
|
||||
webrick (1.9.1)
|
||||
websocket (1.2.11)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-driver (0.8.0)
|
||||
base64
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
will_paginate (4.0.1)
|
||||
|
||||
@@ -78,6 +78,7 @@ class ApplicationController < ActionController::Base
|
||||
:tos_agreement,
|
||||
# profile stuff
|
||||
:bio, :location, :latitude, :longitude,
|
||||
:website_url, :instagram_handle, :facebook_handle, :bluesky_handle, :other_url,
|
||||
# email settings
|
||||
:show_email, :newsletter, :send_notification_email, :send_planting_reminder,
|
||||
# update password
|
||||
|
||||
@@ -75,7 +75,6 @@ class CropsController < ApplicationController
|
||||
end
|
||||
|
||||
def show
|
||||
@problems = Problem.joins(plantings: :crop).where(crops: { id: @crop.id }).distinct
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@posts = @crop.posts.order(created_at: :desc).paginate(page: params[:page])
|
||||
|
||||
@@ -107,7 +107,7 @@ class PlantingsController < DataController
|
||||
:crop_id, :description, :garden_id, :planted_at,
|
||||
:parent_seed_id,
|
||||
:quantity, :sunniness, :planted_from, :finished,
|
||||
:finished_at, :failed, problem_ids: []
|
||||
:finished_at
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ProblemsController < ApplicationController
|
||||
before_action :authenticate_member!, except: %i(index show)
|
||||
load_and_authorize_resource id_param: :slug, class: Problem
|
||||
respond_to :html, :json
|
||||
|
||||
def index
|
||||
@problems = Problem.approved.popular.paginate(page: params[:page])
|
||||
@num_requested_problems = requested_problems.size if current_member
|
||||
respond_with @problems
|
||||
end
|
||||
|
||||
def requested
|
||||
@requested = requested_problems.paginate(page: params[:page])
|
||||
respond_with @requested
|
||||
end
|
||||
|
||||
def show
|
||||
@problem = Problem.friendly.find(params[:id])
|
||||
@plantings = @problem.plantings.paginate(page: params[:page])
|
||||
respond_with @problem
|
||||
end
|
||||
|
||||
def new
|
||||
@problem = Problem.new
|
||||
respond_with @problem
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
def create
|
||||
@problem = Problem.new(problem_params)
|
||||
if current_member.role? :problem_wrangler
|
||||
@problem.creator = current_member
|
||||
else
|
||||
@problem.requester = current_member
|
||||
@problem.approval_status = "pending"
|
||||
end
|
||||
@problem.save
|
||||
respond_with @problem
|
||||
end
|
||||
|
||||
def update
|
||||
if can?(:wrangle, @problem)
|
||||
@problem.approval_status = 'rejected' if params.fetch("reject", false)
|
||||
@problem.approval_status = 'approved' if params.fetch("approve", false)
|
||||
end
|
||||
@problem.update(problem_params)
|
||||
respond_with @problem
|
||||
end
|
||||
|
||||
def wrangle
|
||||
@approval_status = params[:approval_status]
|
||||
@problems = case @approval_status
|
||||
when "pending"
|
||||
Problem.pending_approval
|
||||
when "rejected"
|
||||
Problem.rejected
|
||||
else
|
||||
Problem.recent
|
||||
end.paginate(page: params[:page])
|
||||
@problem_wranglers = Role.problem_wranglers
|
||||
respond_with @problems
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def problem_params
|
||||
params.require(:problem).permit(:name, :reason_for_rejection, :rejection_notes)
|
||||
end
|
||||
|
||||
def requested_problems
|
||||
current_member.requested_problems.pending_approval
|
||||
end
|
||||
end
|
||||
@@ -32,7 +32,7 @@ module ApplicationHelper
|
||||
# of HAML, Tilt, and dynamic compilation with interpolated ruby.
|
||||
def markdownify(text)
|
||||
translator = Haml::Filters::GrowstuffMarkdown.new
|
||||
translator.expand_members!(translator.expand_problems!(translator.expand_crops!(text.to_s)))
|
||||
translator.expand_members!(translator.expand_crops!(text.to_s))
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
@@ -34,8 +34,6 @@ class Ability
|
||||
# are wranglers or admins
|
||||
cannot :read, Crop
|
||||
can :read, Crop, approval_status: "approved"
|
||||
cannot :read, Problem
|
||||
can :read, Problem, approval_status: "approved"
|
||||
# scientific names should only be viewable if associated crop is approved
|
||||
cannot :read, ScientificName
|
||||
can :read, ScientificName do |sn|
|
||||
@@ -58,8 +56,6 @@ class Ability
|
||||
# members can see even rejected or pending crops if they requested it
|
||||
can :read, Crop, requester_id: member.id
|
||||
can :requested, Crop # see list of crops they've requested
|
||||
can :read, Problem, requester_id: member.id
|
||||
can :requested, Problem
|
||||
|
||||
# managing your own user settings
|
||||
can :update, Member, id: member.id
|
||||
@@ -86,14 +82,8 @@ class Ability
|
||||
can :gbif, Crop
|
||||
end
|
||||
|
||||
if member.role? :problem_wrangler
|
||||
can :wrangle, Problem
|
||||
can :manage, Problem
|
||||
end
|
||||
|
||||
# any member can create a crop provisionally
|
||||
can :create, Crop
|
||||
can :create, Problem
|
||||
|
||||
# can create & destroy their own authentications against other sites.
|
||||
can :create, Authentication
|
||||
|
||||
@@ -17,7 +17,6 @@ class Crop < ApplicationRecord
|
||||
has_many :scientific_names, dependent: :delete_all
|
||||
has_many :alternate_names, dependent: :delete_all
|
||||
has_many :plantings, dependent: :destroy
|
||||
has_many :problems, through: :plantings
|
||||
has_many :seeds, dependent: :destroy
|
||||
has_many :harvests, dependent: :destroy
|
||||
has_many :photo_associations, dependent: :delete_all, inverse_of: :crop
|
||||
|
||||
@@ -42,10 +42,6 @@ class Member < ApplicationRecord
|
||||
inverse_of: :requester
|
||||
has_many :created_crops, class_name: 'Crop', foreign_key: 'creator_id', dependent: :nullify,
|
||||
inverse_of: :creator
|
||||
has_many :requested_problems, class_name: 'Problem', foreign_key: 'requester_id', dependent: :nullify,
|
||||
inverse_of: :requester
|
||||
has_many :created_problems, class_name: 'Problem', foreign_key: 'creator_id', dependent: :nullify,
|
||||
inverse_of: :creator
|
||||
has_many :created_alternate_names, class_name: 'AlternateName', foreign_key: 'creator_id', inverse_of: :creator
|
||||
has_many :created_scientific_names, class_name: 'ScientificName', foreign_key: 'creator_id', inverse_of: :creator
|
||||
|
||||
@@ -95,6 +91,9 @@ class Member < ApplicationRecord
|
||||
uniqueness: {
|
||||
case_sensitive: false
|
||||
}
|
||||
validates :website_url, format: { with: /\Ahttps?:\/\//, message: "must start with http:// or https://" }, allow_blank: true
|
||||
validates :other_url, format: { with: /\Ahttps?:\/\//, message: "must start with http:// or https://" }, allow_blank: true
|
||||
validates :instagram_handle, :facebook_handle, :bluesky_handle, format: { without: %r{\Ahttps?:\/\/|\/}, message: "should be a handle, not a URL" }, allow_blank: true
|
||||
|
||||
#
|
||||
# Triggers
|
||||
|
||||
@@ -24,8 +24,6 @@ class Planting < ApplicationRecord
|
||||
belongs_to :crop, counter_cache: true
|
||||
has_many :harvests, dependent: :destroy
|
||||
has_many :activities, dependent: :destroy
|
||||
has_many :planting_problems, dependent: :destroy
|
||||
has_many :problems, through: :planting_problems
|
||||
|
||||
#
|
||||
# Ancestry of food
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PlantingProblem < ApplicationRecord
|
||||
include PhotoCapable
|
||||
|
||||
belongs_to :planting
|
||||
belongs_to :problem
|
||||
end
|
||||
@@ -13,14 +13,11 @@ class Post < ApplicationRecord
|
||||
has_many :comments, dependent: :destroy
|
||||
has_many :crop_posts, dependent: :delete_all
|
||||
has_many :crops, through: :crop_posts
|
||||
has_many :problem_posts, dependent: :delete_all
|
||||
has_many :problems, through: :problem_posts
|
||||
|
||||
after_create :send_notification
|
||||
#
|
||||
# Triggers
|
||||
after_save :update_crop_posts_association
|
||||
after_save :update_problem_posts_association
|
||||
|
||||
default_scope { joins(:author).merge(Member.kept) } # Ensures the owner still exists
|
||||
|
||||
@@ -78,17 +75,6 @@ class Post < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def update_problem_posts_association
|
||||
problems.clear
|
||||
# look for problems mentioned in the post. eg. [aphids](problem)
|
||||
body.scan(Haml::Filters::GrowstuffMarkdown::PROBLEM_REGEX) do |_m|
|
||||
problem_name = Regexp.last_match(1)
|
||||
problem = Problem.case_insensitive_name(problem_name).first
|
||||
# create association
|
||||
problems << problem if problem && problems.exclude?(problem)
|
||||
end
|
||||
end
|
||||
|
||||
def send_notification
|
||||
recipients = []
|
||||
sender = author.id
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Problem < ApplicationRecord
|
||||
extend FriendlyId
|
||||
include PhotoCapable
|
||||
include SearchCrops # Note: This might need to be adapted to SearchProblems
|
||||
|
||||
friendly_id :name, use: %i(slugged finders)
|
||||
|
||||
##
|
||||
## Relationships
|
||||
belongs_to :creator, class_name: 'Member', optional: true, inverse_of: :created_problems
|
||||
belongs_to :requester, class_name: 'Member', optional: true, inverse_of: :requested_problems
|
||||
has_many :planting_problems, dependent: :delete_all
|
||||
has_many :plantings, through: :planting_problems
|
||||
has_many :problem_posts, dependent: :delete_all
|
||||
has_many :posts, through: :problem_posts, dependent: :delete_all
|
||||
|
||||
##
|
||||
## Scopes
|
||||
scope :recent, -> { approved.order(created_at: :desc) }
|
||||
scope :popular, -> { approved.order(Arel.sql("plantings_count desc, lower(name) asc")) }
|
||||
scope :pending_approval, -> { where(approval_status: "pending") }
|
||||
scope :approved, -> { where(approval_status: "approved") }
|
||||
scope :rejected, -> { where(approval_status: "rejected") }
|
||||
scope :interesting, -> { approved.has_photos }
|
||||
scope :has_photos, -> { includes(:photos).where.not(photos: { id: nil }) }
|
||||
|
||||
##
|
||||
## Validations
|
||||
validates :reason_for_rejection, presence: true, if: :rejected?
|
||||
validate :must_be_rejected_if_rejected_reasons_present
|
||||
validate :must_have_meaningful_reason_for_rejection
|
||||
validates :name, uniqueness: { scope: :approval_status }, if: :pending?
|
||||
|
||||
def to_s
|
||||
name
|
||||
end
|
||||
|
||||
def to_param
|
||||
slug
|
||||
end
|
||||
|
||||
def pending?
|
||||
approval_status == "pending"
|
||||
end
|
||||
|
||||
def approved?
|
||||
approval_status == "approved"
|
||||
end
|
||||
|
||||
def rejected?
|
||||
approval_status == "rejected"
|
||||
end
|
||||
|
||||
def approval_statuses
|
||||
%w(rejected pending approved)
|
||||
end
|
||||
|
||||
def reasons_for_rejection
|
||||
["already in database", "not a pest or disease", "not enough information", "other"]
|
||||
end
|
||||
|
||||
def rejection_explanation
|
||||
return rejection_notes if reason_for_rejection == "other"
|
||||
|
||||
reason_for_rejection
|
||||
end
|
||||
|
||||
def self.case_insensitive_name(name)
|
||||
where(["lower(problems.name) = :value", { value: name.downcase }])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def must_be_rejected_if_rejected_reasons_present
|
||||
return if rejected?
|
||||
return unless reason_for_rejection.present? || rejection_notes.present?
|
||||
|
||||
errors.add(:approval_status, "must be rejected if a reason for rejection is present")
|
||||
end
|
||||
|
||||
def must_have_meaningful_reason_for_rejection
|
||||
return unless reason_for_rejection == "other" && rejection_notes.blank?
|
||||
|
||||
errors.add(:rejection_notes, "must be added if the reason for rejection is \"other\"")
|
||||
end
|
||||
end
|
||||
@@ -1,6 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ProblemPost < ApplicationRecord
|
||||
belongs_to :problem
|
||||
belongs_to :post
|
||||
end
|
||||
@@ -8,7 +8,7 @@ class Role < ApplicationRecord
|
||||
has_and_belongs_to_many :members
|
||||
|
||||
class << self
|
||||
%i(crop_wranglers admins problem_wranglers).each do |method|
|
||||
%i(crop_wranglers admins).each do |method|
|
||||
define_method method do
|
||||
slug = method.to_s.singularize.dasherize
|
||||
Role.where(slug:).try(:first).try(:members)
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
There are
|
||||
= link_to "https://openfarm.cc/en/crops/#{CGI.escape @crop.name.gsub(' ', '-').downcase}" do
|
||||
#{crop.guides_count} growing guides on Open Farm
|
||||
|
||||
|
||||
@@ -30,15 +30,6 @@
|
||||
- @crop.companions.each do |companion|
|
||||
= render 'crops/tiny', crop: companion
|
||||
|
||||
- if @problems.any?
|
||||
%section.problems
|
||||
%h2 Problems
|
||||
- @problems.group_by(&:name).each do |problem_name, problems|
|
||||
- problem = problems.first
|
||||
= link_to problem_path(problem) do
|
||||
= problem_name
|
||||
%span.badge.badge-secondary= problems.size
|
||||
|
||||
%section.photos
|
||||
= cute_icon
|
||||
= render 'crops/photos', crop: @crop
|
||||
|
||||
@@ -13,7 +13,32 @@
|
||||
.form-group
|
||||
= f.label :bio, class: 'control-label col-md-2'
|
||||
.col-md-8
|
||||
= f.text_area :bio, rows: 6, class: 'form-control'
|
||||
= f.text_area :bio, rows: 6, class: 'form-control', placeholder: "I'm am XYZ gardener interested in A, B, C"
|
||||
|
||||
.form-group
|
||||
= f.label :website_url, 'Website', class: 'control-label col-md-2'
|
||||
.col-md-8
|
||||
= f.url_field :website_url, class: 'form-control', placeholder: "https://you.example.com/"
|
||||
|
||||
.form-group
|
||||
= f.label :instagram_handle, 'Instagram', class: 'control-label col-md-2'
|
||||
.col-md-8
|
||||
= f.text_field :instagram_handle, class: 'form-control', placeholder: 'your_handle'
|
||||
|
||||
.form-group
|
||||
= f.label :facebook_handle, 'Facebook', class: 'control-label col-md-2'
|
||||
.col-md-8
|
||||
= f.text_field :facebook_handle, class: 'form-control', placeholder: 'your_handle'
|
||||
|
||||
.form-group
|
||||
= f.label :bluesky_handle, 'Bluesky', class: 'control-label col-md-2'
|
||||
.col-md-8
|
||||
= f.text_field :bluesky_handle, class: 'form-control', placeholder: 'your_handle'
|
||||
|
||||
.form-group
|
||||
= f.label :other_url, 'Other URL', class: 'control-label col-md-2'
|
||||
.col-md-8
|
||||
= f.url_field :other_url, class: 'form-control', placeholder: "https://you.example.com/"
|
||||
|
||||
.form-group
|
||||
%label.control-label.col-md-2
|
||||
|
||||
@@ -5,3 +5,5 @@
|
||||
number_crops: link_to(t('.number_crops_linktext', count: Crop.count.to_i), crops_path),
|
||||
number_plantings: link_to(t('.number_plantings_linktext', count: Planting.count.to_i), plantings_path),
|
||||
number_gardens: link_to(t('.number_gardens_linktext', count: Garden.count.to_i), gardens_path))
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
%span.site-name Growstuff
|
||||
.nav= render 'crops/search_bar'
|
||||
.nav
|
||||
%button.navbar-toggler{ "aria-controls" => "navbarSupportedContent", "aria-expanded" => "false", "aria-label" => "Toggle navigation", "data-bs-target" => "#navbarSupportedContent", "data-bs-toggle" => "collapse", type: "button" }
|
||||
%button.navbar-toggler{ "aria-controls" => "offcanvasNavbar", "aria-label" => "Toggle navigation", "data-bs-target" => "#offcanvasNavbar", "data-bs-toggle" => "offcanvas", type: "button" }
|
||||
%i.fas.fa-ellipsis-v.navbar-toggler-icon
|
||||
= render 'layouts/menu'
|
||||
|
||||
@@ -1,98 +1,102 @@
|
||||
#navbarSupportedContent.collapse.navbar-collapse
|
||||
%ul.navbar-nav.mr-auto
|
||||
- if signed_in?
|
||||
%li.nav-item
|
||||
= link_to timeline_index_path, method: :get, class: 'nav-link text-white' do
|
||||
= image_tag 'icons/notification.svg', class: 'img img-icon', alt: "Notifications"
|
||||
%li.nav-item
|
||||
= link_to member_gardens_path(current_member), class: 'nav-link text-white', title: "My gardens" do
|
||||
= image_icon 'gardens'
|
||||
%li.nav-item.dropdown
|
||||
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
|
||||
= image_tag "icons/gardener.svg", class: 'img img-icon', alt: t('.record'), aria: { hidden: "true" }
|
||||
= t('.record')
|
||||
.dropdown-menu
|
||||
= link_to new_planting_path, class: 'dropdown-item' do
|
||||
= image_icon('planting-add')
|
||||
= t('buttons.new_planting')
|
||||
= link_to new_harvest_path, class: 'dropdown-item' do
|
||||
= image_icon('harvest-add')
|
||||
= t('buttons.new_harvest')
|
||||
= link_to new_activity_path, class: 'dropdown-item' do
|
||||
= image_icon('activity-add')
|
||||
= t('buttons.new_activity')
|
||||
= link_to new_seed_path, class: 'dropdown-item' do
|
||||
= image_icon('seed-add')
|
||||
= t('buttons.new_seeds')
|
||||
= link_to new_post_path, class: 'dropdown-item' do
|
||||
= post_icon
|
||||
= t('buttons.new_post')
|
||||
|
||||
- cache("everyone-menu", expires_in: 1.week) do
|
||||
%li.nav-item.dropdown
|
||||
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.crops')
|
||||
.dropdown-menu
|
||||
= link_to crops_path, class: 'dropdown-item' do
|
||||
= t('.browse_crops')
|
||||
= link_to seeds_path, class: 'dropdown-item' do
|
||||
= seed_icon
|
||||
= t('.seeds')
|
||||
= link_to plantings_path, class: 'dropdown-item' do
|
||||
= planting_icon
|
||||
= t('.plantings')
|
||||
= link_to harvests_path, class: 'dropdown-item' do
|
||||
= harvest_icon
|
||||
= t('.harvests')
|
||||
%li.nav-item.dropdown
|
||||
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.community')
|
||||
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
|
||||
= link_to t('.community_map'), places_path, class: 'dropdown-item'
|
||||
= link_to t('.browse_members'), members_path, class: 'dropdown-item'
|
||||
= link_to t('.posts'), posts_path, class: 'dropdown-item'
|
||||
= link_to t('.forums'), forums_path, class: 'dropdown-item'
|
||||
|
||||
- if member_signed_in?
|
||||
- if current_member.role?(:crop_wrangler) || current_member.role?(:admin)
|
||||
.offcanvas.offcanvas-start#offcanvasNavbar{ 'aria-labelledby' => 'offcanvasNavbarLabel', 'tabindex' => '-1' }
|
||||
.offcanvas-header
|
||||
%h5.offcanvas-title#offcanvasNavbarLabel Menu
|
||||
%button.btn-close.text-reset{ 'data-bs-dismiss' => 'offcanvas', 'aria-label' => 'Close' }
|
||||
.offcanvas-body
|
||||
%ul.navbar-nav.mr-auto
|
||||
- if signed_in?
|
||||
%li.nav-item
|
||||
= link_to timeline_index_path, method: :get, class: 'nav-link' do
|
||||
= image_tag 'icons/notification.svg', class: 'img img-icon', alt: "Notifications"
|
||||
%li.nav-item
|
||||
= link_to member_gardens_path(current_member), class: 'nav-link', title: "My gardens" do
|
||||
= image_icon 'gardens'
|
||||
%li.nav-item.dropdown
|
||||
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.admin')
|
||||
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
|
||||
= image_tag "icons/gardener.svg", class: 'img img-icon', alt: t('.record'), aria: { hidden: "true" }
|
||||
= t('.record')
|
||||
.dropdown-menu
|
||||
= link_to new_planting_path, class: 'dropdown-item' do
|
||||
= image_icon('planting-add')
|
||||
= t('buttons.new_planting')
|
||||
= link_to new_harvest_path, class: 'dropdown-item' do
|
||||
= image_icon('harvest-add')
|
||||
= t('buttons.new_harvest')
|
||||
= link_to new_activity_path, class: 'dropdown-item' do
|
||||
= image_icon('activity-add')
|
||||
= t('buttons.new_activity')
|
||||
= link_to new_seed_path, class: 'dropdown-item' do
|
||||
= image_icon('seed-add')
|
||||
= t('buttons.new_seeds')
|
||||
= link_to new_post_path, class: 'dropdown-item' do
|
||||
= post_icon
|
||||
= t('buttons.new_post')
|
||||
|
||||
- cache("everyone-menu", expires_in: 1.week) do
|
||||
%li.nav-item.dropdown
|
||||
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.crops')
|
||||
.dropdown-menu
|
||||
= link_to crops_path, class: 'dropdown-item' do
|
||||
= t('.browse_crops')
|
||||
= link_to seeds_path, class: 'dropdown-item' do
|
||||
= seed_icon
|
||||
= t('.seeds')
|
||||
= link_to plantings_path, class: 'dropdown-item' do
|
||||
= planting_icon
|
||||
= t('.plantings')
|
||||
= link_to harvests_path, class: 'dropdown-item' do
|
||||
= harvest_icon
|
||||
= t('.harvests')
|
||||
%li.nav-item.dropdown
|
||||
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.community')
|
||||
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
|
||||
- if current_member.role?(:crop_wrangler)
|
||||
= link_to t('.crop_wrangling'), wrangle_crops_path, class: 'dropdown-item'
|
||||
- if current_member.role?(:admin)
|
||||
= link_to t('.admin'), admin_path, class: 'dropdown-item'
|
||||
= link_to t('.community_map'), places_path, class: 'dropdown-item'
|
||||
= link_to t('.browse_members'), members_path, class: 'dropdown-item'
|
||||
= link_to t('.posts'), posts_path, class: 'dropdown-item'
|
||||
= link_to t('.forums'), forums_path, class: 'dropdown-item'
|
||||
|
||||
%li.nav-item.dropdown
|
||||
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
|
||||
= image_tag(avatar_uri(current_member, 50), alt: 'Avatar of current member', height: 25, width: 25, aria: { hidden: "true" })
|
||||
= current_member.login_name
|
||||
- if current_member.unread_count.positive?
|
||||
%span.badge.badge-info= current_member.unread_count
|
||||
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
|
||||
= link_to member_path(current_member), class: 'dropdown-item' do
|
||||
= t('.profile')
|
||||
= link_to member_activities_path(current_member), class: 'dropdown-item' do
|
||||
= t('.activities')
|
||||
= link_to member_gardens_path(current_member), class: 'dropdown-item' do
|
||||
= t('.gardens')
|
||||
= link_to member_plantings_path(current_member), class: 'dropdown-item' do
|
||||
= t('.plantings')
|
||||
= link_to member_harvests_path(current_member), class: 'dropdown-item' do
|
||||
= t('.harvest')
|
||||
= link_to member_seeds_path(current_member), class: 'dropdown-item' do
|
||||
= t('.seeds')
|
||||
= link_to t('.posts'), member_posts_path(current_member), class: 'dropdown-item'
|
||||
- if member_signed_in?
|
||||
- if current_member.role?(:crop_wrangler) || current_member.role?(:admin)
|
||||
%li.nav-item.dropdown
|
||||
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}= t('.admin')
|
||||
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
|
||||
- if current_member.role?(:crop_wrangler)
|
||||
= link_to t('.crop_wrangling'), wrangle_crops_path, class: 'dropdown-item'
|
||||
- if current_member.role?(:admin)
|
||||
= link_to t('.admin'), admin_path, class: 'dropdown-item'
|
||||
|
||||
- if current_member.unread_count.positive?
|
||||
%li.nav-item.dropdown
|
||||
%a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"}
|
||||
= image_tag(avatar_uri(current_member, 50), alt: 'Avatar of current member', height: 25, width: 25, aria: { hidden: "true" })
|
||||
= current_member.login_name
|
||||
- if current_member.unread_count.positive?
|
||||
%span.badge.badge-info= current_member.unread_count
|
||||
.dropdown-menu{"aria-labelledby" => "navbarDropdown"}
|
||||
= link_to member_path(current_member), class: 'dropdown-item' do
|
||||
= t('.profile')
|
||||
= link_to member_activities_path(current_member), class: 'dropdown-item' do
|
||||
= t('.activities')
|
||||
= link_to member_gardens_path(current_member), class: 'dropdown-item' do
|
||||
= t('.gardens')
|
||||
= link_to member_plantings_path(current_member), class: 'dropdown-item' do
|
||||
= t('.plantings')
|
||||
= link_to member_harvests_path(current_member), class: 'dropdown-item' do
|
||||
= t('.harvest')
|
||||
= link_to member_seeds_path(current_member), class: 'dropdown-item' do
|
||||
= t('.seeds')
|
||||
= link_to t('.posts'), member_posts_path(current_member), class: 'dropdown-item'
|
||||
|
||||
- if current_member.unread_count.positive?
|
||||
.dropdown-divider
|
||||
%strong
|
||||
= link_to(conversations_path, class: 'dropdown-item') do
|
||||
= t('.inbox')
|
||||
%span.badge.badge-info= current_member.unread_count
|
||||
- else
|
||||
= link_to t('.inbox'), conversations_path, class: 'dropdown-item'
|
||||
.dropdown-divider
|
||||
%strong
|
||||
= link_to(conversations_path, class: 'dropdown-item') do
|
||||
= t('.inbox')
|
||||
%span.badge.badge-info= current_member.unread_count
|
||||
- else
|
||||
= link_to t('.inbox'), conversations_path, class: 'dropdown-item'
|
||||
.dropdown-divider
|
||||
= link_to t('.sign_out'), destroy_member_session_path, method: :delete, class: 'dropdown-item'
|
||||
= link_to t('.sign_out'), destroy_member_session_path, method: :delete, class: 'dropdown-item'
|
||||
|
||||
- else
|
||||
%li.nav-item= link_to t('.sign_in'), new_member_session_path, id: 'navbar-signin', class: 'btn btn-signin'
|
||||
%li.nav-item= link_to t('.sign_up'), new_member_registration_path, id: 'navbar-signup', class: 'btn btn-signup'
|
||||
- else
|
||||
%li.nav-item= link_to t('.sign_in'), new_member_session_path, id: 'navbar-signin', class: 'btn btn-signin'
|
||||
%li.nav-item= link_to t('.sign_up'), new_member_registration_path, id: 'navbar-signup', class: 'btn btn-signup'
|
||||
|
||||
@@ -1,17 +1,42 @@
|
||||
- if twitter_auth || flickr_auth || member.show_email
|
||||
- if member.website_url.present? || member.instagram_handle.present? || member.facebook_handle.present? || member.bluesky_handle.present? || member.other_url.present? || twitter_auth || flickr_auth || member.show_email
|
||||
%h4 Contact
|
||||
|
||||
- if member.website_url.present?
|
||||
%p
|
||||
= icon 'fas', 'globe', class: 'fa-fw'
|
||||
= link_to "Website", member.website_url, target: '_blank', rel: 'noopener noreferrer'
|
||||
|
||||
- if member.instagram_handle.present?
|
||||
%p
|
||||
= icon 'fab', 'instagram', class: 'fa-fw'
|
||||
= link_to member.instagram_handle, "https://instagram.com/#{member.instagram_handle}", target: '_blank', rel: 'noopener noreferrer'
|
||||
|
||||
- if member.facebook_handle.present?
|
||||
%p
|
||||
= icon 'fab', 'facebook', class: 'fa-fw'
|
||||
= link_to member.facebook_handle, "https://facebook.com/#{member.facebook_handle}", target: '_blank', rel: 'noopener noreferrer'
|
||||
|
||||
- if member.bluesky_handle.present?
|
||||
%p
|
||||
= icon 'fas', 'comment-dots', class: 'fa-fw'
|
||||
= link_to member.bluesky_handle, "https://bsky.app/profile/#{member.bluesky_handle}", target: '_blank', rel: 'noopener noreferrer'
|
||||
|
||||
- if member.other_url.present?
|
||||
%p
|
||||
= icon 'fas', 'link', class: 'fa-fw'
|
||||
= link_to "More...", member.other_url, target: '_blank', rel: 'noopener noreferrer'
|
||||
|
||||
- if twitter_auth
|
||||
%p
|
||||
= image_tag "twitter_32.png", size: "32x32", alt: 'Twitter logo'
|
||||
= link_to twitter_auth.name, "https://twitter.com/#{twitter_auth.name}"
|
||||
= link_to twitter_auth.name, "https://twitter.com/#{twitter_auth.name}", target: '_blank', rel: 'noopener noreferrer'
|
||||
|
||||
- if flickr_auth
|
||||
%p
|
||||
= image_tag "flickr_32.png", size: "32x32", alt: 'Flickr logo'
|
||||
= link_to flickr_auth.name, "https://flickr.com/photos/#{flickr_auth.uid}"
|
||||
= link_to flickr_auth.name, "https://flickr.com/photos/#{flickr_auth.uid}", target: '_blank', rel: 'noopener noreferrer'
|
||||
|
||||
- if member.show_email
|
||||
%p
|
||||
Email:
|
||||
= icon 'fas', 'envelope', class: 'fa-fw'
|
||||
= mail_to member.email
|
||||
|
||||
@@ -47,11 +47,6 @@
|
||||
= f.number_field :quantity, label: 'How many?', min: 1
|
||||
= f.text_area :description, rows: 6, label: 'Tell us more about it'
|
||||
|
||||
= f.collection_check_boxes :problem_ids, Problem.approved.order(:name), :id, :name do |b|
|
||||
.form-check
|
||||
= b.check_box
|
||||
= b.label
|
||||
|
||||
.row
|
||||
.col-md-6
|
||||
= f.check_box :finished, label: 'Mark as finished'
|
||||
|
||||
@@ -51,23 +51,6 @@
|
||||
|
||||
.col-md-8.col-xs-12
|
||||
%section= render 'facts', planting: @planting
|
||||
|
||||
- if @planting.problems.any?
|
||||
%section.problems
|
||||
%h2 Problems
|
||||
- @planting.planting_problems.each do |planting_problem|
|
||||
.card
|
||||
.card-header
|
||||
%h3= planting_problem.problem.name
|
||||
.card-body
|
||||
- if planting_problem.photos.any?
|
||||
.row
|
||||
- planting_problem.photos.each do |photo|
|
||||
.col-md-4
|
||||
= image_tag photo.thumbnail_url, class: 'img-fluid'
|
||||
- else
|
||||
%p No photos of this problem for this planting yet.
|
||||
|
||||
- if @planting.description.present?
|
||||
= cute_icon
|
||||
.card
|
||||
|
||||
@@ -9,3 +9,4 @@
|
||||
|
||||
- else
|
||||
%h2 There are no comments yet
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
= bootstrap_form_for(@problem) do |f|
|
||||
- if @problem.errors.any?
|
||||
#error_explanation.alert.alert-warning{role: "alert"}
|
||||
%h3
|
||||
= pluralize(@problem.errors.size, "error")
|
||||
prohibited this problem from being saved:
|
||||
%ul
|
||||
- @problem.errors.full_messages.each do |msg|
|
||||
%li= msg
|
||||
|
||||
.card.col-12.col-md-8.mx-auto.float-none.white
|
||||
.card-header
|
||||
- if content_for? :title
|
||||
%h1.h2-responsive.text-center
|
||||
%strong=yield :title
|
||||
.card-body
|
||||
- if can? :wrangle, @problem
|
||||
%p
|
||||
%span.help-block
|
||||
As a problem wrangler, you can approve or reject problem suggestions.
|
||||
|
||||
%h2 Basic information
|
||||
|
||||
.form-group#new_problem
|
||||
= f.text_field :name, required: true
|
||||
%span.help-block
|
||||
The common name for the problem, in English (required).
|
||||
- if can? :wrangle, @problem
|
||||
Wranglers: please ensure this is singular, and capitalize
|
||||
proper nouns only.
|
||||
|
||||
- if (can?(:wrangle, @problem) && @problem.requester) || (cannot?(:wrangle, @problem) && @problem.new_record?)
|
||||
%h2 Problem request notes
|
||||
= f.text_area :request_notes, rows: 3, id: 'request_notes', label: 'Comments'
|
||||
|
||||
- unless can? :wrangle, @problem
|
||||
%p
|
||||
When you submit this form, your suggestion will be sent to our team of
|
||||
volunteer problem wranglers for review. We'll let you know the outcome as soon as we can.
|
||||
|
||||
- if can?(:wrangle, @problem) && @problem.requester
|
||||
= f.select(:reason_for_rejection, @problem.reasons_for_rejection, include_blank: true)
|
||||
|
||||
= f.text_area :rejection_notes, rows: 3
|
||||
%span.help-block
|
||||
Please provide additional notes why this problem request was rejected if the above reasons do not apply.
|
||||
|
||||
.card-footer
|
||||
.text-right
|
||||
- if @problem.approved?
|
||||
= f.submit 'Save'
|
||||
- else
|
||||
= f.submit 'Reject', class: 'btn btn-danger', name: 'reject'
|
||||
= f.submit 'Approve and save', class: 'btn btn-success', name: 'approve'
|
||||
@@ -1,10 +0,0 @@
|
||||
- cache problem do
|
||||
.card.problem-thumbnail
|
||||
= link_to problem_path(id: problem.slug) do
|
||||
= image_tag(problem.thumbnail_url.presence || placeholder_image,
|
||||
alt: "Image of #{problem.name}",
|
||||
class: 'img img-card')
|
||||
|
||||
|
||||
.text
|
||||
%h3.problem-name= link_to problem.name, problem_path(id: problem.slug)
|
||||
@@ -1,30 +0,0 @@
|
||||
- content_for :title, "Edit problem: #{@problem.name}"
|
||||
|
||||
- if @problem.approval_status == "approved"
|
||||
- if @problem.requester
|
||||
%p
|
||||
Requested by #{link_to @problem.requester, @problem.requester}
|
||||
#{distance_of_time_in_words(@problem.created_at, Time.zone.now)} ago.
|
||||
%p
|
||||
Approved by #{link_to @problem.creator, @problem.creator}.
|
||||
- else
|
||||
%p
|
||||
Added by
|
||||
= link_to @problem.creator, @problem.creator
|
||||
#{distance_of_time_in_words(@problem.created_at, Time.zone.now)} ago.
|
||||
- elsif @problem.approval_status == "pending"
|
||||
.alert.alert-danger
|
||||
%p
|
||||
Requested by #{link_to @problem.requester, @problem.requester}
|
||||
#{distance_of_time_in_words(@problem.created_at, Time.zone.now)} ago.
|
||||
%p
|
||||
Status: #{@problem.approval_status}.
|
||||
- elsif @problem.approval_status == "rejected"
|
||||
.alert.alert-danger
|
||||
%p
|
||||
Requested by #{link_to @problem.requester, @problem.requester}
|
||||
#{distance_of_time_in_words(@problem.created_at, Time.zone.now)} ago.
|
||||
%p
|
||||
Status: #{@problem.approval_status} by #{link_to @problem.creator, @problem.creator}.
|
||||
|
||||
= render 'form'
|
||||
@@ -1,13 +0,0 @@
|
||||
- content_for :title, t('.title')
|
||||
|
||||
- content_for :breadcrumbs do
|
||||
%li.breadcrumb-item.active= link_to 'Problems', problems_path
|
||||
|
||||
%section.problems
|
||||
%h2= t('.title')
|
||||
= will_paginate @problems
|
||||
.index-cards
|
||||
- @problems.each do |p|
|
||||
= render 'problems/thumbnail', problem: p
|
||||
|
||||
= will_paginate @problems
|
||||
@@ -1,15 +0,0 @@
|
||||
- content_for :title, (can?(:wrangle, @problem) ? "New problem" : "Suggest a problem")
|
||||
|
||||
- unless can? :wrangle, @problem
|
||||
%p
|
||||
Thanks for taking the time to suggest a problem! Our problem database is
|
||||
managed by volunteers, and we appreciate your help. Here are some
|
||||
things to consider when suggesting a new problem:
|
||||
%ul
|
||||
%li
|
||||
First, you might want to search our problems
|
||||
to make sure we don't have it already, perhaps under an alternate name.
|
||||
%li
|
||||
The Growstuff database only contains problems related to growing edible plants.
|
||||
|
||||
= render 'form'
|
||||
@@ -1,26 +0,0 @@
|
||||
- content_for :title, @problem.name
|
||||
|
||||
- content_for :breadcrumbs do
|
||||
%li.breadcrumb-item= link_to 'Problems', problems_path
|
||||
%li.breadcrumb-item.active= link_to @problem.name.capitalize, @problem
|
||||
|
||||
.jumbotron
|
||||
%h1= @problem.name
|
||||
|
||||
.row
|
||||
.col-md-9
|
||||
%section.plantings
|
||||
%h2 Plantings with this problem
|
||||
= will_paginate @plantings
|
||||
.index-cards
|
||||
- @plantings.each do |p|
|
||||
= render 'plantings/thumbnail', planting: p
|
||||
= will_paginate @plantings
|
||||
.col-md-3
|
||||
.card
|
||||
.card-body
|
||||
%h4.card-title= @problem.name
|
||||
%p.card-text
|
||||
This problem has been reported on
|
||||
= pluralize(@problem.plantings.count, 'planting')
|
||||
so far.
|
||||
@@ -1,51 +0,0 @@
|
||||
- content_for :title, "Problem Wrangling"
|
||||
|
||||
%h1 Problem Wrangling
|
||||
|
||||
%nav.nav
|
||||
= link_to "Add Problem", new_problem_path, class: 'btn'
|
||||
|
||||
%section.problem_wranglers
|
||||
%h2 Problem Wranglers
|
||||
- @problem_wranglers.each do |problem_wrangler|
|
||||
= render 'members/tiny', member: problem_wrangler
|
||||
|
||||
%hr/
|
||||
|
||||
%section
|
||||
%h2 Problems
|
||||
|
||||
%ul#myTab.nav.nav-tabs{role: "tablist"}
|
||||
%li.nav-item
|
||||
%a#home-tab.nav-link{ href: wrangle_problems_path, role: "tab", class: @approval_status.blank? ? 'active' : ''}
|
||||
Recently added
|
||||
%li.nav-item
|
||||
%a#profile-tab.nav-link{ href: wrangle_problems_path(approval_status: "pending"), role: "tab", class: @approval_status == "pending" ? 'active' : ''}
|
||||
Pending approval
|
||||
%li.nav-item
|
||||
%a#contact-tab.nav-link{ href: wrangle_problems_path(approval_status: "rejected"), role: "tab", class: @approval_status == "rejected" ? 'active' : ''} Rejected
|
||||
|
||||
%table.table.table-striped.table-bordered.table-sm{id: @approval_status.blank? ? 'recently-added-problems' : "#{@approval_status}-problems" }
|
||||
%tr
|
||||
%th Name
|
||||
%th Requested by
|
||||
- if @approval_status == "rejected"
|
||||
%th Rejected by
|
||||
- if @approval_status != "rejected" && @approval_status != "pending"
|
||||
%th Added by
|
||||
%th When
|
||||
- @problems.each do |p|
|
||||
%tr
|
||||
%td
|
||||
= link_to edit_problem_path(p) do
|
||||
= icon 'fas', 'bug'
|
||||
= p.name
|
||||
%td= p.requester.present? ? (link_to p.requester, p.requester) : "N/A"
|
||||
- unless @approval_status == "pending"
|
||||
%td= p.creator.present? ? (link_to p.creator, p.creator) : "N/A"
|
||||
%td
|
||||
= distance_of_time_in_words(p.created_at, Time.zone.now)
|
||||
ago.
|
||||
|
||||
= page_entries_info @problems
|
||||
= will_paginate @problems
|
||||
@@ -89,13 +89,6 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
|
||||
resources :problems, param: :slug do
|
||||
collection do
|
||||
get 'requested'
|
||||
get 'wrangle'
|
||||
end
|
||||
end
|
||||
|
||||
resources :comments
|
||||
resources :forums
|
||||
|
||||
|
||||
9
db/migrate/20240716120000_add_social_media_to_members.rb
Normal file
9
db/migrate/20240716120000_add_social_media_to_members.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class AddSocialMediaToMembers < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :members, :website_url, :string
|
||||
add_column :members, :instagram_handle, :string
|
||||
add_column :members, :facebook_handle, :string
|
||||
add_column :members, :bluesky_handle, :string
|
||||
add_column :members, :other_handle, :string
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class RenameOtherHandleToOtherUrlInMembers < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
rename_column :members, :other_handle, :other_url
|
||||
end
|
||||
end
|
||||
@@ -446,6 +446,11 @@ ActiveRecord::Schema[7.2].define(version: 2024_09_29_041435) do
|
||||
t.integer "photos_count"
|
||||
t.integer "forums_count"
|
||||
t.integer "activities_count"
|
||||
t.string "website_url"
|
||||
t.string "instagram_handle"
|
||||
t.string "facebook_handle"
|
||||
t.string "bluesky_handle"
|
||||
t.string "other_url"
|
||||
t.index ["confirmation_token"], name: "index_members_on_confirmation_token", unique: true
|
||||
t.index ["discarded_at"], name: "index_members_on_discarded_at"
|
||||
t.index ["email"], name: "index_members_on_email", unique: true
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
class Haml::Filters
|
||||
class GrowstuffMarkdown
|
||||
CROP_REGEX = /(?<!\\)\[([^\[\]]+?)\]\(crop\)/
|
||||
PROBLEM_REGEX = /(?<!\\)\[([^\[\]]+?)\]\(problem\)/
|
||||
MEMBER_REGEX = /(?<!\\)\[([^\[\]]+?)\]\(member\)/
|
||||
MEMBER_AT_REGEX = /(?<!\\)(@\w+)/
|
||||
MEMBER_ESCAPE_AT_REGEX = /(?<!\\)\\(?=@\w+)/
|
||||
@@ -43,25 +42,6 @@ class Haml::Filters
|
||||
end
|
||||
end
|
||||
|
||||
def expand_problems!(text)
|
||||
# turn [aphids](problem) into [aphids](http://growstuff.org/problems/aphids)
|
||||
text.gsub(PROBLEM_REGEX) do
|
||||
problem_str = Regexp.last_match(1)
|
||||
# find problem case-insensitively
|
||||
problem = Problem.where('lower(name) = ?', problem_str.downcase).first
|
||||
problem_link problem, problem_str
|
||||
end
|
||||
end
|
||||
|
||||
def problem_link(problem, link_text)
|
||||
if problem
|
||||
url = Rails.application.routes.url_helpers.problem_url(problem, only_path: true)
|
||||
"[#{link_text}](#{url})"
|
||||
else
|
||||
link_text
|
||||
end
|
||||
end
|
||||
|
||||
def crop_link(crop, link_text)
|
||||
if crop
|
||||
url = Rails.application.routes.url_helpers.crop_url(crop, only_path: true)
|
||||
|
||||
Reference in New Issue
Block a user