bible270 1.1.4 → 1.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1d5f984616aed86326fe24bdb808b539bbd6cc8fe28e61f58838e6fba6573ca
4
- data.tar.gz: 661d698c8eaacee191eb27eda7fa75bc83cf0df94ef5dfc85ae18974a26ead0d
3
+ metadata.gz: 323ea67db4332521a07129d64ca3b1e4f6405ba0d87352f14db5dcbcbbe675c7
4
+ data.tar.gz: b60e969effcc93b68b95c29e8054aae754a41210641146ce526eefd6d28e0229
5
5
  SHA512:
6
- metadata.gz: 60ccdd3d39fadb44e75c0eef05e18bc304032636e440f6d8d9b3b0a941b6c1a2a73bdc5231ae353de27d8b5aa8923f028e4dbd16d259ea9079bce20bf6b7bd9b
7
- data.tar.gz: 0c41c6c130e93d42bc1d45dd1757c2f2216a378bf77cf5118dce25f28e72fceeb813f5064bb5ec93397afc077091f1a649b068b69f0616c964cfb2f700ab5cce
6
+ metadata.gz: 799b578048236367edd22c1b75e3c62d9c2b24a8dce6d28174695954a465d97ddffb75164d790b207d7aaca3d225a2a8f1f00e6e2d932cfd90b9824277c53f7d
7
+ data.tar.gz: 6c7c718e5e2c3b434090eb38481371ec0a63399769d0d54ded50fbee47de914bcbd5fe49605169b7a81b012f979826333d28b2aa349e8392955bd9567187a49c
data/CHANGELOG.md CHANGED
@@ -4,7 +4,38 @@ All notable changes to bible270. Format follows [Keep a Changelog](https://keepa
4
4
 
5
5
  ## Unreleased Changes
6
6
 
7
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.1.3...main)**
7
+ ### Changed
8
+
9
+ - show administrators a quiet, linked completed-day count on Community reader rows while keeping progress hidden—and its aggregate query skipped—for ordinary readers and visitors
10
+
11
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.1.5...main)**
12
+
13
+ ## [1.1.5] - 2026-08-25
14
+
15
+ ### Changed
16
+
17
+ - keep Bible270 open while using one Scripture tab for Bible Gateway and Blue Letter Bible: desktop browsers reuse the existing tab, while iOS Safari replaces its background tab so each click reliably switches to the reading
18
+ - centralize daily-reading and Reflections Scripture anchors in one passage-link helper with secure no-JavaScript fallback behavior
19
+
20
+ ### Removed
21
+
22
+ - remove the unused `passage_url_builder` configuration hook; Bible Gateway and Blue Letter Bible are now the intentionally fixed passage providers
23
+
24
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.1.4...v1.1.5)**
25
+
26
+ ## [1.1.4] - 2026-08-24
27
+
28
+ ### Added
29
+
30
+ - let administrators persistently override the current run start date, see which readers are affected, and reset to the configured date without changing check-offs or reflections
31
+
32
+ ### Changed
33
+
34
+ - link reader avatars in Admin to the same reader-management pages as their names
35
+ - show a signed-in reader's effective plan start date beside Home progress without repeating it on My Progress
36
+ - increase the default parallel test workers to six while retaining `PARALLEL_WORKERS` for constrained systems
37
+
38
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.1.3...v1.1.4)**
8
39
 
9
40
  ## [1.1.3] - 2026-08-23
10
41
 
@@ -521,6 +552,8 @@ This is the initial public release.
521
552
 
522
553
  * 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.
523
554
 
555
+ [1.1.5]: https://github.com/avonderluft/bible270/releases/tag/v1.1.5
556
+ [1.1.4]: https://github.com/avonderluft/bible270/releases/tag/v1.1.4
524
557
  [1.1.3]: https://github.com/avonderluft/bible270/releases/tag/v1.1.3
525
558
  [1.1.2]: https://github.com/avonderluft/bible270/releases/tag/v1.1.2
