shimmer 0.0.19 → 0.0.22

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: adda5b02ed66f485505f07b24ba314621c5b0dd1cae9ce3093681651f0a1a509
4
- data.tar.gz: 89c1baa878afdf2bb38c2cc62eeafd298f15916f11466e5ec1e31eb5b42f9be6
3
+ metadata.gz: 36f3761e1bd5856a9fd2078201abda25b544eff5e807566ce85847ba291c47d9
4
+ data.tar.gz: c946f2ec830a472aee08d835dbb697711d46c107152723ee8f9bb43a36bc4173
5
5
  SHA512:
6
- metadata.gz: f2add223e56adabf705dcb3061950ea47613d50caf4b7c240b3c7aa1907dbb0ef52b179b409183d2d205f21789d3cc15719e0ade1ba17387085eb47f9e2945d1
7
- data.tar.gz: e496ccb035f571cd070c2af88968d0ab6d9d45bcf23016655a9a7213beccf9c83907f001f2f26c4188ce5ce1bc4affbd5c29d2b16d43d60a7949df0e9446f908
6
+ metadata.gz: d8bb839595c9e32ab185276bdaac0c8a6f43b4ce1fb1ce0d82d47a1c18c5ae082e29caa68c34d14856db2ebb2001fe4774f5b687599b794706d199767bd46de1
7
+ data.tar.gz: 885c2bdb0326b02c3eebd7d4606d79906b52264ef8b6043dc6f4061dffd6c8b5a786af22f9d25378d7aa21988a34d6412409adcd7acb6acce7f478e342323eba
@@ -5,4 +5,5 @@
5
5
  "editor.formatOnSave": false,
6
6
  "editor.defaultFormatter": "castwide.solargraph"
7
7
  },
