wcc-contentful-app 0.4.0.pre.rc → 1.0.0.pre.rc1
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 +5 -5
- data/.rspec +2 -2
- data/Guardfile +1 -1
- data/README.md +11 -0
- data/Rakefile +121 -0
- data/app/assets/config/manifest.js +5 -0
- data/app/assets/images/down-arrow-primary.svg +3 -0
- data/app/assets/images/up-arrow-primary.svg +3 -0
- data/app/assets/javascripts/wcc/contentful/app/contact-form.js +70 -0
- data/app/assets/javascripts/wcc/contentful/app/index.js +1 -0
- data/app/assets/stylesheets/_wcc-contentful-app.scss +7 -0
- data/app/assets/stylesheets/components/_menu-item.scss +5 -0
- data/app/assets/stylesheets/sections/_faq.scss +39 -0
- data/app/controllers/wcc/contentful/app/contact_form_controller.rb +39 -0
- data/app/controllers/wcc/contentful/app/pages_controller.rb +48 -0
- data/app/helpers/wcc/contentful/app/menu_helper.rb +85 -0
- data/app/helpers/wcc/contentful/app/section_helper.rb +135 -0
- data/app/mailers/wcc/contentful/app/contact_mailer.rb +11 -0
- data/app/models/concerns/wcc/contentful/app/preview_password.rb +19 -0
- data/app/models/wcc/contentful/app/contact_form_submission.rb +9 -0
- data/app/models/wcc/contentful/app/custom_markdown_render.rb +43 -0
- data/app/views/components/_faq_row.html.erb +21 -0
- data/app/views/components/_menu-item.html.erb +41 -0
- data/app/views/components/_other-menu-item.html.erb +4 -0
- data/app/views/components/_section.html.erb +11 -0
- data/app/views/layouts/mailer.html.erb +9 -0
- data/app/views/layouts/mailer.text.erb +1 -0
- data/app/views/pages/show.html.erb +4 -0
- data/app/views/sections/_block_text.html.erb +5 -0
- data/app/views/sections/_code_widget.html.erb +3 -0
- data/app/views/sections/_contact_form.html.erb +53 -0
- data/app/views/sections/_faq.html.erb +36 -0
- data/app/views/sections/_http_error.html.erb +13 -0
- data/app/views/sections/_marquee_text.html.erb +12 -0
- data/app/views/sections/_testimonials.html.erb +38 -0
- data/app/views/sections/_video.html.erb +12 -0
- data/app/views/sections/_video_highlight.html.erb +18 -0
- data/app/views/wcc/contentful/app/contact_mailer/contact_form_email.html.erb +7 -0
- data/bin/rails +14 -0
- data/config/routes.rb +7 -0
- data/lib/generators/wcc/model_generator.rb +16 -6
- data/lib/generators/wcc/templates/menu/migrations/generated_add_menus.ts +285 -0
- data/lib/generators/wcc/templates/menu/models/dropdown_menu.rb +19 -0
- data/lib/generators/wcc/templates/menu/models/menu.rb +0 -4
- data/lib/generators/wcc/templates/menu/models/menu_button.rb +0 -4
- data/lib/generators/wcc/templates/page/migrations/generated_add_pages.ts +147 -0
- data/lib/generators/wcc/templates/page/models/page.rb +0 -4
- data/lib/generators/wcc/templates/page/models/redirect.rb +19 -0
- data/lib/generators/wcc/templates/section-block-text/migrations/generated_add_section-block-texts.ts +40 -0
- data/lib/generators/wcc/templates/section-block-text/models/section_block_text.rb +19 -0
- data/lib/generators/wcc/templates/section-code-widget/migrations/generated_add_section-code-widget.ts +90 -0
- data/lib/generators/wcc/templates/section-code-widget/models/section_code_widget.rb +23 -0
- data/lib/generators/wcc/templates/section-contact-form/migrations/create_wcc_contentful_app_contact_form_submissions.rb +12 -0
- data/lib/generators/wcc/templates/section-contact-form/migrations/generated_add_section-contact-forms.ts +147 -0
- data/lib/generators/wcc/templates/section-contact-form/models/form_field.rb +19 -0
- data/lib/generators/wcc/templates/section-contact-form/models/section_contact_form.rb +19 -0
- data/lib/generators/wcc/templates/section-faq/migrations/generated_add_section-faqs.ts +148 -0
- data/lib/generators/wcc/templates/section-faq/models/section_faq.rb +19 -0
- data/lib/generators/wcc/templates/section-http-error/migrations/generated_add_section-http-errors.ts +87 -0
- data/lib/generators/wcc/templates/section-http-error/models/section_http_error.rb +19 -0
- data/lib/generators/wcc/templates/section-marquee-text/migrations/generated_add_section-marquee-texts.ts +64 -0
- data/lib/generators/wcc/templates/section-marquee-text/models/section_marquee_text.rb +19 -0
- data/lib/generators/wcc/templates/section-testimonial/migrations/generated_add_section-testimonials.ts +182 -0
- data/lib/generators/wcc/templates/section-testimonial/models/section_testimonial.rb +19 -0
- data/lib/generators/wcc/templates/section-video-highlight/migrations/generated_add_section-video-highlights.ts +80 -0
- data/lib/generators/wcc/templates/section-video-highlight/models/section_video_highlight.rb +19 -0
- data/lib/generators/wcc/templates/section-video/migrations/generated_add_section-videos.ts +77 -0
- data/lib/generators/wcc/templates/section-video/models/section_video.rb +19 -0
- data/lib/generators/wcc/templates/site-config/migrations/generated_add_site-configs.ts +97 -0
- data/lib/generators/wcc/templates/site-config/models/site_config.rb +19 -0
- data/lib/generators/wcc/templates/wcc_contentful.rb +1 -1
- data/lib/wcc/contentful/app.rb +46 -3
- data/lib/wcc/contentful/app/configuration.rb +60 -0
- data/lib/wcc/contentful/app/engine.rb +15 -0
- data/lib/wcc/contentful/app/exceptions.rb +9 -0
- data/lib/wcc/contentful/app/rails.rb +6 -0
- data/lib/wcc/contentful/app/version.rb +1 -1
- data/lib/wcc/contentful/model/dropdown_menu.rb +0 -3
- data/lib/wcc/contentful/model/form_field.rb +4 -0
- data/lib/wcc/contentful/model/menu.rb +0 -2
- data/lib/wcc/contentful/model/menu_button.rb +18 -5
- data/lib/wcc/contentful/model/page.rb +0 -4
- data/lib/wcc/contentful/model/redirect.rb +21 -11
- data/lib/wcc/contentful/model/section_contact_form.rb +42 -0
- data/lib/wcc/contentful/model/site_config.rb +7 -0
- data/wcc-contentful-app.gemspec +15 -15
- metadata +164 -103
- data/Gemfile +0 -8
- data/lib/generators/wcc/templates/menu/generated_add_menus.ts +0 -192
- data/lib/generators/wcc/templates/page/generated_add_pages.ts +0 -50
- data/lib/tasks/validate.rake +0 -20
- data/lib/wcc/contentful/app/model_validators.rb +0 -121
- data/lib/wcc/contentful/app/model_validators/dsl.rb +0 -166
- data/lib/wcc/contentful/ext/model.rb +0 -5
data/Gemfile
DELETED
@@ -1,192 +0,0 @@
|
|
1
|
-
|
2
|
-
import Migration from 'contentful-migration-cli'
|
3
|
-
|
4
|
-
export = function (migration: Migration) {
|
5
|
-
const menu = migration.createContentType('menu', {
|
6
|
-
displayField: 'name',
|
7
|
-
name: 'Menu',
|
8
|
-
description: 'A Menu contains a number of Menu Buttons or other Menus, ' +
|
9
|
-
'which will be rendered as drop-downs.'
|
10
|
-
})
|
11
|
-
|
12
|
-
menu.createField('name', {
|
13
|
-
name: 'Menu Name',
|
14
|
-
type: 'Symbol',
|
15
|
-
localized: false,
|
16
|
-
required: true,
|
17
|
-
validations: [],
|
18
|
-
disabled: false,
|
19
|
-
omitted: false
|
20
|
-
})
|
21
|
-
|
22
|
-
menu.createField('items', {
|
23
|
-
name: 'Items',
|
24
|
-
type: 'Array',
|
25
|
-
localized: false,
|
26
|
-
required: false,
|
27
|
-
validations: [],
|
28
|
-
disabled: false,
|
29
|
-
omitted: false,
|
30
|
-
items:
|
31
|
-
{
|
32
|
-
type: 'Link',
|
33
|
-
validations:
|
34
|
-
[{
|
35
|
-
linkContentType:
|
36
|
-
['dropdownMenu',
|
37
|
-
'menuButton'],
|
38
|
-
message: 'The items must be either buttons or drop-down menus.'
|
39
|
-
}],
|
40
|
-
linkType: 'Entry'
|
41
|
-
}
|
42
|
-
})
|
43
|
-
|
44
|
-
menu.changeEditorInterface('name', 'singleLine')
|
45
|
-
menu.changeEditorInterface('items', 'entryLinksEditor')
|
46
|
-
|
47
|
-
const menubutton = migration.createContentType('menuButton', {
|
48
|
-
displayField: 'text',
|
49
|
-
name: 'Menu Button',
|
50
|
-
description: 'A Menu Button is a clickable button that goes on a Menu. It has a link to a Page or a URL.'
|
51
|
-
})
|
52
|
-
|
53
|
-
menubutton.createField('text', {
|
54
|
-
name: 'Text',
|
55
|
-
type: 'Symbol',
|
56
|
-
localized: false,
|
57
|
-
required: true,
|
58
|
-
validations:
|
59
|
-
[{
|
60
|
-
size:
|
61
|
-
{
|
62
|
-
min: 1,
|
63
|
-
max: 60
|
64
|
-
},
|
65
|
-
message: 'A Menu Button should have a very short text field - ideally a single word. Please limit the text to 60 characters.'
|
66
|
-
}],
|
67
|
-
disabled: false,
|
68
|
-
omitted: false
|
69
|
-
})
|
70
|
-
|
71
|
-
menubutton.createField('icon', {
|
72
|
-
name: 'Icon',
|
73
|
-
type: 'Link',
|
74
|
-
localized: false,
|
75
|
-
required: false,
|
76
|
-
validations: [{ linkMimetypeGroup: ['image'] }],
|
77
|
-
disabled: false,
|
78
|
-
omitted: false,
|
79
|
-
linkType: 'Asset'
|
80
|
-
})
|
81
|
-
|
82
|
-
menubutton.createField('externalLink', {
|
83
|
-
name: 'External Link',
|
84
|
-
type: 'Symbol',
|
85
|
-
localized: false,
|
86
|
-
required: false,
|
87
|
-
validations:
|
88
|
-
[{
|
89
|
-
regexp: { pattern: '^(\\w+):(\\/\\/)?(\\w+:{0,1}\\w*@)?((\\w+\\.)+[^\\s\\/#]+)(:[0-9]+)?(\\/|(\\/|\\#)([\\w#!:.?+=&%@!\\-\\/]+))?$|^(\\/|(\\/|\\#)([\\w#!:.?+=&%@!\\-\\/]+))$' },
|
90
|
-
message: 'The external link must be a URL like \'https://www.watermark.org/\', a mailto url like \'mailto:info@watermark.org\', or a relative URL like \'#location-on-page\''
|
91
|
-
}],
|
92
|
-
disabled: false,
|
93
|
-
omitted: false
|
94
|
-
})
|
95
|
-
|
96
|
-
menubutton.createField('link', {
|
97
|
-
name: 'Page Link',
|
98
|
-
type: 'Link',
|
99
|
-
localized: false,
|
100
|
-
required: false,
|
101
|
-
validations:
|
102
|
-
[{
|
103
|
-
linkContentType: ['page'],
|
104
|
-
message: 'The Page Link must be a link to a Page which has a slug.'
|
105
|
-
}],
|
106
|
-
disabled: false,
|
107
|
-
omitted: false,
|
108
|
-
linkType: 'Entry'
|
109
|
-
})
|
110
|
-
|
111
|
-
menubutton.createField('ionIcon', {
|
112
|
-
name: 'Ion Icon',
|
113
|
-
type: 'Symbol',
|
114
|
-
localized: false,
|
115
|
-
required: false,
|
116
|
-
validations:
|
117
|
-
[{
|
118
|
-
regexp: { pattern: '^ion-[a-z\\-]+$' },
|
119
|
-
message: 'The icon should start with \'ion-\', like \'ion-arrow-down-c\'. See http://ionicons.com/'
|
120
|
-
}],
|
121
|
-
disabled: false,
|
122
|
-
omitted: false
|
123
|
-
})
|
124
|
-
|
125
|
-
menubutton.createField('style', {
|
126
|
-
name: 'Style',
|
127
|
-
type: 'Symbol',
|
128
|
-
localized: false,
|
129
|
-
required: false,
|
130
|
-
validations: [{ in: ['oval-border'] }],
|
131
|
-
disabled: false,
|
132
|
-
omitted: false
|
133
|
-
})
|
134
|
-
|
135
|
-
menubutton.changeEditorInterface('text', 'singleLine')
|
136
|
-
menubutton.changeEditorInterface('icon', 'assetLinkEditor')
|
137
|
-
menubutton.changeEditorInterface('externalLink', 'singleLine')
|
138
|
-
menubutton.changeEditorInterface('link', 'entryLinkEditor')
|
139
|
-
menubutton.changeEditorInterface('ionIcon', 'singleLine')
|
140
|
-
menubutton.changeEditorInterface('style', 'dropdown')
|
141
|
-
|
142
|
-
const dropdownmenu = migration.createContentType('dropdownMenu', {
|
143
|
-
displayField: 'name',
|
144
|
-
name: 'Dropdown Menu',
|
145
|
-
description: 'A Dropdown Menu can be attached to a main menu to show additional menu items on click.'
|
146
|
-
})
|
147
|
-
|
148
|
-
dropdownmenu.createField('name', {
|
149
|
-
name: 'Menu Name',
|
150
|
-
type: 'Symbol',
|
151
|
-
localized: false,
|
152
|
-
required: false,
|
153
|
-
validations: [],
|
154
|
-
disabled: false,
|
155
|
-
omitted: false
|
156
|
-
})
|
157
|
-
|
158
|
-
dropdownmenu.createField('label', {
|
159
|
-
name: 'Menu Label',
|
160
|
-
type: 'Link',
|
161
|
-
localized: false,
|
162
|
-
required: false,
|
163
|
-
validations: [{ linkContentType: ['menuButton'] }],
|
164
|
-
disabled: false,
|
165
|
-
omitted: false,
|
166
|
-
linkType: 'Entry'
|
167
|
-
})
|
168
|
-
|
169
|
-
dropdownmenu.createField('items', {
|
170
|
-
name: 'Items',
|
171
|
-
type: 'Array',
|
172
|
-
localized: false,
|
173
|
-
required: false,
|
174
|
-
validations: [],
|
175
|
-
disabled: false,
|
176
|
-
omitted: false,
|
177
|
-
items:
|
178
|
-
{
|
179
|
-
type: 'Link',
|
180
|
-
validations:
|
181
|
-
[{
|
182
|
-
linkContentType:
|
183
|
-
['menuButton']
|
184
|
-
}],
|
185
|
-
linkType: 'Entry'
|
186
|
-
}
|
187
|
-
})
|
188
|
-
|
189
|
-
dropdownmenu.changeEditorInterface('name', 'singleLine')
|
190
|
-
dropdownmenu.changeEditorInterface('label', 'entryLinkEditor')
|
191
|
-
dropdownmenu.changeEditorInterface('items', 'entryLinksEditor')
|
192
|
-
}
|
@@ -1,50 +0,0 @@
|
|
1
|
-
import Migration from 'contentful-migration-cli'
|
2
|
-
|
3
|
-
export = function (migration: Migration) {
|
4
|
-
const page = migration.createContentType('page')
|
5
|
-
.name('Page')
|
6
|
-
.description('A page describes a collection of sections that correspond' +
|
7
|
-
'to a URL slug')
|
8
|
-
.displayField('title')
|
9
|
-
|
10
|
-
page.createField('title')
|
11
|
-
.name('Title')
|
12
|
-
.type('Symbol')
|
13
|
-
.required(true)
|
14
|
-
|
15
|
-
page.createField('slug')
|
16
|
-
.name('Slug')
|
17
|
-
.type('Symbol')
|
18
|
-
.required(true)
|
19
|
-
.validations([
|
20
|
-
{
|
21
|
-
unique: true
|
22
|
-
},
|
23
|
-
{
|
24
|
-
regexp: { pattern: "(\\/|\\/([\w#!:.?+=&%@!\\-\\/]))?$" },
|
25
|
-
message: "The slug must look like the path part of a URL and begin with a forward slash, example: '/my-page-slug'"
|
26
|
-
}
|
27
|
-
])
|
28
|
-
|
29
|
-
page.createField('sections')
|
30
|
-
.name('Sections')
|
31
|
-
.type('Array')
|
32
|
-
.items({
|
33
|
-
type: 'Link',
|
34
|
-
linkType: 'Entry'
|
35
|
-
})
|
36
|
-
|
37
|
-
page.createField('subpages')
|
38
|
-
.name('Subpages')
|
39
|
-
.type('Array')
|
40
|
-
.items({
|
41
|
-
type: 'Link',
|
42
|
-
linkType: 'Entry',
|
43
|
-
validations: [
|
44
|
-
{
|
45
|
-
linkContentType: [ 'page' ]
|
46
|
-
}
|
47
|
-
]
|
48
|
-
})
|
49
|
-
|
50
|
-
}
|
data/lib/tasks/validate.rake
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
namespace :wcc_contentful do
|
4
|
-
desc 'Validates content types in your space against the validations defined on your models'
|
5
|
-
task :validate, :environment do |_t|
|
6
|
-
# Ensure application models are loaded before we validate
|
7
|
-
Rails.application.eager_load!
|
8
|
-
|
9
|
-
client = Services.instance.management_client ||
|
10
|
-
Services.instance.client
|
11
|
-
|
12
|
-
content_types = client.content_types(limit: 1000).items
|
13
|
-
|
14
|
-
content_types = WCC::Contentful::ModelValidators
|
15
|
-
.transform_content_types_for_validation(content_types)
|
16
|
-
|
17
|
-
errors = WCC::Contentful::Model.schema.call(content_types)
|
18
|
-
raise WCC::Contentful::ValidationError, errors.errors unless errors.success?
|
19
|
-
end
|
20
|
-
end
|
@@ -1,121 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'dry-validation'
|
4
|
-
|
5
|
-
require_relative 'model_validators/dsl'
|
6
|
-
|
7
|
-
module WCC::Contentful::App::ModelValidators
|
8
|
-
def schema
|
9
|
-
return if validations.nil? || validations.empty?
|
10
|
-
|
11
|
-
all_field_validations =
|
12
|
-
validations.each_with_object({}) do |(content_type, procs), h|
|
13
|
-
next if procs.empty?
|
14
|
-
|
15
|
-
# "page": {
|
16
|
-
# "sys": { ... }
|
17
|
-
# "fields": {
|
18
|
-
# "title": { ... },
|
19
|
-
# "sections": { ... },
|
20
|
-
# ...
|
21
|
-
# }
|
22
|
-
# }
|
23
|
-
h[content_type] =
|
24
|
-
Dry::Validation.Schema do
|
25
|
-
# Had to dig through the internals of Dry::Validation to find
|
26
|
-
# this magic incantation
|
27
|
-
procs.each { |dsl| instance_eval(&dsl.to_proc) }
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
Dry::Validation.Schema do
|
32
|
-
all_field_validations.each do |content_type, fields_schema|
|
33
|
-
required(content_type).schema do
|
34
|
-
required('fields').schema(fields_schema)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def validations
|
41
|
-
# This needs to be a class variable so that subclasses defined in application
|
42
|
-
# code can add to the total package of model validations
|
43
|
-
# rubocop:disable Style/ClassVars
|
44
|
-
@@validations ||= {}
|
45
|
-
# rubocop:enable Style/ClassVars
|
46
|
-
end
|
47
|
-
|
48
|
-
# Accepts a block which uses the {dry-validation DSL}[http://dry-rb.org/gems/dry-validation/]
|
49
|
-
# to validate the 'fields' object of a content type.
|
50
|
-
def validate_fields(&block)
|
51
|
-
raise ArgumentError, 'validate_fields requires a block' unless block_given?
|
52
|
-
|
53
|
-
dsl = ProcDsl.new(Proc.new(&block))
|
54
|
-
|
55
|
-
ct = try(:content_type) || name.demodulize.camelize(:lower)
|
56
|
-
(validations[ct] ||= []) << dsl
|
57
|
-
end
|
58
|
-
|
59
|
-
# Validates a single field is of the expected type.
|
60
|
-
# Type expectations are one of:
|
61
|
-
#
|
62
|
-
# [:String] the field type must be `Symbol` or `Text`
|
63
|
-
# [:Int] the field type must be `Integer`
|
64
|
-
# [:Float] the field type must be `Number`
|
65
|
-
# [:DateTime] the field type must be 'Date'
|
66
|
-
# [:Asset] the field must be a link and the `linkType` must be `Asset`
|
67
|
-
# [:Link] the field must be a link and the `linkType` must be `Entry`.
|
68
|
-
# [:Location] the field type must be `Location`
|
69
|
-
# [:Boolean] the field type must be `Boolean`
|
70
|
-
# [:Json] the field type must be `Json` - a json blob.
|
71
|
-
# [:Array] the field must be a List.
|
72
|
-
#
|
73
|
-
# Additional validation options can be enforced:
|
74
|
-
#
|
75
|
-
# [:required] the 'Required Field' checkbox must be checked
|
76
|
-
# [:optional] the 'Required Field' checkbox must not be checked
|
77
|
-
# [:link_to] (only `:Link` or `:Array` type) the given content type(s) must be
|
78
|
-
# checked in the 'Accept only specified entry type' validations
|
79
|
-
# Example:
|
80
|
-
# validate_field :button, :Link, link_to: ['button', 'altButton']
|
81
|
-
#
|
82
|
-
# [:items] (only `:Array` type) the items of the list must be of the given type.
|
83
|
-
# Example:
|
84
|
-
# validate_field :my_strings, :Array, items: :String
|
85
|
-
#
|
86
|
-
# Examples:
|
87
|
-
# see WCC::Contentful::Model::Menu and WCC::Contentful::Model::MenuButton
|
88
|
-
def validate_field(field, type, *options)
|
89
|
-
dsl = FieldDsl.new(field, type, options)
|
90
|
-
|
91
|
-
ct = try(:content_type) || name.demodulize.camelize(:lower)
|
92
|
-
(validations[ct] ||= []) << dsl
|
93
|
-
end
|
94
|
-
|
95
|
-
def no_validate_field(field)
|
96
|
-
ct = try(:content_type) || name.demodulize.camelize(:lower)
|
97
|
-
return unless v = validations[ct]
|
98
|
-
|
99
|
-
field = field.to_s.camelize(:lower) unless field.is_a?(String)
|
100
|
-
v.reject! { |dsl| dsl.try(:field) == field }
|
101
|
-
end
|
102
|
-
|
103
|
-
# Accepts a content types response from the API and transforms it
|
104
|
-
# to be acceptible for the validator.
|
105
|
-
def self.transform_content_types_for_validation(content_types)
|
106
|
-
if !content_types.is_a?(Array) && items = content_types.try(:[], 'items')
|
107
|
-
content_types = items
|
108
|
-
end
|
109
|
-
|
110
|
-
# Transform the array into a hash keyed by content type ID
|
111
|
-
content_types.each_with_object({}) do |ct, ct_hash|
|
112
|
-
# Transform the fields into a hash keyed by field ID
|
113
|
-
ct['fields'] =
|
114
|
-
ct['fields'].each_with_object({}) do |f, f_hash|
|
115
|
-
f_hash[f['id']] = f
|
116
|
-
end
|
117
|
-
|
118
|
-
ct_hash[ct.dig('sys', 'id')] = ct
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
@@ -1,166 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WCC::Contentful::App::ModelValidators
|
4
|
-
class ProcDsl
|
5
|
-
def to_proc
|
6
|
-
@proc
|
7
|
-
end
|
8
|
-
|
9
|
-
def initialize(proc)
|
10
|
-
@proc = proc
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
class FieldDsl
|
15
|
-
attr_reader :field
|
16
|
-
|
17
|
-
# "sections": {
|
18
|
-
# "id": "sections",
|
19
|
-
# "name": "Sections",
|
20
|
-
# "type": "Array",
|
21
|
-
# "localized": false,
|
22
|
-
# "required": false,
|
23
|
-
# "validations": [],
|
24
|
-
# "disabled": false,
|
25
|
-
# "omitted": false,
|
26
|
-
# "items": {
|
27
|
-
# "type": "Link",
|
28
|
-
# "validations": [
|
29
|
-
# {
|
30
|
-
# "linkContentType": [
|
31
|
-
# "Section"
|
32
|
-
# ]
|
33
|
-
# }
|
34
|
-
# ],
|
35
|
-
# "linkType": "Entry"
|
36
|
-
# }
|
37
|
-
# }
|
38
|
-
|
39
|
-
def schema
|
40
|
-
return @field_schema if @field_schema
|
41
|
-
|
42
|
-
# example: required('type').value(...)
|
43
|
-
type_pred = parse_type_predicate(@type)
|
44
|
-
|
45
|
-
# example: [required('required').value(eq?: true), ...]
|
46
|
-
procs =
|
47
|
-
@options.map do |opt|
|
48
|
-
if opt.is_a?(Hash)
|
49
|
-
opt.map { |k, v| parse_option(k, v) }
|
50
|
-
else
|
51
|
-
parse_option(opt)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
@field_schema =
|
56
|
-
Dry::Validation.Schema do
|
57
|
-
instance_eval(&type_pred)
|
58
|
-
|
59
|
-
procs.flatten.each { |p| instance_eval(&p) }
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def to_proc
|
64
|
-
f = field
|
65
|
-
s = schema
|
66
|
-
proc { required(f).schema(s) }
|
67
|
-
end
|
68
|
-
|
69
|
-
def initialize(field, field_type, options)
|
70
|
-
@field = field.to_s.camelize(:lower) unless field.is_a?(String)
|
71
|
-
@type = field_type
|
72
|
-
@options = options
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def parse_type_predicate(type)
|
78
|
-
case type
|
79
|
-
when :String
|
80
|
-
proc { required('type').value(included_in?: %w[Symbol Text]) }
|
81
|
-
when :Int
|
82
|
-
proc { required('type').value(eql?: 'Integer') }
|
83
|
-
when :Float
|
84
|
-
proc { required('type').value(eql?: 'Number') }
|
85
|
-
when :DateTime
|
86
|
-
proc { required('type').value(eql?: 'Date') }
|
87
|
-
when :Asset
|
88
|
-
proc {
|
89
|
-
required('type').value(eql?: 'Link')
|
90
|
-
required('linkType').value(eql?: 'Asset')
|
91
|
-
}
|
92
|
-
else
|
93
|
-
proc { required('type').value(eql?: type.to_s.camelize) }
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def parse_option(option, option_arg = nil)
|
98
|
-
case option
|
99
|
-
when :required
|
100
|
-
proc { required('required').value(eql?: true) }
|
101
|
-
when :optional
|
102
|
-
proc { required('required').value(eql?: false) }
|
103
|
-
when :link_to
|
104
|
-
link_to_proc = parse_field_link_to(option_arg)
|
105
|
-
return link_to_proc unless @type.to_s.camelize == 'Array'
|
106
|
-
|
107
|
-
proc {
|
108
|
-
required('items').schema do
|
109
|
-
required('type').value(eql?: 'Link')
|
110
|
-
instance_eval(&link_to_proc)
|
111
|
-
end
|
112
|
-
}
|
113
|
-
when :items
|
114
|
-
type_pred = parse_type_predicate(option_arg)
|
115
|
-
proc {
|
116
|
-
required('items').schema do
|
117
|
-
instance_eval(&type_pred)
|
118
|
-
end
|
119
|
-
}
|
120
|
-
else
|
121
|
-
raise ArgumentError, "unknown validation requirement: #{option}"
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def parse_field_link_to(option_arg)
|
126
|
-
raise ArgumentError, 'validation link_to: requires an argument' unless option_arg
|
127
|
-
|
128
|
-
# this works because a Link can only have one validation in its "validations" array -
|
129
|
-
# this will fail if Contentful ever changes that.
|
130
|
-
|
131
|
-
# the 'validations' schema needs to be optional because if we get the content
|
132
|
-
# types from the CDN instead of the management API, sometimes the validations
|
133
|
-
# don't get sent back.
|
134
|
-
|
135
|
-
# "validations": [
|
136
|
-
# {
|
137
|
-
# "linkContentType": [
|
138
|
-
# "section-CardSearch",
|
139
|
-
# "section-Faq",
|
140
|
-
# "section-Testimonials",
|
141
|
-
# "section-VideoHighlight"
|
142
|
-
# ]
|
143
|
-
# }
|
144
|
-
# ]
|
145
|
-
|
146
|
-
if option_arg.is_a?(Regexp)
|
147
|
-
return proc {
|
148
|
-
optional('validations').each do
|
149
|
-
schema do
|
150
|
-
required('linkContentType').each(format?: option_arg)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
}
|
154
|
-
end
|
155
|
-
|
156
|
-
option_arg = [option_arg] unless option_arg.is_a?(Array)
|
157
|
-
proc {
|
158
|
-
optional('validations').each do
|
159
|
-
schema do
|
160
|
-
required('linkContentType').value(eql?: option_arg)
|
161
|
-
end
|
162
|
-
end
|
163
|
-
}
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|