Module: Bible270::PlanHelper

Defined in:
app/helpers/bible270/plan_helper.rb

Instance Method Summary collapse

Instance Method Details

#b270_avatar(reader, size: 34) ⇒ Object



130
131
132
133
134
135
136
137
138
# File 'app/helpers/bible270/plan_helper.rb', line 130

def b270_avatar(reader, size: 34)
  src = b270_avatar_src(reader)
  if src
    image_tag src, class: 'b270-avatar', width: size, height: size, alt: reader.display_name
  else
     :span, reader.initials, class: 'b270-avatar b270-avatar-fallback',
                                        style: "width:#{size}px;height:#{size}px;line-height:#{size}px"
  end
end

#b270_avatar_src(reader) ⇒ Object



124
125
126
127
128
# File 'app/helpers/bible270/plan_helper.rb', line 124

def b270_avatar_src(reader)
  return main_app.rails_blob_path(reader.avatar, only_path: true) if reader.avatar_uploaded?

  reader.avatar_url.presence
end

#b270_bible_versionObject



108
109
110
# File 'app/helpers/bible270/plan_helper.rb', line 108

def b270_bible_version
  current_reader&.effective_bible_version || Translations.resolve(nil)
end

#b270_can_delete?(comment) ⇒ Boolean

Returns:

  • (Boolean)


159
160
161
162
163
# File 'app/helpers/bible270/plan_helper.rb', line 159

def b270_can_delete?(comment)
  return false unless signed_in?

  comment.reader_id == current_reader.id || Bible270.config.admin?(current_reader)
end

#b270_can_edit?(comment) ⇒ Boolean

Only the writer may change their own words. An admin can remove a reflection but not rewrite it: putting words in someone's mouth is worse than taking them away, and taking them away is already the moderator's job.

Returns:

  • (Boolean)


155
156
157
# File 'app/helpers/bible270/plan_helper.rb', line 155

def b270_can_edit?(comment)
  signed_in? && comment.reader_id == current_reader.id
end


47
48
49
50
51
52
# File 'app/helpers/bible270/plan_helper.rb', line 47

def b270_custom_footer
  case Bible270.config.footer_style
  when :partial then render(Bible270.config.footer_partial)
  when :html then (:footer, Bible270.config.footer_html.html_safe, class: 'b270-footer')
  end
end

#b270_date(time) ⇒ Object

Timestamps in the plan's zone rather than UTC. Dates — started_on and the like — need no conversion and are formatted inline.



142
143
144
145
# File 'app/helpers/bible270/plan_helper.rb', line 142

def b270_date(time)
  local = Bible270.local_time(time)
  local&.strftime('%b %-d, %Y')
end

#b270_datetime(time) ⇒ Object



147
148
149
150
# File 'app/helpers/bible270/plan_helper.rb', line 147

def b270_datetime(time)
  local = Bible270.local_time(time)
  local&.strftime('%b %-d, %Y at %-I:%M %p')
end

#b270_day_cell(day, reader: nil, today: nil) ⇒ Object

One cell of the 270-day grid. Four views drew this by hand in four slightly different ways; now they all call this.

The title attribute carries the readings, so hovering a square says what is on that day — no JavaScript, and it works for keyboard focus too.



170
171
172
173
174
# File 'app/helpers/bible270/plan_helper.rb', line 170

def b270_day_cell(day, reader: nil, today: nil)
  link_to day, day_path(day),
          class: b270_day_cell_classes(day, reader: reader, today: today),
          title: b270_day_readings(day)
end

#b270_day_cell_classes(day, reader: nil, today: nil) ⇒ Object

Shared with the admin grid, which is a button_to rather than a link because it toggles the day rather than navigating to it.



178
179
180
181
182
183
184
# File 'app/helpers/bible270/plan_helper.rb', line 178

def b270_day_cell_classes(day, reader: nil, today: nil)
  classes = ['b270-cell', b270_day_status_class(reader, day)]
  classes << 'talk' if b270_days_with_reflections.include?(day)
  classes << 'now' if today && day == today

  classes.reject(&:blank?).join(' ')
end

The same references as links to the passage, in the reader's own translation when they have chosen one — b270_passage_url falls back to the site default for a visitor.



195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'app/helpers/bible270/plan_helper.rb', line 195

def b270_day_reading_links(day)
  readings = Bible270::Plan.readings_for(day)

  links = Bible270::Plan::TRACKS.keys.filter_map do |track|
    reference = readings[track]
    next if reference.blank?

    link_to reference, b270_passage_url(reference),
            class: 'b270-reflink', target: '_blank', rel: 'noopener'
  end

  safe_join(links, ' · ')
end

#b270_day_readings(day) ⇒ Object

"Genesis 1-3 · Matthew 1 · Psalm 1" as plain text, for a title attribute.



187
188
189
190
# File 'app/helpers/bible270/plan_helper.rb', line 187

def b270_day_readings(day)
  readings = Bible270::Plan.readings_for(day)
  Bible270::Plan::TRACKS.keys.filter_map { |track| readings[track] }.join(' · ')
end

#b270_day_status_class(reader, day) ⇒ Object



223
224
225
226
227
228
229
230
231
# File 'app/helpers/bible270/plan_helper.rb', line 223

def b270_day_status_class(reader, day)
  return '' unless reader

  case reader.day_status(day)
  when :complete then 'complete'
  when :partial  then 'partial'
  else ''
  end
end

