glib-web 3.22.2 → 3.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/glib/json/libs.rb +9 -28
  3. data/app/controllers/concerns/glib/json/new_dynamic_text.rb +0 -0
  4. data/app/helpers/glib/app_feature_support_helper.rb +0 -0
  5. data/app/helpers/glib/dynamic_texts_helper.rb +0 -0
  6. data/app/helpers/glib/json_ui/action_builder/dialogs.rb +2 -0
  7. data/app/helpers/glib/json_ui/action_builder/sheets.rb +0 -0
  8. data/app/helpers/glib/json_ui/action_builder.rb +1 -2
  9. data/app/helpers/glib/json_ui/analytics_helper.rb +0 -0
  10. data/app/helpers/glib/json_ui/generic_builders.rb +0 -0
  11. data/app/helpers/glib/urls_helper.rb +2 -2
  12. data/app/models/glib/active_storage/attachment.rb +0 -0
  13. data/app/models/glib/active_storage/blob.rb +0 -0
  14. data/app/models/glib/dynamic_text_record.rb +0 -0
  15. data/app/models/glib/text.rb +0 -0
  16. data/app/validators/email_typo_validator.rb +0 -0
  17. data/app/validators/email_validator.rb +0 -0
  18. data/app/validators/url_validator.rb +0 -0
  19. data/app/views/json_ui/garage/actions/_dialogs.json.jbuilder +4 -0
  20. data/app/views/json_ui/garage/actions/_dialogs_show.json.jbuilder +1 -1
  21. data/app/views/json_ui/garage/actions/_sheets.json.jbuilder +0 -0
  22. data/app/views/json_ui/garage/actions/dialogs_oauth_post.json.jbuilder +0 -0
  23. data/app/views/json_ui/garage/forms/dialogs_update2.json.jbuilder +14 -0
  24. data/app/views/json_ui/garage/forms/dirty_prompt.json.jbuilder +59 -0
  25. data/app/views/json_ui/garage/forms/dynamic_select_data.json.jbuilder +0 -0
  26. data/app/views/json_ui/garage/forms/generic_post.json.jbuilder +0 -0
  27. data/app/views/json_ui/garage/forms/submission_flow.json.jbuilder +0 -0
  28. data/app/views/json_ui/garage/forms/submission_flow_post.json.jbuilder +0 -0
  29. data/app/views/json_ui/garage/forms/submission_indicator.json.jbuilder +0 -0
  30. data/app/views/json_ui/garage/forms/submission_indicator_post.json.jbuilder +0 -0
  31. data/app/views/json_ui/garage/forms/submit_on_change.json.jbuilder +8 -7
  32. data/app/views/json_ui/garage/home/blank.json.jbuilder +0 -0
  33. data/app/views/json_ui/garage/home/slow.json.jbuilder +0 -0
  34. data/app/views/json_ui/garage/lists/autoload_all.json.jbuilder +0 -0
  35. data/app/views/json_ui/garage/lists/autoload_as_needed.json.jbuilder +0 -0
  36. data/app/views/json_ui/garage/lists/chat_ui.json.jbuilder +0 -0
  37. data/app/views/json_ui/garage/lists/fab.json.jbuilder +0 -0
  38. data/app/views/json_ui/garage/notifications/web_socket.json.jbuilder +0 -0
  39. data/app/views/json_ui/garage/pages/custom_style_class.json.jbuilder +0 -0
  40. data/app/views/json_ui/garage/pages/full_width.json.jbuilder +0 -0
  41. data/app/views/json_ui/garage/pages/full_width_height.json.jbuilder +0 -0
  42. data/app/views/json_ui/garage/pages/layout.json.jbuilder +0 -0
  43. data/app/views/json_ui/garage/pages/lifecycle_hooks.json.jbuilder +0 -0
  44. data/app/views/json_ui/garage/pages/loading_indicator.json.jbuilder +0 -0
  45. data/app/views/json_ui/garage/pages/nested_scroll.json.jbuilder +0 -0
  46. data/app/views/json_ui/garage/pages/redirect_onload.json.jbuilder +0 -0
  47. data/app/views/json_ui/garage/panels/_styled.json.jbuilder +0 -0
  48. data/app/views/json_ui/garage/panels/card.json.jbuilder +0 -0
  49. data/app/views/json_ui/garage/panels/custom.json.jbuilder +0 -0
  50. data/app/views/json_ui/garage/panels/outlined.json.jbuilder +0 -0
  51. data/app/views/json_ui/garage/services/dynamic_text.json.jbuilder +0 -0
  52. data/app/views/json_ui/garage/services/image.json.jbuilder +0 -0
  53. data/app/views/json_ui/garage/services/index.json.jbuilder +0 -0
  54. data/app/views/json_ui/garage/tables/_autoload_section.json.jbuilder +0 -0
  55. data/app/views/json_ui/garage/tables/autoload_all.json.jbuilder +0 -0
  56. data/app/views/json_ui/garage/tables/autoload_as_needed.json.jbuilder +0 -0
  57. data/app/views/json_ui/garage/tables/export_import.json.jbuilder +0 -0
  58. data/app/views/json_ui/garage/tables/index.json.jbuilder +0 -0
  59. data/app/views/json_ui/garage/views/calendar_data.json.jbuilder +0 -0
  60. data/app/views/json_ui/garage/views/links.json.jbuilder +0 -0
  61. data/app/views/json_ui/garage/views/map_data.json.jbuilder +0 -0
  62. data/app/views/json_ui/garage/views/multimedia.json.jbuilder +0 -0
  63. data/app/views/layouts/json_ui/no_custom.html.erb +0 -0
  64. data/lib/generators/glib/install_generator.rb +0 -0
  65. data/lib/generators/templates/20191017062519_create_texts.rb +0 -0
  66. data/lib/generators/templates/20191024063257_add_scope_to_texts.rb +0 -0
  67. data/lib/generators/templates/20191112095018_add_lang_to_texts.rb +0 -0
  68. data/lib/generators/templates/20191126071051_create_active_storage_tables.active_storage.rb +0 -0
  69. data/lib/generators/templates/database.yml +0 -0
  70. data/lib/generators/templates/dynamic_text.rb +0 -0
  71. data/lib/glib/crypt/utils.rb +1 -2
  72. data/lib/glib/crypt.rb +0 -27
  73. data/lib/glib/dynamic_text/config.rb +0 -0
  74. data/lib/glib/dynamic_text.rb +0 -0
  75. data/lib/glib/json_crawler/action_crawler.rb +0 -0
  76. data/lib/glib/json_crawler/action_crawlers/action_http.rb +0 -0
  77. data/lib/glib/json_crawler/action_crawlers/dialogs_alert.rb +0 -0
  78. data/lib/glib/json_crawler/action_crawlers/menu.rb +0 -0
  79. data/lib/glib/json_crawler/action_crawlers/run_multiple.rb +0 -0
  80. data/lib/glib/json_crawler/action_crawlers/windows_open.rb +0 -0
  81. data/lib/glib/json_crawler/coverage.rb +0 -0
  82. data/lib/glib/value.rb +0 -0
  83. data/lib/glib/version.rb +0 -0
  84. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 806df354ed58edcee98f342d40209e0f42b16343f79427941accd17b796d6c33
