locomotivecms_wagon 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +1 -1
- data/README.md +1 -1
- data/Rakefile +2 -2
- data/generators/blank/Gemfile.tt +3 -1
- data/generators/blank/config/deploy.yml +4 -1
- data/generators/bootstrap/Gemfile.tt +3 -1
- data/generators/bootstrap/config/deploy.yml +4 -1
- data/generators/cloned/Gemfile.tt +3 -1
- data/generators/cloned/config/deploy.yml.tt +5 -1
- data/generators/foundation/Gemfile.tt +4 -2
- data/generators/foundation/app/views/pages/index.liquid +3 -1
- data/generators/foundation/app/views/pages/index.liquid.haml +18 -16
- data/generators/foundation/config/deploy.yml +4 -1
- data/generators/foundation/public/javascripts/foundation.min.js +32 -14
- data/generators/foundation/public/javascripts/foundation/foundation.alerts.js +4 -2
- data/generators/foundation/public/javascripts/foundation/foundation.clearing.js +67 -31
- data/generators/foundation/public/javascripts/foundation/foundation.cookie.js +1 -1
- data/generators/foundation/public/javascripts/foundation/foundation.dropdown.js +73 -25
- data/generators/foundation/public/javascripts/foundation/foundation.forms.js +356 -235
- data/generators/foundation/public/javascripts/foundation/foundation.interchange.js +271 -0
- data/generators/foundation/public/javascripts/foundation/foundation.joyride.js +265 -33
- data/generators/foundation/public/javascripts/foundation/foundation.js +130 -55
- data/generators/foundation/public/javascripts/foundation/foundation.magellan.js +6 -4
- data/generators/foundation/public/javascripts/foundation/foundation.orbit.js +38 -13
- data/generators/foundation/public/javascripts/foundation/foundation.placeholder.js +1 -1
- data/generators/foundation/public/javascripts/foundation/foundation.reveal.js +74 -14
- data/generators/foundation/public/javascripts/foundation/foundation.section.js +210 -66
- data/generators/foundation/public/javascripts/foundation/foundation.tooltips.js +25 -12
- data/generators/foundation/public/javascripts/foundation/foundation.topbar.js +126 -54
- data/generators/foundation/public/javascripts/vendor/custom.modernizr.js +1 -1
- data/generators/foundation/public/javascripts/vendor/jquery.js +1 -1
- data/generators/foundation/public/javascripts/vendor/zepto.js +1 -1
- data/generators/foundation/public/stylesheets/foundation.css +870 -438
- data/generators/foundation/public/stylesheets/foundation.min.css +440 -1
- data/generators/foundation/public/stylesheets/normalize.css +89 -146
- data/generators/page/template.liquid.haml.tt +6 -7
- data/generators/page/template.liquid.tt +34 -1
- data/lib/locomotive/wagon.rb +15 -1
- data/lib/locomotive/wagon/cli.rb +8 -4
- data/lib/locomotive/wagon/generators/site/foundation.rb +1 -1
- data/lib/locomotive/wagon/liquid.rb +1 -2
- data/lib/locomotive/wagon/liquid/drops/content_types.rb +6 -22
- data/lib/locomotive/wagon/liquid/drops/page.rb +6 -14
- data/lib/locomotive/wagon/liquid/drops/site.rb +4 -1
- data/lib/locomotive/wagon/liquid/filters/html.rb +1 -1
- data/lib/locomotive/wagon/liquid/filters/translate.rb +10 -6
- data/lib/locomotive/wagon/liquid/tags/nav.rb +3 -2
- data/lib/locomotive/wagon/misc/dragonfly.rb +1 -1
- data/lib/locomotive/wagon/scopeable.rb +28 -0
- data/lib/locomotive/wagon/server/page.rb +7 -1
- data/lib/locomotive/wagon/version.rb +1 -1
- data/locales/de.yml +13 -12
- data/locales/en.yml +13 -12
- data/locales/es.yml +13 -12
- data/locales/et.yml +13 -12
- data/locales/fr.yml +14 -12
- data/locales/it.yml +13 -12
- data/locales/nb.yml +13 -12
- data/locales/nl.yml +11 -10
- data/locales/pl.yml +13 -12
- data/locales/pt-BR.yml +13 -12
- data/locales/ru.yml +13 -12
- data/locomotivecms_wagon.gemspec +2 -1
- data/spec/fixtures/default/app/views/pages/grunge_bands.liquid.haml +8 -0
- data/spec/fixtures/default/app/views/pages/index.liquid.haml +3 -0
- data/spec/fixtures/default/app/views/pages/music.liquid.haml +1 -1
- data/spec/fixtures/default/app/views/pages/songs/template.liquid.haml +4 -0
- data/spec/fixtures/default/app/views/pages/tags/nav.liquid.haml +6 -0
- data/spec/fixtures/default/app/views/pages/unlisted_pages.liquid.haml +9 -0
- data/spec/integration/cassettes/pull.yml +397 -240
- data/spec/integration/cassettes/push.yml +468 -438
- data/spec/integration/cassettes/staging.yml +846 -0
- data/spec/integration/cli_spec.rb +19 -0
- data/spec/integration/server/basic_spec.rb +34 -5
- data/spec/integration/server/liquid_spec.rb +39 -0
- data/spec/integration/sites_spec.rb +28 -4
- data/spec/spec_helper.rb +1 -0
- data/spec/support/helpers.rb +8 -1
- metadata +37 -53
@@ -2,26 +2,25 @@
|
|
2
2
|
title: <%= config[:slug].humanize -%>
|
3
3
|
<% if config[:translated] %>
|
4
4
|
|
5
|
-
#
|
5
|
+
# unique identifier for urls, the same as a permalink
|
6
6
|
slug: <%= config[:slug] -%>
|
7
7
|
<% end %>
|
8
8
|
|
9
|
-
#
|
9
|
+
# true if the page is included in the menu
|
10
10
|
listed: true
|
11
11
|
|
12
|
-
#
|
12
|
+
# true if the page is published
|
13
13
|
published: true
|
14
14
|
|
15
|
-
#
|
15
|
+
# position among sibling pages
|
16
16
|
# position: 1
|
17
17
|
|
18
|
-
#
|
18
|
+
# sets a redirection to the given url (301)
|
19
19
|
# redirect_url: "<url to a page or to a remote url>"
|
20
20
|
|
21
|
-
#
|
21
|
+
# content type that this page is templatizing
|
22
22
|
# content_type: "<slug of one of the content types>"
|
23
23
|
|
24
|
-
# TODO: explain it
|
25
24
|
# editable_elements:
|
26
25
|
# 'some_block/some_slug': "<text>"
|
27
26
|
# 'some_block/some_slug': "<relative path to the file under the public/samples folder>"
|
@@ -1 +1,34 @@
|
|
1
|
-
|
1
|
+
---
|
2
|
+
title: <%= config[:slug].humanize -%>
|
3
|
+
<% if config[:translated] %>
|
4
|
+
|
5
|
+
# unique identifier for urls, the same as a permalink
|
6
|
+
slug: <%= config[:slug] -%>
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
# true if the page is included in the menu
|
10
|
+
listed: true
|
11
|
+
|
12
|
+
# true if the page is published
|
13
|
+
published: true
|
14
|
+
|
15
|
+
# position among sibling pages
|
16
|
+
# position: 1
|
17
|
+
|
18
|
+
# sets a redirection to the given url (301)
|
19
|
+
# redirect_url: "<url to a page or to a remote url>"
|
20
|
+
|
21
|
+
# content type that this page is templatizing
|
22
|
+
# content_type: "<slug of one of the content types>"
|
23
|
+
|
24
|
+
# editable_elements:
|
25
|
+
# 'some_block/some_slug': "<text>"
|
26
|
+
# 'some_block/some_slug': "<relative path to the file under the public/samples folder>"
|
27
|
+
---
|
28
|
+
{% extends parent %}
|
29
|
+
|
30
|
+
{% block main %}
|
31
|
+
|
32
|
+
<p>Hello world</p>
|
33
|
+
|
34
|
+
{% endblock %}
|
data/lib/locomotive/wagon.rb
CHANGED
@@ -57,9 +57,14 @@ module Locomotive
|
|
57
57
|
#
|
58
58
|
def self.push(path, connection_info, options = {})
|
59
59
|
if reader = self.require_mounter(path, true)
|
60
|
+
|
61
|
+
reader.mounting_point.site.domains = connection_info["domains"] if connection_info["domains"]
|
62
|
+
reader.mounting_point.site.subdomain = connection_info["subdomain"] if connection_info["subdomain"]
|
63
|
+
require 'bundler'
|
60
64
|
Bundler.require 'misc'
|
61
65
|
|
62
66
|
writer = Locomotive::Mounter::Writer::Api.instance
|
67
|
+
self.validate_resources(options[:resources], writer.writers)
|
63
68
|
|
64
69
|
connection_info[:uri] = "#{connection_info.delete(:host)}/locomotive/api"
|
65
70
|
|
@@ -88,6 +93,7 @@ module Locomotive
|
|
88
93
|
_options[:only] = _options.delete(:resources)
|
89
94
|
|
90
95
|
reader = Locomotive::Mounter::Reader::Api.instance
|
96
|
+
self.validate_resources(_options[:only], reader.readers)
|
91
97
|
reader.run!(_options.merge(connection_info))
|
92
98
|
|
93
99
|
writer = Locomotive::Mounter::Writer::FileSystem.instance
|
@@ -112,7 +118,7 @@ module Locomotive
|
|
112
118
|
# generate an almost blank site
|
113
119
|
require 'locomotive/wagon/generators/site'
|
114
120
|
generator = Locomotive::Wagon::Generators::Site::Cloned
|
115
|
-
generator.start [name, path, connection_info]
|
121
|
+
generator.start [name, path, connection_info.symbolize_keys]
|
116
122
|
|
117
123
|
# pull the remote site
|
118
124
|
self.pull(target_path, options.merge(connection_info).with_indifferent_access, { disable_misc: true })
|
@@ -160,5 +166,13 @@ module Locomotive
|
|
160
166
|
end
|
161
167
|
end
|
162
168
|
|
169
|
+
protected
|
170
|
+
def self.validate_resources(resources, writers_or_readers)
|
171
|
+
return if resources.nil?
|
172
|
+
valid_resources = writers_or_readers.map { |thing| thing.to_s.demodulize.gsub(/Writer$|Reader$/, '').underscore }
|
173
|
+
resources.each do |resource|
|
174
|
+
raise ArgumentError, "'#{resource}' resource not recognized. Valid resources are #{valid_resources.join(', ')}." unless valid_resources.include?(resource)
|
175
|
+
end
|
176
|
+
end
|
163
177
|
end
|
164
178
|
end
|
data/lib/locomotive/wagon/cli.rb
CHANGED
@@ -135,11 +135,14 @@ module Locomotive
|
|
135
135
|
end
|
136
136
|
end
|
137
137
|
|
138
|
-
desc 'clone NAME HOST
|
139
|
-
method_option :verbose,
|
140
|
-
|
138
|
+
desc 'clone NAME HOST [PATH]', 'Clone a remote LocomotiveCMS site'
|
139
|
+
method_option :verbose, aliases: '-v', type: 'boolean', default: false, desc: 'display the full error stack trace if an error occurs'
|
140
|
+
method_option :email, aliases: '-e', desc: 'email of an administrator account'
|
141
|
+
method_option :password, aliases: '-p', desc: 'password of an administrator account'
|
142
|
+
method_option :api_key, aliases: '-a', desc: 'api key of an administrator account'
|
143
|
+
def clone(name, host, path = '.')
|
141
144
|
begin
|
142
|
-
if Locomotive::Wagon.clone(name, path, host: host
|
145
|
+
if Locomotive::Wagon.clone(name, path, { host: host }.merge(options))
|
143
146
|
self.print_next_instructions_when_site_created(name, path)
|
144
147
|
end
|
145
148
|
rescue Exception => e
|
@@ -179,6 +182,7 @@ module Locomotive
|
|
179
182
|
desc 'push ENV [PATH]', 'Push a site to a remote LocomotiveCMS engine'
|
180
183
|
method_option :resources, aliases: '-r', type: 'array', default: nil, desc: 'Only push the resource(s) passed in argument'
|
181
184
|
method_option :force, aliases: '-f', type: 'boolean', default: false, desc: 'Force the push of a resource'
|
185
|
+
method_option :force_translations, type: 'boolean', default: false, desc: 'Force the push of local translations. You must specify it even if already using -f'
|
182
186
|
method_option :data, aliases: '-d', type: 'boolean', default: false, desc: 'Push the content entries and the editable elements (by default, they are not)'
|
183
187
|
method_option :verbose, aliases: '-v', type: 'boolean', default: false, desc: 'display the full error stack trace if an error occurs'
|
184
188
|
def push(env, path = '.')
|
@@ -1,12 +1,11 @@
|
|
1
|
-
require "locomotive/mounter"
|
2
1
|
require 'liquid'
|
2
|
+
require 'locomotive/mounter'
|
3
3
|
require 'locomotive/wagon/liquid/drops/base'
|
4
4
|
|
5
5
|
%w{. drops tags filters}.each do |dir|
|
6
6
|
Dir[File.join(File.dirname(__FILE__), 'liquid', dir, '*.rb')].each { |lib| require lib }
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
9
|
# add to_liquid methods to main models from the mounter
|
11
10
|
%w{site page content_entry}.each do |name|
|
12
11
|
klass = "Locomotive::Mounter::Models::#{name.classify}".constantize
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "locomotive/wagon/scopeable"
|
2
|
+
|
1
3
|
module Locomotive
|
2
4
|
module Wagon
|
3
5
|
module Liquid
|
@@ -12,6 +14,7 @@ module Locomotive
|
|
12
14
|
end
|
13
15
|
|
14
16
|
class ProxyCollection < ::Liquid::Drop
|
17
|
+
include Scopeable
|
15
18
|
|
16
19
|
def initialize(content_type)
|
17
20
|
@content_type = content_type
|
@@ -31,6 +34,7 @@ module Locomotive
|
|
31
34
|
end
|
32
35
|
|
33
36
|
alias :length :size
|
37
|
+
alias :count :size
|
34
38
|
|
35
39
|
def each(&block)
|
36
40
|
self.collection.each(&block)
|
@@ -91,28 +95,8 @@ module Locomotive
|
|
91
95
|
|
92
96
|
def collection
|
93
97
|
return unless @collection.blank?
|
94
|
-
|
95
|
-
|
96
|
-
@collection = @content_type.entries
|
97
|
-
else
|
98
|
-
@collection = []
|
99
|
-
|
100
|
-
conditions = @context['with_scope'].clone.delete_if { |k, _| %w(order_by per_page page).include?(k) }
|
101
|
-
|
102
|
-
@content_type.entries.each do |content|
|
103
|
-
accepted = (conditions.map do |key, value|
|
104
|
-
case value
|
105
|
-
when TrueClass, FalseClass, String then content.send(key) == value
|
106
|
-
else
|
107
|
-
true
|
108
|
-
end
|
109
|
-
end).all? # all conditions works ?
|
110
|
-
|
111
|
-
@collection << content if accepted
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
@collection
|
98
|
+
|
99
|
+
@collection = apply_scope(@content_type.entries)
|
116
100
|
end
|
117
101
|
end
|
118
102
|
end
|
@@ -4,31 +4,23 @@ module Locomotive
|
|
4
4
|
module Drops
|
5
5
|
class Page < Base
|
6
6
|
|
7
|
-
delegate :title, :slug, :fullpath, :parent, :depth, :seo_title, :redirect_url, :meta_description, :meta_keywords,
|
7
|
+
delegate :title, :slug, :fullpath, :parent, :depth, :seo_title, :redirect_url, :meta_description, :meta_keywords,
|
8
|
+
:templatized?, :published?, :redirect?, :listed?, to: '_source'
|
8
9
|
|
9
10
|
def children
|
10
|
-
_children =
|
11
|
+
_children = _source.children || []
|
11
12
|
_children = _children.sort { |a, b| a.position.to_i <=> b.position.to_i }
|
12
13
|
@children ||= liquify(*_children)
|
13
14
|
end
|
14
15
|
|
15
|
-
def
|
16
|
-
|
16
|
+
def content_type
|
17
|
+
ProxyCollection.new(_source.content_type) if _source.content_type
|
17
18
|
end
|
18
|
-
|
19
|
-
def redirect?
|
20
|
-
self._source.redirect?
|
21
|
-
end
|
22
|
-
|
19
|
+
|
23
20
|
def breadcrumbs
|
24
21
|
# TODO
|
25
22
|
''
|
26
23
|
end
|
27
|
-
|
28
|
-
def listed?
|
29
|
-
@_source.listed?
|
30
|
-
end
|
31
|
-
|
32
24
|
end
|
33
25
|
end
|
34
26
|
end
|
@@ -1,8 +1,11 @@
|
|
1
|
+
require "locomotive/wagon/scopeable"
|
2
|
+
|
1
3
|
module Locomotive
|
2
4
|
module Wagon
|
3
5
|
module Liquid
|
4
6
|
module Drops
|
5
7
|
class Site < Base
|
8
|
+
include Scopeable
|
6
9
|
|
7
10
|
delegate :name, :seo_title, :meta_description, :meta_keywords, :to => '_source'
|
8
11
|
|
@@ -11,7 +14,7 @@ module Locomotive
|
|
11
14
|
end
|
12
15
|
|
13
16
|
def pages
|
14
|
-
@pages ||= liquify(*self.mounting_point.pages.values)
|
17
|
+
@pages ||= liquify(*apply_scope(self.mounting_point.pages.values))
|
15
18
|
end
|
16
19
|
|
17
20
|
end
|
@@ -12,7 +12,7 @@ module Locomotive
|
|
12
12
|
options = args_to_options(args)
|
13
13
|
|
14
14
|
rel = options[:rel] || 'alternate'
|
15
|
-
type = options[:type] ||
|
15
|
+
type = options[:type] || MIME::Types.type_for('rss').first
|
16
16
|
title = options[:title] || 'RSS'
|
17
17
|
|
18
18
|
%{<link rel="#{rel}" type="#{type}" title="#{title}" href="#{input}" />}
|
@@ -4,16 +4,20 @@ module Locomotive
|
|
4
4
|
module Filters
|
5
5
|
module Translate
|
6
6
|
|
7
|
-
def translate(key, locale = nil)
|
8
|
-
|
7
|
+
def translate(key, locale = nil, scope = nil)
|
8
|
+
locale ||= I18n.locale.to_s
|
9
|
+
if scope.blank?
|
10
|
+
translation = @context.registers[:mounting_point].translations[key.to_s]
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
+
if translation
|
13
|
+
translation.get(locale) || translation.get(Locomotive::Mounter.locale.to_s)
|
14
|
+
else
|
15
|
+
"[unknown translation key: #{key}]"
|
16
|
+
end
|
12
17
|
else
|
13
|
-
|
18
|
+
I18n.t(key, scope: scope.split('.'), locale: locale)
|
14
19
|
end
|
15
20
|
end
|
16
|
-
|
17
21
|
end
|
18
22
|
|
19
23
|
::Liquid::Template.register_filter(Translate)
|
@@ -20,7 +20,7 @@ module Locomotive
|
|
20
20
|
def initialize(tag_name, markup, tokens, context)
|
21
21
|
if markup =~ Syntax
|
22
22
|
@source = ($1 || 'page').gsub(/"|'/, '')
|
23
|
-
@options = { :
|
23
|
+
@options = { id: 'nav', class: '', active_class: 'on', bootstrap: false }
|
24
24
|
markup.scan(::Liquid::TagAttributes) { |key, value| @options[key.to_sym] = value.gsub(/"|'/, '') }
|
25
25
|
|
26
26
|
@options[:exclude] = Regexp.new(@options[:exclude]) if @options[:exclude]
|
@@ -55,7 +55,8 @@ module Locomotive
|
|
55
55
|
output = children_output.join("\n")
|
56
56
|
|
57
57
|
if @options[:no_wrapper] != 'true'
|
58
|
-
|
58
|
+
list_class = !@options[:class].blank? ? %( class="#{@options[:class]}") : ''
|
59
|
+
output = %{<nav id="#{@options[:id]}"#{list_class}><ul>\n#{output}</ul></nav>}
|
59
60
|
end
|
60
61
|
|
61
62
|
output
|
@@ -54,7 +54,7 @@ module Locomotive
|
|
54
54
|
|
55
55
|
## configure it ##
|
56
56
|
::Dragonfly[:images].configure do |c|
|
57
|
-
convert = `which convert`.strip.presence || '/usr/
|
57
|
+
convert = `which convert`.strip.presence || '/usr/bin/env convert'
|
58
58
|
c.convert_command = convert
|
59
59
|
c.identify_command = convert
|
60
60
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Locomotive
|
2
|
+
module Wagon
|
3
|
+
module Scopeable
|
4
|
+
def apply_scope(entries)
|
5
|
+
if @context['with_scope'].blank?
|
6
|
+
entries
|
7
|
+
else
|
8
|
+
collection = []
|
9
|
+
|
10
|
+
conditions = @context['with_scope'].clone.delete_if { |k, _| %w(order_by per_page page).include?(k) }
|
11
|
+
|
12
|
+
entries.each do |content|
|
13
|
+
accepted = (conditions.map do |key, value|
|
14
|
+
case value
|
15
|
+
when TrueClass, FalseClass, String then content.send(key) == value
|
16
|
+
else
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end).all? # all conditions works ?
|
20
|
+
|
21
|
+
collection << content if accepted
|
22
|
+
end
|
23
|
+
collection
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -29,10 +29,16 @@ module Locomotive::Wagon
|
|
29
29
|
def fetch_page
|
30
30
|
matchers = self.path_combinations(self.path)
|
31
31
|
|
32
|
-
self.mounting_point.pages.values.
|
32
|
+
pages = self.mounting_point.pages.values.find_all do |_page|
|
33
33
|
matchers.include?(_page.safe_fullpath) ||
|
34
34
|
matchers.include?(_page.safe_fullpath.try(:underscore))
|
35
|
+
end.sort { |a, b| a.position <=> b.position }
|
36
|
+
|
37
|
+
if pages.size > 1
|
38
|
+
self.log "Found multiple pages: #{pages.collect(&:title).join(', ')}"
|
35
39
|
end
|
40
|
+
|
41
|
+
pages.first
|
36
42
|
end
|
37
43
|
|
38
44
|
def path_combinations(path)
|
data/locales/de.yml
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
de:
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
2
|
+
locomotive:
|
3
|
+
locales:
|
4
|
+
en: Englisch
|
5
|
+
de: Deutsch
|
6
|
+
fr: Französisch
|
7
|
+
pl: Polnisch
|
8
|
+
pt-BR: "Bras. Portugiesisch"
|
9
|
+
it: Italienisch
|
10
|
+
nl: Niederländisch
|
11
|
+
nb: Norwegisch
|
12
|
+
es: Spanisch
|
13
|
+
ru: Russisch
|
14
|
+
et: Estnisch
|
14
15
|
|
15
16
|
errors:
|
16
17
|
messages:
|
data/locales/en.yml
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
en:
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
2
|
+
locomotive:
|
3
|
+
locales:
|
4
|
+
en: English
|
5
|
+
de: German
|
6
|
+
fr: French
|
7
|
+
pl: Polish
|
8
|
+
pt-BR: "Brazilian Portuguese"
|
9
|
+
it: Italian
|
10
|
+
nl: Dutch
|
11
|
+
nb: Norwegian
|
12
|
+
es: Spanish
|
13
|
+
ru: Russian
|
14
|
+
et: Estonian
|
14
15
|
|
15
16
|
errors:
|
16
17
|
messages:
|