#b270_days_with_reflectionsObject

The days that have at least one visible reflection, fetched once per request. Called from the layout's grid, which no controller sets up, so it cannot be an instance variable assigned in an action.



212
213
214
215
216
217
218
219
220
221
# File 'app/helpers/bible270/plan_helper.rb', line 212

def b270_days_with_reflections
  @b270_days_with_reflections ||=
    if defined?(Bible270::Comment)
      Bible270::Comment.approved.distinct.pluck(:day).to_set
    else
      Set.new
    end
rescue StandardError
  Set.new
end

#b270_favicon_tagObject



99
100
101
102
103
104
105
106
# File 'app/helpers/bible270/plan_helper.rb', line 99

def b270_favicon_tag
  favicon = Bible270.config.favicon
  return if favicon == false

  href = favicon.presence || Favicon.data_uri
  type = href.start_with?('data:image/svg+xml', 'http') || href.end_with?('.svg') ? 'image/svg+xml' : nil
  tag.link(rel: 'icon', type: type, href: href)
end

The signed-in reader's translation, falling back to the site default for visitors who aren't signed in. Applies to every page the engine's layout renders, i.e. everything under the mount point. Pages rendered inside a host layout need this in that layout's instead. The footer, from whichever source the host configured. A partial is looked up in the host's view paths as well as the engine's, so 'shared/footer' finds app/views/shared/_footer.html.erb. The footer, from whichever source the host configured. A partial is looked up in the host's view paths as well as the engine's, so 'shared/footer' finds app/views/shared/_footer.html.erb. config.footer_placement decides whether a custom footer replaces the engine's or sits alongside it.



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'app/helpers/bible270/plan_helper.rb', line 33

def b270_footer
  config = Bible270.config
  return if config.footer_style == :none
  return render('bible270/shared/footer') if config.footer_style == :default

  parts = [b270_custom_footer]
  if config.keep_default_footer?
    default = render('bible270/shared/footer')
    config.resolved_footer_placement == :after ? parts.unshift(default) : parts.push(default)
  end

  safe_join(parts)
end

#b270_mark(size: 38) ⇒ Object



88
89
90
91
92
93
94
95
96
97
# File 'app/helpers/bible270/plan_helper.rb', line 88

def b270_mark(size: 38)
  configured = Bible270.config.header_mark
  return nil if configured == false

  if configured.is_a?(String) && configured.present?
    return image_tag(configured, width: size, height: size, alt: '', class: 'b270-mark')
  end

   :span, Favicon.inline_svg(size: size).html_safe, class: 'b270-mark'
end

#b270_omniauth_path(provider) ⇒ Object

Path that begins the OmniAuth request phase. Must be POSTed to: OmniAuth 2.0+ refuses GET on its own routes (CVE-2015-9284), so every sign-in control in these views is a button_to, not a link_to.



12
13
14
15
# File 'app/helpers/bible270/plan_helper.rb', line 12

def b270_omniauth_path(provider)
  prefix = Bible270.config.omniauth_path_prefix || "#{request.script_name}/auth"
  "#{prefix}/#{provider}"
end

#b270_passage_url(reference, version = nil) ⇒ Object



112
113
114
115
116
# File 'app/helpers/bible270/plan_helper.rb', line 112

def b270_passage_url(reference, version = nil)
  return '#' if reference.blank?

  Bible270.config.passage_url_builder.call(reference, version || b270_bible_version)
end

#b270_provider_label(provider) ⇒ Object



17
18
19
# File 'app/helpers/bible270/plan_helper.rb', line 17

def b270_provider_label(provider)
  Bible270.config.label_for_provider(provider)
end

#b270_track(track) ⇒ Object



5
6
7
# File 'app/helpers/bible270/plan_helper.rb', line 5

def b270_track(track)
  Plan::TRACKS.fetch(track.to_s)
end

#b270_version_tag(version = nil) ⇒ Object

Small grey "(NKJV)" after a reading, so it's clear which translation the link opens in.



120
121
122
# File 'app/helpers/bible270/plan_helper.rb', line 120

def b270_version_tag(version = nil)
   :span, "(#{version || b270_bible_version})", class: 'b270-version'
end

#b270_with_mentions(body) ⇒ Object

The mark beside the title in the header. Larger than the favicon and inlined as SVG, so it stays crisp at any size and needs no asset pipeline. Renders "@handle" as a link to that reader. An unresolved or ambiguous handle is left as plain text, which is how a writer discovers it found nobody.

Built by walking the matches and escaping everything between them, rather than escaping afterwards: the body is reader-supplied, so the only safe construction is one where every non-link fragment is escaped by hand.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'app/helpers/bible270/plan_helper.rb', line 62

def b270_with_mentions(body)
  text = body.to_s
  readers = Bible270::Reader.mentioned_in(text)

  pieces = []
  cursor = 0

  text.to_enum(:scan, Bible270::Mentions::PATTERN).each do
    match = Regexp.last_match
    pieces << ERB::Util.html_escape(text[cursor...match.begin(0)])

    handle = Bible270::Mentions.normalize(match[1])
    reader = readers.find { |candidate| candidate.answers_to?(handle) }
    pieces << if reader
                link_to(match[0], reader_path(reader), class: 'b270-mention')
              else
                ERB::Util.html_escape(match[0])
              end

    cursor = match.end(0)
  end

  pieces << ERB::Util.html_escape(text[cursor..].to_s)
  safe_join(pieces)
end