4
- data.tar.gz: 8b0de205b315c74ad1a773cf6963f461e2952cf1fbf5f36e63aea66c648c4640
3
+ metadata.gz: 45dc3048668b5ebaea724341b5f6ee89cd90a467f7859d03923c1824ffdbb729
4
+ data.tar.gz: 69d8c59495ac1d84d17ee027a851b0761f1ae7981852a516ac449d36671f3d72
5
5
  SHA512:
6
- metadata.gz: 5e9062feefed9651443c3a128de05df41e63a755cafdc10b66167f60643b9bf847a1dc4c68af9f95e8b9eff4f7a0f3b52d31fa61907575a6f09de7903dffb684
7
- data.tar.gz: 651ae04bd671192239d8bc08e144733a011f6777e6b33347739579ec7511c7ff9bd9d0f23ff36ff110076242424606ac0500cb80ba0212b4487f788b177b7f55
6
+ metadata.gz: 9d31cca0cb02fb134b52c2eb166bd76fa4040ad64a88b4f3a7cd5d3080bf85a390c568e07805fc0fa91c0eab6529247b4d209f28a8c68385be82f31227ba9a4c
7
+ data.tar.gz: 0bc9b7c6b5d1e13fc1a4e6735f33f2819c5799c32ce6ee0001783c62eb9d6d9ed33d2771c80d695276d768e602e90ec591ebfa17b60b2234d696116312a5f49c
@@ -62,27 +62,14 @@ module Glib::Json::Libs
62
62
  JSON.parse(render_to_string(template, locals: args))
