consently 1.0.0 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dab77a672e8b422500eceb9e90f0481fc4f7abb180e22ca7c12fadf98c4ef9ef
4
- data.tar.gz: 802a3a6d8c5915acce9d6e843bb5fd8b10a28a7eafb4dd68d0af8bc8617ba43b
3
+ metadata.gz: 5a4c3433bcb18de24b5fc9849e955ef217502f51f01e4a281e23a493ae18a681
4
+ data.tar.gz: 1347eb53ce32770929522ada6c86a35a068c450cd738466d88913f98019191c8
5
5
  SHA512:
6
- metadata.gz: 3097f21d9037f63e8d27a697e73c2fae5e54b246e1de83e6722a40b2915005400c45ede835a7d8b77aaf1c3da55b877ba6318e23617aad7b3d00de9d0b001e59
7
- data.tar.gz: 4247ab6d468b280bc7b784c52401d1f4ee53d98e627f168206159bea2daebc08ff3c359124afd71849b7cf535de37f9b50b49795e020099c988db74dfd12fc67
6
+ metadata.gz: 532e7e0eab42de4a040fedb640ef70cb575cd651d3b871c743cd3259abf9562f681248c25b1d9d2662321c7d904850544404072ac3da75316cd7fa31962b8a3c
7
+ data.tar.gz: 2b4cbaa8b217c5691836b8b425d716e862642f865018a7dc24da428c9eb3861d35bc3a475238a56ae7857df3038e32462e33b3f7bafb9673dc53af727eebad29
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.0 (2026-10-31)
4
+
5
+ - The banner asks only about categories in use: one with a tag in it (in any
6
+ scope) or one declared with `c.category`. A site with nothing but Google
7
+ Analytics no longer shows a "Marketing" checkbox for marketing it does not
8
+ do. `c.categories = [...]` fixes the list by hand.
9
+ - The banner message follows the categories: analytics alone reads "to
10
+ understand how it is used and improve it", without the "relevant offers"
11
+ that only marketing justifies. Every locale has the new `message_analytics`
12
+ and `message_marketing`.
13
+ - No optional category, no banner: there is nothing to ask.
14
+ - Checkboxes are drawn by the gem's CSS instead of left native, so a reset in
15
+ the host app (`@tailwindcss/forms` turns every checkbox square) no longer
16
+ restyles them. `--consently-checkbox-border` and
17
+ `--consently-checkbox-radius` tune them.
18
+ - **Upgrading:** `consently_embed` raises when its category is not offered,
19
+ since the visitor could never release it. A site whose embeds wait for
20
+ `marketing` without any marketing tag adds `c.category :marketing`.
21
+
3
22
  ## 1.0.0 (2026-09-04)
4
23
 
5
24
  - Events speak the language of the page's tags: `gtag('event', ...)` under
data/README.md CHANGED
@@ -102,11 +102,25 @@ c.tag :custom, as: :inline_thing, category: :marketing, inline: "console.log('hi
102
102
 
103
103
  ## Consent
104
104
 
105
- Three categories out of the box - `necessary`, `analytics`, `marketing` - and
106
- you can add your own:
105
+ Three categories out of the box - `necessary`, `analytics`, `marketing` - but
106
+ the banner only asks about the ones you use: a category is offered when a tag
107
+ sits in it (in any scope) or when you declare it. A site running nothing but
108
+ Google Analytics gets a single checkbox, and a message that does not promise
109
+ "relevant offers" it never makes. With only `necessary` tags there is nothing
110
+ to ask, and no banner.
111
+
112
+ Declare a category that only your views use - an embed, an event - or one of
113
+ your own:
114
+
115
+ ```ruby
116
+ c.category :marketing # a YouTube embed waits for it
117
+ c.category :personalization # name it in your locale file
118
+ ```
119
+
120
+ Or fix the list and its order outright (`necessary` is always added):
107
121
 
108
122
  ```ruby
109
- c.category :personalization
123
+ c.categories = %i[analytics marketing]
110
124
  ```
111
125
 
112
126
  `necessary` never waits for a click. Everything else is blocked until the
@@ -427,7 +441,8 @@ Configuration:
427
441
  | --- | --- |
428
442
  | `c.tag key, **options` | `id:`, `domain:`, `category:`, `as:` (name it, for two of a kind) |