526
559
  [1.1.1]: https://github.com/avonderluft/bible270/releases/tag/v1.1.1
data/README.md CHANGED
@@ -415,9 +415,6 @@ Bible270.configure do |config|
415
415
  config.after_sign_out_path = nil
416
416
 
417
417
  config.bible_version = "NKJV"
418
- config.passage_url_builder = ->(reference, version) {
419
- "https://www.biblegateway.com/passage/?search=#{URI.encode_www_form_component(reference)}&version=#{version}"
420
- }
421
418
  end
422
419
  ```
423
420
  ### Footer
@@ -460,7 +457,9 @@ It's emitted by the engine's layout, so if you point `config.layout` at your own
460
457
  References link to Bible Gateway by default. Readers choose Bible Gateway or Blue Letter Bible from
461
458
  their profile, and an admin can set the same source for any reader. The `HEB/GRK` option always uses
462
459
  Blue Letter Bible: WLC for Old Testament, Psalms, and Proverbs readings, and mGNT for New Testament
463
- readings. Set `passage_url_builder` to use another default reader, or a reader you host yourself.
460
+ readings. Scripture links share one dedicated browser tab so moving among the day's passages does not
461
+ leave a trail of open tabs; Ctrl-click, Cmd-click, middle-click, and the browser's context menu still
462
+ open additional tabs when a reader wants to compare passages.
464
463
 
465
464
  ## With ComfortableMediaSurfer
466
465
 
@@ -62,9 +62,7 @@ module Bible270
62
62
  @shared_calendar_readers = @readers.size
63
63
  @personal_calendar_readers = 0
64
64
  end
65
- counts = Checkoff.group(:reader_id, :day).count
66
- @days_completed = Hash.new(0)
67
- counts.each { |(rid, day), n| @days_completed[rid] += 1 if n >= Plan.total_parts(day) }
65
+ @days_completed = Reader.completed_days_by_id
68
66
  @reachable = Reader.where.not(email: [nil, '']).count
69
67
  @last_broadcast_at = parsed_time(Setting.read(LAST_BROADCAST_AT))
70
68
  @last_broadcast_subject = Setting.read(LAST_BROADCAST_SUBJECT)
@@ -6,6 +6,8 @@ module Bible270
6
6
  # Community is a directory of fellow readers, not a leaderboard. Keep the
7
7
  # order predictable without implying that progress determines standing.
8
8
  @readers = Reader.all.to_a.sort_by(&:sort_name)
9
+ @show_admin_progress = Bible270.config.admin?(current_reader)
10
+ @days_completed = Reader.completed_days_by_id if @show_admin_progress
9
11
  end
10
12
 
11
13
  # The reader's own progress. Deliberately open to visitors: the panel shows an
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Bible270
4
4
  module PlanHelper
5
+ PASSAGE_LINK_TARGET = 'bible270_scripture'
6
+
5
7
  def b270_track(track)
6
8
  Plan::TRACKS.fetch(track.to_s)
7
9
  end
@@ -117,7 +119,18 @@ module Bible270
117
119
  return Translations.passage_url(reference, selected_version, blue_letter: true)
118
120
  end
119
121
 
120
- Bible270.config.passage_url_builder.call(reference, selected_version)
122
+ Translations.passage_url(reference, selected_version)
123
+ end
124
+
125
+ # The named target and noopener are the secure no-JavaScript fallback.
126
+ # Interaction UI retains one opener-backed tab handle on desktop. iOS Safari
127
+ # cannot foreground a reused background tab, so it closes and replaces that
128
+ # script-opened tab on each click. The two fixed providers are intentionally
129
+ # trusted for this enhanced path.
130
+ def b270_passage_link(reference, **options)
131
+ data = options.fetch(:data, {}).merge(b270_passage_link: true)
132
+ options.merge!(data: data, target: PASSAGE_LINK_TARGET, rel: 'noopener')
133
+ link_to reference, b270_passage_url(reference), options
121
134
  end
122
135
 
123
136
  # Small grey "(NKJV)" after a reading, so it's clear which translation the
@@ -224,8 +237,7 @@ module Bible270
224
237
  reference = readings[track]
225
238
  next if reference.blank?
226
239
 
227
- link_to reference, b270_passage_url(reference),
228
- class: 'b270-reflink', target: '_blank', rel: 'noopener'
240
+ b270_passage_link reference, class: 'b270-reflink'
229
241
  end
230
242
 
231
243
  safe_join(links, ' · ')
@@ -55,6 +55,12 @@ module Bible270
55
55
  value.to_s.match?(DAILY_REMINDER_TIME_FORMAT)
56
56
  end
57
57
 
58
+ def self.completed_days_by_id
59
+ Checkoff.group(:reader_id, :day).count.each_with_object(Hash.new(0)) do |((reader_id, day), done), totals|
60
+ totals[reader_id] += 1 if done >= Plan.total_parts(day)
61
+ end
62
+ end
63
+
58
64
  def self.reflections_seen_column?
59
65
  column_names.include?(REFLECTIONS_SEEN_COLUMN)
60
66
  rescue ActiveRecord::StatementInvalid
@@ -27,13 +27,13 @@
27
27
  b270_success: (part_done ? "Reading marked unread." : "Reading marked read."),
28
28
  b270_failure: "Couldn’t update that reading. Try again." } },
29
29
  aria: { label: (part_done ? "Mark #{part_ref} unread" : "Mark #{part_ref} read") } do %>
30
- <span class="b270-check"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg></span>
30
+ <span class="b270-check"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12" /></svg></span>
31
31
  <% end %>
32
32
  <% else %>
33
- <span class="b270-check"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg></span>
33
+ <span class="b270-check"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12" /></svg></span>
34
34
  <% end %>
35
35
  <span class="b270-ref">
36
- <%= link_to part_ref, b270_passage_url(part_ref), target: "_blank", rel: "noopener" %>
36
+ <%= b270_passage_link part_ref %>
37
37
  </span>
38
38
  </li>
39
39
  <% end %>
@@ -51,14 +51,14 @@
51
51
  b270_success: (done ? "Reading marked unread." : "Reading marked read."),
52
52
  b270_failure: "Couldn’t update that reading. Try again." } },
53
53
  aria: { label: (done ? "Mark #{t[:label]} unread" : "Mark #{t[:label]} read") } do %>
54
- <span class="b270-check"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg></span>
54
+ <span class="b270-check"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12" /></svg></span>
55
55
  <% end %>
56
56
  <% else %>
57
- <span class="b270-check"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg></span>
57
+ <span class="b270-check"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12" /></svg></span>
58
58
  <% end %>
59
59
  <div class="b270-rbody">
60
60
  <div class="b270-cat"><%= t[:label] %></div>
61
- <div class="b270-ref"><%= link_to ref, b270_passage_url(ref), target: "_blank", rel: "noopener" %> <%= b270_version_tag(track: track) %></div>
61
+ <div class="b270-ref"><%= b270_passage_link ref %> <%= b270_version_tag(track: track) %></div>
62
62
  </div>
63
63
  <% end %>
64
64
  </div>
@@ -10,7 +10,12 @@
10
10
  <%= link_to b270_avatar(r, size: 72), reader_path(r), "aria-label": r.display_name %>
11
11
  <div>
12
12
  <div class="name"><%= link_to r.display_name, reader_path(r), style: "text-decoration:none" %></div>
13
- <div class="sub">joined <%= Bible270.local_time(r.created_at).strftime("%b %-d %Y") %></div>
13
+ <div class="sub">
14
+ joined <%= Bible270.local_time(r.created_at).strftime("%b %-d %Y") %><% if @show_admin_progress %>
15
+ · <%= link_to "#{@days_completed[r.id]} of #{Bible270::Plan::DAYS} days complete",
16
+ admin_reader_path(r), class: "b270-admin-progress" %>
17
+ <% end %>
18
+ </div>
14
19
  </div>
15
20
  </div>
16
21
  <% end %>
@@ -30,6 +30,35 @@
30
30
  document.querySelectorAll("form[data-b270-submit='true']").forEach(resetForm);
31
31
  }
32
32
 
33
+ let scriptureWindow = null;
34
+ const replacesBackgroundTabs = /iPad|iPhone|iPod/.test(navigator.userAgent) ||
35
+ (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
36
+
37
+ document.addEventListener("click", (event) => {
38
+ const target = event.target;
39
+ const link = target.closest && target.closest("a[data-b270-passage-link='true']");
40
+ if (!link || event.defaultPrevented || event.button !== 0) return;
41
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
42
+
43
+ try {
44
+ if (replacesBackgroundTabs && scriptureWindow && !scriptureWindow.closed) {
45
+ scriptureWindow.close();
46
+ scriptureWindow = null;
47
+ }
48
+
49
+ if (!scriptureWindow || scriptureWindow.closed) {
50
+ scriptureWindow = window.open(link.href, "_blank");
51
+ if (!scriptureWindow) return;
52
+ } else {
53
+ scriptureWindow.location = link.href;
54
+ }
55
+ event.preventDefault();
56
+ scriptureWindow.focus();
57
+ } catch (_error) {
58
+ scriptureWindow = null;
59
+ }
60
+ });
61
+
33
62
  document.addEventListener("submit", (event) => {
34
63
  const form = event.target.closest("form[data-b270-submit='true']");
35
64
  if (!form) return;
@@ -259,6 +259,8 @@
259
259
  .b270-li+.b270-li{border-top:1px solid var(--line-soft)}
260
260
  .b270-li .name{font-weight:600}
261
261
  .b270-li .sub{font-size:12.5px;color:var(--faint)}
262
+ .b270-li .b270-admin-progress{text-decoration:none}
263
+ .b270-li .b270-admin-progress:hover,.b270-li .b270-admin-progress:focus-visible{color:var(--gold)}
262
264
  .b270-li .stat{margin-left:auto;text-align:right}
263
265
  .b270-li .stat .big{font-family:"Spectral",serif;font-size:20px;font-weight:600}
264
266
 
@@ -271,7 +271,6 @@ module Bible270
271
271
  PROVIDER_LABELS[key.to_sym] || key.to_s.tr('_', ' ').split.map(&:capitalize).join(' ')
272
272
  end
273
273
 
274
- # Default Bible translation and a builder for external passage links.
275
274
  # Which translations readers may choose from. Defaults to all of
276
275
  # Bible270::Translations::VERSIONS; narrow it to offer fewer.
277
276
  attr_accessor :bible_versions
@@ -297,7 +296,7 @@ module Bible270
297
296
  # application owns scheduling the bible270:reminders:send task.
298
297
  attr_accessor :daily_reminders
299
298
 
300
- 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, :header_mark, :remember_for, :require_sign_in_to_participate
299
+ attr_accessor :after_sign_out_path, :email_sign_in_max_per_window, :tagline, :footer_html, :footer, :favicon, :avatar_max_bytes, :chapter_breaks, :admin_emails, :admin_resolver, :bible_version, :header_mark, :remember_for, :require_sign_in_to_participate
301
300
 
302
301
  # Public labels.
303
302
  attr_accessor :app_name
@@ -355,7 +354,6 @@ module Bible270
355
354
  @after_sign_out_path = nil
356
355
  @bible_version = 'NKJV'
357
356
  @bible_versions = Translations::VERSIONS.keys
358
- @passage_url_builder = ->(reference, version) { Translations.passage_url(reference, version) }
359
357
  @app_name = 'Daily Bread'
360
358
  @tagline = 'Journeying through Scripture in 9 months'
361
359
  @time_zone = nil
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bible270
4
- VERSION = '1.1.4'
4
+ VERSION = '1.1.6'
5
5
  end
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: 1.1.4
4
+ version: 1.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew vonderLuft