63
63
  end
64
64
 
65
- def glib_html_url(url)
66
- url = url.include?('?') ? url.sub(/.json\?/, '?') : url.sub(/.json$/, '')
67
-
68
- # As much as possible, try retaining the front fragment by matching the back, because
69
- # the front could be either a `?` or `&`
70
- url = url.sub(/format=json\&/, '');
71
-
72
- # If no match, then we replace the front fragment
73
- url.sub(/[\&\?]format=json/, '');
74
- end
75
-
76
- def glib_redirect_to(url, return_to_previous: false)
77
- url = __delete_redirect_back_url(url) if return_to_previous
78
-
65
+ def glib_redirect_to(url)
79
66
  respond_to do |format|
80
67
  format.html do
81
68
  redirect_to url
82
69
  end
83
70
  format.json do
84
71
  if __json_ui_rendering?
85
- redirect_to glib_html_url(url)
72
+ redirect_to url
86
73
  return
87
74
  end
88
75
 
@@ -160,10 +147,10 @@ module Glib::Json::Libs
160
147
  session.delete(REDIRECT_BACK_KEY) || url
161
148
  end
162
149
 
163
- # def html_ui_redirect_back_or_to(default_url)
164
- # redirect_url = __delete_redirect_back_url(default_url)
165
- # redirect_to redirect_url
166
- # end
150
+ def html_ui_redirect_back_or_to(default_url)
151
+ redirect_url = __delete_redirect_back_url(default_url)
152
+ redirect_to redirect_url
153
+ end
167
154
 
168
155
  def json_ui_redirect_back_or_to(default_url, json_action)
169
156
  redirect_url = __delete_redirect_back_url(default_url)
@@ -223,15 +210,9 @@ module Glib::Json::Libs
223
210
  end
224
211
 
225
212
  def json_ui_redirect_to(url)
226
- # `onLoad` also executes in `onResponse` situation, e.g. form submit.
227
- on_load = { action: 'windows/open', url: url }
228
-
229
- if glib_json_dialog_mode?
230
- # Only do this in dialog mode because this seems to add to the rendering time
231
- # (i.e. longer flicker time), presumably due to the use of things like `nextTick()`.
232
- on_load = { action: 'dialogs/close', onClose: on_load }
233
- end
234
- render json: { onLoad: on_load }
213
+ render json: {
214
+ onResponse: { action: 'dialogs/close', onClose: { action: 'windows/open', url: url } }
215
+ }
235
216
  end
236
217
 
237
218
  module ClassMethods
File without changes
File without changes
@@ -51,8 +51,10 @@ class Glib::JsonUi::ActionBuilder
51
51
  length :width
52
52
  length :height
53
53
  bool :closeOnBlur
54
+ bool :updateExisting
54
55
  end
55
56
 
57
+ # deprecated
56
58
  class Reload < Action
57
59
  panels_builder :content, :body
58
60
  string :url, cache: true
File without changes
@@ -16,6 +16,7 @@ module Glib
16
16
  end
17
17
 
18
18
  class Action < JsonUiElement
19
+ string :targetId
19
20
  # def analytics(value)
20
21
  # if value.is_a?(String)
21
22
  # @analyticsEnabled = true
@@ -129,7 +130,6 @@ module Glib
129
130
 
130
131
  module Labels
131
132
  class Set < Action
132
- string :targetId
133
133
  string :text
134
134
  action :onSet
135
135
  end
@@ -137,7 +137,6 @@ module Glib
137
137
 
138
138
  module Lists
139
139
  class Append < Action