429
443
  | `c.scope(name) { \|s\| ... }` | tags for one tenant; `c.scope_resolver` decides which one a request is |
430
- | `c.category :name` | a category of your own beside `necessary`, `analytics`, `marketing` |
444
+ | `c.category :name` | offer a category no tag uses: your own, or a built-in one for embeds and events |
445
+ | `c.categories = [...]` | fix the offered categories instead of deriving them from the tags |
431
446
  | `c.consent_version` | bump it and every older consent stops counting |
432
447
  | `c.enabled` | `true`, `false`, or a callable taking the request |
433
448
  | `c.reload_after_choice` | reload once a choice is made; off by default |
@@ -23,6 +23,8 @@
23
23
  --consently-radius: 0.75rem;
24
24
  --consently-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
25
25
  --consently-max-width: 36rem;
26
+ --consently-checkbox-border: #d1d5db;
27
+ --consently-checkbox-radius: 0.25rem;
26
28
  }
27
29
 
28
30
  .consently {
@@ -90,11 +92,46 @@
90
92
  line-height: 1.5rem;
91
93
  }
92
94
 
93
- .consently__option input {
94
- margin: 0.3rem 0 0;
95
+ /* Drawn by hand rather than left native: CSS resets in the host app
96
+ (@tailwindcss/forms and friends) restyle every [type=checkbox] on the page
97
+ and the banner ends up with square boxes in the wrong colour. These
98
+ selectors outrank such resets, so the box looks the same everywhere. */
99
+ .consently__option input[type="checkbox"] {
100
+ -webkit-appearance: none;
101
+ appearance: none;
102
+ flex: none;
103
+ box-sizing: border-box;
104
+ display: inline-block;
105
+ margin: 0.25rem 0 0;
106
+ padding: 0;
95
107
  width: 1rem;
96
108
  height: 1rem;
97
- accent-color: var(--consently-accent);
109
+ border: 1px solid var(--consently-checkbox-border);
110
+ border-radius: var(--consently-checkbox-radius);
111
+ background: var(--consently-background) none center / 100% 100% no-repeat;
112
+ box-shadow: none;
113
+ cursor: pointer;
114
+ }
115
+
116
+ .consently__option input[type="checkbox"]:checked {
117
+ border-color: transparent;
118
+ background-color: var(--consently-accent);
119
+ background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
120
+ }
121
+
122
+ .consently__option input[type="checkbox"]:disabled {
123
+ cursor: default;
124
+ opacity: 0.6;
125
+ }
126
+
127
+ .consently__option input[type="checkbox"]:focus {
128
+ box-shadow: none;
129
+ outline: none;
130
+ }
131
+
132
+ .consently__option input[type="checkbox"]:focus-visible {
133
+ outline: 2px solid var(--consently-accent);
134
+ outline-offset: 2px;
98
135
  }
99
136
 
100
137
  .consently__option-name {
@@ -42,6 +42,8 @@ module Consently
42
42
  # `consently_preferences_link` has something to reopen.
43
43
  def consently_banner(policy_url: nil)
44
44
  return "".html_safe unless consently_enabled? && Consently.consent_required?(request)
45
+ # Only necessary cookies: nothing to ask, so no banner.
46
+ return "".html_safe if Consently.config.optional_categories.empty?
45
47
 
46
48
  render "consently/banner",
47
49
  consent: consently_consent,
@@ -149,6 +151,13 @@ module Consently
149
151
  # button that opens the preferences panel; the iframe appears the moment
150
152
  # they agree, without a reload.
151
153
  def consently_embed(kind, identifier, category: :marketing, title: nil, ratio: "16 / 9", **iframe_options)
154
+ # The placeholder sends the visitor to a checkbox; without one the
155
+ # embed could never be released.
156
+ unless Consently.config.category?(category)
157
+ raise ArgumentError, "consently_embed waits for the #{category} category, which the banner does not offer - " \
158
+ "add `c.category :#{category}` to config/initializers/consently.rb"
159
+ end
160
+
152
161
  src = consently_embed_src(kind, identifier)
153
162
  return "".html_safe if src.blank?
154
163
 
@@ -32,7 +32,9 @@
32
32
  aria-labelledby="consently-message"
33
33
  tabindex="-1">
34
34
  <p class="consently__text" id="consently-message">
35
- <%= t("consently.banner.message") %>
35
+ <%# The message names only what is asked for: a site without marketing
36
+ tags does not promise "relevant offers". %>
37
+ <%= t("consently.banner.message_#{categories.map(&:to_s).sort.join("_")}", default: t("consently.banner.message")) %>
36
38
  <% if policy_url.present? %>
37
39
  <%= link_to t("consently.banner.policy_link"), policy_url, class: "consently__link" %>
38
40
  <% end %>
@@ -2,6 +2,8 @@ cs:
2
2
  consently:
3
3
  banner:
4
4
  message: "Používáme soubory cookie, aby tento web fungoval, a s vaším souhlasem také k tomu, abychom rozuměli, jak jej používáte, a mohli vám ukazovat relevantní nabídky."
5
+ message_analytics: "Používáme soubory cookie, aby tento web fungoval, a s vaším souhlasem také k tomu, abychom rozuměli, jak jej používáte, a mohli jej zlepšovat."
6
+ message_marketing: "Používáme soubory cookie, aby tento web fungoval, a s vaším souhlasem také k tomu, abychom vám mohli ukazovat relevantní nabídky."
5
7
  policy_link: "Přečtěte si naše zásady cookies."
6
8
  accept_all: "Přijmout vše"
7
9
  reject_all: "Odmítnout vše"
@@ -2,6 +2,8 @@ de:
2
2
  consently:
3
3
  banner:
4
4
  message: "Wir verwenden Cookies, damit diese Website funktioniert - und mit Ihrer Einwilligung, um zu verstehen, wie sie genutzt wird, und Ihnen passende Angebote zu zeigen."
5
+ message_analytics: "Wir verwenden Cookies, damit diese Website funktioniert - und mit Ihrer Einwilligung, um zu verstehen, wie sie genutzt wird, und sie zu verbessern."
6
+ message_marketing: "Wir verwenden Cookies, damit diese Website funktioniert - und mit Ihrer Einwilligung, um Ihnen passende Angebote zu zeigen."
5
7
  policy_link: "Zur Cookie-Richtlinie."
6
8
  accept_all: "Alle akzeptieren"
7
9
  reject_all: "Alle ablehnen"
@@ -2,6 +2,8 @@ en:
2
2
  consently:
3
3
  banner:
4
4
  message: "We use cookies to run this site, and - with your consent - to understand how it is used and to show you relevant offers."
5
+ message_analytics: "We use cookies to run this site, and - with your consent - to understand how it is used and improve it."
6
+ message_marketing: "We use cookies to run this site, and - with your consent - to show you relevant offers."
5
7
  policy_link: "Read our cookie policy."
6
8
  accept_all: "Accept all"
7
9
  reject_all: "Reject all"
@@ -2,6 +2,8 @@ es:
2
2
  consently:
3
3
  banner:
4
4
  message: "Usamos cookies para que este sitio funcione y, con tu consentimiento, para entender cómo se usa y mostrarte ofertas relevantes."
5
+ message_analytics: "Usamos cookies para que este sitio funcione y, con tu consentimiento, para entender cómo se usa y mejorarlo."
6
+ message_marketing: "Usamos cookies para que este sitio funcione y, con tu consentimiento, para mostrarte ofertas relevantes."
5
7
  policy_link: "Lee nuestra política de cookies."
6
8
  accept_all: "Aceptar todo"
7
9
  reject_all: "Rechazar todo"
@@ -2,6 +2,8 @@ fr:
2
2
  consently:
3
3
  banner:
4
4
  message: "Nous utilisons des cookies pour faire fonctionner ce site et, avec votre accord, pour comprendre comment il est utilisé et vous proposer des offres pertinentes."
5
+ message_analytics: "Nous utilisons des cookies pour faire fonctionner ce site et, avec votre accord, pour comprendre comment il est utilisé et l'améliorer."
6
+ message_marketing: "Nous utilisons des cookies pour faire fonctionner ce site et, avec votre accord, pour vous proposer des offres pertinentes."
5
7
  policy_link: "Consulter notre politique de cookies."
6
8
  accept_all: "Tout accepter"
7
9
  reject_all: "Tout refuser"
@@ -2,6 +2,8 @@ hu:
2
2
  consently:
3
3
  banner:
4
4
  message: "Sütiket használunk, hogy az oldal működjön, és - a hozzájárulásoddal - hogy megértsük, hogyan használod, illetve releváns ajánlatokat mutassunk."
5
+ message_analytics: "Sütiket használunk, hogy az oldal működjön, és - a hozzájárulásoddal - hogy megértsük, hogyan használod, és fejleszthessük."
6
+ message_marketing: "Sütiket használunk, hogy az oldal működjön, és - a hozzájárulásoddal - hogy releváns ajánlatokat mutassunk."
5
7
  policy_link: "Olvasd el a süti szabályzatunkat."
6
8
  accept_all: "Összes elfogadása"
7
9
  reject_all: "Összes elutasítása"
@@ -2,6 +2,8 @@ it:
2
2
  consently:
3
3
  banner:
4
4
  message: "Usiamo i cookie per far funzionare questo sito e, con il tuo consenso, per capire come viene usato e mostrarti offerte pertinenti."
5
+ message_analytics: "Usiamo i cookie per far funzionare questo sito e, con il tuo consenso, per capire come viene usato e migliorarlo."
6
+ message_marketing: "Usiamo i cookie per far funzionare questo sito e, con il tuo consenso, per mostrarti offerte pertinenti."
5
7
  policy_link: "Leggi la nostra cookie policy."
6
8
  accept_all: "Accetta tutto"
7
9
  reject_all: "Rifiuta tutto"
@@ -2,6 +2,8 @@ nl:
2
2
  consently:
3
3
  banner:
4
4
  message: "We gebruiken cookies om deze site te laten werken en, met jouw toestemming, om te begrijpen hoe de site gebruikt wordt en je relevante aanbiedingen te tonen."
5
+ message_analytics: "We gebruiken cookies om deze site te laten werken en, met jouw toestemming, om te begrijpen hoe de site gebruikt wordt en hem te verbeteren."
6
+ message_marketing: "We gebruiken cookies om deze site te laten werken en, met jouw toestemming, om je relevante aanbiedingen te tonen."
5
7
  policy_link: "Lees ons cookiebeleid."
6
8
  accept_all: "Alles accepteren"
7
9
  reject_all: "Alles weigeren"
@@ -2,6 +2,8 @@ pl:
2
2
  consently:
3
3
  banner:
4
4
  message: "Używamy plików cookie, żeby ta strona działała, a za Twoją zgodą także po to, by rozumieć, jak z niej korzystasz, i pokazywać Ci trafne oferty."
5
+ message_analytics: "Używamy plików cookie, żeby ta strona działała, a za Twoją zgodą także po to, by rozumieć, jak z niej korzystasz, i ją ulepszać."
6
+ message_marketing: "Używamy plików cookie, żeby ta strona działała, a za Twoją zgodą także po to, by pokazywać Ci trafne oferty."
5
7
  policy_link: "Przeczytaj naszą politykę cookies."
6
8
  accept_all: "Akceptuję wszystkie"
7
9
  reject_all: "Odrzuć wszystkie"
@@ -2,6 +2,8 @@ sk:
2
2
  consently:
3
3
  banner:
4
4
  message: "Používame súbory cookie, aby táto stránka fungovala, a s vaším súhlasom aj na to, aby sme rozumeli, ako ju používate, a mohli vám ukazovať relevantné ponuky."
5
+ message_analytics: "Používame súbory cookie, aby táto stránka fungovala, a s vaším súhlasom aj na to, aby sme rozumeli, ako ju používate, a mohli ju zlepšovať."
6
+ message_marketing: "Používame súbory cookie, aby táto stránka fungovala, a s vaším súhlasom aj na to, aby sme vám mohli ukazovať relevantné ponuky."
5
7
  policy_link: "Prečítajte si naše zásady cookies."
6
8
  accept_all: "Prijať všetko"
7
9
  reject_all: "Odmietnuť všetko"
@@ -1,7 +1,9 @@
1
1
  module Consently
2
2
  # Everything the host application sets in config/initializers/consently.rb.
3
3
  class Configuration
4
- DEFAULT_CATEGORIES = %i[necessary analytics marketing].freeze
4
+ # The categories Consently knows by name, in the order the banner lists
5
+ # them. Only the ones in use are offered - see #categories.
6
+ BUILT_IN_CATEGORIES = %i[necessary analytics marketing].freeze
5
7
 
6
8
  # Where the visitor's choice is kept. It is read by JavaScript, so it is a
7
9
  # plain cookie rather than a signed one.
@@ -113,8 +115,6 @@ module Consently
113
115
  # Nil means every request sees the default configuration.
114
116
  attr_accessor :scope_resolver
115
117
 
116
- attr_reader :categories
117
-
118
118
  def initialize
119
119
  @cookie_name = "consently"
120
120
  @cookie_max_age = 60 * 60 * 24 * 180 # six months, the usual guidance
@@ -134,18 +134,45 @@ module Consently
134
134
  @consent_subject = nil
135
135
  @policy_url = nil
136
136
  @scope_resolver = nil
137
- @categories = DEFAULT_CATEGORIES.dup
137
+ @categories = nil
138
+ @declared_categories = []
138
139
  @scopes = {}
139
140
  end
140
141
 
141
- # A category of your own, shown in the preferences panel alongside the
142
- # built-in ones. Give it a name in your locale file.
142
+ # The categories a visitor is asked about, `necessary` always first.
143
+ #
144
+ # Worked out from the tags (in every scope) plus whatever was declared with
145
+ # `category`: a site running only Google Analytics asks about analytics and
146
+ # nothing else. Asking for consent to marketing nobody does is not just
147
+ # noise - it tells the visitor something about the site that is not true.
148
+ #
149
+ # `categories=` replaces the guess with a fixed list, for categories that
150
+ # only views use (an embed, an event) or to keep the order your own.
151
+ def categories
152
+ return @categories if @categories
153
+
154
+ in_use = (all_tags.map(&:category) + @declared_categories).uniq
155
+ [ Consent::NECESSARY, *(BUILT_IN_CATEGORIES & in_use), *(in_use - BUILT_IN_CATEGORIES) ].uniq
156
+ end
157
+
158
+ def categories=(names)
159
+ @categories = [ Consent::NECESSARY, *Array(names).map(&:to_sym) ].uniq
160
+ end
161
+
162
+ # A category to offer even with no tag in it - one of your own, or a
163
+ # built-in one that only an embed or an event uses. Give your own a name
164
+ # and a description in your locale file.
143
165
  def category(name)
144
166
  name = name.to_sym
145
- @categories << name unless @categories.include?(name)
167
+ @declared_categories << name unless @declared_categories.include?(name)
168
+ @categories << name if @categories && !@categories.include?(name)
146
169
  name
147
170
  end
148
171
 
172
+ def category?(name)
173
+ categories.include?(name.to_sym)
174
+ end
175
+
149
176
  def optional_categories
150
177
  categories - [ Consent::NECESSARY ]
151
178
  end
@@ -178,6 +205,12 @@ module Consently
178
205
  @default_scope ||= Scope.new
179
206
  end
180
207
 
208
+ # Every tag in every scope, overridden ones included: a category one
209
+ # tenant needs is offered to all, since the banner is the same for each.
210
+ def all_tags
211
+ [ default_scope, *@scopes.values ].flat_map { |scope| scope.tags.values }
212
+ end
213
+
181
214
  # A named bundle of tags. Same API as the top-level `tag`, which is what
182
215
  # makes the block form read the way it does.
183
216
  class Scope
@@ -1,3 +1,3 @@
1
1
  module Consently
2
- VERSION = "1.0.0"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -44,7 +44,9 @@ c.google_consent_mode = :basic
44
44
  # The log is anonymous unless you say who made the decision:
45
45
  # c.consent_subject = ->(request) { request.env["warden"]&.user&.id }
46
46
 
47
- # A category of your own, on top of :necessary, :analytics and :marketing.
47
+ # The banner offers only the categories your tags use. Declare one that only
48
+ # an embed or an event needs, or one of your own:
49
+ # c.category :marketing
48
50
  # c.category :personalization
49
51
 
50
52
  # Your tags. Each one waits for its category unless that category is
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: consently
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michał Krzysteczko
@@ -123,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  requirements: []
126
- rubygems_version: 4.0.18
126
+ rubygems_version: 4.0.20
127
127
  specification_version: 4
128
128
  summary: GDPR cookie consent for Rails that actually blocks your analytics tags until
129
129
  the visitor agrees