bible270 1.0.0 → 1.1.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 +57 -9
- data/README.md +7 -10
- data/app/controllers/bible270/admin_controller.rb +2 -3
- data/app/controllers/bible270/days_controller.rb +1 -36
- data/app/controllers/bible270/profiles_controller.rb +20 -1
- data/app/controllers/bible270/readers_controller.rb +1 -66
- data/app/mailers/bible270/notice_mailer.rb +32 -8
- data/app/models/bible270/comment.rb +33 -9
- data/app/models/bible270/reader.rb +8 -40
- data/app/views/bible270/comments/create.turbo_stream.erb +0 -2
- data/app/views/bible270/comments/index.html.erb +0 -24
- data/app/views/bible270/days/_completers.html.erb +0 -2
- data/app/views/bible270/days/_day_progress.html.erb +3 -3
- data/app/views/bible270/days/index.html.erb +1 -27
- data/app/views/bible270/days/show.html.erb +4 -16
- data/app/views/bible270/notice_mailer/{mentioned.html.erb → comment_notice.html.erb} +5 -2
- data/app/views/bible270/notice_mailer/{mentioned.text.erb → comment_notice.text.erb} +5 -2
- data/app/views/bible270/profiles/edit.html.erb +11 -0
- data/app/views/bible270/readers/_next_steps.html.erb +24 -60
- data/app/views/bible270/readers/index.html.erb +2 -3
- data/app/views/bible270/readers/progress.html.erb +6 -30
- data/app/views/bible270/readers/show.html.erb +1 -6
- data/app/views/bible270/shared/_day_index.html.erb +6 -4
- data/app/views/bible270/shared/_header.html.erb +21 -4
- data/app/views/bible270/shared/_progress.html.erb +3 -14
- data/app/views/bible270/shared/_styles.html.erb +3 -32
- data/config/routes.rb +0 -4
- data/db/migrate/20260101000011_add_mention_notices_to_bible270_readers.rb +3 -3
- data/lib/bible270/configuration.rb +8 -5
- data/lib/bible270/mentions.rb +0 -9
- data/lib/bible270/version.rb +1 -1
- data/lib/generators/bible270/install/install_generator.rb +2 -0
- data/lib/generators/bible270/install/templates/bible270.rb.tt +2 -0
- metadata +3 -5
- data/app/views/bible270/days/_start_date.html.erb +0 -43
- data/app/views/bible270/shared/_reading_mode.html.erb +0 -62
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48e80937debff292b2c896ae9eaba55fe5eae8624ef28c6eb9274236fed4fb2c
|
|
4
|
+
data.tar.gz: 72f8fbd17194fd00bbc8602fd69180cb91565d54f995868dacf8edfb4419657f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b493f52caadc3fa2bbb7e3ef4dd59c9822d0045b9fb6f42151f4fad0c9057d017b7604064188e35a3dce03c8c6c3c02cabce8a95a342e84e6fbf5a2682a46309
|
|
7
|
+
data.tar.gz: a38f6e3b979b76ccaf5c666e3698d29302ac97b82692f7c2a33ac1ef0d28f139b28ddaf89705e4a13c132713533edd87c8e804eff06974c089ad2bb8b1322c0b
|
data/CHANGELOG.md
CHANGED
|
@@ -4,25 +4,71 @@ All notable changes to bible270. Format follows [Keep a Changelog](https://keepa
|
|
|
4
4
|
|
|
5
5
|
## Unreleased Changes
|
|
6
6
|
|
|
7
|
+
### Removed
|
|
8
|
+
|
|
9
|
+
- remove the Compact reading preference so daily reading pages have one simple, predictable presentation
|
|
10
|
+
- replace the homepage reader preview with one clear link to the Community page
|
|
11
|
+
- remove the redundant schedule-restart shortcut from progress recovery choices
|
|
12
|
+
- remove reader-facing start-date status, controls, and self-service endpoints while retaining automatic and admin-managed personal calendars
|
|
13
|
+
- remove weekly progress, its unused schedule helper, milestones, and explanatory schedule-recovery copy from My Progress
|
|
14
|
+
- replace the Reflections page’s separate day grid with the standard “View all 270 days” control
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- notify reflection authors directly about replies, share one opt-out with explicit mentions, and stop inserting handles into reply text
|
|
19
|
+
- consolidate duplicate visitor sign-in prompts on day pages into one invitation
|
|
20
|
+
- show “Finish what you started” on My Progress only when partially read days need attention
|
|
21
|
+
- clarify the global day-index control as “View all 270 days” and use that standard collapsed control on My Progress
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- make the mobile navigation menu reliably open and close when its button is tapped
|
|
26
|
+
|
|
27
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.0.0...main)**
|
|
28
|
+
|
|
29
|
+
## [1.0.0] - 2026-08-20
|
|
30
|
+
|
|
7
31
|
### Added
|
|
8
32
|
|
|
9
|
-
-
|
|
33
|
+
- optional daily reading reminders with per-reader opt-in and delivery time, reminder emails, and a scheduler-friendly Rake task
|
|
34
|
+
- immediate day-completion feedback with a preserved next-day or Progress action
|
|
35
|
+
- actionable Progress sections for unfinished portions, the current calendar week, milestones, and schedule recovery without losing reading or reflection history
|
|
36
|
+
- browser-local reflection and reply drafts, day and author filters, pagination, and new-activity markers on the Reflections page
|
|
37
|
+
- a remembered compact reading mode that keeps passages, checkoffs, navigation, and completion next steps in focus
|
|
10
38
|
- a two-worker default `test` task with real-time colorized progress, one aggregate result and duration, merged coverage, a browsable project-local `coverage/` report, and per-file runtime recording, plus `test:serial` when a serial run is needed
|
|
11
|
-
- browser-local reflection and reply drafts that survive refreshes, failed submissions, and accidental navigation
|
|
12
|
-
- day and author filters, pagination, and new-activity markers on the Reflections page
|
|
13
39
|
|
|
14
40
|
### Changed
|
|
15
41
|
|
|
16
42
|
- replace progress-ranked community lists with transparent recent-participation and alphabetical views that emphasize reading together
|
|
17
|
-
-
|
|
18
|
-
- improve accessibility with AA text contrast, consistent focus indicators, descriptive day-grid labels, semantic boundary navigation, touch-friendly tooltips, responsive forms, and reduced-motion support
|
|
19
|
-
- keep profiles and scheduled jobs available while a newly installed daily-reminder database migration is pending
|
|
43
|
+
- show why each reader appears in Reading together, including their latest reading, reflection, or join activity
|
|
20
44
|
- add consistent pending, success, and retry feedback to reading, like, reflection, and reply actions, with repeat-safe checkoff and like requests
|
|
21
|
-
-
|
|
45
|
+
- improve accessibility with AA text contrast, consistent focus indicators, descriptive day-grid labels, semantic boundary navigation, touch-friendly tooltips, responsive forms, and reduced-motion support
|
|
46
|
+
- replace the reminder time field with aligned hour and minute selectors and an accessible time-zone tooltip
|
|
47
|
+
- hide personal start-date controls and calendar prompts when the community configuration does not allow readers to change dates
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- keep profiles and scheduled reminder jobs available while a newly installed daily-reminder database migration is pending
|
|
52
|
+
|
|
53
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.19.0...v1.0.0)**
|
|
54
|
+
|
|
55
|
+
## [0.19.0] - 2026-08-17
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- shared responsive navigation with clearer Community, Reflections, My Progress, and Profile labels plus a mobile menu
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- align page headings with navigation destinations and clarify the Start or Continue reading and Go to today actions
|
|
64
|
+
- always email explicitly mentioned readers, including the original reflection author when a reply prefills their mention
|
|
65
|
+
- normalize packaged file permissions before gem builds
|
|
66
|
+
|
|
67
|
+
### Removed
|
|
22
68
|
|
|
23
|
-
|
|
69
|
+
- the per-reader mention-email opt-out; writing a reader's name explicitly now always sends the mention notice
|
|
24
70
|
|
|
25
|
-
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.18.2...
|
|
71
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.18.2...v0.19.0)**
|
|
26
72
|
|
|
27
73
|
## [0.18.2] - 2026-08-15
|
|
28
74
|
|
|
@@ -442,6 +488,8 @@ This is the initial public release.
|
|
|
442
488
|
|
|
443
489
|
* 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.
|
|
444
490
|
|
|
491
|
+
[1.0.0]: https://github.com/avonderluft/bible270/releases/tag/v1.0.0
|
|
492
|
+
[0.19.0]: https://github.com/avonderluft/bible270/releases/tag/v0.19.0
|
|
445
493
|
[0.18.2]: https://github.com/avonderluft/bible270/releases/tag/v0.18.2
|
|
446
494
|
[0.18.1]: https://github.com/avonderluft/bible270/releases/tag/v0.18.1
|
|
447
495
|
[0.18.0]: https://github.com/avonderluft/bible270/releases/tag/v0.18.0
|
data/README.md
CHANGED
|
@@ -26,9 +26,7 @@ The schedule is pure deterministic Ruby (`Bible270::Plan`) — none of it is sto
|
|
|
26
26
|
|
|
27
27
|
Mutation controls expose pending, success, and retry feedback when JavaScript runs, while ordinary HTML
|
|
28
28
|
forms remain the fallback. Repeated checkoff and like requests converge on the intended state rather
|
|
29
|
-
than accidentally reversing it.
|
|
30
|
-
that keeps passages, checkoffs, day navigation, and the completion next step while hiding community
|
|
31
|
-
and reflection content.
|
|
29
|
+
than accidentally reversing it.
|
|
32
30
|
|
|
33
31
|
## Install
|
|
34
32
|
|
|
@@ -310,17 +308,17 @@ The plan runs **undated** (day numbers only) or **dated** (days mapped onto a ca
|
|
|
310
308
|
|
|
311
309
|
```ruby
|
|
312
310
|
config.start_date = Date.new(2026, 9, 6) # Date, Time, or "YYYY-MM-DD". nil = undated
|
|
313
|
-
config.allow_reader_start_date = true #
|
|
311
|
+
config.allow_reader_start_date = true # allow personal per-reader calendars
|
|
314
312
|
```
|
|
315
313
|
|
|
316
314
|
| `start_date` | `allow_reader_start_date` | What happens |
|
|
317
315
|
|---|---|---|
|
|
318
|
-
| nil | true *(default)* | Each reader gets stamped on first check-off
|
|
319
|
-
| a date | true |
|
|
320
|
-
| a date | false | Everyone pinned to the community date
|
|
316
|
+
| nil | true *(default)* | Each reader gets stamped on their first check-off; an admin may adjust the date |
|
|
317
|
+
| a date | true | The community date is the default; an admin may assign an individual date |
|
|
318
|
+
| a date | false | Everyone is pinned to the community date; stored individual dates are ignored |
|
|
321
319
|
| nil | false | Fully undated — no calendar anywhere |
|
|
322
320
|
|
|
323
|
-
Dated plans show each day's date with a **Today** badge
|
|
321
|
+
Dated plans show each day's date with a **Today** badge and a "Go to today" link. Administrators may re-map an individual reader's calendar from the admin panel; check-offs and reflections remain keyed to day numbers, so nothing in their history moves.
|
|
324
322
|
|
|
325
323
|
Date helpers are pure functions, usable anywhere:
|
|
326
324
|
|
|
@@ -378,6 +376,7 @@ Bible270.configure do |config|
|
|
|
378
376
|
config.allow_reader_start_date = true
|
|
379
377
|
|
|
380
378
|
config.daily_reminders = false # schedule reminders:send every 15 minutes
|
|
379
|
+
config.mention_notifications = true # replies and @mentions; readers may opt out
|
|
381
380
|
config.mailer_host = "example.com" # links in reminder/notice email
|
|
382
381
|
|
|
383
382
|
config.parent_controller = "ActionController::Base" # or "::ApplicationController"
|
|
@@ -467,8 +466,6 @@ GET /day/:day days#show readings, who's finished, refle
|
|
|
467
466
|
POST /day/:day/toggle/:track checkoffs#toggle
|
|
468
467
|
POST /day/:day/comments comments#create
|
|
469
468
|
DELETE /comments/:id comments#destroy
|
|
470
|
-
PATCH /start-date readers#update_start_date
|
|
471
|
-
DELETE /start-date readers#clear_start_date
|
|
472
469
|
GET /community readers#index leaderboard
|
|
473
470
|
GET /readers/:id readers#show
|
|
474
471
|
GET /sign_in sessions#new email form + provider list
|
|
@@ -130,9 +130,8 @@ module Bible270
|
|
|
130
130
|
|
|
131
131
|
def update_start
|
|
132
132
|
if params[:start_date].present?
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
# their own dates.
|
|
133
|
+
# The administrator may store an individual date even when personal
|
|
134
|
+
# calendars are currently disabled for this installation.
|
|
136
135
|
if @reader.set_start_date!(params[:start_date])
|
|
137
136
|
redirect_to admin_reader_path(@reader),
|
|
138
137
|
notice: "Start date set to #{@reader.started_on.strftime('%B %-d, %Y')}."
|
|
@@ -5,32 +5,10 @@ module Bible270
|
|
|
5
5
|
def index
|
|
6
6
|
@plan_totals = Plan.totals
|
|
7
7
|
|
|
8
|
-
# Show recent participation without ranking readers by completion. A checkoff
|
|
9
|
-
# or reflection counts as activity; joining is the fallback for new readers.
|
|
10
|
-
@readers = Reader.all.to_a
|
|
11
|
-
checkoff_activity = Checkoff.group(:reader_id).maximum(:updated_at)
|
|
12
|
-
reflection_activity = Comment.approved.group(:reader_id).maximum(:updated_at)
|
|
13
|
-
@reader_activity = @readers.to_h do |reader|
|
|
14
|
-
candidates = [
|
|
15
|
-
{ kind: :joined, at: reader.created_at },
|
|
16
|
-
{ kind: :reading, at: checkoff_activity[reader.id] },
|
|
17
|
-
{ kind: :reflection, at: reflection_activity[reader.id] }
|
|
18
|
-
].select { |activity| activity[:at] }
|
|
19
|
-
[reader.id, candidates.max_by { |activity| activity[:at] }]
|
|
20
|
-
end
|
|
21
|
-
@readers.sort_by! do |reader|
|
|
22
|
-
[-@reader_activity.fetch(reader.id)[:at].to_f, reader.sort_name]
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
counts = Checkoff.group(:reader_id, :day).count
|
|
26
|
-
@days_completed = Hash.new(0)
|
|
27
|
-
counts.each { |(rid, day), n| @days_completed[rid] += 1 if n >= Plan.total_parts(day) }
|
|
28
|
-
|
|
29
8
|
@start_day = current_reader&.current_day || 1
|
|
30
9
|
# nil outside the plan's window, so no "Go to today" before it begins
|
|
31
10
|
@today_day = current_reader&.today_day
|
|
32
11
|
@community_start_date = Bible270.config.start_date
|
|
33
|
-
@allow_reader_start_date = Bible270.config.allow_reader_start_date
|
|
34
12
|
end
|
|
35
13
|
|
|
36
14
|
def show
|
|
@@ -39,11 +17,8 @@ module Bible270
|
|
|
39
17
|
|
|
40
18
|
@readings = Plan.readings_for(@day)
|
|
41
19
|
@comments = Comment.threads_for_day(@day)
|
|
42
|
-
# Replying prefills the form with the handle rather than needing JavaScript,
|
|
43
|
-
# so it works with Turbo and without an asset pipeline.
|
|
44
20
|
@editing_comment_id = editing_comment_id
|
|
45
|
-
|
|
46
|
-
@new_comment = Comment.new(day: @day, parent_id: parent&.id, body: reply_prefix(parent))
|
|
21
|
+
@new_comment = Comment.new(day: @day, parent_id: reply_parent&.id)
|
|
47
22
|
@reader_tracks = current_reader ? current_reader.read_tracks_for(@day) : []
|
|
48
23
|
|
|
49
24
|
# Every box on the day, not every track: an Old Testament reading of three
|
|
@@ -59,9 +34,6 @@ module Bible270
|
|
|
59
34
|
|
|
60
35
|
private
|
|
61
36
|
|
|
62
|
-
# "@handle " for the reflection being replied to, or nothing. Private, but
|
|
63
|
-
# declared with the keyword rather than trailing the file, so it is obvious
|
|
64
|
-
# this is not an action.
|
|
65
37
|
# The reflection the reader has asked to edit: their own, on this day. Anything
|
|
66
38
|
# else is ignored rather than refused — the page still renders.
|
|
67
39
|
def editing_comment_id
|
|
@@ -76,12 +48,5 @@ module Bible270
|
|
|
76
48
|
parent = Comment.approved.find_by(id: params[:reply_to])
|
|
77
49
|
parent if parent && parent.day == @day && parent.parent_id.nil?
|
|
78
50
|
end
|
|
79
|
-
|
|
80
|
-
# "@handle " so the author is mentioned — and so gets the email — without the
|
|
81
|
-
# reader having to type it.
|
|
82
|
-
def reply_prefix(parent)
|
|
83
|
-
handle = parent&.reader&.mention_handle
|
|
84
|
-
handle ? "@#{handle} " : nil
|
|
85
|
-
end
|
|
86
51
|
end
|
|
87
52
|
end
|
|
@@ -8,10 +8,13 @@ module Bible270
|
|
|
8
8
|
|
|
9
9
|
def edit
|
|
10
10
|
@names = current_reader.suggested_names
|
|
11
|
+
load_comment_notification_state
|
|
11
12
|
load_daily_reminder_state
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def update
|
|
16
|
+
@comment_notifications_available = comment_notifications_available?
|
|
17
|
+
@notify_on_mention = requested_comment_notifications
|
|
15
18
|
@daily_reminders_available = daily_reminders_available?
|
|
16
19
|
problems = []
|
|
17
20
|
problems << 'both a first and last name' unless current_reader.update_names(params[:first_name],
|
|
@@ -32,11 +35,12 @@ module Bible270
|
|
|
32
35
|
end
|
|
33
36
|
|
|
34
37
|
if problems.empty?
|
|
38
|
+
current_reader.update!(notify_on_mention: @notify_on_mention) if @comment_notifications_available
|
|
35
39
|
if @daily_reminders_available
|
|
36
40
|
current_reader.update!(daily_reminders: requested_daily_reminders,
|
|
37
41
|
daily_reminder_time: requested_daily_reminder_time)
|
|
38
42
|
end
|
|
39
|
-
redirect_to
|
|
43
|
+
redirect_to profile_path, notice: 'Your profile has been updated.'
|
|
40
44
|
else
|
|
41
45
|
@names = { first_name: params[:first_name], last_name: params[:last_name] }
|
|
42
46
|
@daily_reminders = requested_daily_reminders
|
|
@@ -53,6 +57,21 @@ module Bible270
|
|
|
53
57
|
|
|
54
58
|
private
|
|
55
59
|
|
|
60
|
+
def load_comment_notification_state
|
|
61
|
+
@comment_notifications_available = comment_notifications_available?
|
|
62
|
+
@notify_on_mention = current_reader.wants_comment_notifications?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def comment_notifications_available?
|
|
66
|
+
Bible270.config.mention_notifications && current_reader.has_attribute?(:notify_on_mention)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def requested_comment_notifications
|
|
70
|
+
return current_reader.wants_comment_notifications? unless params.key?(:notify_on_mention)
|
|
71
|
+
|
|
72
|
+
params[:notify_on_mention] == '1'
|
|
73
|
+
end
|
|
74
|
+
|
|
56
75
|
def load_daily_reminder_state
|
|
57
76
|
@daily_reminders_available = daily_reminders_available?
|
|
58
77
|
if @daily_reminders_available
|
|
@@ -2,22 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Bible270
|
|
4
4
|
class ReadersController < ApplicationController
|
|
5
|
-
MILESTONES = {
|
|
6
|
-
30 => '30-day milestone',
|
|
7
|
-
90 => 'one-third complete',
|
|
8
|
-
135 => 'halfway',
|
|
9
|
-
180 => 'two-thirds complete',
|
|
10
|
-
Plan::DAYS => 'plan complete'
|
|
11
|
-
}.freeze
|
|
12
|
-
|
|
13
5
|
def index
|
|
14
|
-
@readers = Reader.all.to_a
|
|
15
|
-
counts = Checkoff.group(:reader_id, :day).count
|
|
16
|
-
@days_completed = Hash.new(0)
|
|
17
|
-
counts.each { |(rid, day), n| @days_completed[rid] += 1 if n >= Plan.total_parts(day) }
|
|
18
6
|
# Community is a directory of fellow readers, not a leaderboard. Keep the
|
|
19
7
|
# order predictable without implying that progress determines standing.
|
|
20
|
-
@readers.sort_by
|
|
8
|
+
@readers = Reader.all.to_a.sort_by(&:sort_name)
|
|
21
9
|
end
|
|
22
10
|
|
|
23
11
|
# The reader's own progress. Deliberately open to visitors: the panel shows an
|
|
@@ -29,10 +17,6 @@ module Bible270
|
|
|
29
17
|
@recent_comments = @reader ? @reader.comments.recent.limit(5) : Comment.none
|
|
30
18
|
@partial_day_tasks = []
|
|
31
19
|
@remaining_partial_days = 0
|
|
32
|
-
@week_days = []
|
|
33
|
-
@week_completed = 0
|
|
34
|
-
@next_milestone = nil
|
|
35
|
-
@days_to_milestone = nil
|
|
36
20
|
return unless @reader
|
|
37
21
|
|
|
38
22
|
partial_days = @reader.partial_days
|
|
@@ -40,11 +24,6 @@ module Bible270
|
|
|
40
24
|
{ day: day, remaining: @reader.remaining_parts_for(day) }
|
|
41
25
|
end
|
|
42
26
|
@remaining_partial_days = [partial_days.size - @partial_day_tasks.size, 0].max
|
|
43
|
-
@week_days = @reader.scheduled_days_this_week
|
|
44
|
-
@week_completed = @week_days.count { |day| @reader.day_complete?(day) }
|
|
45
|
-
completed = @reader.days_completed
|
|
46
|
-
@next_milestone = MILESTONES.find { |day, _label| day > completed }
|
|
47
|
-
@days_to_milestone = @next_milestone&.first.to_i - completed if @next_milestone
|
|
48
27
|
end
|
|
49
28
|
|
|
50
29
|
def show
|
|
@@ -54,49 +33,5 @@ module Bible270
|
|
|
54
33
|
@days_completed = @reader.days_completed
|
|
55
34
|
@recent_comments = @reader.comments.recent.limit(20)
|
|
56
35
|
end
|
|
57
|
-
|
|
58
|
-
def update_start_date
|
|
59
|
-
return unless require_reader!
|
|
60
|
-
|
|
61
|
-
unless Bible270.config.allow_reader_start_date
|
|
62
|
-
redirect_to(root_path, alert: 'The start date is set for the whole community.') and return
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
if current_reader.update_start_date!(params[:start_date])
|
|
66
|
-
redirect_to root_path,
|
|
67
|
-
notice: "Start date set to #{current_reader.started_on.strftime('%B %-d, %Y')}."
|
|
68
|
-
else
|
|
69
|
-
redirect_to root_path, alert: "That doesn't look like a valid date."
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def clear_start_date
|
|
74
|
-
return unless require_reader!
|
|
75
|
-
|
|
76
|
-
if Bible270.config.allow_reader_start_date
|
|
77
|
-
current_reader.clear_start_date!
|
|
78
|
-
redirect_to root_path, notice: 'Start date cleared — the plan is now undated for you.'
|
|
79
|
-
else
|
|
80
|
-
redirect_to root_path, alert: 'The start date is set for the whole community.'
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def restart_schedule
|
|
85
|
-
return unless require_reader!
|
|
86
|
-
|
|
87
|
-
unless Bible270.config.allow_reader_start_date
|
|
88
|
-
redirect_to progress_path, alert: 'The start date is set for the whole community.' and return
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
next_day = current_reader.current_day
|
|
92
|
-
today = current_reader.today_day
|
|
93
|
-
unless today && next_day < today
|
|
94
|
-
redirect_to progress_path, notice: 'Your reading schedule is already up to date.' and return
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
current_reader.restart_on!(day: next_day)
|
|
98
|
-
redirect_to progress_path,
|
|
99
|
-
notice: "Day #{next_day} is now today. All your reading progress is unchanged."
|
|
100
|
-
end
|
|
101
36
|
end
|
|
102
37
|
end
|
|
@@ -19,16 +19,21 @@ module Bible270
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def mentioned(comment_id:, reader_id:)
|
|
22
|
-
|
|
23
|
-
@reader = Reader.find_by(id: reader_id)
|
|
24
|
-
return message.perform_deliveries = false if @comment.nil? || @reader.nil? || @reader.email.blank?
|
|
22
|
+
return message.perform_deliveries = false unless prepare_comment_notice(comment_id, reader_id)
|
|
25
23
|
|
|
26
|
-
@
|
|
27
|
-
@
|
|
28
|
-
@
|
|
24
|
+
@heading = "#{@author.display_name} mentioned you"
|
|
25
|
+
@introduction = "#{@author.display_name} mentioned you in a reflection on day #{@comment.day}."
|
|
26
|
+
@reason = 'You are getting this because you asked to be emailed about replies and mentions.'
|
|
27
|
+
deliver_comment_notice("#{@app_name}: #{@author.display_name} mentioned you on day #{@comment.day}")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def replied(comment_id:, reader_id:)
|
|
31
|
+
return message.perform_deliveries = false unless prepare_comment_notice(comment_id, reader_id)
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
@heading = "#{@author.display_name} replied to your reflection"
|
|
34
|
+
@introduction = "#{@author.display_name} replied to your reflection on day #{@comment.day}."
|
|
35
|
+
@reason = 'You are getting this because you asked to be emailed about replies and mentions.'
|
|
36
|
+
deliver_comment_notice("#{@app_name}: #{@author.display_name} replied on day #{@comment.day}")
|
|
32
37
|
end
|
|
33
38
|
|
|
34
39
|
def daily_reminder(reader_id:, day:, on: Bible270.today)
|
|
@@ -90,6 +95,25 @@ module Bible270
|
|
|
90
95
|
engine_url(:profile_url)
|
|
91
96
|
end
|
|
92
97
|
|
|
98
|
+
def prepare_comment_notice(comment_id, reader_id)
|
|
99
|
+
return false unless Bible270.config.mention_notifications
|
|
100
|
+
|
|
101
|
+
@comment = Comment.find_by(id: comment_id)
|
|
102
|
+
@reader = Reader.find_by(id: reader_id)
|
|
103
|
+
return false if @comment.nil? || @reader.nil? || @reader.email.blank?
|
|
104
|
+
return false unless @reader.wants_comment_notifications?
|
|
105
|
+
|
|
106
|
+
@author = @comment.reader
|
|
107
|
+
@app_name = Bible270.config.app_name
|
|
108
|
+
@day_url = day_url_for(@comment.day)
|
|
109
|
+
@profile_url = profile_url
|
|
110
|
+
true
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def deliver_comment_notice(subject)
|
|
114
|
+
mail to: @reader.email, subject: subject, template_name: 'comment_notice'
|
|
115
|
+
end
|
|
116
|
+
|
|
93
117
|
def engine_url(helper, *)
|
|
94
118
|
host = Bible270.config.mailer_host
|
|
95
119
|
return nil if host.blank?
|
|
@@ -37,10 +37,11 @@ module Bible270
|
|
|
37
37
|
validate :parent_is_on_the_same_day
|
|
38
38
|
|
|
39
39
|
# Fires once the reflection is really saved, so nothing is sent for one whose
|
|
40
|
-
# transaction rolls back.
|
|
41
|
-
|
|
40
|
+
# transaction rolls back. Replies notify their parent author directly; explicit
|
|
41
|
+
# mentions notify everyone else named in the body.
|
|
42
|
+
after_create_commit :notify_comment_readers
|
|
42
43
|
# Only the people newly named: editing a reflection five times must not mail
|
|
43
|
-
# the same reader five times.
|
|
44
|
+
# the same reader five times or repeat the original reply notice.
|
|
44
45
|
after_update_commit :notify_newly_mentioned_readers
|
|
45
46
|
|
|
46
47
|
scope :approved, -> { where(approved: true) }
|
|
@@ -153,13 +154,19 @@ module Bible270
|
|
|
153
154
|
errors.add(:parent, "is on day #{parent.day}, not #{day}")
|
|
154
155
|
end
|
|
155
156
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
def notify_comment_readers
|
|
158
|
+
return unless Bible270.config.mention_notifications
|
|
159
|
+
|
|
160
|
+
replied_to = reply_notification_reader
|
|
161
|
+
deliver_comment_notice(replied_to, :replied) if replied_to
|
|
162
|
+
notify_mentioned_readers(except: [replied_to&.id].compact)
|
|
163
|
+
end
|
|
164
|
+
|
|
159
165
|
def notify_newly_mentioned_readers
|
|
160
166
|
return unless saved_change_to_body?
|
|
161
167
|
|
|
162
168
|
already = Reader.mentioned_in(body_before_last_save).map(&:id)
|
|
169
|
+
already << parent.reader_id if reply?
|
|
163
170
|
notify_mentioned_readers(except: already)
|
|
164
171
|
end
|
|
165
172
|
|
|
@@ -170,11 +177,28 @@ module Bible270
|
|
|
170
177
|
next if mentioned.id == reader_id
|
|
171
178
|
next if except.include?(mentioned.id)
|
|
172
179
|
|
|
173
|
-
|
|
174
|
-
Bible270.config.registration_notice_deliver_later ? notice.deliver_later : notice.deliver_now
|
|
180
|
+
deliver_comment_notice(mentioned, :mentioned)
|
|
175
181
|
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def reply_notification_reader
|
|
185
|
+
return unless reply?
|
|
186
|
+
|
|
187
|
+
recipient = parent&.reader
|
|
188
|
+
recipient unless recipient&.id == reader_id
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Each recipient is isolated so a mail-server failure for one person cannot
|
|
192
|
+
# prevent other people named in the same reflection from being notified.
|
|
193
|
+
def deliver_comment_notice(recipient, action)
|
|
194
|
+
return unless recipient&.wants_comment_notifications?
|
|
195
|
+
|
|
196
|
+
notice = NoticeMailer.public_send(action, comment_id: id, reader_id: recipient.id)
|
|
197
|
+
Bible270.config.registration_notice_deliver_later ? notice.deliver_later : notice.deliver_now
|
|
176
198
|
rescue StandardError => e
|
|
177
|
-
Rails.logger.error(
|
|
199
|
+
Rails.logger.error(
|
|
200
|
+
"[bible270] could not send #{action} notice for comment #{id}: #{e.class}: #{e.message}"
|
|
201
|
+
)
|
|
178
202
|
end
|
|
179
203
|
end
|
|
180
204
|
end
|
|
@@ -237,19 +237,11 @@ module Bible270
|
|
|
237
237
|
|
|
238
238
|
# ---- mentions ----------------------------------------------------------
|
|
239
239
|
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
Mentions.preferred_handle(first, last_name, ambiguous: self.class.shared_first_name?(first, self))
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
def self.shared_first_name?(first, except = nil)
|
|
250
|
-
scope = where('LOWER(first_name) = ?', first.to_s.downcase)
|
|
251
|
-
scope = scope.where.not(id: except.id) if except&.id
|
|
252
|
-
scope.exists?
|
|
240
|
+
# Older copied databases may briefly lack the preference column while their
|
|
241
|
+
# migrations are being reconciled. Preserve the historical opted-in behavior
|
|
242
|
+
# until the column is available rather than breaking reflection delivery.
|
|
243
|
+
def wants_comment_notifications?
|
|
244
|
+
!has_attribute?(:notify_on_mention) || self[:notify_on_mention] != false
|
|
253
245
|
end
|
|
254
246
|
|
|
255
247
|
# The readers a piece of text mentions. A handle matching more than one reader
|
|
@@ -578,19 +570,6 @@ module Bible270
|
|
|
578
570
|
Plan.end_date_for(effective_start_date)
|
|
579
571
|
end
|
|
580
572
|
|
|
581
|
-
def scheduled_days_this_week(on: Bible270.today)
|
|
582
|
-
return [] unless dated?
|
|
583
|
-
|
|
584
|
-
date = Plan.to_date(on)
|
|
585
|
-
return [] unless date
|
|
586
|
-
|
|
587
|
-
week_start = date - ((date.wday + 6) % 7)
|
|
588
|
-
(week_start..date).filter_map do |scheduled_date|
|
|
589
|
-
day = Plan.day_for(scheduled_date, effective_start_date, clamp: false)
|
|
590
|
-
day if Plan.valid_day?(day)
|
|
591
|
-
end
|
|
592
|
-
end
|
|
593
|
-
|
|
594
573
|
def date_for_day(day)
|
|
595
574
|
Plan.date_for(day, effective_start_date)
|
|
596
575
|
end
|
|
@@ -613,9 +592,9 @@ module Bible270
|
|
|
613
592
|
started_on
|
|
614
593
|
end
|
|
615
594
|
|
|
616
|
-
# Set
|
|
617
|
-
#
|
|
618
|
-
#
|
|
595
|
+
# Set an individual start date for administrative use. This remains ungated
|
|
596
|
+
# so an administrator can prepare or preserve a personal calendar even when
|
|
597
|
+
# config.allow_reader_start_date currently makes the community date authoritative.
|
|
619
598
|
# Returns false only when the value isn't a date.
|
|
620
599
|
def set_start_date!(value)
|
|
621
600
|
date = Plan.to_date(value)
|
|
@@ -624,17 +603,6 @@ module Bible270
|
|
|
624
603
|
update!(started_on: date)
|
|
625
604
|
end
|
|
626
605
|
|
|
627
|
-
# The reader-facing version, which does respect that permission.
|
|
628
|
-
def update_start_date!(value)
|
|
629
|
-
return false unless Bible270.config.allow_reader_start_date
|
|
630
|
-
|
|
631
|
-
set_start_date!(value)
|
|
632
|
-
end
|
|
633
|
-
|
|
634
|
-
def clear_start_date!
|
|
635
|
-
update!(started_on: nil)
|
|
636
|
-
end
|
|
637
|
-
|
|
638
606
|
# Called when a reader first participates. Only stamps a personal start date
|
|
639
607
|
# when per-reader dates are enabled and a shared date isn't already in force.
|
|
640
608
|
def ensure_started!
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<% content_for :title, "Reflections — #{Bible270.config.app_name}" %>
|
|
2
|
-
<% content_for :hide_day_index, true %><%# this page has its own, filtered grid %>
|
|
3
2
|
<% browse_params = { day: @reflection_day, reader_id: @reflection_reader_id,
|
|
4
3
|
since: @reflections_since_param }.compact %>
|
|
5
4
|
|
|
@@ -8,26 +7,8 @@
|
|
|
8
7
|
<%= render "bible270/shared/interaction_status" %>
|
|
9
8
|
|
|
10
9
|
<% if @days_with_reflections.any? %>
|
|
11
|
-
<div class="b270-panel">
|
|
12
|
-
<h2 class="b270-subhead">Days with reflections</h2>
|
|
13
|
-
<p class="b270-hint" style="margin-top:0">
|
|
14
|
-
<%= @days_with_reflections.size %>
|
|
15
|
-
of <%= Bible270::Plan::DAYS %> days have been written about.
|
|
16
|
-
</p>
|
|
17
|
-
|
|
18
|
-
<%# Only the days that have something on them, so the grid is a map of the
|
|
19
|
-
conversation rather than of the plan. Squares keep their usual meaning:
|
|
20
|
-
the fill is reading progress, and hovering shows that day's readings. %>
|
|
21
|
-
<div class="b270-grid">
|
|
22
|
-
<% @days_with_reflections.each do |day| %>
|
|
23
|
-
<%= b270_day_cell(day, reader: current_reader) %>
|
|
24
|
-
<% end %>
|
|
25
|
-
</div>
|
|
26
|
-
</div>
|
|
27
|
-
|
|
28
10
|
<div class="b270-reflection-tools">
|
|
29
11
|
<div>
|
|
30
|
-
<h2 class="b270-section-h">Conversations</h2>
|
|
31
12
|
<p class="b270-reflection-count"><%= pluralize(@reflection_total, "reflection") %></p>
|
|
32
13
|
</div>
|
|
33
14
|
<%= form_with url: reflections_path, method: :get, class: "b270-reflection-filters" do %>
|
|
@@ -83,10 +64,6 @@
|
|
|
83
64
|
<%= link_to "Show all reflections", reflections_path(since: @reflections_since_param) %>
|
|
84
65
|
</div>
|
|
85
66
|
<% end %>
|
|
86
|
-
|
|
87
|
-
<p style="margin-top:18px">
|
|
88
|
-
<%= link_to "Browse the whole plan →", root_path %>
|
|
89
|
-
</p>
|
|
90
67
|
<% else %>
|
|
91
68
|
<div class="b270-panel">
|
|
92
69
|
<p class="b270-signedout" style="margin:0">
|
|
@@ -99,5 +76,4 @@
|
|
|
99
76
|
</p>
|
|
100
77
|
</div>
|
|
101
78
|
<% end %>
|
|
102
|
-
|
|
103
79
|
<%= render "bible270/shared/interaction_ui" %>
|
|
@@ -6,7 +6,5 @@
|
|
|
6
6
|
<%= link_to b270_avatar(reader), reader_path(reader), title: reader.display_name %>
|
|
7
7
|
<% end %>
|
|
8
8
|
<% if completers.size > 12 %><span><%= completers.size - 12 %> more</span><% end %>
|
|
9
|
-
<% else %>
|
|
10
|
-
<span>No one has finished this day yet — be the first.</span>
|
|
11
9
|
<% end %>
|
|
12
10
|
</div>
|