playbook_ui 8.0.0.pre.alpha1 → 8.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +8 -7
  3. data/app/pb_kits/playbook/data/menu.yml +1 -0
  4. data/app/pb_kits/playbook/index.js +1 -0
  5. data/app/pb_kits/playbook/pb_card/card_header.rb +7 -7
  6. data/app/pb_kits/playbook/pb_collapsible/_collapsible.jsx +84 -0
  7. data/app/pb_kits/playbook/pb_collapsible/_collapsible.scss +15 -0
  8. data/app/pb_kits/playbook/pb_collapsible/child_kits/CollapsibleContent.jsx +40 -0
  9. data/app/pb_kits/playbook/pb_collapsible/child_kits/CollapsibleMain.jsx +59 -0
  10. data/app/pb_kits/playbook/pb_collapsible/collapsible.html.erb +7 -0
  11. data/app/pb_kits/playbook/pb_collapsible/collapsible.rb +11 -0
  12. data/app/pb_kits/playbook/pb_collapsible/collapsible.test.js +50 -0
  13. data/app/pb_kits/playbook/pb_collapsible/collapsible_content.html.erb +7 -0
  14. data/app/pb_kits/playbook/pb_collapsible/collapsible_content.rb +17 -0
  15. data/app/pb_kits/playbook/pb_collapsible/collapsible_main.html.erb +16 -0
  16. data/app/pb_kits/playbook/pb_collapsible/collapsible_main.rb +15 -0
  17. data/app/pb_kits/playbook/pb_collapsible/docs/_collapsible_default.html.erb +10 -0
  18. data/app/pb_kits/playbook/pb_collapsible/docs/_collapsible_default.jsx +19 -0
  19. data/app/pb_kits/playbook/pb_collapsible/docs/example.yml +9 -0
  20. data/app/pb_kits/playbook/pb_collapsible/docs/index.js +1 -0
  21. data/app/pb_kits/playbook/pb_collapsible/index.js +79 -0
  22. data/app/pb_kits/playbook/pb_date_time_stacked/_date_time_stacked.jsx +6 -2
  23. data/app/pb_kits/playbook/pb_date_time_stacked/date_time_stacked.html.erb +1 -1
  24. data/app/pb_kits/playbook/pb_docs/kit_api.html.erb +12 -0
  25. data/app/pb_kits/playbook/pb_docs/kit_api.rb +19 -0
  26. data/app/pb_kits/playbook/pb_docs/kit_example.html.erb +28 -0
  27. data/app/pb_kits/playbook/pb_docs/kit_example.rb +46 -0
  28. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +2 -2
  29. data/app/pb_kits/playbook/pb_form/form.rb +11 -2
  30. data/app/pb_kits/playbook/pb_form/form_builder.rb +1 -1
  31. data/app/pb_kits/playbook/pb_form/form_builder/action_area.html.erb +1 -1
  32. data/app/pb_kits/playbook/pb_form/form_with.html.erb +7 -14
  33. data/app/pb_kits/playbook/pb_form/form_with.rb +13 -6
  34. data/app/pb_kits/playbook/pb_nav/_item.jsx +2 -0
  35. data/app/pb_kits/playbook/pb_nav/_nav.jsx +2 -2
  36. data/app/pb_kits/playbook/pb_nav/item.html.erb +2 -2
  37. data/app/pb_kits/playbook/pb_nav/nav.html.erb +1 -1
  38. data/app/pb_kits/playbook/pb_progress_simple/_progress_simple.jsx +1 -1
  39. data/app/pb_kits/playbook/pb_progress_simple/_progress_simple.scss +5 -0
  40. data/app/pb_kits/playbook/pb_progress_simple/docs/_progress_simple_variants.html.erb +5 -0
  41. data/app/pb_kits/playbook/pb_progress_simple/docs/_progress_simple_variants.jsx +7 -0
  42. data/app/pb_kits/playbook/pb_progress_simple/docs/_progress_simple_variants.md +1 -1
  43. data/app/pb_kits/playbook/pb_progress_simple/progress_simple.rb +1 -1
  44. data/app/pb_kits/playbook/pb_time_stacked/_time_stacked.jsx +43 -26
  45. data/app/pb_kits/playbook/pb_time_stacked/_time_stacked.scss +18 -0
  46. data/app/pb_kits/playbook/pb_time_stacked/docs/_time_stacked_default.html.erb +18 -5
  47. data/app/pb_kits/playbook/pb_time_stacked/docs/_time_stacked_default.jsx +10 -5
  48. data/app/pb_kits/playbook/pb_time_stacked/time_stacked.html.erb +7 -3
  49. data/app/pb_kits/playbook/pb_time_stacked/time_stacked.rb +8 -30
  50. data/app/pb_kits/playbook/pb_time_stacked/time_stacked.test.js +33 -0
  51. data/app/pb_kits/playbook/pb_tooltip/_tooltip.scss +4 -3
  52. data/app/pb_kits/playbook/vendor.js +3 -0
  53. data/lib/playbook.rb +13 -8
  54. data/lib/playbook/markdown.rb +4 -0
  55. data/lib/playbook/markdown/helper.rb +153 -0
  56. data/lib/playbook/pb_doc_helper.rb +89 -0
  57. data/lib/playbook/props/base.rb +1 -1
  58. data/lib/playbook/version.rb +1 -1
  59. metadata +34 -10
  60. data/app/controllers/playbook/application_controller.rb +0 -14
  61. data/app/helpers/playbook/application_helper.rb +0 -30
  62. data/app/helpers/playbook/pb_doc_helper.rb +0 -210
  63. data/app/helpers/playbook/pb_sample_helper.rb +0 -64
  64. data/app/helpers/playbook/redcarpet_helper.rb +0 -151
  65. data/app/pb_kits/playbook/pb_form/form.html.erb +0 -7
