bible270 0.16.1 → 0.17.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/app/controllers/bible270/comments_controller.rb +68 -1
- data/app/controllers/bible270/days_controller.rb +9 -0
- data/app/helpers/bible270/plan_helper.rb +71 -0
- data/app/models/bible270/comment.rb +16 -1
- data/app/views/bible270/admin/show.html.erb +2 -1
- data/app/views/bible270/comments/_comment.html.erb +24 -3
- data/app/views/bible270/comments/_edit_fields.html.erb +25 -0
- data/app/views/bible270/comments/_edit_form.html.erb +7 -0
- data/app/views/bible270/comments/edit.turbo_stream.erb +3 -0
- data/app/views/bible270/comments/index.html.erb +52 -0
- data/app/views/bible270/comments/update.turbo_stream.erb +4 -0
- data/app/views/bible270/days/index.html.erb +2 -2
- data/app/views/bible270/readers/progress.html.erb +2 -2
- data/app/views/bible270/readers/show.html.erb +2 -1
- data/app/views/bible270/shared/_day_index.html.erb +2 -2
- data/app/views/bible270/shared/_grid_legend.html.erb +16 -0
- data/app/views/bible270/shared/_header.html.erb +3 -1
- data/app/views/bible270/shared/_styles.html.erb +29 -2
- data/config/routes.rb +3 -0
- data/lib/bible270/configuration.rb +5 -2
- data/lib/bible270/version.rb +1 -1
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f2e0821b84cb0bc7ece339c39775d9b7b814b4f274e721f112cc393aabfc5093
|
|
4
|
+
data.tar.gz: 53f7e5d9386bfd4f9ae14ed6ac56daedab5df0139c0043e4bac6576bde257ed2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ef8060ffe3f6a7c99e35978886609c580e091a0bb0b6f86b809ef7bf8afa8bdda919f7e94c97f04ca5bf69e30b5b70b79b37333fb0850073864d40ed3bf8b909
|
|
7
|
+
data.tar.gz: c17db24c8ca3ab97e950f2fa516f5366b9d98dcbaa0422a4130acab6031bdeeb1a7faefed1d28e6f4ea44f77404ea3a50a28c041b8bba48edab4936bf158313c
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Bible270
|
|
4
4
|
class CommentsController < ApplicationController
|
|
5
|
+
def index
|
|
6
|
+
@days_with_reflections = Comment.approved.distinct.pluck(:day).sort
|
|
7
|
+
@threads = recent_threads
|
|
8
|
+
end
|
|
9
|
+
|
|
5
10
|
def create
|
|
6
11
|
return unless require_reader!
|
|
7
12
|
|
|
@@ -27,10 +32,34 @@ module Bible270
|
|
|
27
32
|
end
|
|
28
33
|
end
|
|
29
34
|
|
|
35
|
+
def update
|
|
36
|
+
return unless require_reader!
|
|
37
|
+
|
|
38
|
+
@comment = own_comment
|
|
39
|
+
head :not_found and return unless @comment
|
|
40
|
+
|
|
41
|
+
@day = @comment.day
|
|
42
|
+
if @comment.update(comment_params.except(:parent_id))
|
|
43
|
+
respond_to do |format|
|
|
44
|
+
format.turbo_stream
|
|
45
|
+
format.html { redirect_to day_path(@day, anchor: "comment-#{@comment.id}") }
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
respond_to do |format|
|
|
49
|
+
format.turbo_stream do
|
|
50
|
+
render turbo_stream: turbo_stream.replace("comment-#{@comment.id}",
|
|
51
|
+
partial: 'bible270/comments/edit_form',
|
|
52
|
+
locals: { comment: @comment })
|
|
53
|
+
end
|
|
54
|
+
format.html { redirect_to day_path(@day), alert: @comment.errors.full_messages.to_sentence }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
30
59
|
def destroy
|
|
31
60
|
return unless require_reader!
|
|
32
61
|
|
|
33
|
-
@comment =
|
|
62
|
+
@comment = deletable_comment
|
|
34
63
|
head :not_found and return unless @comment
|
|
35
64
|
|
|
36
65
|
@day = @comment.day
|
|
@@ -43,8 +72,46 @@ module Bible270
|
|
|
43
72
|
|
|
44
73
|
private
|
|
45
74
|
|
|
75
|
+
# The most recently active conversations, newest first. A reply counts as
|
|
76
|
+
# activity, so answering an old reflection brings the whole thread back up —
|
|
77
|
+
# otherwise a lively discussion on day 3 would stay buried while quieter, newer
|
|
78
|
+
# reflections sat above it.
|
|
79
|
+
def recent_threads
|
|
80
|
+
wanted = Bible270.config.reflections_page_size.to_i
|
|
81
|
+
wanted = 10 unless wanted.positive?
|
|
82
|
+
|
|
83
|
+
# Look at rather more messages than threads, since several may belong to one.
|
|
84
|
+
recent = Comment.approved.order(created_at: :desc).limit(wanted * 5)
|
|
85
|
+
.pluck(:id, :parent_id, :created_at)
|
|
86
|
+
|
|
87
|
+
roots = {}
|
|
88
|
+
recent.each do |id, parent_id, created_at|
|
|
89
|
+
root = parent_id || id
|
|
90
|
+
roots[root] ||= created_at
|
|
91
|
+
roots[root] = created_at if created_at > roots[root]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
ids = roots.sort_by { |_root, at| -at.to_i }.first(wanted).map(&:first)
|
|
95
|
+
threads = Comment.approved.where(id: ids).includes(:reader, replies: :reader).index_by(&:id)
|
|
96
|
+
ids.filter_map { |id| threads[id] }
|
|
97
|
+
end
|
|
98
|
+
|
|
46
99
|
def comment_params
|
|
47
100
|
params.require(:comment).permit(:body, :track, :parent_id)
|
|
48
101
|
end
|
|
102
|
+
|
|
103
|
+
# Scoped to the reader, so someone else's reflection is simply not found —
|
|
104
|
+
# it reveals nothing about what exists.
|
|
105
|
+
def own_comment
|
|
106
|
+
current_reader.comments.find_by(id: params[:id])
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# An admin may remove any reflection; everyone else only their own. Editing
|
|
110
|
+
# stays with the writer either way.
|
|
111
|
+
def deletable_comment
|
|
112
|
+
return own_comment unless Bible270.config.admin?(current_reader)
|
|
113
|
+
|
|
114
|
+
Comment.find_by(id: params[:id])
|
|
115
|
+
end
|
|
49
116
|
end
|
|
50
117
|
end
|
|
@@ -27,6 +27,7 @@ module Bible270
|
|
|
27
27
|
@comments = Comment.threads_for_day(@day)
|
|
28
28
|
# Replying prefills the form with the handle rather than needing JavaScript,
|
|
29
29
|
# so it works with Turbo and without an asset pipeline.
|
|
30
|
+
@editing_comment_id = editing_comment_id
|
|
30
31
|
parent = reply_parent
|
|
31
32
|
@new_comment = Comment.new(day: @day, parent_id: parent&.id, body: reply_prefix(parent))
|
|
32
33
|
@reader_tracks = current_reader ? current_reader.read_tracks_for(@day) : []
|
|
@@ -50,6 +51,14 @@ module Bible270
|
|
|
50
51
|
# "@handle " for the reflection being replied to, or nothing. Private, but
|
|
51
52
|
# declared with the keyword rather than trailing the file, so it is obvious
|
|
52
53
|
# this is not an action.
|
|
54
|
+
# The reflection the reader has asked to edit: their own, on this day. Anything
|
|
55
|
+
# else is ignored rather than refused — the page still renders.
|
|
56
|
+
def editing_comment_id
|
|
57
|
+
return nil if params[:edit].blank? || current_reader.nil?
|
|
58
|
+
|
|
59
|
+
current_reader.comments.where(day: @day, id: params[:edit]).pick(:id)
|
|
60
|
+
end
|
|
61
|
+
|
|
53
62
|
# The reflection being replied to, if the link carried one and it is a
|
|
54
63
|
# top-level reflection: a reply to a reply is not allowed.
|
|
55
64
|
def reply_parent
|
|
@@ -137,6 +137,77 @@ module Bible270
|
|
|
137
137
|
end
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
+
# Only the writer may change their own words. An admin can remove a reflection
|
|
141
|
+
# but not rewrite it: putting words in someone's mouth is worse than taking
|
|
142
|
+
# them away, and taking them away is already the moderator's job.
|
|
143
|
+
def b270_can_edit?(comment)
|
|
144
|
+
signed_in? && comment.reader_id == current_reader.id
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def b270_can_delete?(comment)
|
|
148
|
+
return false unless signed_in?
|
|
149
|
+
|
|
150
|
+
comment.reader_id == current_reader.id || Bible270.config.admin?(current_reader)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# One cell of the 270-day grid. Four views drew this by hand in four slightly
|
|
154
|
+
# different ways; now they all call this.
|
|
155
|
+
#
|
|
156
|
+
# The title attribute carries the readings, so hovering a square says what is
|
|
157
|
+
# on that day — no JavaScript, and it works for keyboard focus too.
|
|
158
|
+
def b270_day_cell(day, reader: nil, today: nil)
|
|
159
|
+
link_to day, day_path(day),
|
|
160
|
+
class: b270_day_cell_classes(day, reader: reader, today: today),
|
|
161
|
+
title: b270_day_readings(day)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Shared with the admin grid, which is a button_to rather than a link because
|
|
165
|
+
# it toggles the day rather than navigating to it.
|
|
166
|
+
def b270_day_cell_classes(day, reader: nil, today: nil)
|
|
167
|
+
classes = ['b270-cell', b270_day_status_class(reader, day)]
|
|
168
|
+
classes << 'talk' if b270_days_with_reflections.include?(day)
|
|
169
|
+
classes << 'now' if today && day == today
|
|
170
|
+
|
|
171
|
+
classes.reject(&:blank?).join(' ')
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# "Genesis 1-3 · Matthew 1 · Psalm 1" as plain text, for a title attribute.
|
|
175
|
+
def b270_day_readings(day)
|
|
176
|
+
readings = Bible270::Plan.readings_for(day)
|
|
177
|
+
Bible270::Plan::TRACKS.keys.filter_map { |track| readings[track] }.join(' · ')
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# The same references as links to the passage, in the reader's own translation
|
|
181
|
+
# when they have chosen one — b270_passage_url falls back to the site default
|
|
182
|
+
# for a visitor.
|
|
183
|
+
def b270_day_reading_links(day)
|
|
184
|
+
readings = Bible270::Plan.readings_for(day)
|
|
185
|
+
|
|
186
|
+
links = Bible270::Plan::TRACKS.keys.filter_map do |track|
|
|
187
|
+
reference = readings[track]
|
|
188
|
+
next if reference.blank?
|
|
189
|
+
|
|
190
|
+
link_to reference, b270_passage_url(reference),
|
|
191
|
+
class: 'b270-reflink', target: '_blank', rel: 'noopener'
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
safe_join(links, ' · ')
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# The days that have at least one visible reflection, fetched once per request.
|
|
198
|
+
# Called from the layout's grid, which no controller sets up, so it cannot be an
|
|
199
|
+
# instance variable assigned in an action.
|
|
200
|
+
def b270_days_with_reflections
|
|
201
|
+
@b270_days_with_reflections ||=
|
|
202
|
+
if defined?(Bible270::Comment)
|
|
203
|
+
Bible270::Comment.approved.distinct.pluck(:day).to_set
|
|
204
|
+
else
|
|
205
|
+
Set.new
|
|
206
|
+
end
|
|
207
|
+
rescue StandardError
|
|
208
|
+
Set.new
|
|
209
|
+
end
|
|
210
|
+
|
|
140
211
|
def b270_day_status_class(reader, day)
|
|
141
212
|
return '' unless reader
|
|
142
213
|
|
|
@@ -28,6 +28,9 @@ module Bible270
|
|
|
28
28
|
# Fires once the reflection is really saved, so nothing is sent for one whose
|
|
29
29
|
# transaction rolls back.
|
|
30
30
|
after_create_commit :notify_mentioned_readers
|
|
31
|
+
# Only the people newly named: editing a reflection five times must not mail
|
|
32
|
+
# the same reader five times.
|
|
33
|
+
after_update_commit :notify_newly_mentioned_readers
|
|
31
34
|
|
|
32
35
|
scope :approved, -> { where(approved: true) }
|
|
33
36
|
scope :hidden, -> { where(approved: false) }
|
|
@@ -43,6 +46,10 @@ module Bible270
|
|
|
43
46
|
def hidden? = !approved
|
|
44
47
|
def reply? = parent_id.present?
|
|
45
48
|
|
|
49
|
+
# A second's grace: created_at and updated_at differ by microseconds on
|
|
50
|
+
# insert, which would mark every reflection as edited.
|
|
51
|
+
def edited? = updated_at - created_at > 1
|
|
52
|
+
|
|
46
53
|
# Visible replies, oldest first: a conversation reads forwards.
|
|
47
54
|
def visible_replies = replies.approved.order(created_at: :asc)
|
|
48
55
|
def moderated? = moderated_at.present?
|
|
@@ -70,11 +77,19 @@ module Bible270
|
|
|
70
77
|
|
|
71
78
|
# A mention emails the person named. Failures are logged rather than raised:
|
|
72
79
|
# nobody should lose a reflection because the mail server is down.
|
|
73
|
-
def
|
|
80
|
+
def notify_newly_mentioned_readers
|
|
81
|
+
return unless saved_change_to_body?
|
|
82
|
+
|
|
83
|
+
already = Reader.mentioned_in(body_before_last_save).map(&:id)
|
|
84
|
+
notify_mentioned_readers(except: already)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def notify_mentioned_readers(except: [])
|
|
74
88
|
return unless Bible270.config.mention_notifications
|
|
75
89
|
|
|
76
90
|
Reader.mentioned_in(body).each do |mentioned|
|
|
77
91
|
next if mentioned.id == reader_id
|
|
92
|
+
next if except.include?(mentioned.id)
|
|
78
93
|
next unless mentioned.wants_mention_notices?
|
|
79
94
|
|
|
80
95
|
notice = NoticeMailer.mentioned(comment_id: id, reader_id: mentioned.id)
|
|
@@ -80,7 +80,8 @@
|
|
|
80
80
|
<div class="b270-grid" style="margin-top:16px">
|
|
81
81
|
<% (1..Bible270::Plan::DAYS).each do |d| %>
|
|
82
82
|
<%= button_to d, admin_reader_day_path(@reader, d), method: :post,
|
|
83
|
-
class:
|
|
83
|
+
class: b270_day_cell_classes(d, reader: @reader),
|
|
84
|
+
title: b270_day_readings(d),
|
|
84
85
|
form: { style: 'display:contents', id: "day-#{d}" } %>
|
|
85
86
|
<% end %>
|
|
86
87
|
</div>
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
<%# locals: (comment:, reply: false) %>
|
|
2
|
+
<%#
|
|
3
|
+
Whether this reflection is being edited comes from the controller rather than a
|
|
4
|
+
local, so it works for a reply as well as a top-level reflection without the day
|
|
5
|
+
view having to thread a flag through the collection. It is nil everywhere else
|
|
6
|
+
the partial is rendered.
|
|
7
|
+
%>
|
|
8
|
+
<% editing = @editing_comment_id.present? && @editing_comment_id == comment.id %>
|
|
2
9
|
<div class="b270-comment <%= 'b270-reply' if reply %>" id="comment-<%= comment.id %>">
|
|
3
10
|
<%= b270_avatar(comment.reader, size: reply ? 28 : 34) %>
|
|
4
11
|
<div style="flex:1">
|
|
@@ -6,13 +13,27 @@
|
|
|
6
13
|
<strong><%= link_to comment.reader.display_name, reader_path(comment.reader), style: "text-decoration:none" %></strong>
|
|
7
14
|
· <%= comment.created_at.strftime("%b %-d, %Y") %>
|
|
8
15
|
<% if comment.track.present? %>· <%= b270_track(comment.track)[:label] %><% end %>
|
|
9
|
-
<% if
|
|
16
|
+
<% if comment.edited? %>· <span class="b270-edited" title="<%= comment.updated_at.strftime('%b %-d, %Y at %-I:%M %p') %>">edited</span><% end %>
|
|
17
|
+
<% if b270_can_edit?(comment) %>
|
|
18
|
+
<%= link_to "edit", day_path(comment.day, edit: comment.id, anchor: "comment-#{comment.id}"),
|
|
19
|
+
class: "b270-cedit" %>
|
|
20
|
+
<% end %>
|
|
21
|
+
<% if b270_can_delete?(comment) %>
|
|
22
|
+
<%# The confirm goes on the form, since button_to renders one and that is
|
|
23
|
+
where Turbo looks. Only when removing someone else's: confirming your
|
|
24
|
+
own deletion every time would be nagging. %>
|
|
10
25
|
<%= button_to "delete", comment_path(comment), method: :delete, class: "b270-cdel",
|
|
11
|
-
form: { style: "display:inline",
|
|
26
|
+
form: { style: "display:inline",
|
|
27
|
+
data: { turbo_stream: true,
|
|
28
|
+
turbo_confirm: (b270_can_edit?(comment) ? nil : "Delete #{comment.reader.display_name}'s reflection?") } } %>
|
|
12
29
|
<% end %>
|
|
13
30
|
</div>
|
|
14
31
|
|
|
15
|
-
|
|
32
|
+
<% if editing %>
|
|
33
|
+
<%= render partial: "bible270/comments/edit_fields", locals: { comment: comment } %>
|
|
34
|
+
<% else %>
|
|
35
|
+
<p class="b270-cbody"><%= b270_with_mentions(comment.body) %></p>
|
|
36
|
+
<% end %>
|
|
16
37
|
|
|
17
38
|
<%# Only a top-level reflection can be replied to: one level deep, so a reply
|
|
18
39
|
offers no Reply of its own. %>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<%# locals: (comment:) %>
|
|
2
|
+
<%#
|
|
3
|
+
The edit form for one reflection. Reached by ?edit=<id> on the day page, which
|
|
4
|
+
needs no JavaScript — the same approach as ?reply_to=. Turbo will upgrade the
|
|
5
|
+
save to a stream when it is driving the page, and fall back to a redirect when
|
|
6
|
+
it is not.
|
|
7
|
+
%>
|
|
8
|
+
<%= form_with model: comment, url: comment_path(comment), method: :patch,
|
|
9
|
+
class: "b270-form b270-editform", data: { turbo_stream: true } do |f| %>
|
|
10
|
+
<% if comment.errors.any? %>
|
|
11
|
+
<p class="b270-editerror"><%= comment.errors.full_messages.to_sentence %></p>
|
|
12
|
+
<% end %>
|
|
13
|
+
|
|
14
|
+
<%= f.text_area :body %>
|
|
15
|
+
<div class="row">
|
|
16
|
+
<% unless comment.reply? %>
|
|
17
|
+
<%= f.select :track,
|
|
18
|
+
options_for_select([["The whole day", ""]] + Bible270::Plan::TRACKS.map { |k, v| [v[:label], k] },
|
|
19
|
+
comment.track.to_s),
|
|
20
|
+
{}, class: "b270-select", style: "width:auto" %>
|
|
21
|
+
<% end %>
|
|
22
|
+
<%= f.submit "Save", class: "b270-btn" %>
|
|
23
|
+
<%= link_to "Cancel", day_path(comment.day, anchor: "comment-#{comment.id}"), class: "b270-linkbtn" %>
|
|
24
|
+
</div>
|
|
25
|
+
<% end %>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<% content_for :title, "Reflections — #{Bible270.config.app_name}" %>
|
|
2
|
+
<% content_for :hide_day_index, true %><%# this page has its own, filtered grid %>
|
|
3
|
+
|
|
4
|
+
<p class="b270-eyebrow">Reflections</p>
|
|
5
|
+
<h1>What people are saying</h1>
|
|
6
|
+
|
|
7
|
+
<% if @days_with_reflections.any? %>
|
|
8
|
+
<div class="b270-panel">
|
|
9
|
+
<h2 class="b270-subhead">Days with reflections</h2>
|
|
10
|
+
<p class="b270-hint" style="margin-top:0">
|
|
11
|
+
<%= @days_with_reflections.size %>
|
|
12
|
+
of <%= Bible270::Plan::DAYS %> days have been written about.
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<%# Only the days that have something on them, so the grid is a map of the
|
|
16
|
+
conversation rather than of the plan. Squares keep their usual meaning:
|
|
17
|
+
the fill is reading progress, and hovering shows that day's readings. %>
|
|
18
|
+
<div class="b270-grid">
|
|
19
|
+
<% @days_with_reflections.each do |day| %>
|
|
20
|
+
<%= b270_day_cell(day, reader: current_reader) %>
|
|
21
|
+
<% end %>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<h2 class="b270-section-h">Most recent</h2>
|
|
26
|
+
<div id="comments">
|
|
27
|
+
<% @threads.each do |thread| %>
|
|
28
|
+
<div class="b270-thread">
|
|
29
|
+
<p class="b270-threadday">
|
|
30
|
+
<%= link_to "Day #{thread.day}", day_path(thread.day) %>
|
|
31
|
+
· <%= b270_day_reading_links(thread.day) %>
|
|
32
|
+
</p>
|
|
33
|
+
<%= render partial: "bible270/comments/comment", locals: { comment: thread, reply: false } %>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<p style="margin-top:18px">
|
|
39
|
+
<%= link_to "Browse the whole plan →", root_path %>
|
|
40
|
+
</p>
|
|
41
|
+
<% else %>
|
|
42
|
+
<div class="b270-panel">
|
|
43
|
+
<p class="b270-signedout" style="margin:0">
|
|
44
|
+
No one has written a reflection yet.
|
|
45
|
+
<% if signed_in? %>
|
|
46
|
+
<%= link_to "Be the first", day_path(current_reader.current_day) %>.
|
|
47
|
+
<% else %>
|
|
48
|
+
<%= link_to "Sign in", sign_in_path %> to add one.
|
|
49
|
+
<% end %>
|
|
50
|
+
</p>
|
|
51
|
+
</div>
|
|
52
|
+
<% end %>
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
<%= b270_avatar(r) %>
|
|
16
16
|
<div>
|
|
17
17
|
<div class="name"><%= link_to r.display_name, reader_path(r), style: "text-decoration:none" %></div>
|
|
18
|
-
<%
|
|
19
|
-
<div class="sub"><%= @days_completed[r.id] %> <%=
|
|
18
|
+
<% day_word = @days_completed[r.id] == 1 ? "day" : "days" %>
|
|
19
|
+
<div class="sub"><%= @days_completed[r.id] %> <%= day_word %> read</div>
|
|
20
20
|
</div>
|
|
21
21
|
<div class="stat"><span class="big"><%= (@days_completed[r.id].to_f / Bible270::Plan::DAYS * 100).round %>%</span></div>
|
|
22
22
|
</div>
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
<div class="b270-grid">
|
|
19
19
|
<% today = @reader.today_day %>
|
|
20
20
|
<% (1..Bible270::Plan::DAYS).each do |d| %>
|
|
21
|
-
<%=
|
|
22
|
-
class: "b270-cell #{b270_day_status_class(@reader, d)} #{'now' if d == today}" %>
|
|
21
|
+
<%= b270_day_cell(d, reader: @reader, today: today) %>
|
|
23
22
|
<% end %>
|
|
24
23
|
</div>
|
|
24
|
+
<p class="b270-gridnote">Dot indicates days with reflections.</p>
|
|
25
25
|
<p class="b270-hint">Filled squares are days you have finished; a lighter square means you have
|
|
26
26
|
read some of that day but not all of it.</p>
|
|
27
27
|
</div>
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
<div class="b270-grid">
|
|
24
24
|
<% (1..Bible270::Plan::DAYS).each do |d| %>
|
|
25
|
-
<%=
|
|
25
|
+
<%= b270_day_cell(d, reader: @reader) %>
|
|
26
26
|
<% end %>
|
|
27
27
|
</div>
|
|
28
|
+
<p class="b270-gridnote">Dot indicates days with reflections.</p>
|
|
28
29
|
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
<div class="b270-grid">
|
|
5
5
|
<% today = signed_in? ? current_reader.current_day : nil %>
|
|
6
6
|
<% (1..Bible270::Plan::DAYS).each do |d| %>
|
|
7
|
-
<%=
|
|
8
|
-
class: "b270-cell #{b270_day_status_class(current_reader, d)} #{'now' if d == today}" %>
|
|
7
|
+
<%= b270_day_cell(d, reader: current_reader, today: today) %>
|
|
9
8
|
<% end %>
|
|
10
9
|
</div>
|
|
10
|
+
<p class="b270-gridnote">Dot indicates days with reflections.</p>
|
|
11
11
|
</details>
|
|
12
12
|
<% end %>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%# locals: (reflections: true) %>
|
|
2
|
+
<%#
|
|
3
|
+
Six states need saying out loud: three degrees of progress, each of which may
|
|
4
|
+
also have reflections. Hovering a square shows that day's readings.
|
|
5
|
+
|
|
6
|
+
reflections: false on the Reflections page, where every square has them by
|
|
7
|
+
definition and the entry would explain nothing.
|
|
8
|
+
%>
|
|
9
|
+
<p class="b270-legend">
|
|
10
|
+
<span><i></i> not yet read</span>
|
|
11
|
+
<span><i class="partial"></i> partly read</span>
|
|
12
|
+
<span><i class="complete"></i> finished</span>
|
|
13
|
+
<% if reflections %>
|
|
14
|
+
<span><i class="talk"></i> has reflections</span>
|
|
15
|
+
<% end %>
|
|
16
|
+
</p>
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
<% end %>
|
|
10
10
|
</div>
|
|
11
11
|
<nav class="b270-nav">
|
|
12
|
-
|
|
12
|
+
<!--<%= link_to "Plan", root_path %>-->
|
|
13
13
|
<%= link_to "People", community_path %>
|
|
14
|
+
<%# Public, like the community page: anyone may read reflections. %>
|
|
15
|
+
<%= link_to "Reflections", reflections_path %>
|
|
14
16
|
<% if signed_in? %>
|
|
15
17
|
<% if Bible270.config.admin?(current_reader) %><%= link_to "Admin", admin_path %><% end %>
|
|
16
18
|
<%= link_to "Progress", progress_path %>
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
.b270-flash.notice{background:#e7efe6;color:#2f5d34;border:1px solid #c6dcc6}
|
|
55
55
|
|
|
56
56
|
.b270-eyebrow{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--gold);font-weight:600;margin:0 0 10px}
|
|
57
|
-
.b270 h1{font-family:"Spectral",serif;font-weight:600;font-size:clamp(
|
|
57
|
+
.b270 h1{font-family:"Spectral",serif;font-weight:600;font-size:clamp(22px,6vw,33px);line-height:1.03;letter-spacing:-.01em;margin:0 0 6px}
|
|
58
58
|
.b270 h1 em{font-style:italic;color:var(--muted);font-weight:400}
|
|
59
59
|
.b270 .lede{color:var(--muted);max-width:77ch;margin:0 0 24px;font-size:15px}
|
|
60
60
|
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
.b270-startform{display:flex;align-items:center;gap:10px;margin-top:10px;flex-wrap:wrap}
|
|
77
77
|
.b270-startform input[type=date]{font:inherit;font-size:14px;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:#fff;color:var(--ink)}
|
|
78
78
|
.b270-linkbtn{background:none;border:none;padding:0;margin-top:8px;font:inherit;font-size:12.5px;color:var(--oxblood);cursor:pointer;text-decoration:underline}
|
|
79
|
-
.b270-hint{margin:8px 0 0;font-size:12px;color:var(--faint);max-width:
|
|
79
|
+
.b270-hint{margin:8px 0 0;font-size:12px;color:var(--faint);max-width:111ch}
|
|
80
80
|
.b270-tracks{margin-top:16px;display:grid;gap:11px}
|
|
81
81
|
.b270-tm{display:grid;grid-template-columns:130px 1fr auto;align-items:center;gap:12px;font-size:12.5px}
|
|
82
82
|
.b270-tm .lab{color:var(--muted);display:flex;align-items:center;gap:8px}
|
|
@@ -163,6 +163,14 @@
|
|
|
163
163
|
.b270-mention:hover{text-decoration:underline}
|
|
164
164
|
.b270-creply{margin-top:6px}
|
|
165
165
|
.b270-cbody{margin:3px 0 0;white-space:pre-wrap}
|
|
166
|
+
.b270-editform{margin-top:6px}
|
|
167
|
+
.b270-editform textarea{min-height:64px}
|
|
168
|
+
.b270-editerror{margin:0 0 8px;font-size:13px;color:var(--oxblood)}
|
|
169
|
+
.b270-cedit{font-size:12px;color:var(--muted);margin-left:8px}
|
|
170
|
+
.b270-cedit:hover{color:var(--gold)}
|
|
171
|
+
.b270-edited{font-style:italic;color:var(--faint)}
|
|
172
|
+
.b270-comment.b270-editing{background:rgba(165,129,44,.05);border-radius:10px;padding:12px}
|
|
173
|
+
.b270-editing textarea{min-height:64px}
|
|
166
174
|
.b270-cdel{font-size:12px;color:var(--oxblood);background:none;border:none;cursor:pointer;padding:0;margin-left:8px}
|
|
167
175
|
.b270-form textarea{width:100%;font:inherit;font-size:15px;border:1px solid var(--line);border-radius:10px;padding:11px 13px;background:#fff;resize:vertical;min-height:76px}
|
|
168
176
|
.b270-form .row{display:flex;align-items:center;gap:10px;margin-top:10px}
|
|
@@ -185,8 +193,27 @@
|
|
|
185
193
|
.b270-cell:hover{border-color:var(--gold);color:var(--gold)}
|
|
186
194
|
.b270-cell.partial{background:linear-gradient(135deg,var(--card) 50%,rgba(165,129,44,.35) 50%);color:var(--muted)}
|
|
187
195
|
.b270-cell.complete{background:var(--teal);border-color:var(--teal);color:#eef3f2;font-weight:600}
|
|
196
|
+
/* Days that have reflections carry a dot and nothing else: the fill stays
|
|
197
|
+
free to mean reading progress, so the two facts never compete. On a finished
|
|
198
|
+
square the dot goes pale, since oxblood on teal is hard to pick out. */
|
|
199
|
+
.b270-cell.talk{position:relative}
|
|
200
|
+
.b270-cell.talk::after{content:"";position:absolute;top:3px;right:3px;width:7px;height:7px;border-radius:50%;background:var(--oxblood)}
|
|
201
|
+
.b270-cell.talk.complete::after{background:#f4efe6}
|
|
188
202
|
.b270-cell.now{box-shadow:0 0 0 2px var(--gold)}
|
|
189
203
|
|
|
204
|
+
/* On the Reflections page each thread carries the day it belongs to, since
|
|
205
|
+
there is no day heading above it. */
|
|
206
|
+
.b270-thread{margin-bottom:8px}
|
|
207
|
+
.b270-threadday{margin:0;font-size:12px;letter-spacing:.04em;color:var(--faint)}
|
|
208
|
+
.b270-threadday a{color:var(--gold);font-weight:600;text-decoration:none}
|
|
209
|
+
.b270-threadday a:hover{text-decoration:underline}
|
|
210
|
+
/* Scripture references stay undecorated, hover included, so a line of three of
|
|
211
|
+
them does not read as a row of buttons. */
|
|
212
|
+
.b270-threadday .b270-reflink,
|
|
213
|
+
.b270-threadday .b270-reflink:hover{color:var(--muted);font-weight:400;text-decoration:none}
|
|
214
|
+
.b270-threadday .b270-reflink:hover{color:var(--ink)}
|
|
215
|
+
.b270-gridnote{margin:10px 0 0;font-size:11.5px;color:var(--faint)}
|
|
216
|
+
|
|
190
217
|
.b270-footer{margin-top:20px;padding-top:11px;border-top:1px solid var(--line-soft);color:var(--faint);font-size:13px}
|
|
191
218
|
.b270-footer p{margin:0;line-height:1.5}
|
|
192
219
|
details.b270-index summary{cursor:pointer;font-family:"Spectral",serif;font-size:18px;font-weight:600;list-style:none;margin-top:30px}
|
data/config/routes.rb
CHANGED
|
@@ -12,6 +12,7 @@ Bible270::Engine.routes.draw do
|
|
|
12
12
|
constraints: { day: %r{\d+} }
|
|
13
13
|
post 'day/:day/comments', to: 'comments#create', as: :day_comments,
|
|
14
14
|
constraints: { day: %r{\d+} }
|
|
15
|
+
patch 'comments/:id', to: 'comments#update'
|
|
15
16
|
delete 'comments/:id', to: 'comments#destroy', as: :comment
|
|
16
17
|
|
|
17
18
|
# Admin panel — 404s unless config.admin_emails / admin_resolver allows you.
|
|
@@ -34,6 +35,8 @@ Bible270::Engine.routes.draw do
|
|
|
34
35
|
patch 'profile', to: 'profiles#update'
|
|
35
36
|
delete 'profile/avatar', to: 'profiles#remove_avatar', as: :profile_avatar
|
|
36
37
|
|
|
38
|
+
get 'reflections', to: 'comments#index', as: :reflections
|
|
39
|
+
|
|
37
40
|
get 'progress', to: 'readers#progress', as: :progress
|
|
38
41
|
|
|
39
42
|
get 'community', to: 'readers#index', as: :community
|
|
@@ -292,7 +292,7 @@ module Bible270
|
|
|
292
292
|
# from, so without this the notice names the reader but cannot link to them.
|
|
293
293
|
attr_accessor :mailer_host
|
|
294
294
|
|
|
295
|
-
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
|
|
295
|
+
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, :mention_notifications
|
|
296
296
|
|
|
297
297
|
# Public labels.
|
|
298
298
|
attr_accessor :app_name
|
|
@@ -331,7 +331,9 @@ module Bible270
|
|
|
331
331
|
|
|
332
332
|
# Email a reader when someone writes "@them" in a reflection. Readers can opt
|
|
333
333
|
# out individually on their profile; this switches the whole feature off.
|
|
334
|
-
|
|
334
|
+
# How many recent threads the Reflections page shows. A thread resurfaces when
|
|
335
|
+
# someone replies to it, so this is a count of conversations, not of messages.
|
|
336
|
+
attr_accessor :reflections_page_size
|
|
335
337
|
|
|
336
338
|
def remember_signed_in?
|
|
337
339
|
remember_for.to_i.positive?
|
|
@@ -354,6 +356,7 @@ module Bible270
|
|
|
354
356
|
@app_name = 'Daily Bread'
|
|
355
357
|
@tagline = 'Journeying through Scripture in 9 months'
|
|
356
358
|
@time_zone = nil
|
|
359
|
+
@reflections_page_size = 10
|
|
357
360
|
@mention_notifications = true
|
|
358
361
|
@header_mark = nil
|
|
359
362
|
@remember_for = 365 * 24 * 60 * 60
|
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: 0.
|
|
4
|
+
version: 0.17.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew vonderLuft
|
|
@@ -103,9 +103,14 @@ files:
|
|
|
103
103
|
- app/views/bible270/admin/show.html.erb
|
|
104
104
|
- app/views/bible270/checkoffs/toggle.turbo_stream.erb
|
|
105
105
|
- app/views/bible270/comments/_comment.html.erb
|
|
106
|
+
- app/views/bible270/comments/_edit_fields.html.erb
|
|
107
|
+
- app/views/bible270/comments/_edit_form.html.erb
|
|
106
108
|
- app/views/bible270/comments/_form.html.erb
|
|
107
109
|
- app/views/bible270/comments/create.turbo_stream.erb
|
|
108
110
|
- app/views/bible270/comments/destroy.turbo_stream.erb
|
|
111
|
+
- app/views/bible270/comments/edit.turbo_stream.erb
|
|
112
|
+
- app/views/bible270/comments/index.html.erb
|
|
113
|
+
- app/views/bible270/comments/update.turbo_stream.erb
|
|
109
114
|
- app/views/bible270/days/_reading.html.erb
|
|
110
115
|
- app/views/bible270/days/_start_date.html.erb
|
|
111
116
|
- app/views/bible270/days/index.html.erb
|
|
@@ -121,6 +126,7 @@ files:
|
|
|
121
126
|
- app/views/bible270/sessions/new.html.erb
|
|
122
127
|
- app/views/bible270/shared/_day_index.html.erb
|
|
123
128
|
- app/views/bible270/shared/_footer.html.erb
|
|
129
|
+
- app/views/bible270/shared/_grid_legend.html.erb
|
|
124
130
|
- app/views/bible270/shared/_header.html.erb
|
|
125
131
|
- app/views/bible270/shared/_progress.html.erb
|
|
126
132
|
- app/views/bible270/shared/_reflections.html.erb
|