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 +4 -4
- data/.vscode/settings.json +1 -0
- data/lib/shimmer/form/builder.rb +88 -0
- data/lib/shimmer/form/checkboxes_field.rb +13 -0
- data/lib/shimmer/form/date_field.rb +13 -0
- data/lib/shimmer/form/email_field.rb +19 -0
- data/lib/shimmer/form/field.rb +26 -0
- data/lib/shimmer/form/number_field.rb +13 -0
- data/lib/shimmer/form/password_field.rb +19 -0
- data/lib/shimmer/form/pdf_field.rb +19 -0
- data/lib/shimmer/form/radio_field.rb +13 -0
- data/lib/shimmer/form/select_field.rb +19 -0
- data/lib/shimmer/form/text_area_field.rb +13 -0
- data/lib/shimmer/form/text_field.rb +13 -0
- data/lib/shimmer/form/time_field.rb +13 -0
- data/lib/shimmer/form.rb +16 -0
- data/lib/shimmer/version.rb +1 -1
- data/lib/shimmer.rb +1 -0
- data/src/consent.ts +42 -4
- data/src/controllers/analytics.ts +43 -0
- data/src/controllers/consent.ts +3 -2
- data/src/index.ts +10 -5
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36f3761e1bd5856a9fd2078201abda25b544eff5e807566ce85847ba291c47d9
|
4
|
+
data.tar.gz: c946f2ec830a472aee08d835dbb697711d46c107152723ee8f9bb43a36bc4173
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8bb839595c9e32ab185276bdaac0c8a6f43b4ce1fb1ce0d82d47a1c18c5ae082e29caa68c34d14856db2ebb2001fe4774f5b687599b794706d199767bd46de1
|
7
|
+
data.tar.gz: 885c2bdb0326b02c3eebd7d4606d79906b52264ef8b6043dc6f4061dffd6c8b5a786af22f9d25378d7aa21988a34d6412409adcd7acb6acce7f478e342323eba
|
data/.vscode/settings.json
CHANGED
@@ -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,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,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,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
|
data/lib/shimmer/form.rb
ADDED
@@ -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
|
data/lib/shimmer/version.rb
CHANGED
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
|
-
|
5
|
-
|
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:
|
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 =
|
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
|
+
}
|
data/src/controllers/consent.ts
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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
|