@@ -19,6 +19,13 @@ const ProgressSimpleVariants = () => {
19
19
  percent={10}
20
20
  variant="negative"
21
21
  />
22
+
23
+ <br />
24
+
25
+ <ProgressSimple
26
+ percent={40}
27
+ variant="warning"
28
+ />
22
29
  </div>
23
30
  )
24
31
  }
@@ -1 +1 @@
1
- Progress Simple can pass colors - primary, green and red. Variants names are `default`, `positive`, and `negative` respectively.
1
+ Progress Simple can pass colors - primary, green, red, and yellow. Variants names are `default`, `positive`, `negative`, and `warning`, respectively.
@@ -18,7 +18,7 @@ module Playbook
18
18
  # could this possibly be [sm, md, lg]?
19
19
  prop :width, default: "100%"
20
20
  prop :variant, type: Playbook::Props::Enum,
21
- values: %w[default positive negative],
21
+ values: %w[default positive negative warning],
22
22
  default: "default"
23
23
 
24
24
  def number_value
@@ -4,50 +4,67 @@ import React from 'react'
4
4
  import classnames from 'classnames'
5
5
 
6
6
  import DateTime from '../pb_kit/dateTime.js'
7
- import { buildCss } from '../utilities/props'
8
- import { Body, Caption } from '../'
7
+ import { buildCss, buildDataProps } from '../utilities/props'
9
8
  import { deprecatedProps, globalProps } from '../utilities/globalProps.js'
10
9
 
10
+ import { Body, Caption } from '../'
11
+
11
12
  type TimeStackedProps = {
12
13
  align?: 'left' | 'center' | 'right',
13
14
  className?: string | array<string>,
14
15
  dark?: boolean,
15
- data?: string,
16
- date: string,
16
+ data?: object,
17
+ date?: string,
17
18
  id?: string,
18
- tag?: 'body' | 'caption',
19
+ time: number | Date,
20
+ timeZone?: string,
19
21
  }
20
22
 
