lookbook 1.2.1 → 1.3.0

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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +47 -14
  4. data/app/components/lookbook/code/component.html.erb +1 -1
  5. data/app/components/lookbook/inspector_panel/component.rb +3 -5
  6. data/app/components/lookbook/nav/item/component.html.erb +1 -1
  7. data/app/components/lookbook/params/editor/component.rb +3 -10
  8. data/app/components/lookbook/params/field/component.html.erb +8 -8
  9. data/app/components/lookbook/params/field/component.rb +21 -72
  10. data/app/controllers/concerns/lookbook/targetable_concern.rb +156 -0
  11. data/app/controllers/concerns/lookbook/with_preview_controller_concern.rb +13 -0
  12. data/app/controllers/lookbook/application_controller.rb +13 -3
  13. data/app/controllers/lookbook/inspector_controller.rb +45 -0
  14. data/app/controllers/lookbook/page_controller.rb +11 -7
  15. data/app/controllers/lookbook/previews_controller.rb +4 -210
  16. data/app/helpers/lookbook/output_helper.rb +5 -5
  17. data/app/views/layouts/lookbook/skeleton.html.erb +3 -3
  18. data/app/views/lookbook/index.html.erb +12 -1
  19. data/app/views/lookbook/{previews → inspector}/inputs/_color.html.erb +0 -0
  20. data/app/views/lookbook/{previews → inspector}/inputs/_range.html.erb +0 -0
  21. data/app/views/lookbook/{previews → inspector}/inputs/_select.html.erb +0 -0
  22. data/app/views/lookbook/{previews → inspector}/inputs/_text.html.erb +0 -0
  23. data/app/views/lookbook/{previews → inspector}/inputs/_textarea.html.erb +0 -0
  24. data/app/views/lookbook/{previews → inspector}/inputs/_toggle.html.erb +3 -3
  25. data/app/views/lookbook/{previews → inspector}/panels/_content.html.erb +0 -0
  26. data/app/views/lookbook/{previews → inspector}/panels/_notes.html.erb +0 -0
  27. data/app/views/lookbook/{previews → inspector}/panels/_output.html.erb +0 -0
  28. data/app/views/lookbook/{previews → inspector}/panels/_params.html.erb +4 -4
  29. data/app/views/lookbook/{previews → inspector}/panels/_preview.html.erb +0 -0
  30. data/app/views/lookbook/{previews → inspector}/panels/_source.html.erb +0 -0
  31. data/app/views/lookbook/{previews → inspector}/show.html.erb +4 -1
  32. data/config/app.yml +8 -1
  33. data/config/inputs.yml +12 -12
  34. data/config/panels.yml +5 -5
  35. data/config/routes.rb +5 -5
  36. data/config/tags.yml +4 -1
  37. data/lib/lookbook/engine.rb +101 -150
  38. data/lib/lookbook/file_watcher.rb +47 -0
  39. data/lib/lookbook/page.rb +15 -16
  40. data/lib/lookbook/param.rb +99 -0
  41. data/lib/lookbook/preview.rb +8 -1
  42. data/lib/lookbook/{preview_controller.rb → preview_actions.rb} +14 -3
  43. data/lib/lookbook/preview_example.rb +1 -1
  44. data/lib/lookbook/preview_group.rb +0 -4
  45. data/lib/lookbook/preview_parser.rb +53 -0
  46. data/lib/lookbook/process.rb +21 -0
  47. data/lib/lookbook/services/code/code_beautifier.rb +21 -0
  48. data/lib/lookbook/services/code/code_highlighter.rb +69 -0
  49. data/lib/lookbook/services/data/parsers/data_parser.rb +22 -0
  50. data/lib/lookbook/services/data/parsers/json_parser.rb +7 -0
  51. data/lib/lookbook/services/data/parsers/yaml_parser.rb +7 -0
  52. data/lib/lookbook/services/data/resolvers/data_resolver.rb +70 -0
  53. data/lib/lookbook/services/data/resolvers/eval_resolver.rb +10 -0
  54. data/lib/lookbook/services/data/resolvers/file_resolver.rb +28 -0
  55. data/lib/lookbook/services/data/resolvers/method_resolver.rb +10 -0
  56. data/lib/lookbook/services/data/resolvers/yaml_resolver.rb +18 -0
  57. data/lib/lookbook/services/markdown_renderer.rb +29 -0
  58. data/lib/lookbook/services/string_value_caster.rb +60 -0
  59. data/lib/lookbook/services/tags/tag_options_parser.rb +62 -0
  60. data/lib/lookbook/services/templates/action_view_annotations_handler.rb +21 -0
  61. data/lib/lookbook/services/templates/action_view_annotations_stripper.rb +15 -0
  62. data/lib/lookbook/services/templates/frontmatter_extractor.rb +28 -0
  63. data/lib/lookbook/services/templates/styles_extractor.rb +38 -0
  64. data/lib/lookbook/services/{search_param_builder.rb → urls/search_param_builder.rb} +1 -1
  65. data/lib/lookbook/services/{search_param_parser.rb → urls/search_param_parser.rb} +1 -1
  66. data/lib/lookbook/source_inspector.rb +26 -45
  67. data/lib/lookbook/stores/config_store.rb +7 -8
  68. data/lib/lookbook/stores/input_store.rb +7 -3
  69. data/lib/lookbook/stores/tag_store.rb +3 -5
  70. data/lib/lookbook/support/null_object.rb +10 -0
  71. data/lib/lookbook/support/service.rb +2 -2
  72. data/lib/lookbook/support/store.rb +1 -1
  73. data/lib/lookbook/support/utils/attribute_utils.rb +6 -1
  74. data/lib/lookbook/support/utils/path_utils.rb +6 -3
  75. data/lib/lookbook/tags/component_tag.rb +7 -0
  76. data/lib/lookbook/tags/custom_tag.rb +59 -0
  77. data/lib/lookbook/tags/display_tag.rb +15 -0
  78. data/lib/lookbook/tags/hidden_tag.rb +13 -0
  79. data/lib/lookbook/tags/id_tag.rb +7 -0
  80. data/lib/lookbook/tags/label_tag.rb +4 -0
  81. data/lib/lookbook/tags/logical_path.rb +4 -0
  82. data/lib/lookbook/tags/param_tag.rb +61 -0
  83. data/lib/lookbook/tags/position_tag.rb +16 -0
  84. data/lib/lookbook/tags/tag_provider.rb +18 -0
  85. data/lib/lookbook/tags/yard_tag.rb +62 -0
  86. data/lib/lookbook/utils.rb +0 -40
  87. data/lib/lookbook/version.rb +1 -1
  88. data/lib/lookbook/websocket.rb +60 -0
  89. data/lib/lookbook.rb +2 -1
  90. data/public/lookbook-assets/css/lookbook.css +30 -77
  91. data/public/lookbook-assets/css/lookbook.css.map +1 -1
  92. data/public/lookbook-assets/js/lookbook.js +7 -2
  93. data/public/lookbook-assets/js/lookbook.js.map +1 -1
  94. metadata +55 -26
  95. data/lib/lookbook/code_formatter.rb +0 -68
  96. data/lib/lookbook/markdown.rb +0 -22
  97. data/lib/lookbook/params.rb +0 -157
  98. data/lib/lookbook/parser.rb +0 -42
  99. data/lib/lookbook/tag.rb +0 -122
  100. data/lib/lookbook/tag_options.rb +0 -111
  101. data/lib/lookbook/tags.rb +0 -17
  102. data/lib/lookbook/template_parser.rb +0 -72
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 746247239c9d22b8537a0327dfa40e3d4a2b687a9d48e0a4b61819cba7766bec
4
- data.tar.gz: c288e6e752520bb2b4b90b5e1f682cd68c99961939496b5d698abf2604962efc
3
+ metadata.gz: d6235a52e2104d528b7c3d4a56fee91f3db334ef9ce3d9da43cc11e50792cf77
4
+ data.tar.gz: 5bd16de7e7dbe99e7552efb07d719230bd1c108d13340d4fcb790dd8e034ef1e
5
5
  SHA512:
6
- metadata.gz: 6b0aae039cf5d29f2d9f93aca8f2fd62996ecbce1ad9cc9ab8c8b19e82343f71a3dc6694406182e5ebd41b0a6ad97c160a7c156e51463cda51f522b186a25a1d
7
- data.tar.gz: cf44c51bfa10e5eabc5dc6c88931d3e1676e5b72599e00304c83aca343d6a080ef31afa0044e39383d1c4b4ccc179260832afb5951d5427858767b09ff749a4a
6
+ metadata.gz: 990f07de7b0ab03d9e9577d3c84768542ebc2af7e5d587fc85f95532ade53f88abc34b2f371a776a0f48b2a3c0db5a1be606e6a42653f57e92ae19e6735ac81c
7
+ data.tar.gz: a2e17e85420de7603fdd3277eebd473b3bcff413f9d8507b38600290d6d35c081a034c4f70c14d8e3be9075b6b22caade8d2d6610ab3d6a2fd5e863bf1555bc8
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2021 allmarkedup
3
+ Copyright (c) 2021 Mark Perkins
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,30 +1,42 @@
1
- # Lookbook
2
-
3
- <div>
4
- <a href="https://rubygems.org/gems/lookbook"><img src="https://img.shields.io/gem/v/lookbook" alt="Gem version"></a>
5
- <a href="https://github.com/allmarkedup/lookbook/actions/workflows/ci.yml"><img src="https://github.com/allmarkedup/lookbook/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI status"></a>
6
- </div>
7
1
  <br>
