bible270 0.11.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -5
- data/README.md +38 -2
- data/app/controllers/bible270/admin_controller.rb +21 -5
- data/app/controllers/bible270/profiles_controller.rb +3 -0
- data/app/helpers/bible270/plan_helper.rb +54 -4
- data/app/models/bible270/reader.rb +17 -1
- data/app/views/bible270/admin/show.html.erb +26 -5
- data/app/views/bible270/days/_reading.html.erb +1 -1
- data/app/views/bible270/days/index.html.erb +5 -3
- data/app/views/bible270/profiles/edit.html.erb +8 -0
- data/app/views/bible270/readers/show.html.erb +4 -0
- data/app/views/bible270/shared/_footer.html.erb +3 -0
- data/app/views/bible270/shared/_styles.html.erb +3 -1
- data/app/views/layouts/bible270/application.html.erb +4 -4
- data/config/routes.rb +2 -1
- data/db/migrate/20260101000008_add_bible_version_to_bible270_readers.rb +9 -0
- data/lib/bible270/bible270.rb +14 -0
- data/lib/bible270/configuration.rb +69 -3
- data/lib/bible270/favicon.rb +36 -0
- data/lib/bible270/translations.rb +71 -0
- data/lib/bible270/version.rb +1 -1
- data/lib/bible270.rb +2 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d1cc4a6173e82beae097bc870026ba3ae1ae0869a32f77efab2ea12fa1dbe2b
|
|
4
|
+
data.tar.gz: be16d337547c10478a9160bc9a7b229d1994f37305c0d68801564549a2c1bb78
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16dafcc090a16f119b5ca9db84c362116c44375ef5e729ca8a915cbe0b6421fb0772847372392624cdaef22d45a39d52e1f33426b1d4ca082dd3d7cc16ad547f
|
|
7
|
+
data.tar.gz: c24039c41a16d88e2cfc6f400f6054ab552c8a2fb3669260e3dee1eaddf2fbcbb0745f8acd4693a7875f6c2c4b8a7b42d87978c5869cde0fb5e40ee16bdeae8f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
All notable changes to bible270. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
|
4
|
-
this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html), treating pre-1.0 minor bumps as the place breaking changes may land.
|
|
3
|
+
All notable changes to bible270. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html), treating pre-1.0 minor bumps as the place breaking changes may land.
|
|
5
4
|
|
|
6
5
|
## Unreleased Changes
|
|
7
6
|
|
|
8
7
|
See the changes since the last release:
|
|
9
8
|
|
|
10
|
-
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.
|
|
9
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.12.0...main)**
|
|
10
|
+
|
|
11
|
+
## [0.12.0] — 2026-07-29
|
|
12
|
+
|
|
13
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.11.0...v0.12.0)**
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Readers can select their preferred Bible translation in profile edit view
|
|
18
|
+
- Scripture links show bible version in parens
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Top menu shows 'Progress' in place of user display name
|
|
23
|
+
- Dependabot gem updates to minitest and rubocop
|
|
11
24
|
|
|
12
25
|
## [0.11.0] — 2026-07-28
|
|
13
|
-
|
|
26
|
+
|
|
27
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.10.0...v0.11.0)**
|
|
28
|
+
|
|
14
29
|
### Added
|
|
15
30
|
|
|
16
31
|
- **Readers can edit their own name and picture** at `<mount>/profile`, linked from the nav. An admin
|
|
@@ -234,7 +249,8 @@ This is the initial public release.
|
|
|
234
249
|
|
|
235
250
|
* Email sign-in requires working Action Mailer delivery in the host application. Set `config.mailer_from`; delivery is inline by default, or set `email_sign_in_deliver_later` when a queue backend is available.
|
|
236
251
|
|
|
237
|
-
[0.
|
|
252
|
+
[0.12.0]: https://github.com/avonderluft/bible270/releases/tag/v0.12.0
|
|
253
|
+
[0.11.0]: https://github.com/avonderluft/bible270/releases/tag/v0.11.0
|
|
238
254
|
[0.10.0]: https://github.com/avonderluft/bible270/releases/tag/v0.10.0
|
|
239
255
|
[0.9.0]: https://github.com/avonderluft/bible270/releases/tag/v0.9.0
|
|
240
256
|
[0.6.2]: https://github.com/avonderluft/bible270/releases/tag/v0.6.2
|
data/README.md
CHANGED
|
@@ -297,8 +297,8 @@ Bible270.configure do |config|
|
|
|
297
297
|
config.tagline = "A 270-day journey through Scripture"
|
|
298
298
|
config.admin_emails = %w[andrew@example.org]
|
|
299
299
|
|
|
300
|
-
|
|
301
|
-
config.start_date = Date.new(2026, 9, 6)
|
|
300
|
+
config.start_date = nil # community start date, or nil for undated
|
|
301
|
+
# config.start_date = Date.new(2026, 9, 6)
|
|
302
302
|
config.allow_reader_start_date = true
|
|
303
303
|
|
|
304
304
|
config.parent_controller = "ActionController::Base" # or "::ApplicationController"
|
|
@@ -322,6 +322,42 @@ Bible270.configure do |config|
|
|
|
322
322
|
}
|
|
323
323
|
end
|
|
324
324
|
```
|
|
325
|
+
### Footer
|
|
326
|
+
|
|
327
|
+
The engine's pages end with a short note about how the plan works. Replace it with your own:
|
|
328
|
+
|
|
329
|
+
```ruby
|
|
330
|
+
config.footer_partial = 'shared/footer' # a partial in your app
|
|
331
|
+
config.footer_html = '<p>© 2026 …</p>'
|
|
332
|
+
config.footer = false # no footer at all
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
By default yours replaces the engine's. To keep both:
|
|
336
|
+
|
|
337
|
+
```ruby
|
|
338
|
+
config.footer_placement = :after # engine's note, then yours
|
|
339
|
+
config.footer_placement = :before # yours, then the engine's note
|
|
340
|
+
config.footer_placement = :replace # default
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
A partial is looked up in your app's view paths as well as the engine's, so `'shared/footer'` finds
|
|
344
|
+
`app/views/shared/_footer.html.erb`. Keep it reasonably self-contained: engine controllers include
|
|
345
|
+
the engine's helpers, not your app's, so a partial calling your own helpers needs
|
|
346
|
+
`helper ::ApplicationHelper` adding to the engine's controller — or just inline the markup.
|
|
347
|
+
|
|
348
|
+
### Favicon
|
|
349
|
+
|
|
350
|
+
Pages under the mount point carry their own favicon — a rustic loaf, inlined as an SVG data URI, so
|
|
351
|
+
there's nothing to add to your asset pipeline.
|
|
352
|
+
|
|
353
|
+
```ruby
|
|
354
|
+
config.favicon = nil # built-in loaf (default)
|
|
355
|
+
config.favicon = '/icons/my-icon.svg' # your own
|
|
356
|
+
config.favicon = false # none; the host's favicon applies
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
It's emitted by the engine's layout, so if you point `config.layout` at your own layout, add
|
|
360
|
+
`<%= b270_favicon_tag %>` to its `<head>`.
|
|
325
361
|
|
|
326
362
|
References link to Bible Gateway by default — point `passage_url_builder` at your another reader if you wish, or your own reader if you host one.
|
|
327
363
|
|
|
@@ -10,7 +10,7 @@ module Bible270
|
|
|
10
10
|
class AdminController < ApplicationController
|
|
11
11
|
before_action :require_admin!
|
|
12
12
|
before_action :load_reader,
|
|
13
|
-
only: %i[show destroy update_start
|
|
13
|
+
only: %i[show destroy update_start update_profile remove_avatar complete_through toggle_day]
|
|
14
14
|
before_action :load_comment, only: %i[hide_comment unhide_comment destroy_comment]
|
|
15
15
|
|
|
16
16
|
# ---- enrolment --------------------------------------------------------
|
|
@@ -95,14 +95,30 @@ module Bible270
|
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
# Name and picture together, mirroring what a reader can change about
|
|
99
|
+
# themselves on their own profile.
|
|
100
|
+
def update_profile
|
|
101
|
+
problems = []
|
|
102
|
+
if (params[:first_name].present? || params[:last_name].present?) && !@reader.update_names(params[:first_name],
|
|
103
|
+
params[:last_name])
|
|
104
|
+
problems << 'both a first and last name'
|
|
105
|
+
end
|
|
106
|
+
if params[:avatar].present? && !@reader.attach_avatar(params[:avatar])
|
|
107
|
+
problems << (@reader.errors[:avatar].first || 'a valid image')
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if problems.empty?
|
|
111
|
+
redirect_to admin_reader_path(@reader), notice: 'Updated.'
|
|
101
112
|
else
|
|
102
|
-
redirect_to admin_reader_path(@reader), alert:
|
|
113
|
+
redirect_to admin_reader_path(@reader), alert: "Needs #{problems.to_sentence}."
|
|
103
114
|
end
|
|
104
115
|
end
|
|
105
116
|
|
|
117
|
+
def remove_avatar
|
|
118
|
+
@reader.remove_avatar!
|
|
119
|
+
redirect_to admin_reader_path(@reader), notice: "Removed #{@reader.display_name}'s picture."
|
|
120
|
+
end
|
|
121
|
+
|
|
106
122
|
def complete_through
|
|
107
123
|
day = params[:day].to_i
|
|
108
124
|
if @reader.mark_through!(day)
|
|
@@ -14,6 +14,9 @@ module Bible270
|
|
|
14
14
|
problems = []
|
|
15
15
|
problems << 'both a first and last name' unless current_reader.update_names(params[:first_name],
|
|
16
16
|
params[:last_name])
|
|
17
|
+
if params[:bible_version].present? && !current_reader.update_bible_version(params[:bible_version])
|
|
18
|
+
problems << 'a translation from the list'
|
|
19
|
+
end
|
|
17
20
|
if params[:avatar].present? && !current_reader.attach_avatar(params[:avatar])
|
|
18
21
|
problems << (current_reader.errors[:avatar].first || 'a valid image')
|
|
19
22
|
end
|
|
@@ -18,14 +18,64 @@ module Bible270
|
|
|
18
18
|
Bible270.config.label_for_provider(provider)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
# The signed-in reader's translation, falling back to the site default for
|
|
22
|
+
# visitors who aren't signed in.
|
|
23
|
+
# Applies to every page the engine's layout renders, i.e. everything under
|
|
24
|
+
# the mount point. Pages rendered inside a host layout need this in that
|
|
25
|
+
# layout's <head> instead.
|
|
26
|
+
# The footer, from whichever source the host configured. A partial is looked
|
|
27
|
+
# up in the host's view paths as well as the engine's, so 'shared/footer'
|
|
28
|
+
# finds app/views/shared/_footer.html.erb.
|
|
29
|
+
# The footer, from whichever source the host configured. A partial is looked
|
|
30
|
+
# up in the host's view paths as well as the engine's, so 'shared/footer'
|
|
31
|
+
# finds app/views/shared/_footer.html.erb. config.footer_placement decides
|
|
32
|
+
# whether a custom footer replaces the engine's or sits alongside it.
|
|
33
|
+
def b270_footer
|
|
34
|
+
config = Bible270.config
|
|
35
|
+
return if config.footer_style == :none
|
|
36
|
+
return render('bible270/shared/footer') if config.footer_style == :default
|
|
37
|
+
|
|
38
|
+
parts = [b270_custom_footer]
|
|
39
|
+
if config.keep_default_footer?
|
|
40
|
+
default = render('bible270/shared/footer')
|
|
41
|
+
config.resolved_footer_placement == :after ? parts.unshift(default) : parts.push(default)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
safe_join(parts)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def b270_custom_footer
|
|
48
|
+
case Bible270.config.footer_style
|
|
49
|
+
when :partial then render(Bible270.config.footer_partial)
|
|
50
|
+
when :html then content_tag(:footer, Bible270.config.footer_html.html_safe, class: 'b270-footer')
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def b270_favicon_tag
|
|
55
|
+
favicon = Bible270.config.favicon
|
|
56
|
+
return if favicon == false
|
|
57
|
+
|
|
58
|
+
href = favicon.presence || Favicon.data_uri
|
|
59
|
+
type = href.start_with?('data:image/svg+xml', 'http') || href.end_with?('.svg') ? 'image/svg+xml' : nil
|
|
60
|
+
tag.link(rel: 'icon', type: type, href: href)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def b270_bible_version
|
|
64
|
+
current_reader&.effective_bible_version || Translations.resolve(nil)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def b270_passage_url(reference, version = nil)
|
|
22
68
|
return '#' if reference.blank?
|
|
23
69
|
|
|
24
|
-
Bible270.config.passage_url_builder.call(reference,
|
|
70
|
+
Bible270.config.passage_url_builder.call(reference, version || b270_bible_version)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Small grey "(NKJV)" after a reading, so it's clear which translation the
|
|
74
|
+
# link opens in.
|
|
75
|
+
def b270_version_tag(version = nil)
|
|
76
|
+
content_tag :span, "(#{version || b270_bible_version})", class: 'b270-version'
|
|
25
77
|
end
|
|
26
78
|
|
|
27
|
-
# An uploaded avatar wins over whatever the sign-in provider gave us.
|
|
28
|
-
# Active Storage routes live in the host app, hence main_app.
|
|
29
79
|
def b270_avatar_src(reader)
|
|
30
80
|
return main_app.rails_blob_path(reader.avatar, only_path: true) if reader.avatar_uploaded?
|
|
31
81
|
|
|
@@ -114,6 +114,22 @@ module Bible270
|
|
|
114
114
|
Names.first_with_last_initial(first_name, last_name).presence || display_name
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
+
# The translation this reader reads in. Null means "whatever the site default
|
|
118
|
+
# is", so changing config.bible_version moves everyone who hasn't chosen.
|
|
119
|
+
def effective_bible_version
|
|
120
|
+
Translations.resolve(bible_version)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def bible_version_label
|
|
124
|
+
Translations.label(effective_bible_version)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def update_bible_version(code)
|
|
128
|
+
return false unless Translations.valid?(code)
|
|
129
|
+
|
|
130
|
+
update(bible_version: Translations.normalize(code))
|
|
131
|
+
end
|
|
132
|
+
|
|
117
133
|
def sort_name
|
|
118
134
|
[last_name, first_name].map { |n| n.to_s.strip.downcase }.join(' ').strip.presence || display_name.to_s.downcase
|
|
119
135
|
end
|
|
@@ -209,7 +225,7 @@ module Bible270
|
|
|
209
225
|
def suggested_names
|
|
210
226
|
return { first_name: first_name, last_name: last_name } if full_name
|
|
211
227
|
|
|
212
|
-
Names.
|
|
228
|
+
Names.split_display_name(display_name) || { first_name: display_name, last_name: nil }
|
|
213
229
|
end
|
|
214
230
|
|
|
215
231
|
# ---- administrative adjustments --------------------------------------
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<% content_for :title, "#{@reader.display_name} — Admin" %>
|
|
2
|
+
<% content_for :hide_day_index, true %><%# this page has its own grid %>
|
|
2
3
|
<p class="b270-eyebrow"><%= link_to 'Admin', admin_path %></p>
|
|
3
4
|
<h1><%= @reader.display_name %></h1>
|
|
4
5
|
<p class="lede">
|
|
@@ -9,17 +10,37 @@
|
|
|
9
10
|
</p>
|
|
10
11
|
|
|
11
12
|
<div class="b270-panel">
|
|
12
|
-
<h2 class="b270-subhead">Name</h2>
|
|
13
|
-
<%= form_with url:
|
|
13
|
+
<h2 class="b270-subhead">Name and picture</h2>
|
|
14
|
+
<%= form_with url: admin_reader_profile_path(@reader), method: :patch, class: "b270-emailform",
|
|
15
|
+
html: { multipart: true } do %>
|
|
16
|
+
<div class="b270-avatarrow">
|
|
17
|
+
<%= b270_avatar(@reader, size: 64) %>
|
|
18
|
+
<% if Bible270::Reader.avatar_uploads? %>
|
|
19
|
+
<label class="b270-field" style="flex:1">
|
|
20
|
+
<span>Picture</span>
|
|
21
|
+
<%= file_field_tag :avatar, accept: Bible270::Avatars.content_types.join(',') %>
|
|
22
|
+
<span class="b270-hint" style="margin:0">
|
|
23
|
+
PNG, JPEG, GIF or WebP, up to <%= Bible270::Avatars.human_size(Bible270::Avatars.max_bytes) %>.
|
|
24
|
+
</span>
|
|
25
|
+
</label>
|
|
26
|
+
<% end %>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
14
29
|
<label class="b270-field">
|
|
15
30
|
<span>First name</span>
|
|
16
|
-
<%= text_field_tag :first_name, @reader.suggested_names[:first_name]
|
|
31
|
+
<%= text_field_tag :first_name, @reader.suggested_names[:first_name] %>
|
|
17
32
|
</label>
|
|
18
33
|
<label class="b270-field">
|
|
19
34
|
<span>Last name</span>
|
|
20
|
-
<%= text_field_tag :last_name, @reader.suggested_names[:last_name]
|
|
35
|
+
<%= text_field_tag :last_name, @reader.suggested_names[:last_name] %>
|
|
21
36
|
</label>
|
|
22
|
-
<%= submit_tag 'Save
|
|
37
|
+
<%= submit_tag 'Save', class: 'b270-btn' %>
|
|
38
|
+
<% end %>
|
|
39
|
+
|
|
40
|
+
<% if @reader.avatar_uploaded? %>
|
|
41
|
+
<%= button_to "Remove picture", admin_reader_avatar_path(@reader), method: :delete,
|
|
42
|
+
class: 'b270-linkbtn',
|
|
43
|
+
form: { data: { turbo_confirm: "Remove #{@reader.display_name}'s picture?" } } %>
|
|
23
44
|
<% end %>
|
|
24
45
|
</div>
|
|
25
46
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<% end %>
|
|
12
12
|
<div class="b270-rbody">
|
|
13
13
|
<div class="b270-cat"><%= t[:label] %></div>
|
|
14
|
-
<div class="b270-ref"><%= link_to ref, b270_passage_url(ref), target: "_blank", rel: "noopener" %></div>
|
|
14
|
+
<div class="b270-ref"><%= link_to ref, b270_passage_url(ref), target: "_blank", rel: "noopener" %> <%= b270_version_tag %></div>
|
|
15
15
|
</div>
|
|
16
16
|
</div>
|
|
17
17
|
<% else %>
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
<% end %>
|
|
24
24
|
</div>
|
|
25
25
|
<p style="margin:16px 0 0">
|
|
26
|
-
|
|
26
|
+
<% @day_verb = @start_day == 1 ? "Start" : "Continue" %>
|
|
27
|
+
<%= link_to "#{@day_verb} reading — Day #{@start_day} →", day_path(@start_day), class: "b270-btn" %>
|
|
27
28
|
<% if @today_day && @today_day != @start_day %>
|
|
28
29
|
<%= link_to "Go to today (Day #{@today_day})", day_path(@today_day), style: "margin-left:12px" %>
|
|
29
30
|
<% end %>
|
|
@@ -41,9 +42,10 @@
|
|
|
41
42
|
</div>
|
|
42
43
|
|
|
43
44
|
<% if @readers.any? %>
|
|
44
|
-
|
|
45
|
+
<% rankings= @readers.count < 5 ? @readers.count : 5 %>
|
|
46
|
+
<h2 class="b270-section-h">Top <%= rankings %> reader leaders ...</h2>
|
|
45
47
|
<div class="b270-list">
|
|
46
|
-
<% @readers.first(
|
|
48
|
+
<% @readers.first(rankings).each_with_index do |r, i| %>
|
|
47
49
|
<div class="b270-li">
|
|
48
50
|
<span class="rank"><%= i + 1 %></span>
|
|
49
51
|
<%= b270_avatar(r) %>
|
|
@@ -27,6 +27,14 @@
|
|
|
27
27
|
<span>Last name</span>
|
|
28
28
|
<%= text_field_tag :last_name, @names[:last_name], required: true, autocomplete: "family-name" %>
|
|
29
29
|
</label>
|
|
30
|
+
<label class="b270-field">
|
|
31
|
+
<span>Bible translation</span>
|
|
32
|
+
<%= select_tag :bible_version,
|
|
33
|
+
options_for_select(Bible270::Translations.options, current_reader.effective_bible_version),
|
|
34
|
+
class: "b270-select" %>
|
|
35
|
+
<span class="b270-hint" style="margin:0">Reading links open in this translation.</span>
|
|
36
|
+
</label>
|
|
37
|
+
|
|
30
38
|
<%= submit_tag "Save", class: "b270-btn" %>
|
|
31
39
|
<% end %>
|
|
32
40
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
<% content_for :title, "#{@reader.display_name} — #{Bible270.config.app_name}" %>
|
|
2
|
+
<% content_for :hide_day_index, true %><%# this page has its own grid %>
|
|
2
3
|
<div style="display:flex;align-items:center;gap:16px;margin-bottom:8px">
|
|
3
4
|
<%= b270_avatar(@reader, size: 56) %>
|
|
4
5
|
<div>
|
|
5
6
|
<h1 style="margin:0"><%= @reader.display_name %></h1>
|
|
7
|
+
<% if signed_in? && current_reader.id == @reader.id %>
|
|
8
|
+
<p style="margin:2px 0 0"><%= link_to 'Edit your name', profile_path, class: 'b270-linkbtn' %></p>
|
|
9
|
+
<% end %>
|
|
6
10
|
<p style="margin:2px 0 0;color:var(--faint);font-size:14px"><%= @days_completed %> of <%= Bible270::Plan::DAYS %> days complete · <%= @reader.completion_percent %>%</p>
|
|
7
11
|
</div>
|
|
8
12
|
</div>
|
|
@@ -107,6 +107,8 @@
|
|
|
107
107
|
.b270-rbody{flex:1;min-width:0}
|
|
108
108
|
.b270-cat{font-size:11px;letter-spacing:.13em;text-transform:uppercase;color:var(--faint);font-weight:600}
|
|
109
109
|
.b270-ref{font-family:"Spectral",serif;font-size:19px;font-weight:500;margin-top:1px}
|
|
110
|
+
.b270-version{font-size:11.5px;color:var(--faint);font-weight:400;letter-spacing:.02em;white-space:nowrap}
|
|
111
|
+
.b270-select{font:inherit;font-size:14px;padding:10px 30px 10px 12px;border:1px solid var(--line);border-radius:9px;background:#fff;color:var(--ink);width:100%;max-width:100%;text-overflow:ellipsis}
|
|
110
112
|
.b270-ref a{text-decoration:none;border-bottom:1px solid transparent}
|
|
111
113
|
.b270-ref a:hover{border-bottom-color:var(--gold)}
|
|
112
114
|
.b270-reading.done .b270-ref{color:var(--muted)}
|
|
@@ -149,7 +151,7 @@
|
|
|
149
151
|
.b270-cell.complete{background:var(--teal);border-color:var(--teal);color:#eef3f2;font-weight:600}
|
|
150
152
|
.b270-cell.now{box-shadow:0 0 0 2px var(--gold)}
|
|
151
153
|
|
|
152
|
-
.b270-footer{margin-top:
|
|
154
|
+
.b270-footer{margin-top:20px;padding-top:11px;border-top:1px solid var(--line-soft);color:var(--faint);font-size:13px}
|
|
153
155
|
.b270-footer p{margin:0;line-height:1.5}
|
|
154
156
|
details.b270-index summary{cursor:pointer;font-family:"Spectral",serif;font-size:18px;font-weight:600;list-style:none;margin-top:30px}
|
|
155
157
|
details.b270-index summary::-webkit-details-marker{display:none}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
11
|
<link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,400;0,500;0,600;1,400&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
|
12
|
+
<%= b270_favicon_tag %>
|
|
12
13
|
<%= render "bible270/shared/styles" %>
|
|
13
14
|
<%= yield :head %>
|
|
14
15
|
</head>
|
|
@@ -18,10 +19,9 @@
|
|
|
18
19
|
<% if flash[:alert] %><div class="b270-flash alert"><%= flash[:alert] %></div><% end %>
|
|
19
20
|
<% if flash[:notice] %><div class="b270-flash notice"><%= flash[:notice] %></div><% end %>
|
|
20
21
|
<%= yield %>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
</footer>
|
|
22
|
+
<%# Pages with their own day grid opt out with content_for :hide_day_index %>
|
|
23
|
+
<%= render 'bible270/shared/day_index' unless content_for?(:hide_day_index) %>
|
|
24
|
+
<%= b270_footer %>
|
|
25
25
|
</div>
|
|
26
26
|
</body>
|
|
27
27
|
</html>
|
data/config/routes.rb
CHANGED
|
@@ -15,7 +15,8 @@ Bible270::Engine.routes.draw do
|
|
|
15
15
|
get 'admin', to: 'admin#index', as: :admin
|
|
16
16
|
get 'admin/readers/:id', to: 'admin#show', as: :admin_reader
|
|
17
17
|
delete 'admin/readers/:id', to: 'admin#destroy'
|
|
18
|
-
patch 'admin/readers/:id/
|
|
18
|
+
patch 'admin/readers/:id/profile', to: 'admin#update_profile', as: :admin_reader_profile
|
|
19
|
+
delete 'admin/readers/:id/avatar', to: 'admin#remove_avatar', as: :admin_reader_avatar
|
|
19
20
|
patch 'admin/readers/:id/start', to: 'admin#update_start', as: :admin_reader_start
|
|
20
21
|
patch 'admin/readers/:id/through', to: 'admin#complete_through', as: :admin_reader_through
|
|
21
22
|
post 'admin/readers/:id/days/:day', to: 'admin#toggle_day', as: :admin_reader_day
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddBibleVersionToBible270Readers < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
# Null means "use whatever the site default is", so changing
|
|
6
|
+
# config.bible_version moves everyone who hasn't chosen for themselves.
|
|
7
|
+
add_column :bible270_readers, :bible_version, :string
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bible270/version'
|
|
4
|
+
require 'bible270/plan'
|
|
5
|
+
require 'bible270/email_sign_in'
|
|
6
|
+
require 'bible270/names'
|
|
7
|
+
require 'bible270/avatars'
|
|
8
|
+
require 'bible270/translations'
|
|
9
|
+
require 'bible270/favicon'
|
|
10
|
+
require 'bible270/configuration'
|
|
11
|
+
require 'bible270/engine' if defined?(Rails::Engine)
|
|
12
|
+
|
|
13
|
+
module Bible270
|
|
14
|
+
end
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
# URI.encode_www_form_component produces byte-identical output to CGI.escape.
|
|
7
7
|
require 'uri'
|
|
8
8
|
require 'bible270/plan'
|
|
9
|
+
require 'bible270/email_sign_in'
|
|
9
10
|
require 'bible270/avatars'
|
|
11
|
+
require 'bible270/translations'
|
|
12
|
+
require 'bible270/favicon'
|
|
10
13
|
|
|
11
14
|
module Bible270
|
|
12
15
|
class Configuration
|
|
@@ -140,6 +143,59 @@ module Bible270
|
|
|
140
143
|
# from the panel at runtime; setting this to false launches closed.
|
|
141
144
|
attr_accessor :enrollment_open
|
|
142
145
|
|
|
146
|
+
# Favicon for the engine's pages. nil uses the built-in loaf, false renders
|
|
147
|
+
# no link tag at all (so the host's own favicon applies), and a string is
|
|
148
|
+
# used as the href — a path, a URL, or your own data URI.
|
|
149
|
+
# Footer for the engine's pages. Three ways to set it, and they're checked in
|
|
150
|
+
# this order:
|
|
151
|
+
#
|
|
152
|
+
# config.footer_partial = 'shared/footer' # a partial in your app
|
|
153
|
+
# config.footer_html = '<p>…</p>' # raw HTML, marked safe
|
|
154
|
+
# config.footer = false # no footer at all
|
|
155
|
+
#
|
|
156
|
+
# With none of them set you get the engine's own note about how the plan works.
|
|
157
|
+
# Rails adds the leading underscore when looking a partial up, so
|
|
158
|
+
# 'layouts/_bible270_footer' would search for '__bible270_footer' and raise
|
|
159
|
+
# MissingTemplate. Accept either spelling and normalise.
|
|
160
|
+
attr_reader :footer_partial
|
|
161
|
+
|
|
162
|
+
def footer_partial=(path)
|
|
163
|
+
@footer_partial =
|
|
164
|
+
if path.nil?
|
|
165
|
+
nil
|
|
166
|
+
else
|
|
167
|
+
segments = path.to_s.strip.split('/')
|
|
168
|
+
segments[-1] = segments[-1].sub(%r{\A_}, '') unless segments.empty?
|
|
169
|
+
segments.join('/')
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Where your footer goes relative to the engine's own: :replace (default),
|
|
174
|
+
# :after, or :before. Anything else is treated as :replace.
|
|
175
|
+
attr_accessor :footer_placement
|
|
176
|
+
|
|
177
|
+
PLACEMENTS = %i[replace after before].freeze
|
|
178
|
+
|
|
179
|
+
def resolved_footer_placement
|
|
180
|
+
placement = footer_placement.to_s.downcase.to_sym
|
|
181
|
+
PLACEMENTS.include?(placement) ? placement : :replace
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Whether the engine's own footer is shown alongside yours.
|
|
185
|
+
def keep_default_footer?
|
|
186
|
+
footer_style != :none && %i[after before].include?(resolved_footer_placement)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# :none, :partial, :html or :default.
|
|
190
|
+
def footer_style
|
|
191
|
+
# Plain Ruby, not present?: this file has to load without ActiveSupport.
|
|
192
|
+
return :none if footer == false
|
|
193
|
+
return :partial unless footer_partial.to_s.strip.empty?
|
|
194
|
+
return :html unless footer_html.to_s.strip.empty?
|
|
195
|
+
|
|
196
|
+
:default
|
|
197
|
+
end
|
|
198
|
+
|
|
143
199
|
# Optional YAML file holding the same thing, re-read whenever it changes so
|
|
144
200
|
# breaks can be tuned without restarting:
|
|
145
201
|
#
|
|
@@ -147,7 +203,6 @@ module Bible270
|
|
|
147
203
|
# Psalm 35: []
|
|
148
204
|
# Psalm 78: [20, 39, 55]
|
|
149
205
|
attr_accessor :chapter_breaks_path
|
|
150
|
-
attr_accessor :after_sign_out_path, :email_sign_in_max_per_window, :passage_url_builder, :tagline, :avatar_max_bytes, :chapter_breaks, :admin_emails, :admin_resolver
|
|
151
206
|
|
|
152
207
|
def admin?(reader)
|
|
153
208
|
return false if reader.nil?
|
|
@@ -202,7 +257,11 @@ module Bible270
|
|
|
202
257
|
end
|
|
203
258
|
|
|
204
259
|
# Default Bible translation and a builder for external passage links.
|
|
205
|
-
|
|
260
|
+
# Which translations readers may choose from. Defaults to all of
|
|
261
|
+
# Bible270::Translations::VERSIONS; narrow it to offer fewer.
|
|
262
|
+
attr_accessor :bible_versions
|
|
263
|
+
|
|
264
|
+
attr_accessor :after_sign_out_path, :email_sign_in_max_per_window, :passage_url_builder, :tagline, :footer_html, :footer, :favicon, :avatar_max_bytes, :chapter_breaks, :admin_emails, :admin_resolver, :bible_version
|
|
206
265
|
|
|
207
266
|
# Public labels.
|
|
208
267
|
attr_accessor :app_name
|
|
@@ -230,9 +289,11 @@ module Bible270
|
|
|
230
289
|
@after_sign_in_path = nil
|
|
231
290
|
@after_sign_out_path = nil
|
|
232
291
|
@bible_version = 'NKJV'
|
|
292
|
+
@bible_versions = Translations::VERSIONS.keys
|
|
233
293
|
@passage_url_builder = ->(reference, version) do
|
|
234
294
|
search = URI.encode_www_form_component(reference)
|
|
235
|
-
|
|
295
|
+
# Translations.gateway_code maps 'NASB95' to Bible Gateway's 'NASB1995'.
|
|
296
|
+
version = URI.encode_www_form_component(Translations.gateway_code(version))
|
|
236
297
|
"https://www.biblegateway.com/passage/?search=#{search}&version=#{version}"
|
|
237
298
|
end
|
|
238
299
|
@app_name = 'Daily Bread'
|
|
@@ -252,6 +313,11 @@ module Bible270
|
|
|
252
313
|
@email_sign_in_require_name = true
|
|
253
314
|
@email_sign_in_log_link = nil
|
|
254
315
|
@enrollment_open = true
|
|
316
|
+
@footer_partial = nil
|
|
317
|
+
@footer_placement = :replace
|
|
318
|
+
@footer_html = nil
|
|
319
|
+
@footer = nil
|
|
320
|
+
@favicon = nil
|
|
255
321
|
@avatar_max_bytes = Avatars::DEFAULT_MAX_BYTES
|
|
256
322
|
@chapter_breaks = {}
|
|
257
323
|
@chapter_breaks_path = nil
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bible270
|
|
4
|
+
# A favicon for the engine's own pages, inlined as a data URI.
|
|
5
|
+
#
|
|
6
|
+
# Inlined rather than served as an asset because the engine deliberately has no
|
|
7
|
+
# asset-pipeline dependency — its CSS is inlined the same way — so this works
|
|
8
|
+
# whether the host uses Propshaft, Sprockets or neither, and costs no request.
|
|
9
|
+
module Favicon
|
|
10
|
+
# A rustic loaf: a domed round on an ink board, with three slashed scores
|
|
11
|
+
# across the crust and a paler crumb showing at the base.
|
|
12
|
+
# Colours match the engine's palette (--ink, --gold, --paper).
|
|
13
|
+
SVG = <<~SVG
|
|
14
|
+
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'>
|
|
15
|
+
<rect width='32' height='32' rx='7' fill='%23231f18'/>
|
|
16
|
+
<path d='M4.5 21c0-6.2 5.1-10.5 11.5-10.5S27.5 14.8 27.5 21c0 1.4-1 2.4-2.4 2.4H6.9C5.5 23.4 4.5 22.4 4.5 21z'
|
|
17
|
+
fill='%23c6a24a'/>
|
|
18
|
+
<path d='M6.9 23.4h18.2c1.1 0 2-.6 2.3-1.5H4.6c.3.9 1.2 1.5 2.3 1.5z' fill='%23ece6da'/>
|
|
19
|
+
<g stroke='%23231f18' stroke-width='1.6' stroke-linecap='round' opacity='.75'>
|
|
20
|
+
<path d='M10.6 17.6l2.6-3'/>
|
|
21
|
+
<path d='M15 16.8l2.6-3'/>
|
|
22
|
+
<path d='M19.4 17.6l2.6-3'/>
|
|
23
|
+
</g>
|
|
24
|
+
</svg>
|
|
25
|
+
SVG
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# Percent-encoding matters here: a raw '#' would truncate the URI at the
|
|
30
|
+
# fragment, and raw spaces are invalid in one.
|
|
31
|
+
def data_uri
|
|
32
|
+
compact = SVG.gsub(%r{\s+}, ' ').strip
|
|
33
|
+
"data:image/svg+xml,#{compact.gsub(' ', '%20')}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bible270
|
|
4
|
+
# The translations a reader may choose between, and how each maps onto the
|
|
5
|
+
# external reader's own code. Kept free of Rails so it can be unit tested.
|
|
6
|
+
#
|
|
7
|
+
# The distinction matters: readers know the NASB 1995 as "NASB95", but Bible
|
|
8
|
+
# Gateway's parameter for it is "NASB1995". Sending the display code straight
|
|
9
|
+
# through would land every link on a search page instead of the passage.
|
|
10
|
+
module Translations
|
|
11
|
+
# `label` is the full name, for prose. `short` is what goes in the select,
|
|
12
|
+
# where "New American Standard Bible 1995" overflows the box.
|
|
13
|
+
VERSIONS = {
|
|
14
|
+
'NKJV' => { label: 'New King James Version', short: 'New King James Version', gateway: 'NKJV' },
|
|
15
|
+
'NASB95' => { label: 'New American Standard Bible 1995', short: 'New American Standard 1995', gateway: 'NASB1995' },
|
|
16
|
+
'LSB' => { label: 'Legacy Standard Bible', short: 'Legacy Standard Version', gateway: 'LSB' },
|
|
17
|
+
'ESV' => { label: 'English Standard Version', short: 'English Standard Version', gateway: 'ESV' },
|
|
18
|
+
'KJV' => { label: 'King James Version', short: 'King James Version', gateway: 'KJV' }
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
DEFAULT = 'NKJV'
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# The codes offered to readers, narrowed by config.bible_versions if the host
|
|
26
|
+
# only wants some of them.
|
|
27
|
+
def codes
|
|
28
|
+
allowed = Bible270.config.bible_versions if Bible270.config.respond_to?(:bible_versions)
|
|
29
|
+
requested = Array(allowed).map { |code| code.to_s.upcase }.select { |code| VERSIONS.key?(code) }
|
|
30
|
+
requested.any? ? requested : VERSIONS.keys
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def valid?(code)
|
|
34
|
+
codes.include?(normalize(code))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def normalize(code)
|
|
38
|
+
code.to_s.strip.upcase
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def label(code)
|
|
42
|
+
VERSIONS.dig(normalize(code), :label)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def short_label(code)
|
|
46
|
+
VERSIONS.dig(normalize(code), :short) || label(code)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The code the external reader expects, which is not always the code readers
|
|
50
|
+
# recognise.
|
|
51
|
+
def gateway_code(code)
|
|
52
|
+
VERSIONS.dig(normalize(code), :gateway) || normalize(code)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# [[label, code], ...] for a select field.
|
|
56
|
+
def options
|
|
57
|
+
codes.map { |code| ["#{code} — #{short_label(code)}", code] }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Falls back to the configured default, then to ESV, so a link is never built
|
|
61
|
+
# with a blank version.
|
|
62
|
+
def resolve(code)
|
|
63
|
+
return normalize(code) if valid?(code)
|
|
64
|
+
|
|
65
|
+
configured = Bible270.config.bible_version if Bible270.config.respond_to?(:bible_version)
|
|
66
|
+
return normalize(configured) if valid?(configured)
|
|
67
|
+
|
|
68
|
+
codes.include?(DEFAULT) ? DEFAULT : codes.first
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/bible270/version.rb
CHANGED
data/lib/bible270.rb
CHANGED
|
@@ -5,6 +5,8 @@ require 'bible270/plan'
|
|
|
5
5
|
require 'bible270/email_sign_in'
|
|
6
6
|
require 'bible270/names'
|
|
7
7
|
require 'bible270/avatars'
|
|
8
|
+
require 'bible270/translations'
|
|
9
|
+
require 'bible270/favicon'
|
|
8
10
|
require 'bible270/configuration'
|
|
9
11
|
require 'bible270/engine' if defined?(Rails::Engine)
|
|
10
12
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bible270
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew vonderLuft
|
|
@@ -100,6 +100,7 @@ files:
|
|
|
100
100
|
- app/views/bible270/readers/show.html.erb
|
|
101
101
|
- app/views/bible270/sessions/new.html.erb
|
|
102
102
|
- app/views/bible270/shared/_day_index.html.erb
|
|
103
|
+
- app/views/bible270/shared/_footer.html.erb
|
|
103
104
|
- app/views/bible270/shared/_header.html.erb
|
|
104
105
|
- app/views/bible270/shared/_styles.html.erb
|
|
105
106
|
- app/views/bible270/sign_in_mailer/magic_link.html.erb
|
|
@@ -113,13 +114,17 @@ files:
|
|
|
113
114
|
- db/migrate/20260101000005_add_names_to_bible270_readers.rb
|
|
114
115
|
- db/migrate/20260101000006_add_approval_to_bible270_comments.rb
|
|
115
116
|
- db/migrate/20260101000007_create_bible270_settings.rb
|
|
117
|
+
- db/migrate/20260101000008_add_bible_version_to_bible270_readers.rb
|
|
116
118
|
- lib/bible270.rb
|
|
117
119
|
- lib/bible270/avatars.rb
|
|
120
|
+
- lib/bible270/bible270.rb
|
|
118
121
|
- lib/bible270/configuration.rb
|
|
119
122
|
- lib/bible270/email_sign_in.rb
|
|
120
123
|
- lib/bible270/engine.rb
|
|
124
|
+
- lib/bible270/favicon.rb
|
|
121
125
|
- lib/bible270/names.rb
|
|
122
126
|
- lib/bible270/plan.rb
|
|
127
|
+
- lib/bible270/translations.rb
|
|
123
128
|
- lib/bible270/versification.rb
|
|
124
129
|
- lib/bible270/version.rb
|
|
125
130
|
- lib/generators/bible270/install/install_generator.rb
|