21
- const TimeStacked = (props: TimeStackedProps) => {
22
- const { align, className, dark, date } = props
23
- deprecatedProps('TimeStacked', ['tag'])
23
+ const TimeStackedDefault = (props: TimeStackedProps) => {
24
+ if (props.date) deprecatedProps('Time Stacked', ['date']) //date prop is deprecated, use time instead
25
+
26
+ const {
27
+ align = 'left',
28
+ className,
29
+ dark,
30
+ data = {},
31
+ date,
32
+ time,
33
+ timeZone,
34
+ } = props
35
+
24
36
  const classes = classnames(
25
37
  buildCss('pb_time_stacked_kit', align),
26
38
  globalProps(props),
27
- className,
39
+ className
28
40
  )
41
+ const dataProps = buildDataProps(data)
29
42
 
30
- const dateTimestamp = new DateTime({ value: date })
43
+ const dateTimestamp = new DateTime({ value: date ? date : new Date(time), zone: timeZone })
31
44
 
32
45
  return (
33
- <div className={classes}>
34
- <div
35
- align={align}
36
- className="pb_time_stacked_day_month"
46
+ <div
47
+ className={classes}
48
+ {...dataProps}
49
+ >
50
+ <Body
51
+ className={classnames('pb_time_stacked', 'time-spacing')}
52
+ color="light"
53
+ dark={dark}
37
54
  >
38
- <Body
39
- color="light"
40
- dark={dark}
41
- text={dateTimestamp.toTimeWithMeridian()}
42
- />
43
- <Caption
44
- color="light"
45
- dark={dark}
46
- text={dateTimestamp.toTimezone()}
47
- />
48
- </div>
55
+ <time>
56
+ {dateTimestamp.toTimeWithMeridian()}
57
+ <Caption
58
+ className="pb_time_stacked"
59
+ color="light"
60
+ dark={dark}
61
+ tag="span"
62
+ text={dateTimestamp.toTimezone()}
63
+ />
64
+ </time>
65
+ </Body>
49
66
  </div>
50
67
  )
51
68
  }
52
69
 
53
- export default TimeStacked
70
+ export default TimeStackedDefault
@@ -1,3 +1,21 @@
1
1
  @import "../pb_title/title";
2
2
  @import "../pb_caption/caption";
3
3
  @import "../tokens/colors";
4
+
5
+ [class^=pb_time_stacked_kit] {
6
+ &[class*=_center] {
7
+ text-align: center;
8
+ }
9
+
10
+ &[class*=_right] {
11
+ text-align: right;
12
+ }
13
+
14
+ }
15
+
16
+ .time-spacing {
17
+ time {
18
+ display: inline-grid;
19
+ margin-bottom: $space_xs - 6px;
20
+ }
21
+ }
@@ -1,5 +1,18 @@
1
- <%= pb_rails("time_stacked", props: { date: DateTime.now }) %>
2
- <br/>
3
- <%= pb_rails("time_stacked", props: { align: "center", date: DateTime.now }) %>
4
- <br/>
5
- <%= pb_rails("time_stacked", props: { align: "right", date: DateTime.now }) %>
1
+ <%= pb_rails("time_stacked", props: {
2
+ time: DateTime.now,
3
+ align: "left",
4
+ }) %>
5
+
6
+ <br>
7
+
8
+ <%= pb_rails("time_stacked", props: {
9
+ time: Time.now,
10
+ align: "center"
11
+ }) %>
12
+
13
+ <br>
14
+
15
+ <%= pb_rails("time_stacked", props: {
16
+ time: Time.now,
17
+ align: "right"
18
+ }) %>
@@ -5,18 +5,23 @@ const TimeStackedDefault = (props) => {
5
5
  return (
6
6
  <div>
7
7
  <TimeStacked
8
+ time={new Date()}
9
+ timeZone="America/New_York"
8
10
  {...props}
9
- date={new Date()}
10
11
  />
12
+ <br />
11
13
  <TimeStacked
12
- {...props}
13
14
  align="center"
14
- date={new Date()}
15
+ time={new Date()}
16
+ timeZone="America/New_York"
17
+ {...props}
15
18
  />
19
+ <br />
16
20
  <TimeStacked
17
- {...props}
18
21
  align="right"
19
- date={new Date()}
22
+ time={new Date()}
23
+ timeZone="America/New_York"
24
+ {...props}
20
25
  />
21
26
  </div>
22
27
  )
@@ -1,8 +1,12 @@
1
1
  <%= content_tag(:div,
2
- align: object.align,
3
2
  id: object.id,
4
3
  data: object.data,
5
4
  class: object.classname) do %>
6
- <%= pb_rails("body", props: { text: object.format_time_string, color: "light" }) %>
7
- <%= pb_rails("caption", props: { text: object.format_timezone, color: "light" }) %>
5
+
6
+ <%= pb_rails("body", props: { color: "light", classname: "pb_time_stacked time-spacing" }) do %>
7
+ <time>
8
+ <%= object.format_time_string %>
9
+ <%= pb_rails("caption", props: { color: "light", tag: "span", text: object.pb_date_time.to_timezone.upcase, classname: "pb_time_stacked" }) %>
10
+ </time>
11
+ <% end %>
8
12
  <% end %>
@@ -3,49 +3,27 @@
3
3
  module Playbook
4
4
  module PbTimeStacked
5
5
  class TimeStacked < Playbook::KitBase
6
+ prop :time, required: true
6
7
  prop :align, type: Playbook::Props::Enum,
7
8
  values: %w[left center right],
8
9
  default: "left"
9
- prop :classnames, type: Playbook::Props::String,
10
- default: nil
11
- prop :dark, type: Playbook::Props::Boolean,
12
- default: false
13
- prop :date, type: Playbook::Props::Date, required: true
10
+ prop :timezone, default: "America/New_York"
14
11
 
15
12
  def classname
16
- generate_classname("pb_time_stacked_kit", dark_class)
17
- end
18
-
19
- def day
20
- day = Playbook::PbKit::PbDateTime.new(date)
21
- content_tag(:time, datetime: day.to_iso) do
22
- day.to_day.to_s
23
- end
24
- end
25
-
26
- def month
27
- month = Playbook::PbKit::PbDateTime.new(date)
28
- content_tag(:time, datetime: month.to_iso) do
29
- month.to_month.to_s
30
- end
13
+ # convert deprecated prop values
14
+ generate_classname("pb_time_stacked_kit", align)
31
15
  end
32
16
 
33
17
  def format_time_string
34
- "#{pb_date_time.to_full_hour}:#{pb_date_time.to_minutes}#{pb_date_time.to_meridian}"
18
+ "#{pb_date_time.to_hour}:#{pb_date_time.to_minutes}#{pb_date_time.to_meridian}"
35
19
  end
36
20
 
37
- def format_timezone
38
- pb_date_time.to_timezone
39
- end
40
-
41
- private
42
-
43
- def dark_class
44
- dark ? "dark" : nil
21
+ def format_timezone_string
22
+ pb_date_time.to_timezone.to_s
45
23
  end
46
24
 
47
25
  def pb_date_time
48
- Playbook::PbKit::PbDateTime.new(date)
26
+ Playbook::PbKit::PbDateTime.new(time, timezone)
49
27
  end
50
28
  end
51
29
  end
@@ -0,0 +1,33 @@
1
+ import { renderKit, screen } from '../utilities/test-utils'
2
+
3
+ import TimeStacked from './_time_stacked'
4
+
5
+ /*eslint no-multiple-empty-lines: 0*/
6
+
7
+ test('returns the namespaced class', () => {
8
+ const props = {
9
+ data: { testid: 'default' },
10
+ time: new Date,
11
+ }
12
+
13
+ renderKit(TimeStacked, props)
14
+ expect(screen.getByTestId('default')).toHaveClass('pb_time_stacked_kit_left')
15
+
16
+ renderKit(TimeStacked, props, {
17
+ align: 'center',
18
+ data: { testid: 'center' },
19
+ })
20
+ expect(screen.getByTestId('center')).toHaveClass('pb_time_stacked_kit_center')
21
+
22
+ renderKit(TimeStacked, props, {
23
+ align: 'right',
24
+ data: { testid: 'right' },
25
+ })
26
+ expect(screen.getByTestId('right')).toHaveClass('pb_time_stacked_kit_right')
27
+
28
+ renderKit(TimeStacked, props, {
29
+ dark: true,
30
+ data: { testid: 'dark' },
31
+ })
32
+ expect(screen.getByTestId('dark')).toHaveClass('pb_time_stacked_kit_left dark')
33
+ })
@@ -46,7 +46,7 @@ $tooltip_shadow: rgba(60, 106, 172, 0.18);
46
46
  content: " ";
47
47
  position: absolute;
48
48
  top: 100%;
49
- left: 50%;
49
+ left: calc(50% - 10px);
50
50
  border-color: $white transparent transparent transparent;
51
51
  border-style: solid;
52
52
  border-width: 10px;
@@ -95,9 +95,10 @@ $tooltip_shadow: rgba(60, 106, 172, 0.18);
95
95
  box-shadow: -8px 0 28px 0 $tooltip_shadow;
96
96
  margin: 0 0 0 $space_sm;
97
97
  .arrow {
98
- left: -#{$space_xs};
99
- top: $arrow_vertical_offset;
98
+ left: -18px;
99
+ right: auto;
100
100
  margin-bottom: 0;
101
+ top: $arrow_vertical_offset;
101
102
  transform: rotate(90deg);
102
103
  }
103
104
  &.flipped .arrow {
@@ -12,6 +12,9 @@ window.datePickerHelper = datePickerHelper
12
12
  // Lazy image loading
13
13
  import 'lazysizes'
14
14
 
15
+ import PbCollapsible from './pb_collapsible'
16
+ PbCollapsible.start()
17
+
15
18
  import PbPopover from './pb_popover'
16
19
  PbPopover.start()
17
20
 
data/lib/playbook.rb CHANGED
@@ -9,9 +9,10 @@ require "view_component/engine"
9
9
  require "playbook/props"
10
10
  require "playbook/version"
11
11
  require "playbook/pb_kit_helper"
12
+ require "playbook/pb_doc_helper"
12
13
  require "playbook/kit_base"
13
14
  require "playbook/kit_resolver"
14
- require "playbook/markdown/template_handler"
15
+ require "playbook/markdown"
15
16
  require "playbook/engine" if defined?(Rails)
16
17
 
17
18
  module Playbook
@@ -20,12 +21,16 @@ module Playbook
20
21
  class ConflictingPropsError < StandardError; end
21
22
  class MissingPropError < StandardError; end
22
23
 
23
- class << self
24
- def webpacker
25
- @webpacker ||= ::Webpacker::Instance.new(
26
- root_path: ROOT_PATH,
27
- config_path: ROOT_PATH.join("config/webpacker.yml")
28
- )
29
- end
24
+ module_function
25
+
26
+ def webpacker
27
+ @webpacker ||= ::Webpacker::Instance.new(
28
+ root_path: ROOT_PATH,
29
+ config_path: ROOT_PATH.join("config/webpacker.yml")
30
+ )
31
+ end
32
+
33
+ def kit_path(kit, *args)
34
+ Playbook::Engine.root.join("app/pb_kits/playbook/pb_#{kit}", *args)
30
35
  end
31
36
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "playbook/markdown/template_handler"
4
+ require "playbook/markdown/helper"
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redcarpet"
4
+ require "rouge"
5
+ require "rouge/plugins/redcarpet"
6
+ require "action_view"
7
+
8
+ module Playbook
9
+ module Markdown
10
+ module Helper
11
+ def markdown(text)
12
+ options = {
13
+ filter_html: false,
14
+ hard_wrap: true,
15
+ link_attributes: { rel: "nofollow", target: "_blank" },
16
+ space_after_headers: true,
17
+ fenced_code_blocks: true,
18
+ no_styles: false,
19
+ safe_links_only: true,
20
+ }
21
+
22
+ extensions = {
23
+ autolink: true,
24
+ superscript: true,
25
+ fenced_code_blocks: true,
26
+ tables: true,
27
+ disable_indented_code_blocks: false,
28
+ strikethrough: true,
29
+ underline: true,
30
+ highlight: true,
31
+ footnotes: true,
32
+ with_toc_data: true,
33
+ }
34
+
35
+ renderer = HTMLBlockCode.new(options)
36
+ markdown = Redcarpet::Markdown.new(renderer, extensions)
37
+
38
+ markdown.render(text).html_safe
39
+ end
40
+
41
+ def rouge(text, language)
42
+ formatter = Rouge::Formatters::HTML.new(scope: ".highlight")
43
+ lexer = Rouge::Lexer.find(language)
44
+ formatter.format(lexer.lex(text))
45
+ end
46
+
47
+ def rouge_markdown(text)
48
+ render_options = {
49
+ filter_html: true,
50
+ hard_wrap: true,
51
+ link_attributes: { rel: "nofollow" },
52
+ }
53
+ renderer = HTML.new(render_options)
54
+
55
+ extensions = {
56
+ autolink: true,
57
+ fenced_code_blocks: true,
58
+ lax_spacing: true,
59
+ no_intra_emphasis: true,
60
+ strikethrough: true,
61
+ superscript: true,
62
+ }
63
+ markdown = Redcarpet::Markdown.new(renderer, extensions)
64
+ markdown.render(text)
65
+ end
66
+ end
67
+
68
+ class HTML < Redcarpet::Render::HTML
69
+ include Rouge::Plugins::Redcarpet
70
+ end
71
+
72
+ class HTMLBlockCode < Redcarpet::Render::HTML
73
+ include ActionView::Helpers::AssetTagHelper
74
+ def header(title, level)
75
+ if level == 7
76
+ @headers ||= []
77
+ permalink = title.gsub(/\W+/, "-")
78
+ if @headers.include?(permalink)
79
+ permalink += "_1"
80
+ loop do
81
+ break unless @headers.include?(permalink)
82
+
83
+ permalink.gsub!(/\_(\d+)$/, "_#{Regexp.last_match(1).to_i + 1}")
84
+ end
85
+ end
86
+ @headers << permalink
87
+ %(\n<a name="#{permalink}" class="markdown-header-anchor anchor" href="##{permalink}"><span class="fa fa-link anchor-icon"></span></a><h#{level} id=\"#{permalink}\">#{title}</h#{level}>\n)
88
+ else
89
+ %(\n<h#{level}>#{title}</h#{level}>\n)
90
+ end
91
+ end
92
+
93
+ def image(link, title, alt_text)
94
+ image_tag(link, title: title, alt: alt_text, class: "imageloader lazyload")
95
+ end
96
+
97
+ def preprocess(full_document)
98
+ full_document.gsub(/\[component (.*)\]/) do
99
+ @string = Regexp.last_match(1)
100
+ @default_height = "160"
101
+ @attr = ["", @default_height]
102
+
103
+ # Set src from attributes
104
+ @string.gsub(/src="(.*?)"/) do
105
+ @attr[0] = Regexp.last_match(1)
106
+ end
107
+
108
+ # Set height from attributes
109
+ @string.gsub(/height="(.*?)"/) do
110
+ @attr[1] = (Regexp.last_match(1) || @default_height)
111
+ end
112
+
113
+ %(\n<div class="uix-component-frame"><iframe scrolling="no" id="component-preview" src="#{@attr[0]}" width="100%" height="#{@attr[1]}"></iframe><a href="#{@attr[0]}" target="_blank" class="uix-component-link">View component</a></div>\n)
114
+ end
115
+ end
116
+
117
+ def list(contents, _list_type)
118
+ @contents = contents
119
+ @list_items = contents.split("\n")
120
+
121
+ if @list_items[0].include?("[do]") || @list_items[0].include?("[dont]")
122
+ @element_items = []
123
+ @list_items.each do |item, _index|
124
+ item.gsub(%r{\<li>(.*)\</li>}) do
125
+ @element_items.push(Regexp.last_match(1))
126
+ end
127
+ end
128
+
129
+ # Doing both because we could have either/both
130
+ # clean up
131
+ @dont_items, @trash_items_dont = @element_items.partition { |x, _i| x.include? "[dont]" }
132
+ @do_items, @trash_items_do = @element_items.partition { |x, _i| x.include? "[do]" }
133
+
134
+ @do_list = []
135
+ @do_items.each do |item, _index|
136
+ @do_list.push("<li>#{item.sub('[do] ', '')}</li>")
137
+ end
138
+ @do_list = "<ul class='uix-ruleset do-list'>#{@do_list.join('')}</ul>"
139
+
140
+ @dont_list = []
141
+ @dont_items.each do |item, _index|
142
+ @dont_list.push("<li>#{item.sub('[dont] ', '')}</li>")
143
+ end
144
+ @dont_list = "<ul class='uix-ruleset dont-list'>#{@dont_list.join('')}</ul>"
145
+
146
+ "<div class='row uix-ruleset-block'><div class='col-sm-6'>#{@do_list}</div><div class='col-sm-6'>#{@dont_list}</div></div>"
147
+ else
148
+ @contents
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end