2
+ <img src=".github/assets/lookbook_logo.svg" width="180">
8
3
 
9
4
  A tool to help browse, develop, test & document [ViewComponents](https://viewcomponent.org/) in Ruby on Rails apps.
10
5
 
6
+ [![Gem version](https://img.shields.io/gem/v/lookbook)](https://rubygems.org/gems/lookbook)
7
+ [![CI status](https://github.com/allmarkedup/lookbook/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/allmarkedup/lookbook/actions/workflows/ci.yml)
8
+ <br>
9
+
11
10
  ## Documentation
12
11
 
13
- **Lookbook (v1.x)** documentation: **[https://lookbook.build](https://lookbook.build)**
12
+ **Lookbook guide and API docs**: [lookbook.build](https://lookbook.build)
14
13
 
15
- > _Looking for v0.9.x docs? [Head over here](https://github.com/allmarkedup/lookbook/tree/0.9.x)._
14
+ > _Looking for pre-v1.0 documentation? [Head over here](https://github.com/allmarkedup/lookbook/tree/0.9.x)._
16
15
 
17
16
 
18
17
  ## Demo
19
18
 
20
- **Online Demo:** [https://lookbook-demo-app.herokuapp.com/lookbook](https://lookbook-demo-app.herokuapp.com/lookbook)
19
+ **Online Demo**: [lookbook-demo-app.herokuapp.com/lookbook](https://lookbook-demo-app.herokuapp.com/lookbook)
20
+
21
+ ✨ **Demo repo**: [github.com/allmarkedup/lookbook-demo](https://github.com/allmarkedup/lookbook-demo)
21
22
 
22
23
  [![Lookbook UI](.github/assets/lookbook_screenshot_v1.0_beta.png)](https://lookbook-demo-app.herokuapp.com/lookbook/)
23
24
 
24
25
 
25
26
  ## Development
26
27
 
27
- Lookbook's UI is itself built using ViewComponents. To preview these components in a Lookbook instance you can run the included `workbench` app:
28
+ Lookbook is implemented as an isolated [Rails Engine](https://guides.rubyonrails.org/engines.html) and uses [ViewComponent](https://viewcomponent.org), [Tailwind](https://tailwindcss.com/) and [Alpine](https://alpinejs.dev/) for it's UI.
29
+
30
+ This repository contains:
31
+
32
+ * The Lookbook source code ([`/app`](https://github.com/allmarkedup/lookbook/tree/main/app), [`/lib`](https://github.com/allmarkedup/lookbook/tree/main/lib), [`/config`](https://github.com/allmarkedup/lookbook/tree/main/config), etc)
33
+ * A '[workbench](#workbench)' app used for Lookbook component development ([`/workbench`](https://github.com/allmarkedup/lookbook/tree/main/workbench)).
34
+ * The Lookbook [documentation site](#docs-site) source code and content ([`/docs`](https://github.com/allmarkedup/lookbook/tree/main/docs)).
35
+ * A [test suite](#testing) with a 'runable' dummy app ([`/spec`](https://github.com/allmarkedup/lookbook/tree/main/spec)).
36
+
37
+ ### Workbench
38
+
39
+ To preview the Lookbook components within a Lookbook instance you can run the included `workbench` app:
28
40
 
29
41
  1. Clone this repo
30
42
  2. Install dependencies: `bundle install & npm install`
@@ -33,21 +45,42 @@ Lookbook's UI is itself built using ViewComponents. To preview these components
33
45
 
34
46
  The `workbench` app will be started in development mode and any changes to Lookbook's views or assets will immediately be reflected in the UI.
35
47
 
36
- ### Docs site
48
+ ### Documentation site
37
49
 
38
50
  The [Lookbook docs site](https://lookbook.build) is built using [Bridgetown](https://www.bridgetownrb.com/) and the source files can be found in the `./docs` directory.
39
51
 
40
- To see a local version of the site run `bin/docs` from the root of this repo and then visit http://localhost:4000 in your browser.
52
+ To preview changes locally you can run a development version of the docs site:
53
+
54
+ 1. Clone this repo
55
+ 2. Install dependencies: `bundle install`
56
+ 3. Start the app: `bin/docs`
57
+ 4. Visit http://localhost:4000
41
58
 
42
59
  ### Testing
43
60
 
44
- Lookbook uses RSpec for testing.
61
+ Lookbook uses [RSpec](https://relishapp.com/rspec) for testing.
45
62
 
46
63
  Tests can be run using the `rake spec` or `bundle exec rspec` commands.
47
64
 
48
65
  The dummy app that the tests are being run against can be viewed by running the `bin/dummy` command and then browsing to http://localhost:9292/lookbook
49
66
 
50
67
 
68
+ ## Contributing
69
+
70
+ Lookbook is an un-funded open source project and contributions of all types and sizes are most welcome!
71
+
72
+ Please take the time to read over the [Contributing](./CONTRIBUTING.md) guide before making your first contribution and if anything isn't clear then [start a discussion](https://github.com/allmarkedup/lookbook/discussions) and we will do our best to help you out.
73
+
74
+ ## Contributors
75
+
76
+ Lookbook was created by [Mark Perkins](https://github.com/allmarkedup) and continues to grow
77
+ &amp; improve thanks to the ideas, suggestions and hard work of all of [these excellent humans](https://github.com/allmarkedup/lookbook/graphs/contributors):
78
+ <br>
79
+ <br>
80
+ <a href="https://github.com/allmarkedup/lookbook/graphs/contributors">
81
+ <img src="https://contrib.rocks/image?repo=allmarkedup/lookbook&columns=14" width="800" />
82
+ </a>
83
+
51
84
  ## License
52
85
 
53
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
86
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -6,5 +6,5 @@
6
6
  "h-full": full_height?
7
7
  }
8
8
  ] do %>
9
- <%= Lookbook::CodeFormatter.highlight(source, **@highlight_opts) %>
9
+ <%= Lookbook::CodeHighlighter.call(source, **@highlight_opts) %>
10
10
  <% end %>
@@ -1,5 +1,3 @@
1
- require "css_parser"
2
-
3
1
  module Lookbook
4
2
  class InspectorPanel::Component < Lookbook::BaseComponent
5
3
  attr_reader :panel_styles, :panel_html
@@ -14,9 +12,9 @@ module Lookbook
14
12
  end
15
13
 
16
14
  def before_render
17
- tpl = TemplateParser.new(content)
18
- @panel_styles = tpl.styles.map { |s| "##{id} #{s}" }.join("\n")
19
- @panel_html = tpl.content
15
+ styles, html = StylesExtractor.call(content)
16
+ @panel_styles = styles.map { |s| "##{id} #{s}" }.join("\n")
17
+ @panel_html = html.html_safe
20
18
  end
21
19
  end
22
20
  end
@@ -6,7 +6,7 @@
6
6
  "entity-type": item.type
7
7
  },
8
8
  cloak: true do %>
9
- <%= lookbook_tag href.present? ? :a : :div,
9
+ <%= lookbook_tag href.present? ? :a : :button,
10
10
  href: href,
11
11
  class: "flex items-center py-1 select-none cursor-pointer text-lookbook-nav-text hover:bg-lookbook-nav-item-hover transition",
12
12
  style: "padding-left: #{left_pad}px",
@@ -1,20 +1,13 @@
1
1
  module Lookbook
2
2
  module Params
3
3
  class Editor::Component < Lookbook::BaseComponent
4
- renders_many :fields, ->(input:, description: nil, **attrs) do
4
+ renders_many :fields, ->(**attrs) do
5
5
  @field_count += 1
6
- @descriptions = true if description.present?
7
- input_config = @inputs[input.tr("-", "_").to_sym]
8
- Lookbook::Params::Field::Component.new(input: input,
9
- description: description,
10
- index: @field_count,
11
- config: input_config, **attrs)
6
+ Lookbook::Params::Field::Component.new(**attrs, index: @field_count)
12
7
  end
13
8
 
14
- def initialize(inputs: nil, **html_attrs)
15
- @inputs = inputs.to_h
9
+ def initialize(**html_attrs)
16
10
  @field_count = -1
17
- @descriptions = false
18
11
  @@input_styles = {}
19
12
  super(**html_attrs)
20
13
  end
@@ -1,27 +1,27 @@
1
1
  <%= render_component_tag :tr, "@keydown.stop": true do %>
2
2
  <td class="param-label">
3
- <label for="param-<%= @name %>">
4
- <span class="mr-0.5"><%== @label %></span>
5
- <% if hint? %>
3
+ <label for="param-<%= param.name %>">
4
+ <span class="mr-0.5"><%== param.label %></span>
5
+ <% if param.hint.present? %>
6
6
  <span x-data="tooltipComponent" class="inline-block cursor-help relative top-[2px]">
7
7
  <%= icon :help_circle, size: 3.5, class: "opacity-40 hover:opacity-100 transition" %>
8
8
  <div class="hidden" x-ref="tooltip">
9
- <%= @hint %>
9
+ <%= param.hint %>
10
10
  </div>
11
11
  </span>
12
12
  <% end %>
13
13
  </label>
14
14
  </td>
15
- <td class="param-description <%= "param-description-empty" unless description? %>">
16
- <% if description? %>
17
- <p class="opacity-70"><%= @description %></p>
15
+ <td class="param-description <%= "param-description-empty" unless param.description %>">
16
+ <% if param.description %>
17
+ <p class="opacity-70"><%= param.description %></p>
18
18
  <% else %>
19
19
  <p class="italic opacity-40">&mdash;</p>
20
20
  <% end %>
21
21
  </td>
22
22
  <td class="param-input">
23
23
  <div class="param-input-wrapper">
24
- <%= input %>
24
+ <%= rendered_input %>
25
25
  </div>
26
26
  </td>
27
27
  <% end %>
@@ -1,92 +1,41 @@
1
1
  module Lookbook
2
2
  module Params
3
3
  class Field::Component < Lookbook::BaseComponent
4
- def initialize(name:, input:, index:, label: nil, hint: nil, description: nil, value: nil, value_default: nil, value_type: nil, input_options: {}, config: nil, **html_attrs)
5
- @input_name = input
6
- @name = name
7
- @label = label || name.titleize
8
- @hint = hint
9
- @description = description
10
- @value = value
4
+ attr_reader :param, :rendered_input
5
+
6
+ def initialize(param:, index:, **html_attrs)
7
+ @param = param
11
8
  @index = index
12
- @input_options = input_options
13
- @value_default = value_default
14
- @value_type = value_type
15
- @config = config || {}
16
9
  @rendered_input = nil
17
10
  super(**html_attrs)
18
11
  end
19
12
 
20
- def hint?
21
- @hint.present?
22
- end
23
-
24
- def description?
25
- @description.present?
26
- end
27
-
28
- def input
29
- @rendered_input
30
- end
31
-
32
13
  def before_render
33
- tpl = TemplateParser.new(render_input)
34
- Editor::Component.add_styles(@input_name, tpl.styles)
14
+ styles, html = StylesExtractor.call(render_input)
15
+ Editor::Component.add_styles(param.input, styles)
35
16
 
17
+ escaped_value = json_escape(param.value.to_json)
36
18
  wrapper_attrs = {
37
- data: {"param-input": @input_name},
38
- "x-data": "paramsInputComponent({name: '#{@name}', value: #{escaped_value}})"
19
+ data: {"param-input": param.input},
20
+ "x-data": "paramsInputComponent({name: '#{param.name}', value: #{escaped_value}})"
39
21
  }
40
-
41
- @rendered_input = tag.div(**wrapper_attrs) do
42
- tpl.content
43
- end
22
+ @rendered_input = tag.div(**wrapper_attrs) { html.html_safe }
44
23
  end
45
24
 
46
25
  protected
47
26
 
48
- def input_error(error)
49
- tag.div error, class: "p-2 text-red-500 italic"
50
- end
51
-
52
- def value
53
- val = @value.presence || @value_default
54
- @value_type.downcase == "boolean" ? val == "true" || val == true : val
55
- end
56
-
57
- def escaped_value
58
- json_escape(value.to_json)
59
- end
60
-
61
- def input_options
62
- config_options = @config.fetch(:opts, {})
63
- opts = config_options.merge(@input_options).symbolize_keys
64
- opts[:id] = "param-#{@name}"
65
- opts
66
- end
67
-
68
- def render_props
69
- {
70
- name: @name,
71
- input: @input_name,
72
- value: value,
73
- value_type: @value_type,
74
- value_default: @value_default,
75
- input_options: input_options.except(:choices),
76
- choices: input_options[:choices]
77
- }
78
- end
79
-
80
27
  def render_input
81
- target = @config[:partial]
82
- if target
83
- render(target, **render_props)
84
- else
85
- input_error "No param input defined for input type '#{@input_name}'."
86
- end
87
- rescue ::ActionView::MissingTemplate => exception
88
- Lookbook.logger.error exception
89
- input_error "Param input partial '#{@config[:partial]}' could not be found."
28
+ input_options = param.input_options.to_h
29
+ input_options[:id] = "param-#{param.name}"
30
+
31
+ render(param.input_partial,
32
+ name: param.name,
33
+ input: param.input,
34
+ value: param.value,
35
+ value_type: param.value_type,
36
+ value_default: param.value_default,
37
+ input_options: input_options.except(:choices),
38
+ choices: input_options[:choices])
90
39
  end
91
40
 
92
41
  def alpine_component
@@ -0,0 +1,156 @@
1
+ module Lookbook
2
+ module TargetableConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_action :lookup_entities, only: [:show]
7
+ before_action :set_title
8
+ before_action :set_display_options
9
+ before_action :set_params
10
+ end
11
+
12
+ def set_title
13
+ @title = @target.present? ? [@target&.label, @preview&.label].compact.join(" :: ") : "Not found"
14
+ end
15
+
16
+ def lookup_entities
17
+ @target = Lookbook.previews.find_example(params[:path])
18
+ if @target.present?
19
+ @preview = @target.preview
20
+ if params[:path] == @preview&.lookup_path
21
+ redirect_to lookbook_inspect_path "#{params[:path]}/#{@preview.default_example.name}"
22
+ end
23
+ else
24
+ @preview = Lookbook.previews.find(params[:path])
25
+ if @preview.present?
26
+ first_example = @preview.examples.first
27
+ redirect_to lookbook_inspect_path(first_example.lookup_path) if first_example
28
+ else
29
+ @preview = Lookbook.previews.find(path_segments.slice(0, path_segments.size - 1).join("/"))
30
+ end
31
+ end
32
+ end
33
+
34
+ def set_display_options
35
+ @dynamic_display_options = []
36
+ @static_display_options = []
37
+
38
+ if @target.present?
39
+ opts = @target.display_options
40
+ @dynamic_display_options = opts.select { _2.is_a?(Array) || _2.is_a?(Hash) }
41
+ @static_display_options = opts.except(*@dynamic_display_options.keys)
42
+
43
+ if params[:_display]
44
+ display_params = SearchParamParser.call(params[:_display])
45
+ display_params.each do |name, value|
46
+ if @dynamic_display_options.key?(name)
47
+ cookies["lookbook-display-#{name}"] = value
48
+ end
49
+ end
50
+ end
51
+
52
+ @dynamic_display_options.each do |name, opts|
53
+ choices = opts.is_a?(Hash) ? opts[:choices].to_a : opts
54
+ @static_display_options[name] ||= cookies.fetch("lookbook-display-#{name}", choices.first)
55
+ end
56
+
57
+ unless params[:_display]
58
+ display_params = @dynamic_display_options.map do |name, opts|
59
+ [name, @static_display_options[name]]
60
+ end.to_h
61
+ request.query_parameters[:_display] = SearchParamBuilder.call(display_params)
62
+ end
63
+ end
64
+ end
65
+
66
+ def set_params
67
+ @params = []
68
+
69
+ if @target
70
+ @params = @target.tags("param").map do |param_tag|
71
+ Param.from_tag(
72
+ param_tag,
73
+ value: preview_controller.params[param_tag.name]
74
+ )
75
+ end
76
+
77
+ # cast known param values to correct type
78
+ @params.each do |param|
79
+ if preview_controller.params.key?(param.name)
80
+ preview_controller.params[param.name] = param.cast_value
81
+ end
82
+ end
83
+
84
+ # set display and data params for use in preview layouts
85
+ preview_controller.params[:lookbook] = {
86
+ display: @static_display_options,
87
+ data: Lookbook.data
88
+ }
89
+ end
90
+
91
+ preview_controller.params.permit!
92
+ @preview_params = preview_controller.params.to_h.select do |key, value|
93
+ !!@params.find { |param_tag| param_tag.name == key.to_s }
94
+ end
95
+ end
96
+
97
+ def inspector_data
98
+ return @inspector_data if @inspector_data.present?
99
+
100
+ context_data = {
101
+ preview_params: @preview_params,
102
+ path: params[:path]
103
+ }
104
+
105
+ preview = @preview
106
+ target_examples = @target.type == :group ? @target.examples : [@target]
107
+
108
+ examples = target_examples.map do |example|
109
+ render_args = @preview.render_args(example.name, params: preview_controller.params)
110
+ has_template = render_args[:template] != "view_components/preview"
111
+ output = preview_controller.process(:render_example_to_string, @preview, example.name)
112
+ source = has_template ? example.template_source(render_args[:template]) : example.method_source
113
+ source_lang = has_template ? example.template_lang(render_args[:template]) : example.lang
114
+
115
+ example.define_singleton_method(:output, proc { output })
116
+ example.define_singleton_method(:source, proc { source })
117
+ example.define_singleton_method(:source_lang, proc { source_lang })
118
+ example
119
+ end
120
+
121
+ target = @target.type == :group ? @target : examples.find { |e| e.lookup_path == @target.lookup_path }
122
+
123
+ params_ref = @params
124
+ preview.define_singleton_method(:params, proc { params_ref })
125
+
126
+ @inspector_data ||= Lookbook::Store.new({
127
+ context: context_data,
128
+ preview: preview,
129
+ examples: examples,
130
+ example: examples.first,
131
+ target: target,
132
+ data: Lookbook.data,
133
+ app: Lookbook
134
+ })
135
+ end
136
+
137
+ def show_404(layout: nil)
138
+ locals = if @preview
139
+ {
140
+ message: "Example not found",
141
+ description: "The '#{@preview.label}' preview does not have an example named '#{path_segments.last}'."
142
+ }
143
+ else
144
+ {
145
+ message: "Not found",
146
+ description: "Looked for '#{params[:path]}'.<br>The preview may have been renamed or deleted."
147
+ }
148
+ end
149
+ render_in_layout "lookbook/404", layout: layout, **locals
150
+ end
151
+
152
+ def path_segments
153
+ params[:path].split("/")
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,13 @@
1
+ module Lookbook
2
+ module WithPreviewControllerConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ def preview_controller
6
+ return @preview_controller if @preview_controller
7
+ controller = Lookbook::Engine.preview_controller.new
8
+ controller.request = request
9
+ controller.response = response
10
+ @preview_controller ||= controller
11
+ end
12
+ end
13
+ end
@@ -1,8 +1,6 @@
1
1
  module Lookbook
2
2
  class ApplicationController < ActionController::Base
3
- if respond_to?(:content_security_policy)
4
- content_security_policy false, if: -> { Rails.env.development? }
5
- end
3
+ content_security_policy(false) if respond_to?(:content_security_policy)
6
4
 
7
5
  protect_from_forgery with: :exception
8
6
 
@@ -45,5 +43,17 @@ module Lookbook
45
43
  @error = locals[:error]
46
44
  render path, layout: layout.presence || (params[:lookbook_embed] ? "lookbook/basic" : "lookbook/application"), locals: locals
47
45
  end
46
+
47
+ def prettify_error(exception)
48
+ error_params = {}
49
+ if exception.is_a?(ViewComponent::PreviewTemplateError)
50
+ error_params = {
51
+ file_path: @preview&.full_path,
52
+ line_number: 0,
53
+ source_code: @target&.source
54
+ }
55
+ end
56
+ Lookbook::Error.new(exception, **error_params)
57
+ end
48
58
  end
49
59
  end
@@ -0,0 +1,45 @@
1
+ module Lookbook
2
+ class InspectorController < ApplicationController
3
+ include TargetableConcern
4
+ include WithPreviewControllerConcern
5
+
6
+ layout "lookbook/inspector"
7
+ helper Lookbook::PreviewHelper
8
+
9
+ def self.controller_path
10
+ "lookbook/inspector"
11
+ end
12
+
13
+ def show
14
+ if @target
15
+ begin
16
+ @main_panels = main_panels
17
+ @drawer_panels = drawer_panels
18
+ rescue => exception
19
+ render_in_layout "lookbook/error", layout: "lookbook/inspector", error: prettify_error(exception)
20
+ end
21
+ else
22
+ show_404
23
+ end
24
+ end
25
+
26
+ def show_legacy
27
+ Lookbook.logger.warn("Legacy URL path detected. These paths are deprecated and will be removed in a future version")
28
+ redirect_to lookbook_inspect_path params[:path]
29
+ end
30
+
31
+ private
32
+
33
+ def main_panels
34
+ Engine.panels.in_group(:main).map do |config|
35
+ PanelStore.resolve_config(config, inspector_data)
36
+ end
37
+ end
38
+
39
+ def drawer_panels
40
+ Engine.panels.in_group(:drawer).map do |config|
41
+ PanelStore.resolve_config(config, inspector_data)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -13,13 +13,17 @@ module Lookbook
13
13
  @pages = Lookbook.pages
14
14
  @next_page = @pages.find_next(@page)
15
15
  @previous_page = @pages.find_previous(@page)
16
- content = render_to_string inline: @page.content, locals: {
17
- page: @page,
18
- next_page: @next_page,
19
- previous_page: @previous_page,
20
- pages: @pages
21
- }
22
- @page.markdown? ? Lookbook::Markdown.render(content) : content
16
+
17
+ content = ActionViewAnnotationsHandler.call(disable_annotations: true) do
18
+ render_to_string inline: @page.content, locals: {
19
+ page: @page,
20
+ next_page: @next_page,
21
+ previous_page: @previous_page,
22
+ pages: @pages
23
+ }
24
+ end
25
+
26
+ @page.markdown? ? MarkdownRenderer.call(content, Lookbook.config.markdown_options) : content
23
27
  end
24
28
  end
25
29
  end