bible270 1.6.2 → 1.7.1
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 +33 -2
- data/README.md +16 -1
- data/app/controllers/bible270/admin_controller.rb +11 -2
- data/app/helpers/bible270/plan_helper.rb +10 -0
- data/app/models/bible270/checkoff.rb +27 -0
- data/app/views/bible270/admin/index.html.erb +7 -0
- data/app/views/bible270/shared/_markdown_toolbar.html.erb +2 -0
- data/app/views/bible270/shared/_scripture_popups.html.erb +131 -0
- data/app/views/layouts/bible270/application.html.erb +1 -0
- data/lib/bible270/reader_progress_report.rb +17 -3
- data/lib/bible270/translations.rb +15 -0
- data/lib/bible270/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fcb72fefa9ed7c0dc470ac07e7cdd1a143e8376e2d08be4adcdcc18d9633a286
|
|
4
|
+
data.tar.gz: 651c9fdc0d0332d79e2cdc72f96ccdb0a1a8e22b6fafec398d62bc4f84f8c7b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 326a54581672c51728662557d7fc7178ed1b4ce44c03e480ae09829790c8f457274504121d30ae1d816fb41cd7f00acbdaadf649d75e3e7a6283626556cec57a
|
|
7
|
+
data.tar.gz: 24335e6617785300b54733aff19f217bc1c85365282ff4798a6a5c5d04636f910e2d2d56f2e8895d2961c30c6e94873a64008de6ae7d7e27a80778312af62a60
|
data/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,36 @@ All notable changes to bible270. Format follows [Keep a Changelog](https://keepa
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- show Blue Letter Bible Scripture popups when readers hover over references in rendered reflections, replies, and Markdown previews; popups follow each reader's selected translation, handle original-language choices by testament, and rescan content added by Turbo without forcing a page reload
|
|
10
|
+
- let administrators sort the reader list and Reader Stats report by most or least recent reading activity, while keeping readers with no activity at the end
|
|
11
|
+
|
|
12
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.6.2...main)**
|
|
13
|
+
|
|
14
|
+
## [1.6.2] - 2026-09-14
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- keep ordered and unordered Markdown lists compact when writers leave blank lines between items, removing visible indentation and extra line-break artifacts from previews and rendered reflections
|
|
19
|
+
|
|
20
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.6.1...v1.6.2)**
|
|
21
|
+
|
|
22
|
+
## [1.6.1] - 2026-09-14
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- let administrators edit any reflection or reply inline while preserving its recorded author and retaining the existing restrictions for ordinary readers
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- select only the `list item` placeholder when inserting a list, and continue unordered or incrementing ordered list markers when the writer presses Enter
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- render pasted `http://` and `https://` URLs as safe links in Markdown previews and displayed reflections without nesting explicit Markdown links or swallowing trailing punctuation
|
|
35
|
+
|
|
36
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.6.0...v1.6.1)**
|
|
8
37
|
|
|
9
38
|
## [1.6.0] - 2026-09-14
|
|
10
39
|
|
|
@@ -701,7 +730,9 @@ This is the initial public release.
|
|
|
701
730
|
|
|
702
731
|
* 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.
|
|
703
732
|
|
|
704
|
-
[Unreleased]: https://github.com/avonderluft/bible270/compare/v1.6.
|
|
733
|
+
[Unreleased]: https://github.com/avonderluft/bible270/compare/v1.6.2...HEAD
|
|
734
|
+
[1.6.2]: https://github.com/avonderluft/bible270/releases/tag/v1.6.2
|
|
735
|
+
[1.6.1]: https://github.com/avonderluft/bible270/releases/tag/v1.6.1
|
|
705
736
|
[1.6.0]: https://github.com/avonderluft/bible270/releases/tag/v1.6.0
|
|
706
737
|
[1.5.1]: https://github.com/avonderluft/bible270/releases/tag/v1.5.1
|
|
707
738
|
[1.5.0]: https://github.com/avonderluft/bible270/releases/tag/v1.5.0
|
data/README.md
CHANGED
|
@@ -23,7 +23,7 @@ The schedule is pure deterministic Ruby (`Bible270::Plan`) — none of it is sto
|
|
|
23
23
|
## Requirements
|
|
24
24
|
|
|
25
25
|
- **Ruby** >= 3.2. Plain Ruby, no C extensions. Runs on 4.0.6 — it avoids everything 4.0 dropped or unbundled (notably `cgi`; URL escaping uses `URI.encode_www_form_component`).
|
|
26
|
-
- **Rails** >= 7.
|
|
26
|
+
- **Rails** >= 7.2. On Ruby 4.0 your host app is the real constraint, not this engine — check your lockfile with RailsBump first.
|
|
27
27
|
- A host app, and Turbo if you want check-offs without a page reload.
|
|
28
28
|
|
|
29
29
|
Mutation controls expose pending, success, and retry feedback when JavaScript runs, while ordinary HTML
|
|
@@ -507,6 +507,21 @@ dedicated browser tab so moving among the day's passages does not
|
|
|
507
507
|
leave a trail of open tabs; Ctrl-click, Cmd-click, middle-click, and the browser's context menu still
|
|
508
508
|
open additional tabs when a reader wants to compare passages.
|
|
509
509
|
|
|
510
|
+
Rendered reflections and replies also use [Blue Letter Bible ScriptTagger](https://www.blueletterbible.org/webtools/BLB_ScriptTagger.cfm)
|
|
511
|
+
to show Scripture text when a reader hovers over a reference. The popup follows that reader's selected
|
|
512
|
+
translation (or the site default for a visitor); `HEB/GRK` and `ALLGRK` choose the appropriate original
|
|
513
|
+
language separately for Old and New Testament references. The engine layout loads this automatically.
|
|
514
|
+
If `config.layout` points to a host layout, render the integration once near the end of its `<body>`:
|
|
515
|
+
|
|
516
|
+
```erb
|
|
517
|
+
<%= render "bible270/shared/scripture_popups" %>
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
ScriptTagger loads JavaScript and CSS from Blue Letter Bible at runtime. A host with Content Security
|
|
521
|
+
Policy enabled must allow `https://www.blueletterbible.org` in its existing `script-src` and `style-src`
|
|
522
|
+
directives. This sends the visitor's IP address and normal request metadata to Blue Letter Bible when an
|
|
523
|
+
engine page loads; hovering a reference also sends that reference and selected translation to BLB.
|
|
524
|
+
|
|
510
525
|
## With ComfortableMediaSurfer
|
|
511
526
|
|
|
512
527
|
The CMS serves your content; this is a separate mounted app. Either **just link to it** (mount, add a nav link, done — the engine renders its own themed pages), or **match your chrome** by setting `config.parent_controller = "::ApplicationController"` and `config.layout = "layouts/application"` so it sits inside your header and footer. All engine CSS is prefixed `b270-`, so nothing collides.
|
|
@@ -15,7 +15,9 @@ module Bible270
|
|
|
15
15
|
'first_name' => 'First Name',
|
|
16
16
|
'last_name' => 'Last Name',
|
|
17
17
|
'most_completed' => 'Most days completed',
|
|
18
|
-
'least_completed' => 'Least days completed'
|
|
18
|
+
'least_completed' => 'Least days completed',
|
|
19
|
+
'most_recent_activity' => 'Most recent activity',
|
|
20
|
+
'least_recent_activity' => 'Least recent activity'
|
|
19
21
|
}.freeze
|
|
20
22
|
|
|
21
23
|
before_action :require_admin!
|
|
@@ -65,7 +67,14 @@ module Bible270
|
|
|
65
67
|
@days_completed = Reader.completed_days_by_id
|
|
66
68
|
@reader_sort_options = READER_SORT_OPTIONS
|
|
67
69
|
@reader_sort = READER_SORT_OPTIONS.key?(params[:sort]) ? params[:sort] : 'first_name'
|
|
68
|
-
|
|
70
|
+
readers = Reader.all.load
|
|
71
|
+
@recent_reader_activities = Checkoff.recent_activity_by_reader(readers.map(&:id))
|
|
72
|
+
@reader_report = ReaderProgressReport.new(
|
|
73
|
+
readers: readers,
|
|
74
|
+
completed_days: @days_completed,
|
|
75
|
+
recent_activities: @recent_reader_activities,
|
|
76
|
+
sort: @reader_sort
|
|
77
|
+
)
|
|
69
78
|
@readers = @reader_report.sorted_readers
|
|
70
79
|
@run_start_date = Setting.run_start_date
|
|
71
80
|
@configured_start_date = Bible270.config.start_date
|
|
@@ -181,6 +181,16 @@ module Bible270
|
|
|
181
181
|
local&.strftime('%b %-d, %Y at %-I:%M %p')
|
|
182
182
|
end
|
|
183
183
|
|
|
184
|
+
def b270_reader_activity_description(activity)
|
|
185
|
+
if activity.day_completed?
|
|
186
|
+
readings = Plan.readings_for(activity.day).values.compact.join(', ')
|
|
187
|
+
"completed Day #{activity.day} (#{readings})"
|
|
188
|
+
else
|
|
189
|
+
reference = Plan.parts_for(activity.day, activity.track).fetch(activity.part)
|
|
190
|
+
"checked off “#{reference}” for Day #{activity.day}"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
184
194
|
# Writers may revise their own words; administrators may also correct or
|
|
185
195
|
# moderate any reflection without changing its recorded author.
|
|
186
196
|
def b270_can_edit?(comment)
|
|
@@ -5,6 +5,8 @@ module Bible270
|
|
|
5
5
|
class Checkoff < ApplicationRecord
|
|
6
6
|
self.table_name = 'bible270_checkoffs'
|
|
7
7
|
|
|
8
|
+
RecentActivity = Struct.new(:occurred_at, :day, :track, :part, :day_completed?, keyword_init: true)
|
|
9
|
+
|
|
8
10
|
belongs_to :reader, class_name: 'Bible270::Reader'
|
|
9
11
|
|
|
10
12
|
validates :day, inclusion: { in: 1..Plan::DAYS }
|
|
@@ -14,6 +16,31 @@ module Bible270
|
|
|
14
16
|
validate :track_present_on_day
|
|
15
17
|
validate :part_within_reading
|
|
16
18
|
|
|
19
|
+
# The most recently created check-off still present for each reader. This uses
|
|
20
|
+
# three bulk queries regardless of the number of readers displayed in Admin.
|
|
21
|
+
def self.recent_activity_by_reader(reader_ids)
|
|
22
|
+
latest_ids = where(reader_id: reader_ids).group(:reader_id).maximum(:id).values
|
|
23
|
+
latest_checkoffs = where(id: latest_ids).index_by(&:reader_id)
|
|
24
|
+
day_counts = recent_activity_day_counts(latest_checkoffs.values)
|
|
25
|
+
|
|
26
|
+
latest_checkoffs.transform_values do |checkoff|
|
|
27
|
+
RecentActivity.new(
|
|
28
|
+
occurred_at: checkoff.created_at,
|
|
29
|
+
day: checkoff.day,
|
|
30
|
+
track: checkoff.track,
|
|
31
|
+
part: checkoff.part,
|
|
32
|
+
day_completed?: day_counts.fetch([checkoff.reader_id, checkoff.day], 0) >= Plan.total_parts(checkoff.day)
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.recent_activity_day_counts(checkoffs)
|
|
38
|
+
return {} if checkoffs.empty?
|
|
39
|
+
|
|
40
|
+
where(reader_id: checkoffs.map(&:reader_id), day: checkoffs.map(&:day)).group(:reader_id, :day).count
|
|
41
|
+
end
|
|
42
|
+
private_class_method :recent_activity_day_counts
|
|
43
|
+
|
|
17
44
|
private
|
|
18
45
|
|
|
19
46
|
def track_present_on_day
|
|
@@ -89,6 +89,13 @@
|
|
|
89
89
|
· <%= @days_completed[reader.id] %>/<%= Bible270::Plan::DAYS %> days
|
|
90
90
|
<% if reader.dated? %>· started <%= reader.effective_start_date.strftime('%b %-d, %Y') %><% end %>
|
|
91
91
|
</div>
|
|
92
|
+
<div class="sub b270-reader-activity">
|
|
93
|
+
<% if (activity = @recent_reader_activities[reader.id]) %>
|
|
94
|
+
<%= b270_date(activity.occurred_at) %>: <%= b270_reader_activity_description(activity) %>
|
|
95
|
+
<% else %>
|
|
96
|
+
No reading activity yet
|
|
97
|
+
<% end %>
|
|
98
|
+
</div>
|
|
92
99
|
</div>
|
|
93
100
|
<div class="stat">
|
|
94
101
|
<span class="big"><%= (@days_completed[reader.id].to_f / Bible270::Plan::DAYS * 100).round %>%</span>
|
|
@@ -139,7 +139,9 @@
|
|
|
139
139
|
if (!response.ok) throw new Error("Preview unavailable");
|
|
140
140
|
return response.text();
|
|
141
141
|
}).then((html) => {
|
|
142
|
+
preview.removeAttribute("data-b270-blb-tagged");
|
|
142
143
|
preview.innerHTML = html;
|
|
144
|
+
window.Bible270ScripturePopups?.scan(preview);
|
|
143
145
|
}).catch((error) => {
|
|
144
146
|
if (error.name !== "AbortError") preview.textContent = "Preview unavailable. Return to Write and try again.";
|
|
145
147
|
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<% popup_translations = Bible270::Translations.blue_letter_tagger_translations(b270_bible_version) %>
|
|
2
|
+
<script src="https://www.blueletterbible.org/assets/scripts/blbToolTip/BLB_ScriptTagger-min.js"
|
|
3
|
+
type="text/javascript" nonce="<%= content_security_policy_nonce %>" data-turbo-eval="false"></script>
|
|
4
|
+
<script nonce="<%= content_security_policy_nonce %>"
|
|
5
|
+
data-old-testament-translation="<%= popup_translations.fetch(:old_testament) %>"
|
|
6
|
+
data-new-testament-translation="<%= popup_translations.fetch(:new_testament) %>">
|
|
7
|
+
(() => {
|
|
8
|
+
const settings = document.currentScript.dataset;
|
|
9
|
+
const requestedTranslations = {
|
|
10
|
+
oldTestament: settings.oldTestamentTranslation,
|
|
11
|
+
newTestament: settings.newTestamentTranslation
|
|
12
|
+
};
|
|
13
|
+
if (window.Bible270ScripturePopups) {
|
|
14
|
+
window.Bible270ScripturePopups.configure(requestedTranslations);
|
|
15
|
+
window.Bible270ScripturePopups.scan();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const tagger = window.BLB && window.BLB.Tagger;
|
|
20
|
+
if (!tagger) return;
|
|
21
|
+
|
|
22
|
+
const translations = { ...requestedTranslations };
|
|
23
|
+
const selector = ".b270-cbody";
|
|
24
|
+
const pending = new Set();
|
|
25
|
+
let retry;
|
|
26
|
+
|
|
27
|
+
function configure(requested) {
|
|
28
|
+
translations.oldTestament = requested.oldTestament;
|
|
29
|
+
translations.newTestament = requested.newTestament;
|
|
30
|
+
tagger.Translation = translations.oldTestament;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
configure(requestedTranslations);
|
|
34
|
+
tagger.HyperLinks = "hover";
|
|
35
|
+
tagger.HideTanslationAbbrev = false;
|
|
36
|
+
tagger.HideTranslationAbbrev = false;
|
|
37
|
+
tagger.TargetNewWindow = true;
|
|
38
|
+
tagger.DarkTheme = false;
|
|
39
|
+
tagger.Style = "par";
|
|
40
|
+
tagger.NoSearchTagNames = "h1 h2 h3 h4";
|
|
41
|
+
tagger.NoSearchClassNames = "noTag doNotTag b270-ref b270-reflink b270-blb-existing-link";
|
|
42
|
+
|
|
43
|
+
function ready() {
|
|
44
|
+
return tagger.RegExSet && tagger.NoSearchTags && tagger.NoSearchClasses &&
|
|
45
|
+
window.YAHOO && window.YAHOO.util && window.YAHOO.util.Dom && window.BLB.BookNames;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function explicitTranslation(text, translation) {
|
|
49
|
+
const escaped = translation.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
50
|
+
return new RegExp(`(?:^|[\\s,(])${escaped}\\)?$`, "i").test(text);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function newTestamentReference(reference) {
|
|
54
|
+
const normalized = tagger.normalizeReference(reference);
|
|
55
|
+
const book = normalized.split(" ", 1)[0].replaceAll("_", " ").toLowerCase();
|
|
56
|
+
const canonical = window.BLB.BookNames[book];
|
|
57
|
+
return Number(window.BLB.BookOrder[canonical]) >= 40;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const originalInsertRefNode = tagger.insertRefNode;
|
|
61
|
+
tagger.insertRefNode = function(reference, text, translation, node) {
|
|
62
|
+
let popupTranslation = translation;
|
|
63
|
+
const testamentSpecific = translations.oldTestament !== translations.newTestament;
|
|
64
|
+
if (testamentSpecific && !explicitTranslation(text, translation)) {
|
|
65
|
+
popupTranslation = newTestamentReference(reference) ?
|
|
66
|
+
translations.newTestament : translations.oldTestament;
|
|
67
|
+
}
|
|
68
|
+
return originalInsertRefNode.call(tagger, reference, text, popupTranslation, node);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const originalWalkDomTree = tagger.walkDomTree;
|
|
72
|
+
tagger.walkDomTree = function(root, linkedAnchor, depth) {
|
|
73
|
+
if (root === document.body) {
|
|
74
|
+
window.Bible270ScripturePopups.scan(root);
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
return originalWalkDomTree.call(tagger, root, linkedAnchor, depth);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function collect(root) {
|
|
81
|
+
if (!root) return;
|
|
82
|
+
if (root.nodeType === Node.ELEMENT_NODE && root.matches(selector)) pending.add(root);
|
|
83
|
+
if (root.querySelectorAll) root.querySelectorAll(selector).forEach((body) => pending.add(body));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function flush() {
|
|
87
|
+
retry = null;
|
|
88
|
+
if (!ready()) {
|
|
89
|
+
retry = window.setTimeout(flush, 25);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const bodies = Array.from(pending);
|
|
94
|
+
pending.clear();
|
|
95
|
+
bodies.forEach((body) => {
|
|
96
|
+
if (!body.isConnected || body.dataset.b270BlbTagged === "true") return;
|
|
97
|
+
|
|
98
|
+
body.querySelectorAll("a").forEach((link) => link.classList.add("b270-blb-existing-link"));
|
|
99
|
+
body.dataset.b270BlbTagged = "true";
|
|
100
|
+
tagger.walkDomTree(body);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function scan(root = document) {
|
|
105
|
+
collect(root);
|
|
106
|
+
if (pending.size && !retry) retry = window.setTimeout(flush, 0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function reset(root = document) {
|
|
110
|
+
root.querySelectorAll(`${selector} a.BLBST_a`).forEach((link) => {
|
|
111
|
+
link.replaceWith(document.createTextNode(link.textContent));
|
|
112
|
+
});
|
|
113
|
+
root.querySelectorAll(selector).forEach((body) => {
|
|
114
|
+
body.removeAttribute("data-b270-blb-tagged");
|
|
115
|
+
});
|
|
116
|
+
document.getElementById("scriptDiv")?.remove();
|
|
117
|
+
tagger.ContainerToHide = null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
window.Bible270ScripturePopups = { configure, scan };
|
|
121
|
+
new MutationObserver((mutations) => {
|
|
122
|
+
mutations.forEach((mutation) => mutation.addedNodes.forEach((node) => {
|
|
123
|
+
if (node.nodeType === Node.ELEMENT_NODE) scan(node);
|
|
124
|
+
}));
|
|
125
|
+
}).observe(document.documentElement, { childList: true, subtree: true });
|
|
126
|
+
document.addEventListener("turbo:load", () => scan());
|
|
127
|
+
document.addEventListener("turbo:before-cache", () => reset());
|
|
128
|
+
window.addEventListener("pageshow", () => scan());
|
|
129
|
+
scan();
|
|
130
|
+
})();
|
|
131
|
+
</script>
|
|
@@ -4,13 +4,17 @@ module Bible270
|
|
|
4
4
|
# Builds the plain-text reader progress table used by administrative tasks.
|
|
5
5
|
class ReaderProgressReport
|
|
6
6
|
HEADERS = ['Name', 'Days Read', 'Status'].freeze
|
|
7
|
-
SORTS = %w[
|
|
7
|
+
SORTS = %w[
|
|
8
|
+
first_name last_name most_completed least_completed most_recent_activity least_recent_activity
|
|
9
|
+
].freeze
|
|
8
10
|
DEFAULT_SORT = 'most_completed'
|
|
9
11
|
Row = Struct.new(:name, :days_read, :status, keyword_init: true)
|
|
10
12
|
|
|
11
|
-
def initialize(readers: Reader.all, completed_days: Reader.completed_days_by_id,
|
|
13
|
+
def initialize(readers: Reader.all, completed_days: Reader.completed_days_by_id, recent_activities: {},
|
|
14
|
+
sort: DEFAULT_SORT)
|
|
12
15
|
@readers = readers
|
|
13
16
|
@completed_days = completed_days
|
|
17
|
+
@recent_activities = recent_activities
|
|
14
18
|
@sort = SORTS.include?(sort.to_s) ? sort.to_s : DEFAULT_SORT
|
|
15
19
|
end
|
|
16
20
|
|
|
@@ -40,7 +44,7 @@ module Bible270
|
|
|
40
44
|
|
|
41
45
|
private
|
|
42
46
|
|
|
43
|
-
attr_reader :readers, :completed_days, :sort
|
|
47
|
+
attr_reader :readers, :completed_days, :recent_activities, :sort
|
|
44
48
|
|
|
45
49
|
def reader_sort_key(reader)
|
|
46
50
|
case sort
|
|
@@ -51,11 +55,21 @@ module Bible270
|
|
|
51
55
|
[last_name.to_s.downcase, reader.sort_name, reader.id]
|
|
52
56
|
when 'least_completed'
|
|
53
57
|
[completed_days[reader.id].to_i, reader.sort_name, reader.id]
|
|
58
|
+
when 'most_recent_activity'
|
|
59
|
+
activity_sort_key(reader, descending: true)
|
|
60
|
+
when 'least_recent_activity'
|
|
61
|
+
activity_sort_key(reader, descending: false)
|
|
54
62
|
else
|
|
55
63
|
[-completed_days[reader.id].to_i, reader.sort_name, reader.id]
|
|
56
64
|
end
|
|
57
65
|
end
|
|
58
66
|
|
|
67
|
+
def activity_sort_key(reader, descending:)
|
|
68
|
+
activity = recent_activities[reader.id]
|
|
69
|
+
timestamp = activity&.occurred_at&.to_f
|
|
70
|
+
[activity.nil? ? 1 : 0, descending && timestamp ? -timestamp : timestamp.to_f, reader.sort_name, reader.id]
|
|
71
|
+
end
|
|
72
|
+
|
|
59
73
|
def status(reader, days_read)
|
|
60
74
|
calendar_day = reader.calendar_day
|
|
61
75
|
return 'undated' unless calendar_day
|
|
@@ -87,6 +87,21 @@ module Bible270
|
|
|
87
87
|
VERSIONS.dig(normalize(code), :gateway) || normalize(code)
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
+
# ScriptTagger accepts one default translation, but Bible270's two original-
|
|
91
|
+
# language choices vary by testament. Returning both lets the browser select
|
|
92
|
+
# the correct BLB text for each detected reference.
|
|
93
|
+
def blue_letter_tagger_translations(code)
|
|
94
|
+
translation = resolve(code)
|
|
95
|
+
case translation
|
|
96
|
+
when ORIGINAL_LANGUAGES
|
|
97
|
+
{ old_testament: 'WLC', new_testament: 'mGNT' }
|
|
98
|
+
when ALL_GREEK
|
|
99
|
+
{ old_testament: 'LXX', new_testament: 'mGNT' }
|
|
100
|
+
else
|
|
101
|
+
{ old_testament: translation, new_testament: translation }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
90
105
|
# [[label, code], ...] for a select field.
|
|
91
106
|
def options
|
|
92
107
|
codes.map { |code| ["#{code} — #{short_label(code)}", code] }
|
data/lib/bible270/version.rb
CHANGED
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.
|
|
4
|
+
version: 1.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew vonderLuft
|
|
@@ -187,6 +187,7 @@ files:
|
|
|
187
187
|
- app/views/bible270/shared/_passage_source.html.erb
|
|
188
188
|
- app/views/bible270/shared/_progress.html.erb
|
|
189
189
|
- app/views/bible270/shared/_reflections.html.erb
|
|
190
|
+
- app/views/bible270/shared/_scripture_popups.html.erb
|
|
190
191
|
- app/views/bible270/shared/_styles.html.erb
|
|
191
192
|
- app/views/bible270/shared/_track_bars.html.erb
|
|
192
193
|
- app/views/bible270/sign_in_mailer/magic_link.html.erb
|
|
@@ -261,7 +262,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
261
262
|
- !ruby/object:Gem::Version
|
|
262
263
|
version: '0'
|
|
263
264
|
requirements: []
|
|
264
|
-
rubygems_version: 4.0.
|
|
265
|
+
rubygems_version: 4.0.20
|
|
265
266
|
specification_version: 4
|
|
266
267
|
summary: 'A mountable Rails engine: a 270-day, verse-balanced Bible reading plan (OT
|
|
267
268
|
and NT once, Psalms/Proverbs alongside) with per-user check-offs, comments, and
|