shimmer 0.0.19 → 0.0.22

Sign up to get free protection for your applications and to get access to all the features.
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