8
+ "typescript.tsdk": "node_modules/typescript/lib"
8
9
  }
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ class Builder < ActionView::Helpers::FormBuilder
6
+ class << self
7
+ def input_registry
8
+ @input_registry ||= {}
9
+ end
10
+
11
+ def register(klass)
12
+ input_registry[klass.type] = klass
13
+ end
14
+ end
15
+
16
+ def input(method, as: guess_type(method), **options)
17
+ as ||= guess_type(method)
18
+ options[:class] ||= "input__input"
19
+ collection = options.delete :collection
20
+ collection_based = !collection.nil? || as == :select
21
+ collection ||= guess_collection(method) if collection_based
22
+ name_method ||= guess_name_method(method) if collection_based
23
+ id_method ||= :id if collection_based
24
+ classes = []
25
+ options[:required] ||= true if options[:required].nil? && required_attributes.include?(method)
26
+ options[:data] ||= {}
27
+ options[:data][:controller] = options.delete(:controller) if options[:controller]
28
+ wrapper_data = {}
29
+ extra = []
30
+ input_class = self.class.input_registry[as]
31
+ raise "Unknown type #{as}" unless input_class
32
+ input = input_class.new(builder: self, method: method, options: options, id_method: id_method, collection: collection, name_method: name_method)
33
+ wrap method: method, content: input.render, classes: classes + ["input--#{as}"], label: options[:label], data: wrapper_data, extra: extra
34
+ end
35
+
36
+ private
37
+
38
+ def required_attributes
39
+ []
40
+ end
41
+
42
+ def guess_type(method)
43
+ self.class.input_registry.values.find { |e| e.can_handle?(method) }&.type || :string
44
+ end
45
+
46
+ def guess_collection(method)
47
+ association_for(method)&.klass&.all || enum_for(method).map { |e| OpenStruct.new(id: e, name: e) } || []
48
+ end
49
+
50
+ def guess_name_method(method)
51
+ klass = association_for(method)&.klass
52
+ return :name unless klass
53
+
54
+ [:display_name, :name, :title].each do |key|
55
+ return key if klass.instance_methods.include?(key)
56
+ end
57
+ end
58
+
59
+ def association_for(method)
60
+ collection_method = method.to_s.delete_suffix("_id")
61
+ object.class.reflect_on_association(collection_method) if object.respond_to?(collection_method)
62
+ end
63
+
64
+ def enum_for(method)
65
+ object.class.types.keys if object.class.respond_to?(method.to_s.pluralize)
66
+ end
67
+
68
+ def wrap(method:, content:, classes:, label:, data: nil, extra: nil)
69
+ if object&.errors&.[](method)&.any?
70
+ classes << "input--error"
71
+ errors = safe_join(object.errors[method].map { |e| content_tag :div, e, class: "input__error" })
72
+ end
73
+ label = label == false ? nil : self.label(method, label, class: "input__label")
74
+ content_tag(:div, safe_join([label, content, errors, extra].compact), class: ["input"] + classes, data: data)
75
+ end
76
+
77
+ def helper
78
+ @template
79
+ end
80
+
81
+ def value_for(method)
82
+ object.public_send(method)
83
+ end
84
+
85
+ delegate :content_tag, :t, :safe_join, :icon, :tag, to: :helper
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ class CheckboxesField < Field
6
+ self.type = :checkboxes
7
+
8
+ def render
9
+ builder.collection_check_boxes method, collection, id_method, name_method, {}, options
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ class DateField < Field
6
+ self.type = :date
7
+
8
+ def render
9
+ builder.date_field method, options
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ class EmailField < Field
6
+ self.type = :email
7
+
8
+ class << self
9
+ def can_handle?(method)
10
+ method.to_s.end_with?("email")
11
+ end
12
+ end
13
+
14
+ def render
15
+ builder.email_field method, options
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ class Field
6
+ class_attribute :type
7
+
8
+ attr_reader :builder, :method, :collection, :id_method, :name_method, :options
9
+
10
+ class << self
11
+ def can_handle?(method)
12
+ false
13
+ end
14
+ end
15
+
16
+ def initialize(builder:, method:, collection:, id_method:, name_method:, options: {})
17
+ @builder = builder
18
+ @method = method
19
+ @collection = collection
20
+ @id_method = id_method
21
+ @name_method = name_method
22
+ @options = options
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ class NumberField < Field
6
+ self.type = :number
7
+
8
+ def render
9
+ builder.number_field method, options
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ class PasswordField < Field
6
+ self.type = :password
7
+
8
+ class << self
9
+ def can_handle?(method)
10
+ method.to_s.end_with?("password")
11
+ end
12
+ end
13
+
14
+ def render
15
+ builder.password_field method, options
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ class PdfField < Field
6
+ self.type = :pdf
7
+
8
+ class << self
9
+ def can_handle?(method)
10
+ method.to_s.end_with?("pdf")
11
+ end
12
+ end
13
+
14
+ def render
15
+ builder.file_field method, options.reverse_merge(accept: "application/pdf")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ class RadioField < Field
6
+ self.type = :radio
7
+
8
+ def render
9
+ builder.collection_radio_buttons method, collection, id_method, name_method, {}, options
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ class SelectField < Field
6
+ self.type = :select
7
+
8
+ class << self
9
+ def can_handle?(method)
10
+ method.to_s.end_with?("_id")
11
+ end
12
+ end
13
+
14
+ def render
15
+ builder.collection_select method, collection, id_method, name_method, {}, options
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ class TextAreaField < Field
6
+ self.type = :text
7
+
8
+ def render
9
+ builder.text_area method, options
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ class TextField < Field
6
+ self.type = :string
7
+
8
+ def render
9
+ builder.text_field method, options
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ class TimeField < Field
6
+ self.type = :time
7
+
8
+ def render
9
+ builder.time_field method, options.reverse_merge(value: object.public_send(method))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Form
5
+ end
6
+ end
7
+
8
+ require_relative "./form/builder"
9
+ require_relative "./form/field"
10
+
11
+ Dir["#{File.expand_path("./form", __dir__)}/*"].sort.each do |e|
12
+ require e
13
+ name = e.split("/").last.delete_suffix(".rb")
14
+ next unless name.end_with?("_field")
15
+ Shimmer::Form::Builder.register("Shimmer::Form::#{name.classify}".constantize)
16
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shimmer
4
- VERSION = "0.0.19"
4
+ VERSION = "0.0.22"
5
5
  end