140
- string :targetId
141
140
  hash :row
142
141
  end
143
142
  end
File without changes
File without changes
@@ -1,7 +1,7 @@
1
1
  module Glib
2
2
  module UrlsHelper
3
- def glib_url_current(override_params: params.to_unsafe_h, extra_params: {})
4
- url_for(override_params.merge(extra_params.merge(only_path: false)))
3
+ def glib_url_current(new_params = {})
4
+ url_for(params.to_unsafe_h.merge(new_params.merge(only_path: false)))
5
5
  end
6
6
 
7
7
  def glib_url_equals_current?(url)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -80,6 +80,10 @@ section.rows builder: ->(template) do
80
80
  action.dialogs_open url: json_ui_garage_url(path: 'forms/dialogs_update', mode: 'dialog')
81
81
  end
82
82
 
83
+ template.thumbnail title: 'dialogs/open (page with dialogs/open with updateExisting)', onClick: ->(action) do
84
+ action.dialogs_open url: json_ui_garage_url(path: 'forms/dialogs_update2')
85
+ end
86
+
83
87
  template.thumbnail title: 'dialogs/oauth', onClick: ->(action) do
84
88
  if respond_to?(:user_facebook_omniauth_authorize_url)
85
89
  provider = {
@@ -18,7 +18,7 @@ action.send "dialogs_#{dialog_mode}", **options, content: ->(dialog) do
18
18
  split.left childViews: ->(left) do
19
19
  left.panels_horizontal height: 'matchParent', align: 'middle', childViews: ->(horizontal) do
20
20
  horizontal.label text: 'Open current', onClick: ->(subaction) do
21
- subaction.dialogs_reload width: 900, fullscreen: 'mobile', url: json_ui_garage_url(path: 'forms/basic', mode: 'dialog_reload')
21
+ subaction.dialogs_open width: 900, fullscreen: 'mobile', url: json_ui_garage_url(path: 'forms/basic')
22
22
  end
23
23
  end
24
24
  end
@@ -0,0 +1,14 @@
1
+ json.title 'Forms'
2
+
3
+ page = json_ui_page json
4
+ render "#{@path_prefix}/nav_menu", json: json, page: page
5
+
6
+ page.scroll childViews: ->(scroll) do
7
+ scroll.label text: 'This content will be replaced'
8
+ end
9
+
10
+ page.on load: ->(action) do
11
+ action.timeouts_set interval: 500, onTimeout: ->(subaction) do
12
+ subaction.dialogs_open url: json_ui_garage_url(path: 'forms/basic', mode: 'dialog'), updateExisting: true
13
+ end
14
+ end
@@ -19,7 +19,66 @@ page.form \
19
19
  form.fields_text name: 'user[dirty_check_enabled]', width: 'matchParent', label: 'Dirty check enabled'
20
20
  form.spacer height: 14
21
21
  form.fields_text name: 'user[dirty_check_disabled]', width: 'matchParent', label: 'Dirty check disabled', disableDirtyCheck: true
22
+ form.spacer height: 14
23
+ form.fields_text name: 'user[dirty_check_with_value]', width: 'matchParent', label: 'Dirty check with value', value: 'Hello'
24
+ form.spacer height: 14
25
+
26
+ form.h3 text: 'Other inputs'
27
+ form.spacer height: 14
28
+ form.fields_number name: 'user[dirty_number]', width: 'matchParent', label: 'Number'
29
+ form.spacer height: 14
30
+ options = [{ text: 'Option 1', value: 'option_1' }, { text: 'Option 2', value: 'option_2' }]
31
+ form.fields_select name: 'user[dirty_select]', width: 'matchParent', label: 'Select', options: options
32
+ form.spacer height: 14
33
+ form.fields_select multiple: true, name: 'user[dirty_select_multiple]', width: 'matchParent', label: 'Select (multiple)', options: options, value: ['option_1']
34
+ form.spacer height: 14
35
+ form.fields_date name: 'user[dirty_date]', width: 'matchParent', label: 'Date', value: DateTime.current
36
+ form.spacer height: 14
37
+ form.label text: 'Chip group', padding: { bottom: 4 }
38
+ form.fields_chipGroup name: 'user[dirty_chipgroup]', width: 'matchParent', label: 'Chipgroup', value: ['option_2'], options: options
39
+ form.spacer height: 14
40
+
41
+ form.h3 text: 'valueIf'
42
+ form.fields_check name: 'user[check_all]', label: 'All', checkValue: true
43
+
44
+ form.fields_check name: 'user[check1]', label: 'Check 1', checkValue: true, valueIf: {
45
+ "==": [
46
+ {
47
+ "var": 'user[check_all]'
48
+ },
49
+ true
50
+ ]
51
+ }
52
+ form.fields_check name: 'user[check2]', label: 'Check 2', checkValue: true, valueIf: {
53
+ "==": [
54
+ {
55
+ "var": 'user[check_all]'
56
+ },
57
+ true
58
+ ]
59
+ }
60
+ form.fields_check name: 'user[check3]', label: 'Check 3', checkValue: true, valueIf: {
61
+ "==": [
62
+ {
63
+ "var": 'user[check_all]'
64
+ },
65
+ true
66
+ ]
67
+ }
68
+
69
+ form.h3 text: 'Radio'
70
+ form.spacer height: 14
71
+ form.fields_radioGroup name: 'user[gender]', value: 'F', childViews: ->(group) do
72
+ group.fields_radio value: '', label: 'Unknown'
73
+ group.fields_radio value: 'M', label: 'Male'
74
+ group.fields_radio value: 'F', label: 'Female'
75
+ end
76
+
77
+ form.h3 text: 'Rich text'
78
+ form.spacer height: 14
79
+ form.fields_richText name: 'user[richtext]', width: 'matchParent', value: 'lorem ipsum dumet...'
22
80
 
81
+ form.spacer height: 24
23
82
  form.panels_split width: 'matchParent', content: ->(split) do
24
83
  split.left childViews: ->(left) do
25
84
  left.panels_horizontal height: 'matchParent', align: 'middle', childViews: ->(horizontal) do
@@ -5,6 +5,7 @@ render "#{@path_prefix}/nav_menu", json: json, page: page
5
5
 
6
6
  page.scroll childViews: ->(scroll) do
7
7
  scroll.panels_form \
8
+ id: 'form',
8
9
  url: json_ui_garage_url(path: 'forms/generic_post'),
9
10
  method: 'post',
10
11
  padding: glib_json_padding_body,
@@ -20,7 +21,7 @@ page.scroll childViews: ->(scroll) do
20
21
  paramNameForFormData: 'message',
21
22
  onChange: ->(action) do
22
23
  action.dialogs_alert title: 'Submit data?', onClose: ->(subaction) do
23
- action.forms_submit
24
+ action.forms_submit targetId: 'form'
24
25
  end
25
26
  end
26
27
 
@@ -30,7 +31,7 @@ page.scroll childViews: ->(scroll) do
30
31
  label: 'I am an employer (no default value)',
31
32
  onChange: ->(action) do
32
33
  action.dialogs_alert message: 'The form will be submitted when the dialog is closed', onClose: ->(subaction) do
33
- action.forms_submit
34
+ action.forms_submit targetId: 'form'
34
35
  end
35
36
  end
36
37
 
@@ -46,7 +47,7 @@ page.scroll childViews: ->(scroll) do
46
47
  onChange: ->(action) do
47
48
  action.snackbars_select message: 'Submit data?', buttons: ->(menu) do
48
49
  menu.button text: 'Yes, submit', onClick: ->(subaction) do
49
- subaction.forms_submit
50
+ subaction.forms_submit targetId: 'form'
50
51
  end
51
52
  end
52
53
  end
@@ -64,7 +65,7 @@ page.scroll childViews: ->(scroll) do
64
65
  onChange: ->(action) do
65
66
  action.sheets_select message: 'Submit data?', buttons: ->(menu) do
66
67
  menu.button text: 'Yes, submit', onClick: ->(subaction) do
67
- action.forms_submit
68
+ action.forms_submit targetId: 'form'
68
69
  end
69
70
  end
70
71
  end
@@ -77,7 +78,7 @@ page.scroll childViews: ->(scroll) do
77
78
  onChange: ->(action) do
78
79
  action.sheets_select message: 'Submit data?', buttons: ->(menu) do
79
80
  menu.button text: 'Yes, submit', onClick: ->(subaction) do
80
- action.forms_submit
81
+ action.forms_submit targetId: 'form'
81
82
  end
82
83
  end
83
84
  end
@@ -91,7 +92,7 @@ page.scroll childViews: ->(scroll) do
91
92
  onChange: ->(action) do
92
93
  action.sheets_select message: 'Submit data?', buttons: ->(menu) do
93
94
  menu.button text: 'Yes, submit', onClick: ->(subaction) do
94
- action.forms_submit
95
+ action.forms_submit targetId: 'form'
95
96
  end
96
97
  end
97
98
  end
@@ -117,7 +118,7 @@ page.scroll childViews: ->(scroll) do
117
118
  onChange: ->(action) do
118
119
  action.sheets_select message: 'Submit data?', buttons: ->(menu) do
119
120
  menu.button text: 'Yes, submit', onClick: ->(subaction) do
120
- action.forms_submit
121
+ action.forms_submit targetId: 'form'
121
122
  end
122
123
  end
123
124
  end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,6 +1,5 @@
1
- # Deprecated. Use Glib::Crypt instead.
2
1
  module Glib
3
- class Crypt
2
+ module Crypt
4
3
  class Utils
5
4
  def self.encrypt(original_message, encryption_secret, encryption_salt)
6
5
  encryptor = message_encryptor(encryption_secret, encryption_salt)
data/lib/glib/crypt.rb CHANGED
@@ -1,28 +1 @@
1
1
  require_relative './crypt/utils'
2
-
3
- # A replacement of Glib::Crypt::Utils
4
- module Glib
5
- class Crypt
6
- # Recommended digest: 'SHA256'
7
- def self.encrypt(original_message, encryption_secret, encryption_salt, digest)
8
- encryptor = message_encryptor(encryption_secret, encryption_salt, digest)
9
- CGI.escape(encryptor.encrypt_and_sign(original_message))
10
- end
11
-
12
- def self.decrypt(encrypted_message, encryption_secret, encryption_salt, digest)
13
- encryptor = message_encryptor(encryption_secret, encryption_salt, digest)
14
- begin
15
- encryptor.decrypt_and_verify(CGI.unescape(encrypted_message))
16
- rescue ActiveSupport::MessageEncryptor::InvalidMessage
17
- nil
18
- end
19
- end
20
-
21
- private
22
- def self.message_encryptor(encryption_secret, encryption_salt, digest)
23
- key_generator = ActiveSupport::KeyGenerator.new(encryption_secret, iterations: 1000)
24
- key_secret = key_generator.generate_key(encryption_salt, 32)
25
- ActiveSupport::MessageEncryptor.new(key_secret, digest: digest, serializer: JSON)
26
- end
27
- end
28
- end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
data/lib/glib/value.rb CHANGED
File without changes
data/lib/glib/version.rb CHANGED
File without changes
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glib-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.22.2
4
+ version: 3.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''
@@ -164,6 +164,7 @@ files:
164
164
  - app/views/json_ui/garage/forms/conditional_value.json.jbuilder
165
165
  - app/views/json_ui/garage/forms/dialogs_close.json.jbuilder
166
166
  - app/views/json_ui/garage/forms/dialogs_update.json.jbuilder
167
+ - app/views/json_ui/garage/forms/dialogs_update2.json.jbuilder
167
168
  - app/views/json_ui/garage/forms/dirty_prompt.json.jbuilder
168
169
  - app/views/json_ui/garage/forms/dynamic_group.json.jbuilder
169
170
  - app/views/json_ui/garage/forms/dynamic_select.json.jbuilder
@@ -335,8 +336,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
335
336
  - !ruby/object:Gem::Version
336
337
  version: '0'
337
338
  requirements: []
338
- rubyforge_project:
339
- rubygems_version: 2.7.6
339
+ rubygems_version: 3.4.6
340
340
  signing_key:
341
341
  specification_version: 4
342
342
  summary: ''