recourse 7.10.0 → 7.11.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 +27 -0
- data/app/controllers/concerns/recourse/paging.rb +5 -0
- data/app/controllers/concerns/recourse/public_caching.rb +45 -0
- data/app/controllers/concerns/recourse/zoning.rb +4 -0
- data/app/controllers/recourse/base_controller.rb +2 -2
- data/app/views/layouts/recourses.html.erb +5 -1
- data/app/views/recourses/_density.html.erb +10 -0
- data/app/views/recourses/_sidebar.html.erb +5 -3
- data/config/locales/recourse.en.yml +0 -2
- data/lib/recourse/helpers/densities.rb +3 -2
- data/lib/recourse/public_caches.rb +36 -0
- data/lib/recourse/version.rb +1 -1
- data/lib/recourse.rb +1 -0
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5d5b8233bcbb5c24c4df425ab9c5a43f5db42baf5f5801b29ec118ce792871bc
|
|
4
|
+
data.tar.gz: 7c5a0bd68a7d5c22d752b2a8fdb2667d0291b6d6bbfbc376218cb5186fa5a2ed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67e9c27e1c7489a189e9a001b1c3d191f68ef480555a06f701d68d561bbc8c97f66de6eb307939e6c1bb980fc7afd5ec1b9195f48289e28cc3450e0148de8e59
|
|
7
|
+
data.tar.gz: b0415018f1b632c3da2d61e1a77e8b96aafced9fcdbac997a4cb316821f30311805f7ed2e91e6e93d2a270ccbffae119394835f93d072becf9395f18aa159147
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@ For more information about changelogs, check [Keep a Changelog](http://keepachan
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## 7.11.1 - 2026-09-26
|
|
11
|
+
|
|
12
|
+
* [FIX] A tooltip in the sidebar's foot closes when its icon is clicked
|
|
13
|
+
|
|
14
|
+
Needs bh 6.10.1, which closes an icon's tooltip on a click on it or on its button. The moon
|
|
15
|
+
and the sun swap places on a click, and the tooltip of the one that went was left open and
|
|
16
|
+
put in the page's corner.
|
|
17
|
+
|
|
18
|
+
* [FIX] The moon and the sun say `Dark` and `Light`
|
|
19
|
+
|
|
20
|
+
The words the phone shows beside them, and no longer `Dark theme` and `Light theme`. The
|
|
21
|
+
`recourse.dark_theme` and `recourse.light_theme` words 7.10.0 added are gone: a host that
|
|
22
|
+
translated them translates `recourse.darken` and `recourse.lighten`, which are the same words.
|
|
23
|
+
|
|
24
|
+
## 7.11.0 - 2026-09-26
|
|
25
|
+
|
|
26
|
+
* [FEATURE] A host can say which pages are the same for everybody, and they are sent to be kept
|
|
27
|
+
|
|
28
|
+
`Recourse.public_pages = -> { !signed_in? }` runs in the controller for every request. A GET
|
|
29
|
+
it approves is sent `Cache-Control: public` with `max-age`, `s-maxage`, `stale-while-revalidate`
|
|
30
|
+
and `stale-if-error` (`Recourse.public_cache` sets the seconds), and `Vary: Turbo-Frame`. It is
|
|
31
|
+
drawn once for everybody, so none of what the reader's own browser tells the server is in
|
|
32
|
+
it: not their zone, not their page size, not their density (the layout widens the page in
|
|
33
|
+
the browser instead), no form token and no way out. `Recourse.public_version = -> { … }` gives
|
|
34
|
+
such a page an ETag, so a CDN or a browser asks whether it stands and is told `304`. A frame's
|
|
35
|
+
request is never public: the same address answers a page.
|
|
36
|
+
|
|
10
37
|
## 7.10.0 - 2026-09-26
|
|
11
38
|
|
|
12
39
|
* [FEATURE] The sidebar's foot says what each icon does
|
|
@@ -8,7 +8,12 @@ module Recourse
|
|
|
8
8
|
# a stranger can write, and an unchecked one is `?limit=100000` by another route.
|
|
9
9
|
# Which is also why pagy's `max_limit` stays unset: the query string is still not
|
|
10
10
|
# asked, and this is the one thing that is.
|
|
11
|
+
#
|
|
12
|
+
# A page drawn once for everybody has the first size, whatever this reader chose: the
|
|
13
|
+
# switch under the table redraws the table alone, which is not that page.
|
|
11
14
|
def recourse_limit
|
|
15
|
+
return Recourse::LIMITS.first if public_page?
|
|
16
|
+
|
|
12
17
|
limit = cookies[Recourse::LIMIT_STORAGE].to_i
|
|
13
18
|
|
|
14
19
|
Recourse::LIMITS.include?(limit) ? limit : Recourse::LIMITS.first
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Recourse
|
|
2
|
+
# A page the host says is the same for everybody is sent for everybody to keep: a CDN
|
|
3
|
+
# in front of the host serves it for a day, and asks the host only when it must.
|
|
4
|
+
module PublicCaching
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
before_action :cache_for_everybody
|
|
9
|
+
helper_method :public_page?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
# Whether this page is drawn once for everybody. Only a read: a page that changes
|
|
15
|
+
# something is nobody else's, and a frame's request is answered with the frame alone,
|
|
16
|
+
# which the same address answers with a whole page. Asked once per request, since the
|
|
17
|
+
# host's answer may be a query and the layout, the zone and the paging all ask.
|
|
18
|
+
def public_page?
|
|
19
|
+
return @public_page if defined? @public_page
|
|
20
|
+
|
|
21
|
+
@public_page = Recourse.public_pages.present? && (request.get? || request.head?) &&
|
|
22
|
+
!turbo_frame_request? && instance_exec(&Recourse.public_pages)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def cache_for_everybody
|
|
26
|
+
return unless public_page?
|
|
27
|
+
|
|
28
|
+
response.cache_control.merge! public_cache_control
|
|
29
|
+
response.headers['Vary'] = [response.headers['Vary'], 'Turbo-Frame'].compact_blank.join ', '
|
|
30
|
+
version = Recourse.public_version
|
|
31
|
+
fresh_when etag: instance_exec(&version) if version
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# `s-maxage` is the CDN's alone: the reader's browser keeps the page for `max-age`.
|
|
35
|
+
def public_cache_control
|
|
36
|
+
keep = Recourse.public_cache
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
public: true, max_age: keep[:max_age], extras: ["s-maxage=#{keep[:shared_max_age]}"],
|
|
40
|
+
stale_while_revalidate: keep[:stale_while_revalidate],
|
|
41
|
+
stale_if_error: keep[:stale_if_error],
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -20,7 +20,11 @@ module Recourse
|
|
|
20
20
|
# Nil where the browser has not said, or said something no zone answers to — and
|
|
21
21
|
# `Time.zone = nil` falls back to the host's own setting, so an absent cookie and a
|
|
22
22
|
# forged one both leave the page exactly as it is drawn today.
|
|
23
|
+
# And nil for a page drawn once for everybody, whose times are the host's own: this
|
|
24
|
+
# reader's zone is no business of the next reader's page.
|
|
23
25
|
def recourse_zone
|
|
26
|
+
return if public_page?
|
|
27
|
+
|
|
24
28
|
name = cookies[Recourse::ZONE_STORAGE]
|
|
25
29
|
|
|
26
30
|
ActiveSupport::TimeZone[name] if name.present?
|
|
@@ -5,8 +5,8 @@ module Recourse
|
|
|
5
5
|
class BaseController < ApplicationController
|
|
6
6
|
include Pagy::Method, Positioned, AttachmentResolution, AttachmentWriting,
|
|
7
7
|
Landing, Paging, ListResolution, ParameterResolution, ParentNaming,
|
|
8
|
-
ParentResolution, ReferenceResolution, ResourceResolution,
|
|
9
|
-
Weeks, Zoning
|
|
8
|
+
ParentResolution, PublicCaching, ReferenceResolution, ResourceResolution,
|
|
9
|
+
Retrieving, Weeks, Zoning
|
|
10
10
|
|
|
11
11
|
helper Helpers
|
|
12
12
|
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
never has to be remembered here. -%>
|
|
12
12
|
<meta name='turbo-refresh-method' content='morph'>
|
|
13
13
|
<meta name='turbo-refresh-scroll' content='preserve'>
|
|
14
|
-
|
|
14
|
+
<%# No token on a page everybody is shown: the next reader would submit this one's. -%>
|
|
15
|
+
<%= csrf_meta_tags unless public_page? %>
|
|
15
16
|
<%= csp_meta_tag %>
|
|
16
17
|
|
|
17
18
|
<%= yield :head %>
|
|
@@ -52,6 +53,9 @@
|
|
|
52
53
|
own: every screen the gem draws is one to read against their clock. -%>
|
|
53
54
|
<body class='<%= class_names 'recourse-shell', 'recourse-expanded': expanded? %>'
|
|
54
55
|
data-controller='timezone' data-timezone-storage-value='<%= Recourse::ZONE_STORAGE %>'>
|
|
56
|
+
<%# What the reader's browser says, put back before anything is painted, on a page the
|
|
57
|
+
server drew for everybody: the words beside the sidebar's icons. -%>
|
|
58
|
+
<%= render 'recourses/density' if public_page? %>
|
|
55
59
|
<%# First in the page, since a phone reads it first: the row of icons at the top,
|
|
56
60
|
above the trail. From 768px the layout's grid seats it beside the content instead,
|
|
57
61
|
under the navbar, whatever order it was written in. -%>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<%# The density a reader chose last time, back before the page is painted, as the server was
|
|
2
|
+
not told: a page drawn once for everybody is compact, and this widens it. Classic and
|
|
3
|
+
inline for the reason the scheme is: a module runs after the first paint. -%>
|
|
4
|
+
<script>
|
|
5
|
+
(function () {
|
|
6
|
+
if (/(?:^|; )<%= Recourse::DENSITY_STORAGE %>=expanded(?:;|$)/.test(document.cookie)) {
|
|
7
|
+
document.body.classList.add('recourse-expanded')
|
|
8
|
+
}
|
|
9
|
+
})()
|
|
10
|
+
</script>
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
<%= tag.button type: 'button', class: 'nav-link border-0 bg-transparent',
|
|
42
42
|
data: scheme_data do %>
|
|
43
43
|
<span class='recourse-scheme-light'>
|
|
44
|
-
<%= tag.i class: 'bi bi-moon-fill', data: tooltip_data(t('recourse.
|
|
44
|
+
<%= tag.i class: 'bi bi-moon-fill', data: tooltip_data(t('recourse.darken'), placement: 'right') %>
|
|
45
45
|
<span class='recourse-foot-word'><%= t 'recourse.darken' %></span>
|
|
46
46
|
</span>
|
|
47
47
|
<span class='recourse-scheme-dark'>
|
|
48
|
-
<%= tag.i class: 'bi bi-sun-fill', data: tooltip_data(t('recourse.
|
|
48
|
+
<%= tag.i class: 'bi bi-sun-fill', data: tooltip_data(t('recourse.lighten'), placement: 'right') %>
|
|
49
49
|
<span class='recourse-foot-word'><%= t 'recourse.lighten' %></span>
|
|
50
50
|
</span>
|
|
51
51
|
<% end %>
|
|
@@ -60,7 +60,9 @@
|
|
|
60
60
|
<span class='recourse-foot-word'><%= t 'recourse.enter' %></span>
|
|
61
61
|
<% end %>
|
|
62
62
|
<% end %>
|
|
63
|
-
|
|
63
|
+
<%# Nobody in particular is signed in on a page drawn once for everybody, and the button
|
|
64
|
+
carries a token that is theirs alone. -%>
|
|
65
|
+
<% if exit? && !public_page? %>
|
|
64
66
|
<%= button_to exit_path, method: :delete, class: 'nav-link border-0 bg-transparent',
|
|
65
67
|
form: { data: { turbo: false } } do %>
|
|
66
68
|
<%= tag.i class: 'bi bi-box-arrow-right', data: tooltip_data(exit_tooltip, placement: 'right') %>
|
|
@@ -47,7 +47,6 @@ en:
|
|
|
47
47
|
create: Create
|
|
48
48
|
created: "%{record} was created."
|
|
49
49
|
created_error: "%{model} could not be created."
|
|
50
|
-
dark_theme: Dark theme
|
|
51
50
|
darken: Dark
|
|
52
51
|
delete: Delete %{model}
|
|
53
52
|
deleted: "%{model} was deleted."
|
|
@@ -70,7 +69,6 @@ en:
|
|
|
70
69
|
items:
|
|
71
70
|
one: 1 item
|
|
72
71
|
other: "%{count} items"
|
|
73
|
-
light_theme: Light theme
|
|
74
72
|
lighten: Light
|
|
75
73
|
missing_model: You declared `recourses :%{name}` in your routes file, but this app
|
|
76
74
|
has no %{model} model.
|
|
@@ -5,8 +5,9 @@ module Recourse
|
|
|
5
5
|
private
|
|
6
6
|
|
|
7
7
|
# Whether the reader asked for the words: the cookie the arrows write, read back so
|
|
8
|
-
# the page arrives expanded rather than compact and then widened by a script.
|
|
9
|
-
|
|
8
|
+
# the page arrives expanded rather than compact and then widened by a script. Not on a
|
|
9
|
+
# page drawn once for everybody, which the layout's own script widens in the browser.
|
|
10
|
+
def expanded? = !public_page? && cookies[Recourse::DENSITY_STORAGE] == 'expanded'
|
|
10
11
|
|
|
11
12
|
# What the arrows need to switch: the class they toggle and the cookie they write.
|
|
12
13
|
def density_data
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Reopened for what a host says about the pages a shared cache may keep for everybody.
|
|
2
|
+
module Recourse
|
|
3
|
+
# How long each keeper may hold a page a host called cacheable: the reader's browser
|
|
4
|
+
# (`max_age`), a CDN in front of the host (`shared_max_age`), and for how long past
|
|
5
|
+
# that either may show an old page while a new one is fetched (`stale_while_revalidate`)
|
|
6
|
+
# or when the host does not answer (`stale_if_error`). In seconds.
|
|
7
|
+
PUBLIC_CACHE = {
|
|
8
|
+
max_age: 300, shared_max_age: 86_400, stale_while_revalidate: 86_400, stale_if_error: 604_800,
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
# A Proc answering, for one request, whether its page is the same for everybody: run
|
|
13
|
+
# in the controller, so `request` and whatever the host knows of who is reading are
|
|
14
|
+
# there to ask. Nil, as it is until a host says, keeps every page private to the
|
|
15
|
+
# reader, as before. A page it approves is sent with `public` cache headers, and is
|
|
16
|
+
# drawn with none of what a reader's own browser tells the server — their zone, their
|
|
17
|
+
# page size, their density, a form's token — since that page will be shown to others.
|
|
18
|
+
attr_accessor :public_pages
|
|
19
|
+
|
|
20
|
+
# A Proc answering what a public page's ETag is made of, in the controller: whatever
|
|
21
|
+
# changes when the page would, and nothing that changes when it would not. A browser or
|
|
22
|
+
# a CDN then asks whether it still stands and is told `304`, which costs a
|
|
23
|
+
# `SELECT` and no render. Nil sends no ETag of the gem's own.
|
|
24
|
+
attr_accessor :public_version
|
|
25
|
+
|
|
26
|
+
# The numbers PUBLIC_CACHE holds, or a host's own where it has said.
|
|
27
|
+
attr_writer :public_cache
|
|
28
|
+
|
|
29
|
+
# @return [Hash{Symbol => Integer}] the seconds each keeper keeps a public page for.
|
|
30
|
+
def public_cache = PUBLIC_CACHE.merge(@public_cache || {})
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@public_pages = nil
|
|
34
|
+
@public_version = nil
|
|
35
|
+
@public_cache = nil
|
|
36
|
+
end
|
data/lib/recourse/version.rb
CHANGED
data/lib/recourse.rb
CHANGED
|
@@ -25,6 +25,7 @@ require_relative 'recourse/maps'
|
|
|
25
25
|
require_relative 'recourse/orders'
|
|
26
26
|
require_relative 'recourse/positions'
|
|
27
27
|
require_relative 'recourse/positioning'
|
|
28
|
+
require_relative 'recourse/public_caches'
|
|
28
29
|
require_relative 'recourse/writes'
|
|
29
30
|
require_relative 'recourse/zones'
|
|
30
31
|
require_relative 'recourse/positionable'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: recourse
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.
|
|
4
|
+
version: 7.11.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- claudiob
|
|
@@ -43,14 +43,14 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
46
|
+
version: 6.10.1
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version:
|
|
53
|
+
version: 6.10.1
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: pagy
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -144,6 +144,7 @@ files:
|
|
|
144
144
|
- app/controllers/concerns/recourse/parent_naming.rb
|
|
145
145
|
- app/controllers/concerns/recourse/parent_resolution.rb
|
|
146
146
|
- app/controllers/concerns/recourse/positioned.rb
|
|
147
|
+
- app/controllers/concerns/recourse/public_caching.rb
|
|
147
148
|
- app/controllers/concerns/recourse/reference_resolution.rb
|
|
148
149
|
- app/controllers/concerns/recourse/resource_resolution.rb
|
|
149
150
|
- app/controllers/concerns/recourse/retrieving.rb
|
|
@@ -159,6 +160,7 @@ files:
|
|
|
159
160
|
- app/views/recourses/_card.html.erb
|
|
160
161
|
- app/views/recourses/_color.html.erb
|
|
161
162
|
- app/views/recourses/_combobox.html.erb
|
|
163
|
+
- app/views/recourses/_density.html.erb
|
|
162
164
|
- app/views/recourses/_fields.html.erb
|
|
163
165
|
- app/views/recourses/_footer.html.erb
|
|
164
166
|
- app/views/recourses/_form.html.erb
|
|
@@ -254,6 +256,7 @@ files:
|
|
|
254
256
|
- lib/recourse/positionable.rb
|
|
255
257
|
- lib/recourse/positioning.rb
|
|
256
258
|
- lib/recourse/positions.rb
|
|
259
|
+
- lib/recourse/public_caches.rb
|
|
257
260
|
- lib/recourse/recoursive.rb
|
|
258
261
|
- lib/recourse/registry.rb
|
|
259
262
|
- lib/recourse/routes.rb
|