consently 0.1.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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +279 -0
  5. data/Rakefile +6 -0
  6. data/app/assets/stylesheets/consently.css +252 -0
  7. data/app/controllers/consently/application_controller.rb +4 -0
  8. data/app/controllers/consently/consents_controller.rb +44 -0
  9. data/app/helpers/consently/application_helper.rb +4 -0
  10. data/app/helpers/consently/tags_helper.rb +176 -0
  11. data/app/javascript/consently/banner_controller.js +190 -0
  12. data/app/models/consently/application_record.rb +5 -0
  13. data/app/models/consently/consent_record.rb +18 -0
  14. data/app/views/consently/_banner.html.erb +93 -0
  15. data/app/views/consently/_policy.html.erb +85 -0
  16. data/config/importmap.rb +3 -0
  17. data/config/locales/cs.yml +33 -0
  18. data/config/locales/de.yml +33 -0
  19. data/config/locales/en.yml +33 -0
  20. data/config/locales/es.yml +33 -0
  21. data/config/locales/fr.yml +33 -0
  22. data/config/locales/hu.yml +33 -0
  23. data/config/locales/it.yml +33 -0
  24. data/config/locales/nl.yml +33 -0
  25. data/config/locales/pl.yml +33 -0
  26. data/config/locales/sk.yml +33 -0
  27. data/config/routes.rb +5 -0
  28. data/lib/consently/configuration.rb +150 -0
  29. data/lib/consently/consent.rb +62 -0
  30. data/lib/consently/cookie.rb +12 -0
  31. data/lib/consently/engine.rb +33 -0
  32. data/lib/consently/providers/base.rb +95 -0
  33. data/lib/consently/providers/clarity.rb +34 -0
  34. data/lib/consently/providers/custom.rb +34 -0
  35. data/lib/consently/providers/google_ads.rb +32 -0
  36. data/lib/consently/providers/google_analytics.rb +45 -0
  37. data/lib/consently/providers/google_tag_manager.rb +34 -0
  38. data/lib/consently/providers/hotjar.rb +39 -0
  39. data/lib/consently/providers/meta_pixel.rb +37 -0
  40. data/lib/consently/providers/plausible.rb +30 -0
  41. data/lib/consently/providers.rb +9 -0
  42. data/lib/consently/script.rb +17 -0
  43. data/lib/consently/version.rb +3 -0
  44. data/lib/consently.rb +74 -0
  45. data/lib/generators/consently/consent_log/consent_log_generator.rb +28 -0
  46. data/lib/generators/consently/consent_log/templates/create_consently_consent_records.rb +19 -0
  47. data/lib/generators/consently/install/install_generator.rb +44 -0
  48. data/lib/generators/consently/install/templates/consently.rb +63 -0
  49. data/lib/generators/consently/views/views_generator.rb +15 -0
  50. data/lib/tasks/consently_tasks.rake +4 -0
  51. metadata +121 -0