data/lib/shimmer.rb CHANGED
@@ -7,6 +7,7 @@ Dir["#{File.expand_path("../lib/shimmer/controllers", __dir__)}/*"].sort.each {
7
7
  Dir["#{File.expand_path("../lib/shimmer/jobs", __dir__)}/*"].sort.each { |e| require e }
8
8
  Dir["#{File.expand_path("../lib/shimmer/utils", __dir__)}/*"].sort.each { |e| require e }
9
9
  require_relative "shimmer/auth"
10
+ require_relative "shimmer/form"
10
11
 
11
12
  module Shimmer
12
13
  class Error < StandardError; end
data/src/consent.ts CHANGED
@@ -1,12 +1,24 @@
1
1
  import { getCookie, setCookie } from "./util";
2
2
 
3
+ const consentCategories = ["essential", "targeting", "statistic"] as const;
4
+ export type ConsentCategory = typeof consentCategories[number];
5
+
3
6
  export class Consent {
4
- get permitted(): string[] {
5
- return (getCookie("consent") ?? "").split(",");
7
+ #consentListeners: Record<ConsentCategory, Array<() => void>> = {
8
+ essential: [],
9
+ targeting: [],
10
+ statistic: [],
11
+ };
12
+
13
+ get permitted(): ConsentCategory[] {
14
+ return (getCookie("consent") ?? "").split(",") as ConsentCategory[];
6
15
  }
7
16
 
8
- set permitted(settings: string[]) {
17
+ set permitted(settings: ConsentCategory[]) {
9
18
  setCookie("consent", settings.join(","));
19
+ settings.forEach((category) => {
20
+ this.#consentListeners[category].forEach((e) => e());
21
+ });
10
22
  }
11
23
 
12
24
  get given(): boolean {
@@ -14,7 +26,7 @@ export class Consent {
14
26
  }
15
27
 
16
28
  permitAll(): void {
17
- this.permitted = ["essential", "targeting", "statistic"];
29
+ this.permitted = consentCategories.slice();
18
30
  }
19
31
 
20
32
  denyAll(): void {
@@ -25,4 +37,30 @@ export class Consent {
25
37
  const element = document.getElementById("personalization-settings");
26
38
  if (element) element.hidden = false;
27
39
  }
40
+
41
+ consentFor(category: ConsentCategory): Promise<void> {
42
+ return new Promise((res) => {
43
+ if (this.permitted.includes(category)) {
44
+ res();
45
+ } else {
46
+ this.#consentListeners[category].push(res);
47
+ }
48
+ });
49
+ }
50
+
51
+ async enableGoogleAnalytics(
52
+ id: string,
53
+ role: ConsentCategory = "statistic"
54
+ ): Promise<void> {
55
+ await this.consentFor(role);
56
+ window.gtag("js", new Date());
57
+ window.gtag("config", id);
58
+ const script = document.createElement("script");
59
+ script.async = true;
60
+ script.setAttribute(
61
+ "src",
62
+ `https://www.googletagmanager.com/gtag/js?id=${id}`
63
+ );
64
+ document.head.prepend(script);
65
+ }
28
66
  }
@@ -0,0 +1,43 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ interface DataEvent {
4
+ event: string;
5
+ }
6
+
7
+ declare global {
8
+ interface Window {
9
+ dataLayer?: DataEvent[];
10
+ gtag(...arg): void;
11
+ }
12
+ }
13
+
14
+ const dataLayer = (window.dataLayer = window.dataLayer ?? []);
15
+ function gtag(): void {
16
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
17
+ // @ts-ignore
18
+ // eslint-disable-next-line prefer-rest-params
19
+ dataLayer.push(arguments);
20
+ }
21
+ window.gtag = gtag;
22
+
23
+ export default class extends Controller {
24
+ connect(): void {
25
+ this.recordPage();
26
+ }
27
+
28
+ recordAction(event: MouseEvent): void {
29
+ const data = JSON.parse(
30
+ (event.target as HTMLElement).dataset.analytics ?? "{}"
31
+ );
32
+ dataLayer.push(data);
33
+ }
34
+
35
+ recordPage(): void {
36
+ const event = {
37
+ event: "Pageview",
38
+ path: location.pathname + location.search,
39
+ host: location.host,
40
+ };
41
+ dataLayer.push(event);
42
+ }
43
+ }
@@ -1,4 +1,5 @@
1
1
  import { Controller } from "@hotwired/stimulus";
2
+ import { ConsentCategory } from "../consent";
2
3
 
3
4
  export default class extends Controller {
4
5
  static targets = ["check"];
@@ -7,7 +8,7 @@ export default class extends Controller {
7
8
  connect(): void {
8
9
  this.checkTargets.forEach((input) => {
9
10
  input.checked =
10
- window.ui?.consent.permitted.includes(input.name) ||
11
+ window.ui?.consent.permitted.includes(input.name as ConsentCategory) ||
11
12
  input.name === "essential";
12
13
  });
13
14
  }
@@ -29,7 +30,7 @@ export default class extends Controller {
29
30
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
30
31
  window.ui!.consent.permitted = this.checkTargets
31
32
  .filter((e) => e.checked)
32
- .map((e) => e.name);
33
+ .map((e) => e.name) as ConsentCategory[];
33
34
  this.closeAll();
34
35
  }
35
36
 
data/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import { PopoverPresenter } from "./popover";
4
4
  import { Consent } from "./consent";
5
5
  import RemoteNavigationController from "./controllers/remote-navigation";
6
6
  import ConsentController from "./controllers/consent";
7
+ import AnalyticsController from "./controllers/analytics";
7
8
  import "./touch";
8
9
 
9
10
  export { registerServiceWorker } from "./serviceworker";
@@ -19,6 +20,12 @@ declare global {
19
20
  }
20
21
  }
21
22
 
23
+ window.ui = {
24
+ modal: new ModalPresenter(),
25
+ popover: new PopoverPresenter(),
26
+ consent: new Consent(),
27
+ };
28
+
22
29
  function createRemoteDestination(): void {
23
30
  if (document.getElementById("shimmer")) {
24
31
  return;
@@ -37,9 +44,7 @@ export async function start({
37
44
  createRemoteDestination();
38
45
  application.register("remote-navigation", RemoteNavigationController);
39
46
  application.register("consent", ConsentController);
40
- window.ui = {
41
- modal: new ModalPresenter(),
42
- popover: new PopoverPresenter(),
43
- consent: new Consent(),
44
- };
47
+ application.register("analytics", AnalyticsController);
45
48
  }
49
+
50
+ export const ui = window.ui;
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shimmer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.19
4
+ version: 0.0.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jens Ravens
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-07-12 00:00:00.000000000 Z
11
+ date: 2022-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -145,6 +145,20 @@ files:
145
145
  - lib/shimmer/auth/user.rb
146
146
  - lib/shimmer/controllers/files_controller.rb
147
147
  - lib/shimmer/controllers/sitemaps_controller.rb
148
+ - lib/shimmer/form.rb
149
+ - lib/shimmer/form/builder.rb
150
+ - lib/shimmer/form/checkboxes_field.rb
151
+ - lib/shimmer/form/date_field.rb
152
+ - lib/shimmer/form/email_field.rb
153
+ - lib/shimmer/form/field.rb
154
+ - lib/shimmer/form/number_field.rb
155
+ - lib/shimmer/form/password_field.rb
156
+ - lib/shimmer/form/pdf_field.rb
157
+ - lib/shimmer/form/radio_field.rb
158
+ - lib/shimmer/form/select_field.rb
159
+ - lib/shimmer/form/text_area_field.rb
160
+ - lib/shimmer/form/text_field.rb
161
+ - lib/shimmer/form/time_field.rb
148
162
  - lib/shimmer/helpers/meta_helper.rb
149
163
  - lib/shimmer/jobs/sitemap_job.rb
150
164
  - lib/shimmer/middlewares/cloudflare.rb
@@ -165,6 +179,7 @@ files:
165
179
  - package.json
166
180
  - rollup.config.js
167
181
  - src/consent.ts
182
+ - src/controllers/analytics.ts
168
183
  - src/controllers/consent.ts
169
184
  - src/controllers/remote-navigation.ts
170
185
  - src/index.ts