@@ -0,0 +1,34 @@
1
+ module Consently
2
+ module Providers
3
+ # The escape hatch for a vendor Consently does not know about yet. Blocked
4
+ # and released exactly like a built-in one.
5
+ #
6
+ # c.tag :custom, as: :piwik, category: :analytics,
7
+ # src: "https://cdn.example.com/piwik.js"
8
+ #
9
+ # c.tag :custom, as: :whatever, category: :marketing, inline: <<~JS
10
+ # console.log("hello");
11
+ # JS
12
+ class Custom < Base
13
+ self.provider_key = :custom
14
+ self.default_category = :marketing
15
+
16
+ def scripts
17
+ [ Script.new(src: options[:src], inline: options[:inline], async: options.fetch(:async, true), data: options[:data]) ]
18
+ end
19
+
20
+ def noscript
21
+ options[:noscript]
22
+ end
23
+
24
+ private
25
+
26
+ def validate!
27
+ if options[:src].blank? && options[:inline].blank?
28
+ raise MissingOption, "Consently: a :custom tag needs src: or inline:"
29
+ end
30
+ raise MissingOption, "Consently: a :custom tag needs as: to name it" if options[:as].blank?
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ module Consently
2
+ module Providers
3
+ class GoogleAds < Base
4
+ self.provider_key = :google_ads
5
+ self.default_category = :marketing
6
+
7
+ cookie "_gcl_au", days: 90
8
+
9
+ def scripts
10
+ [
11
+ Script.new(src: "https://www.googletagmanager.com/gtag/js?id=#{id}", async: true),
12
+ Script.new(inline: <<~JS.strip)
13
+ window.dataLayer = window.dataLayer || [];
14
+ function gtag(){dataLayer.push(arguments);}
15
+ gtag('js', new Date());
16
+ gtag('config', '#{id}');
17
+ JS
18
+ ]
19
+ end
20
+
21
+ private
22
+
23
+ def id
24
+ @id ||= require_option(:id)
25
+ end
26
+
27
+ def validate!
28
+ id
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,45 @@
1
+ module Consently
2
+ module Providers
3
+ # GA4. Pass `anonymize: false` to leave IP anonymisation off; it is on by
4
+ # default because most European deployments want it.
5
+ class GoogleAnalytics < Base
6
+ self.provider_key = :google_analytics
7
+ self.default_category = :analytics
8
+
9
+ # What GA4 leaves behind. The container-scoped one is written per
10
+ # measurement id, hence the wildcard.
11
+ cookie "_ga", days: 730
12
+ cookie "_ga_*", days: 730
13
+ cookie "_gid", days: 1
14
+ cookie "_gat", days: nil
15
+
16
+ def scripts
17
+ [
18
+ Script.new(src: "https://www.googletagmanager.com/gtag/js?id=#{id}", async: true),
19
+ Script.new(inline: <<~JS.strip)
20
+ window.dataLayer = window.dataLayer || [];
21
+ function gtag(){dataLayer.push(arguments);}
22
+ gtag('js', new Date());
23
+ gtag('config', '#{id}'#{config_options});
24
+ JS
25
+ ]
26
+ end
27
+
28
+ private
29
+
30
+ def id
31
+ @id ||= require_option(:id)
32
+ end
33
+
34
+ def config_options
35
+ return "" unless options.fetch(:anonymize, true)
36
+
37
+ ", { 'anonymize_ip': true }"
38
+ end
39
+
40
+ def validate!
41
+ id
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,34 @@
1
+ module Consently
2
+ module Providers
3
+ class GoogleTagManager < Base
4
+ self.provider_key = :google_tag_manager
5
+ self.default_category = :analytics
6
+
7
+ # None of its own: whatever it loads brings its own cookies, so list
8
+ # those tags here as well if you manage them through the container.
9
+
10
+ def scripts
11
+ [ Script.new(inline: <<~JS.strip) ]
12
+ (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});
13
+ var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';
14
+ j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
15
+ })(window,document,'script','dataLayer','#{id}');
16
+ JS
17
+ end
18
+
19
+ def noscript
20
+ %(<iframe src="https://www.googletagmanager.com/ns.html?id=#{id}" height="0" width="0" style="display:none;visibility:hidden"></iframe>)
21
+ end
22
+
23
+ private
24
+
25
+ def id
26
+ @id ||= require_option(:id)
27
+ end
28
+
29
+ def validate!
30
+ id
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ module Consently
2
+ module Providers
3
+ class Hotjar < Base
4
+ self.provider_key = :hotjar
5
+ self.default_category = :analytics
6
+
7
+ cookie "_hjSessionUser_*", days: 365
8
+ cookie "_hjSession_*", days: nil
9
+
10
+ def scripts
11
+ [ Script.new(inline: <<~JS.strip) ]
12
+ (function(h,o,t,j,a,r){h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
13
+ h._hjSettings={hjid:#{id},hjsv:#{version}};a=o.getElementsByTagName('head')[0];
14
+ r=o.createElement('script');r.async=1;r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
15
+ a.appendChild(r);})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
16
+ JS
17
+ end
18
+
19
+ private
20
+
21
+ # Numeric on purpose: the snippet uses the id unquoted.
22
+ def id
23
+ @id ||= Integer(require_option(:id))
24
+ rescue ArgumentError
25
+ raise MissingOption, "Consently: hotjar id: must be a number"
26
+ end
27
+
28
+ # Hotjar's snippet version, not the site id; it has been 6 for years.
29
+ def version
30
+ Integer(options.fetch(:snippet_version, 6))
31
+ end
32
+
33
+ def validate!
34
+ id
35
+ version
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,37 @@
1
+ module Consently
2
+ module Providers
3
+ class MetaPixel < Base
4
+ self.provider_key = :meta_pixel
5
+ self.default_category = :marketing
6
+
7
+ cookie "_fbp", days: 90
8
+ cookie "fr", days: 90
9
+
10
+ def scripts
11
+ [ Script.new(inline: <<~JS.strip) ]
12
+ !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
13
+ n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
14
+ n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
15
+ t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
16
+ document,'script','https://connect.facebook.net/en_US/fbevents.js');
17
+ fbq('init', '#{id}');
18
+ fbq('track', 'PageView');
19
+ JS
20
+ end
21
+
22
+ def noscript
23
+ %(<img height="1" width="1" style="display:none" alt="" src="https://www.facebook.com/tr?id=#{id}&ev=PageView&noscript=1">)
24
+ end
25
+
26
+ private
27
+
28
+ def id
29
+ @id ||= require_option(:id)
30
+ end
31
+
32
+ def validate!
33
+ id
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,30 @@
1
+ module Consently
2
+ module Providers
3
+ # Cookieless, and often run without a banner at all - but whether that is
4
+ # allowed is a legal call, not ours, so it waits for consent like any
5
+ # other analytics tag. Move it with category: :necessary if your legal
6
+ # advice says it may run right away.
7
+ class Plausible < Base
8
+ self.provider_key = :plausible
9
+ self.default_category = :analytics
10
+
11
+ def scripts
12
+ [ Script.new(src: "#{host}/js/script.js", defer: true, data: { "domain" => domain }) ]
13
+ end
14
+
15
+ private
16
+
17
+ def domain
18
+ @domain ||= require_option(:domain)
19
+ end
20
+
21
+ def host
22
+ @host ||= options.fetch(:host, "https://plausible.io").to_s.chomp("/")
23
+ end
24
+
25
+ def validate!
26
+ domain
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ require "consently/providers/base"
2
+ require "consently/providers/google_tag_manager"
3
+ require "consently/providers/google_analytics"
4
+ require "consently/providers/google_ads"
5
+ require "consently/providers/clarity"
6
+ require "consently/providers/meta_pixel"
7
+ require "consently/providers/hotjar"
8
+ require "consently/providers/plausible"
9
+ require "consently/providers/custom"
@@ -0,0 +1,17 @@
1
+ module Consently
2
+ # One <script> a provider wants on the page - either an external file or an
3
+ # inline body. Keeping it as data rather than as HTML is what lets the view
4
+ # render the very same script either live or blocked, without the provider
5
+ # knowing which of the two happened.
6
+ Script = Struct.new(:src, :inline, :async, :defer, :data, keyword_init: true) do
7
+ def external?
8
+ src.present?
9
+ end
10
+
11
+ # Extra data-* attributes some vendors configure the tag with, e.g.
12
+ # Plausible's data-domain.
13
+ def data_attributes
14
+ data || {}
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Consently
2
+ VERSION = "0.1.0"
3
+ end
data/lib/consently.rb ADDED
@@ -0,0 +1,74 @@
1
+ require "json"
2
+ require "active_support"
3
+ require "active_support/core_ext/object/blank"
4
+
5
+ require "consently/version"
6
+ require "consently/script"
7
+ require "consently/cookie"
8
+ require "consently/consent"
9
+ require "consently/providers/base"
10
+ require "consently/providers"
11
+ require "consently/configuration"
12
+ require "consently/engine" if defined?(Rails::Engine)
13
+
14
+ # Tag snippets and cookie consent in one place.
15
+ #
16
+ # Everything the host application needs goes through `Consently.configure`;
17
+ # the views ask this module what to render for the current request.
18
+ module Consently
19
+ class Error < StandardError; end
20
+
21
+ # A provider key that was never registered - almost always a typo in the
22
+ # initializer, so the message lists what is available.
23
+ class UnknownProvider < Error
24
+ def initialize(key)
25
+ super("Unknown Consently provider #{key.inspect}. Available: #{Providers::Base.registry.keys.sort.join(", ")}")
26
+ end
27
+ end
28
+
29
+ class MissingOption < Error; end
30
+
31
+ class << self
32
+ def configure
33
+ yield config
34
+ config
35
+ end
36
+
37
+ def config
38
+ @config ||= Configuration.new
39
+ end
40
+
41
+ # Mostly for tests and for reloading an initializer in development.
42
+ def reset!
43
+ @config = Configuration.new
44
+ end
45
+
46
+ # Tags configured for this request: the defaults, plus whatever the
47
+ # matching scope adds or overrides.
48
+ def tags_for(request = nil)
49
+ config.tags_for(scope_for(request))
50
+ end
51
+
52
+ def scope_for(request)
53
+ return nil unless config.scope_resolver && request
54
+
55
+ config.scope_resolver.call(request)
56
+ end
57
+
58
+ def enabled?(request = nil)
59
+ resolve(config.enabled, request)
60
+ end
61
+
62
+ # Whether this visitor has to be asked. When they do not, every category
63
+ # counts as granted and no banner is rendered - see config.consent_required.
64
+ def consent_required?(request = nil)
65
+ resolve(config.consent_required, request)
66
+ end
67
+
68
+ private
69
+
70
+ def resolve(setting, request)
71
+ setting.respond_to?(:call) ? !!setting.call(request) : !!setting
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,28 @@
1
+ require "rails/generators/base"
2
+ require "rails/generators/active_record"
3
+
4
+ module Consently
5
+ module Generators
6
+ class ConsentLogGenerator < Rails::Generators::Base
7
+ include ActiveRecord::Generators::Migration
8
+
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ desc "Adds the table behind Consently::ConsentRecord, for proof of consent."
12
+
13
+ def create_migration_file
14
+ migration_template "create_consently_consent_records.rb", "db/migrate/create_consently_consent_records.rb"
15
+ end
16
+
17
+ def show_next_steps
18
+ say <<~TEXT
19
+
20
+ Next:
21
+ 1. rails db:migrate
22
+ 2. mount Consently::Engine => "/consently" # config/routes.rb
23
+ 3. c.log_consents = true # config/initializers/consently.rb
24
+ TEXT
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ class CreateConsentlyConsentRecords < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :consently_consent_records do |t|
4
+ t.string :categories, null: false, default: ""
5
+ t.string :consent_version, null: false
6
+ t.string :subject
7
+ t.string :scope
8
+ t.string :user_agent
9
+ # A salted digest rather than the address itself: enough to tell two
10
+ # decisions apart, not enough to identify anyone.
11
+ t.string :ip_hash
12
+
13
+ t.datetime :created_at, null: false
14
+ end
15
+
16
+ add_index :consently_consent_records, :created_at
17
+ add_index :consently_consent_records, :consent_version
18
+ end
19
+ end
@@ -0,0 +1,44 @@
1
+ require "rails/generators/base"
2
+
3
+ module Consently
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Creates the Consently initializer and wires the banner controller up."
9
+
10
+ def create_initializer
11
+ template "consently.rb", "config/initializers/consently.rb"
12
+ end
13
+
14
+ # Stimulus is how the banner releases the blocked tags, so registering
15
+ # the controller is part of installing, not a step in a README nobody
16
+ # reads.
17
+ def register_stimulus_controller
18
+ index = "app/javascript/controllers/index.js"
19
+ return say_status :skip, "#{index} not found - register consently-banner yourself", :yellow unless File.exist?(index)
20
+
21
+ append_to_file index, <<~JS
22
+
23
+ import ConsentlyBannerController from "consently/banner_controller"
24
+ application.register("consently-banner", ConsentlyBannerController)
25
+ JS
26
+ end
27
+
28
+ def show_readme
29
+ say <<~TEXT
30
+
31
+ Consently is installed. One thing left - put the helpers in your layout:
32
+
33
+ <head> <%= consently_tags %>
34
+ <body> <%= consently_noscript_tags %> ... <%= consently_banner %>
35
+
36
+ The banner brings its own CSS; restyle it with the custom properties
37
+ on .consently, or run `rails g consently:views` to take the markup over.
38
+
39
+ Consent logging (optional): rails g consently:consent_log
40
+ TEXT
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,63 @@
1
+ Consently.configure do |c|
2
+ # Where the tags run. Anything callable gets the request.
3
+ c.enabled = ->(request) { Rails.env.production? }
4
+
5
+ # Bump when your cookie policy changes: older consents stop counting and
6
+ # the banner asks again.
7
+ c.consent_version = 1
8
+
9
+ # Link shown in the banner. A callable receives the view context, which is
10
+ # what you want when the URL is locale dependent.
11
+ # c.policy_url = -> (view) { view.main_app.cookie_policy_path }
12
+
13
+ # Reload the page once a choice is made. Off by default: the tags start
14
+ # running in place, so there is nothing to reload for. Turn it on if the
15
+ # page renders differently depending on consent.
16
+ # c.reload_after_choice = true
17
+
18
+ # Whether this visitor has to be asked at all. Return false and nothing is
19
+ # held back and no banner is shown - for traffic outside the EU, if your
20
+ # legal advice says so.
21
+ # c.consent_required = ->(request) { EU.include?(request.headers["CF-IPCountry"]) }
22
+
23
+ # Global Privacy Control (binding in California and Colorado) counts as a
24
+ # rejection and is honoured by default. Do Not Track is advisory, so it is
25
+ # not - switch it on if you want it treated the same way.
26
+ # c.respect_do_not_track = true
27
+
28
+ # Google consent mode v2: defaults denied before any Google tag, updated
29
+ # when the visitor chooses. Leave on if you use anything Google.
30
+ c.google_consent_mode = true
31
+
32
+ # Store a row per decision as proof of consent. Needs the migration from
33
+ # `rails g consently:consent_log` and the engine mounted in routes.rb:
34
+ # mount Consently::Engine => "/consently"
35
+ # c.log_consents = true
36
+ #
37
+ # The log is anonymous unless you say who made the decision:
38
+ # c.consent_subject = ->(request) { request.env["warden"]&.user&.id }
39
+
40
+ # A category of your own, on top of :necessary, :analytics and :marketing.
41
+ # c.category :personalization
42
+
43
+ # Your tags. Each one waits for its category unless that category is
44
+ # :necessary.
45
+ # c.tag :google_tag_manager, id: "GTM-XXXXXXX"
46
+ # c.tag :google_analytics, id: "G-XXXXXXXXXX"
47
+ # c.tag :clarity, id: "xxxxxxxxxx"
48
+ # c.tag :meta_pixel, id: "000000000000000"
49
+ # c.tag :hotjar, id: 1234567
50
+ # c.tag :plausible, domain: "example.com"
51
+ #
52
+ # Anything else:
53
+ # c.tag :custom, as: :piwik, category: :analytics, src: "https://cdn.example.com/piwik.js"
54
+
55
+ # Different tags per domain, tenant or shop. Whatever the resolver returns
56
+ # is matched against the scope names below; scopes inherit the tags above
57
+ # and may override them by declaring the same provider again.
58
+ # c.scope_resolver = ->(request) { request.host }
59
+ #
60
+ # c.scope "example.com" do |s|
61
+ # s.tag :google_analytics, id: "G-EXAMPLE"
62
+ # end
63
+ end
@@ -0,0 +1,15 @@
1
+ require "rails/generators/base"
2
+
3
+ module Consently
4
+ module Generators
5
+ class ViewsGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("../../../../app/views/consently", __dir__)
7
+
8
+ desc "Copies the banner markup into your app so you can restyle it."
9
+
10
+ def copy_views
11
+ directory ".", "app/views/consently"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :consently do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: consently
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michał Krzysteczko
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.1'
26
+ description: 'Most Rails cookie banners ask for consent and then load Google Analytics
27
+ anyway. Consently is the other half: declare your tags once - GA4, Google Tag Manager,
28
+ Google Ads, Microsoft Clarity, Meta Pixel, Hotjar, Plausible or anything custom
29
+ - and it renders every non-essential one as an inert script the browser will not
30
+ even fetch, then turns them into live scripts the instant the visitor agrees, with
31
+ no page reload. Google Consent Mode v2 defaults are emitted before any Google tag
32
+ and updated on the click. The banner and its preferences panel ship translated into
33
+ ten European languages, styled in plain CSS with no Tailwind and no build step,
34
+ and driven by one Stimulus controller. Your cookie policy page is generated from
35
+ the same configuration - every vendor, every cookie it sets and for how long - so
36
+ it cannot drift out of date. Multi-tenant applications get per-domain or per-shop
37
+ tag sets from a single initializer, and an optional consent log stores proof of
38
+ each decision in your own database, with no third-party service and nothing leaving
39
+ your infrastructure.'
40
+ email:
41
+ - m.krzysteczko@icloud.com
42
+ executables: []
43
+ extensions: []
44
+ extra_rdoc_files: []
45
+ files:
46
+ - CHANGELOG.md
47
+ - MIT-LICENSE
48
+ - README.md
49
+ - Rakefile
50
+ - app/assets/stylesheets/consently.css
51
+ - app/controllers/consently/application_controller.rb
52
+ - app/controllers/consently/consents_controller.rb
53
+ - app/helpers/consently/application_helper.rb
54
+ - app/helpers/consently/tags_helper.rb
55
+ - app/javascript/consently/banner_controller.js
56
+ - app/models/consently/application_record.rb
57
+ - app/models/consently/consent_record.rb
58
+ - app/views/consently/_banner.html.erb
59
+ - app/views/consently/_policy.html.erb
60
+ - config/importmap.rb
61
+ - config/locales/cs.yml
62
+ - config/locales/de.yml
63
+ - config/locales/en.yml
64
+ - config/locales/es.yml
65
+ - config/locales/fr.yml
66
+ - config/locales/hu.yml
67
+ - config/locales/it.yml
68
+ - config/locales/nl.yml
69
+ - config/locales/pl.yml
70
+ - config/locales/sk.yml
71
+ - config/routes.rb
72
+ - lib/consently.rb
73
+ - lib/consently/configuration.rb
74
+ - lib/consently/consent.rb
75
+ - lib/consently/cookie.rb
76
+ - lib/consently/engine.rb
77
+ - lib/consently/providers.rb
78
+ - lib/consently/providers/base.rb
79
+ - lib/consently/providers/clarity.rb
80
+ - lib/consently/providers/custom.rb
81
+ - lib/consently/providers/google_ads.rb
82
+ - lib/consently/providers/google_analytics.rb
83
+ - lib/consently/providers/google_tag_manager.rb
84
+ - lib/consently/providers/hotjar.rb
85
+ - lib/consently/providers/meta_pixel.rb
86
+ - lib/consently/providers/plausible.rb
87
+ - lib/consently/script.rb
88
+ - lib/consently/version.rb
89
+ - lib/generators/consently/consent_log/consent_log_generator.rb
90
+ - lib/generators/consently/consent_log/templates/create_consently_consent_records.rb
91
+ - lib/generators/consently/install/install_generator.rb
92
+ - lib/generators/consently/install/templates/consently.rb
93
+ - lib/generators/consently/views/views_generator.rb
94
+ - lib/tasks/consently_tasks.rake
95
+ homepage: https://github.com/Xeross99/consently
96
+ licenses:
97
+ - MIT
98
+ metadata:
99
+ homepage_uri: https://github.com/Xeross99/consently
100
+ bug_tracker_uri: https://github.com/Xeross99/consently/issues
101
+ changelog_uri: https://github.com/Xeross99/consently/blob/main/CHANGELOG.md
102
+ rubygems_mfa_required: 'true'
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '3.2'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubygems_version: 4.0.3
118
+ specification_version: 4
119
+ summary: GDPR cookie consent for Rails that actually blocks your analytics tags until
120
+ the visitor agrees
121
+ test_files: []