lookbook 0.7.0 → 0.7.2.beta.1
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 +4 -4
- data/app/assets/lookbook/css/app.css +19 -2
- data/app/controllers/lookbook/pages_controller.rb +17 -5
- data/app/controllers/lookbook/previews_controller.rb +35 -12
- data/app/helpers/lookbook/component_helper.rb +4 -0
- data/app/helpers/lookbook/page_helper.rb +0 -4
- data/app/views/lookbook/components/_code.html.erb +9 -5
- data/app/views/lookbook/components/_errors.html.erb +13 -0
- data/app/views/lookbook/components/_preview.html.erb +1 -1
- data/app/views/lookbook/components/_sidebar.html.erb +17 -3
- data/app/views/lookbook/error.html.erb +46 -0
- data/lib/lookbook/code_formatter.rb +6 -3
- data/lib/lookbook/engine.rb +10 -11
- data/lib/lookbook/error.rb +121 -0
- data/lib/lookbook/page.rb +27 -13
- data/lib/lookbook/preview.rb +68 -9
- data/lib/lookbook/preview_example.rb +1 -1
- data/lib/lookbook/version.rb +1 -1
- data/lib/lookbook.rb +1 -0
- data/public/lookbook-assets/css/app.css +1 -3
- data/public/lookbook-assets/css/app.css.map +1 -1
- data/public/lookbook-assets/js/app.js +1 -1
- data/public/lookbook-assets/js/app.js.map +1 -1
- data/public/lookbook-assets/js/embed.js +1 -1
- data/public/lookbook-assets/js/embed.js.map +1 -1
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c26bf5a4901bcb0fd09b8742ae3e9c2df8f8c337c3f312152cb31dc6a74aa23
|
4
|
+
data.tar.gz: e1e8a016b29cf87357c3b291c99eaaf8d1e21d1a4acb552bab7777699579f265
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd771d407e79c1958ad0c4873fa242d7c3ad345091291275c5efd261be1d4617d8e60a6a0e82da97f2e01da8aa3a4781f169c13b1559dd481a1939598585b8d1
|
7
|
+
data.tar.gz: d4a40a44f2864c7c9b7bdb3b849092973619044fae370e056dda42b7382bc324fcc6932b26b4a15962d283fa8030782bd3a37e0f1e33c50f4aaf5046ba5c0ccd
|
@@ -93,7 +93,7 @@
|
|
93
93
|
.code.numbered:before {
|
94
94
|
content: "";
|
95
95
|
left: 2.7em;
|
96
|
-
@apply absolute top-0 bottom-0 border-r border-gray-200;
|
96
|
+
@apply absolute top-0 bottom-0 border-r border-gray-200 z-10;
|
97
97
|
}
|
98
98
|
|
99
99
|
.code.numbered .line {
|
@@ -115,8 +115,25 @@
|
|
115
115
|
@apply flex-none pr-4;
|
116
116
|
}
|
117
117
|
|
118
|
+
.code.focussed .line:not(.highlighted-line) *,
|
119
|
+
.code.focussed .line:not(.highlighted-line) .line-content * {
|
120
|
+
@apply !text-gray-700 !text-opacity-40;
|
121
|
+
}
|
122
|
+
|
123
|
+
.code.focussed .line:not(.highlighted-line) .line-number {
|
124
|
+
@apply !opacity-70;
|
125
|
+
}
|
126
|
+
|
127
|
+
.code.focussed .line.highlighted-line {
|
128
|
+
@apply bg-yellow-50;
|
129
|
+
}
|
130
|
+
|
131
|
+
.code.focussed .line.highlighted-line .line-number {
|
132
|
+
@apply text-gray-900;
|
133
|
+
}
|
134
|
+
|
118
135
|
.prose .code {
|
119
|
-
@apply !bg-white border border-gray-300 text-gray-600 my-8
|
136
|
+
@apply !bg-white border border-gray-300 text-gray-600 my-8 rounded-md py-4 text-sm overflow-auto max-w-3xl w-full mx-auto;
|
120
137
|
}
|
121
138
|
|
122
139
|
.prose .code:not(.numbered) .line {
|
@@ -18,12 +18,24 @@ module Lookbook
|
|
18
18
|
@pages = Lookbook.pages
|
19
19
|
@page = @pages.find_by_path(params[:path])
|
20
20
|
if @page
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
if @page.errors.any?
|
22
|
+
render "lookbook/error", locals: {error: @page.errors.first}
|
23
|
+
else
|
24
|
+
begin
|
25
|
+
@page_content = page_controller.render_page(@page)
|
26
|
+
@next_page = @pages.find_next(@page)
|
27
|
+
@previous_page = @pages.find_previous(@page)
|
28
|
+
@title = @page.title
|
29
|
+
rescue => exception
|
30
|
+
render "lookbook/error", locals: {
|
31
|
+
error: Lookbook::Error.new(exception, {
|
32
|
+
file_path: @page.full_path,
|
33
|
+
source_code: @page.content
|
34
|
+
})
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
25
38
|
else
|
26
|
-
@title = "Not found"
|
27
39
|
render "not_found"
|
28
40
|
end
|
29
41
|
end
|
@@ -1,12 +1,5 @@
|
|
1
1
|
module Lookbook
|
2
2
|
class PreviewsController < ApplicationController
|
3
|
-
EXCEPTIONS = [
|
4
|
-
ViewComponent::PreviewTemplateError,
|
5
|
-
ViewComponent::ComponentError,
|
6
|
-
ViewComponent::TemplateError,
|
7
|
-
ActionView::Template::Error
|
8
|
-
]
|
9
|
-
|
10
3
|
def self.controller_path
|
11
4
|
"lookbook/previews"
|
12
5
|
end
|
@@ -17,7 +10,14 @@ module Lookbook
|
|
17
10
|
def preview
|
18
11
|
if @example
|
19
12
|
set_params
|
20
|
-
|
13
|
+
begin
|
14
|
+
render html: render_examples(examples_data)
|
15
|
+
rescue => exception
|
16
|
+
render_in_layout "lookbook/error",
|
17
|
+
layout: "lookbook/basic",
|
18
|
+
error: prettify_error(exception),
|
19
|
+
disable_header: true
|
20
|
+
end
|
21
21
|
else
|
22
22
|
render_in_layout "not_found"
|
23
23
|
end
|
@@ -30,8 +30,8 @@ module Lookbook
|
|
30
30
|
@examples = examples_data
|
31
31
|
@drawer_panels = drawer_panels.filter { |name, panel| panel[:show] }
|
32
32
|
@preview_panels = preview_panels.filter { |name, panel| panel[:show] }
|
33
|
-
rescue
|
34
|
-
render_in_layout "error"
|
33
|
+
rescue => exception
|
34
|
+
render_in_layout "lookbook/error", error: prettify_error(exception)
|
35
35
|
end
|
36
36
|
else
|
37
37
|
render_in_layout "not_found"
|
@@ -152,8 +152,31 @@ module Lookbook
|
|
152
152
|
@preview_controller ||= controller
|
153
153
|
end
|
154
154
|
|
155
|
-
def render_in_layout(path)
|
156
|
-
render
|
155
|
+
def render_in_layout(path, layout: nil, **locals)
|
156
|
+
render path, layout: layout.presence || (params[:lookbook_embed] ? "lookbook/basic" : "lookbook/application"), locals: locals
|
157
|
+
end
|
158
|
+
|
159
|
+
def prettify_error(exception)
|
160
|
+
error_params = if exception.is_a?(ViewComponent::PreviewTemplateError)
|
161
|
+
{
|
162
|
+
file_path: @preview&.full_path,
|
163
|
+
line_number: 0,
|
164
|
+
source_code: @example&.source
|
165
|
+
}
|
166
|
+
elsif exception.is_a?(ActionView::Template::Error) & exception.message.include?("implements a reserved method")
|
167
|
+
message_parts = exception.message.split("\n").first.split
|
168
|
+
component_class = message_parts.first.constantize
|
169
|
+
naughty_method = message_parts.last.delete("#").delete("`").delete(".")
|
170
|
+
p naughty_method
|
171
|
+
method = component_class.instance_method(naughty_method.to_sym)
|
172
|
+
if method
|
173
|
+
{
|
174
|
+
file_path: method.source_location.first,
|
175
|
+
line_number: method.source_location[1]
|
176
|
+
}
|
177
|
+
end
|
178
|
+
end
|
179
|
+
Lookbook::Error.new(exception, error_params || {})
|
157
180
|
end
|
158
181
|
end
|
159
182
|
end
|
@@ -9,6 +9,10 @@ module Lookbook
|
|
9
9
|
render_component "icon", name: name, size: size, **attrs
|
10
10
|
end
|
11
11
|
|
12
|
+
def code(language = "ruby", **opts, &block)
|
13
|
+
render_component "code", {language: language, **opts}, &block
|
14
|
+
end
|
15
|
+
|
12
16
|
if Rails.version.to_f < 6.1
|
13
17
|
def class_names(*args)
|
14
18
|
tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
|
@@ -7,10 +7,6 @@ module Lookbook
|
|
7
7
|
lookbook.page_path page.lookup_path
|
8
8
|
end
|
9
9
|
|
10
|
-
def code(language = "ruby", line_numbers: false, &block)
|
11
|
-
render_component "code", language: language, line_numbers: line_numbers, &block
|
12
|
-
end
|
13
|
-
|
14
10
|
def embed(*args, params: {}, type: :preview, **opts)
|
15
11
|
return unless args.any?
|
16
12
|
|
@@ -1,13 +1,17 @@
|
|
1
1
|
<%
|
2
|
-
line_numbers ||= false
|
3
2
|
language ||= "html"
|
4
3
|
wrap ||= nil;
|
5
4
|
%>
|
6
5
|
<% code ||= capture do %><%= yield %><% end %>
|
7
|
-
|
6
|
+
<% output = highlight(code, language, {
|
7
|
+
strip: defined?(strip) ? strip : true,
|
8
|
+
line_numbers: line_numbers ||= false,
|
9
|
+
highlight_lines: highlight_lines ||= [],
|
10
|
+
start_line: start_line ||= 0
|
11
|
+
}) %>
|
12
|
+
<div class="code not-prose <%= "numbered" if line_numbers %> <%= classes ||= "" %> <%= "focussed" if highlight_lines.any? %>"
|
8
13
|
x-data="code"
|
9
14
|
:class="{'wrapped': wrap}"
|
10
|
-
<% if wrap.present? %>x-effect="wrap = <%= wrap %>"<% end
|
11
|
-
>
|
12
|
-
<pre><code class="highlight"><%= highlight(code.strip, language, line_numbers: line_numbers) %></code></pre>
|
15
|
+
<% if wrap.present? %>x-effect="wrap = <%= wrap %>"<% end %>>
|
16
|
+
<pre><code class="highlight"><%= output %></code></pre>
|
13
17
|
</div>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<div class="bg-red-50 w-full overflow-auto h-full">
|
2
|
+
<ul class="text-sm divide-y divide-red-200">
|
3
|
+
<% errors.each do |error| %>
|
4
|
+
<% error = error.is_a?(Lookbook::Error) ? error : Lookbook::Error.new(error) %>
|
5
|
+
<li class="px-4 py-3">
|
6
|
+
<h4 class="break-all leading-tight">
|
7
|
+
<%= error.file_name %><%= ":#{error.line_number}" if error.line_number %>
|
8
|
+
</h4>
|
9
|
+
<pre class="text-red-800 text-xs mt-2 whitespace-pre-wrap opacity-80 font-mono"><%= error.message %></pre>
|
10
|
+
</li>
|
11
|
+
<% end %>
|
12
|
+
</ul>
|
13
|
+
</div>
|
@@ -22,7 +22,7 @@
|
|
22
22
|
</div>
|
23
23
|
<div class="flex items-stretch h-full ml-auto space-x-3">
|
24
24
|
<div
|
25
|
-
class="flex items-center text-xs font-
|
25
|
+
class="flex items-center text-xs font-mono text-gray-700 space-x-1 opacity-50 hover:opacity-100 transition"
|
26
26
|
:class="{'opacity-100': $store.inspector.preview.resizing}"
|
27
27
|
x-show="isActivePreviewPanel('preview')">
|
28
28
|
<span x-text="`${preview.width}px`"></span>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<div
|
2
2
|
x-data="sidebar"
|
3
3
|
@page:morphed.window="setActiveNavItem"
|
4
|
-
class="h-full bg-gray-100 overflow-hidden flex flex-col"
|
4
|
+
class="h-full bg-gray-100 overflow-hidden flex flex-col relative"
|
5
5
|
x-show="$store.sidebar.open"
|
6
6
|
x-cloak>
|
7
7
|
|
@@ -14,8 +14,8 @@
|
|
14
14
|
|
15
15
|
<% if feature_enabled?(:pages) && Lookbook.pages.any? %>
|
16
16
|
<div
|
17
|
-
class="grid overflow-hidden"
|
18
|
-
:style="`grid-template-rows: ${$store.sidebar.panelSplits[0] || 1}fr 1px ${$store.sidebar.panelSplits[1] || 1}fr
|
17
|
+
class="grid overflow-hidden flex-grow"
|
18
|
+
:style="`grid-template-rows: ${$store.sidebar.panelSplits[0] || 1}fr 1px ${$store.sidebar.panelSplits[1] || 1}fr;`"
|
19
19
|
x-ref="sidebarPanels">
|
20
20
|
<div class="flex flex-col overflow-hidden">
|
21
21
|
<div class="flex items-center flex-none border-b border-gray-300 h-10 bg-white relative px-4">
|
@@ -52,4 +52,18 @@
|
|
52
52
|
</div>
|
53
53
|
<% end %>
|
54
54
|
|
55
|
+
<% if Lookbook::Preview.errors.any? %>
|
56
|
+
<div class="flex-none" x-ref="preview-errors" id="preview-errors">
|
57
|
+
<div class="flex items-center border-b border-t border-gray-300 h-10 bg-white px-4">
|
58
|
+
<h2 class="flex items-center flex-none">
|
59
|
+
<%= icon "alert-triangle", size: 4, class: "text-red-700" %>
|
60
|
+
<span class="ml-2">Preview load errors</span>
|
61
|
+
</h2>
|
62
|
+
</div>
|
63
|
+
<div class="h-full max-h-[300px] overflow-hidden">
|
64
|
+
<%= component "errors", errors: Lookbook::Preview.errors %>
|
65
|
+
</div>
|
66
|
+
</div>
|
67
|
+
<% end %>
|
68
|
+
|
55
69
|
</div>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<%
|
2
|
+
error = error.is_a?(Lookbook::Error) ? error : Lookbook::Error.new(error)
|
3
|
+
@title = error.title
|
4
|
+
disable_header ||= false
|
5
|
+
%>
|
6
|
+
<%= component "header" unless disable_header %>
|
7
|
+
<div class="h-full w-full bg-red-50 flex flex-col border-red-300" id="error-<%= Time.now %>">
|
8
|
+
|
9
|
+
<header class="mx-8 pt-8 mb-8 flex-none">
|
10
|
+
<h2 class="text-xl font-bold text-red-700"><%= error.title %></h2>
|
11
|
+
</header>
|
12
|
+
|
13
|
+
<div class="flex-none px-8 py-6 mb-8 border-t border-b border-red-200 bg-red-100 text-base font-mono leading-relaxed">
|
14
|
+
<pre class="whitespace-pre-wrap font-sans leading-tight text-red-900"><%= error.message %></pre>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<% if error.file_name %>
|
18
|
+
<div class="text-gray-800 text-sm mx-8 mb-2 font-mono flex-none <%= "pl-2" if error.source_code %>">
|
19
|
+
<span><%= error.file_name %></span>
|
20
|
+
<% if error.line_number %>
|
21
|
+
<span>[line <strong><%= error.line_number %></strong>]</span>
|
22
|
+
<% end %>
|
23
|
+
</div>
|
24
|
+
<% end %>
|
25
|
+
|
26
|
+
<% if error.source_code %>
|
27
|
+
<div class="prose max-w-full mx-8">
|
28
|
+
<%= code error.file_lang, highlight_lines: [error.source_code[:highlighted_line]],
|
29
|
+
line_numbers: true,
|
30
|
+
start_line: error.source_code[:start_line],
|
31
|
+
strip: false,
|
32
|
+
class: "py-4 !max-w-full !border-red-200 !mt-1" do %><%= h(error.source_code[:code]) %><% end %>
|
33
|
+
</div>
|
34
|
+
<% end %>
|
35
|
+
|
36
|
+
<h3 class="font-bold mb-4 px-8 mt-8 flex-none">Full stack trace</h3>
|
37
|
+
<div class="text-xs font-mono flex-grow h-full overflow-hidden">
|
38
|
+
<div class="h-full overflow-auto px-8 pb-10 text-gray-400 leading-relaxed">
|
39
|
+
<% error.backtrace.each do |line| %>
|
40
|
+
<div class="hover:text-gray-900 transition-colors duration-100">
|
41
|
+
<%= line %>
|
42
|
+
</div>
|
43
|
+
<% end %>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
</div>
|
@@ -5,7 +5,8 @@ module Lookbook
|
|
5
5
|
module CodeFormatter
|
6
6
|
class << self
|
7
7
|
def highlight(source, language, opts = {})
|
8
|
-
source&.
|
8
|
+
source&.strip! unless opts[:strip] == false
|
9
|
+
source&.gsub!(">", ">")&.gsub!("<", "<")
|
9
10
|
language ||= "ruby"
|
10
11
|
formatter = Formatter.new(opts)
|
11
12
|
lexer = Rouge::Lexer.find(language.to_s) || Rouge::Lexer.find("plaintext")
|
@@ -23,12 +24,14 @@ module Lookbook
|
|
23
24
|
class Formatter < Rouge::Formatters::HTML
|
24
25
|
def initialize(opts = {})
|
25
26
|
@opts = opts
|
27
|
+
@highlight_lines = opts[:highlight_lines].to_a || []
|
28
|
+
@start_line = opts[:start_line] || 0
|
26
29
|
end
|
27
30
|
|
28
31
|
def stream(tokens, &block)
|
29
32
|
token_lines(tokens).each_with_index do |line_tokens, i|
|
30
|
-
yield "<div class='line'>"
|
31
|
-
yield "<span class='line-number'>#{i}</span>" if @opts[:line_numbers]
|
33
|
+
yield "<div class='line #{"highlighted-line" if @highlight_lines.include?(i + 1)}'>"
|
34
|
+
yield "<span class='line-number'>#{@start_line + i}</span>" if @opts[:line_numbers]
|
32
35
|
yield "<span class='line-content'>"
|
33
36
|
line_tokens.each do |token, value|
|
34
37
|
yield span(token, value)
|
data/lib/lookbook/engine.rb
CHANGED
@@ -82,22 +82,21 @@ module Lookbook
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
-
initializer "lookbook.helpers" do
|
86
|
-
config.action_controller.include_all_helpers = false
|
87
|
-
end
|
88
|
-
|
89
85
|
config.after_initialize do
|
90
|
-
Array(config.view_component.preview_paths).each do |preview_path|
|
91
|
-
Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
|
92
|
-
end
|
93
|
-
|
94
86
|
@preview_listener = Listen.to(*config.lookbook.listen_paths, only: /\.(rb|html.*)$/) do |modified, added, removed|
|
95
|
-
|
87
|
+
if Lookbook::Preview.errors.any?
|
88
|
+
Lookbook::Preview.reload
|
89
|
+
end
|
90
|
+
begin
|
91
|
+
parser.parse
|
92
|
+
rescue
|
93
|
+
end
|
96
94
|
if Lookbook::Engine.websocket
|
97
|
-
if
|
95
|
+
if modified.any? || removed.any? || added.none?
|
98
96
|
Lookbook::Engine.websocket.broadcast("reload", {
|
99
97
|
modified: modified,
|
100
|
-
removed: removed
|
98
|
+
removed: removed,
|
99
|
+
added: added
|
101
100
|
})
|
102
101
|
end
|
103
102
|
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Lookbook
|
2
|
+
class Error < StandardError
|
3
|
+
delegate :full_message, :backtrace, :to_s, to: :target
|
4
|
+
|
5
|
+
LINES_AROUND = 3
|
6
|
+
|
7
|
+
def initialize(original = nil, title: nil, message: nil, file_path: nil, file_name: nil, line_number: nil, source_code: nil)
|
8
|
+
@original = original
|
9
|
+
@title = title
|
10
|
+
@message = message
|
11
|
+
@file_path = file_path
|
12
|
+
@file_name = file_name
|
13
|
+
@line_number = line_number
|
14
|
+
@source_code = source_code
|
15
|
+
super()
|
16
|
+
end
|
17
|
+
|
18
|
+
def source_code
|
19
|
+
lines = source_code_lines
|
20
|
+
|
21
|
+
if lines.present? && line_number.is_a?(Integer)
|
22
|
+
start_line = source_code_start_line(lines)
|
23
|
+
end_line = source_code_end_line(lines)
|
24
|
+
highlighted_line = source_code_highlighted_line(lines)
|
25
|
+
|
26
|
+
line_count = end_line - start_line
|
27
|
+
relevant_lines = lines.slice(start_line - 1, line_count + 1)
|
28
|
+
if relevant_lines.present?
|
29
|
+
empty_start_lines = 0
|
30
|
+
relevant_lines.each do |line|
|
31
|
+
break unless line.strip.empty?
|
32
|
+
empty_start_lines += 1
|
33
|
+
end
|
34
|
+
|
35
|
+
{
|
36
|
+
code: relevant_lines.join("\n").lstrip,
|
37
|
+
start_line: start_line - empty_start_lines,
|
38
|
+
end_line: end_line - empty_start_lines,
|
39
|
+
highlighted_line: highlighted_line - empty_start_lines
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def source_code_lines
|
47
|
+
if file_path || @source_code
|
48
|
+
if @source_code
|
49
|
+
@source_code.split("\n")
|
50
|
+
else
|
51
|
+
full_path = Rails.root.join(file_path)
|
52
|
+
File.read(full_path).split("\n") if File.exist? full_path
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def file_lang
|
58
|
+
lang = Lookbook::Lang.guess(file_path)
|
59
|
+
lang.present? ? lang[:name] : "plaintext"
|
60
|
+
end
|
61
|
+
|
62
|
+
def title
|
63
|
+
@title || target.class.to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
def message
|
67
|
+
(@message || target.message).gsub("(<unknown>):", "").strip.upcase_first
|
68
|
+
end
|
69
|
+
|
70
|
+
def file_name
|
71
|
+
if @file_name == false
|
72
|
+
nil
|
73
|
+
else
|
74
|
+
@file_name || file_path
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def file_path
|
79
|
+
path = if @file_path.nil?
|
80
|
+
parsed_backtrace[0][0] if parsed_backtrace.any?
|
81
|
+
else
|
82
|
+
@file_path.presence || nil
|
83
|
+
end
|
84
|
+
path.nil? ? nil : path.to_s.delete_prefix("#{Rails.root}/")
|
85
|
+
end
|
86
|
+
|
87
|
+
def line_number
|
88
|
+
number = if @line_number.nil?
|
89
|
+
parsed_backtrace[0][1] if parsed_backtrace.any?
|
90
|
+
else
|
91
|
+
@line_number.presence || nil
|
92
|
+
end
|
93
|
+
number.present? ? number.to_i : number
|
94
|
+
end
|
95
|
+
|
96
|
+
def parsed_backtrace
|
97
|
+
backtrace.map do |x|
|
98
|
+
x =~ /^(.+?):(\d+)(|:in `(.+)')$/
|
99
|
+
[$1, $2, $4]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
def target
|
106
|
+
@original.presence || self
|
107
|
+
end
|
108
|
+
|
109
|
+
def source_code_start_line(lines)
|
110
|
+
[(line_number - LINES_AROUND), 1].max unless line_number.nil?
|
111
|
+
end
|
112
|
+
|
113
|
+
def source_code_end_line(lines)
|
114
|
+
[line_number + LINES_AROUND, lines&.size || Infinity].min
|
115
|
+
end
|
116
|
+
|
117
|
+
def source_code_highlighted_line(lines)
|
118
|
+
[line_number - source_code_start_line(lines) + 1, 1].max unless line_number.nil?
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/lib/lookbook/page.rb
CHANGED
@@ -15,9 +15,13 @@ module Lookbook
|
|
15
15
|
:data
|
16
16
|
]
|
17
17
|
|
18
|
+
attr_reader :errors
|
19
|
+
|
18
20
|
def initialize(path, base_path)
|
19
21
|
@pathname = Pathname.new path
|
20
22
|
@base_path = base_path
|
23
|
+
@options = nil
|
24
|
+
@errors = []
|
21
25
|
end
|
22
26
|
|
23
27
|
def path
|
@@ -58,7 +62,7 @@ module Lookbook
|
|
58
62
|
end
|
59
63
|
|
60
64
|
def content
|
61
|
-
@content ||= strip_frontmatter(file_contents)
|
65
|
+
@content ||= strip_frontmatter(file_contents).strip
|
62
66
|
end
|
63
67
|
|
64
68
|
def matchers
|
@@ -97,18 +101,28 @@ module Lookbook
|
|
97
101
|
|
98
102
|
def options
|
99
103
|
return @options if @options
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
@options
|
104
|
+
begin
|
105
|
+
frontmatter = (get_frontmatter(file_contents) || {}).deep_symbolize_keys
|
106
|
+
rescue => exception
|
107
|
+
frontmatter = {}
|
108
|
+
line_number_match = exception.message.match(/.*line\s(\d+)/)
|
109
|
+
@errors.push(Lookbook::Error.new(exception, {
|
110
|
+
title: "YAML frontmatter parsing error",
|
111
|
+
file_path: @pathname.to_s,
|
112
|
+
line_number: line_number_match ? line_number_match[1] : false
|
113
|
+
}))
|
114
|
+
end
|
115
|
+
@options = Lookbook.config.page_options.deep_merge(frontmatter).with_indifferent_access
|
116
|
+
@options[:id] = @options[:id] ? generate_id(@options[:id]) : generate_id(lookup_path)
|
117
|
+
@options[:label] ||= name.titleize
|
118
|
+
@options[:title] ||= @options[:label]
|
119
|
+
@options[:hidden] ||= false
|
120
|
+
@options[:landing] ||= false
|
121
|
+
@options[:position] = @options[:position] ? @options[:position].to_i : get_position_prefix(path_name)
|
122
|
+
@options[:markdown] ||= markdown_file?
|
123
|
+
@options[:header] = true unless @options.key? :header
|
124
|
+
@options[:footer] = true unless @options.key? :footer
|
125
|
+
@options
|
112
126
|
end
|
113
127
|
|
114
128
|
def path_name
|
data/lib/lookbook/preview.rb
CHANGED
@@ -34,7 +34,7 @@ module Lookbook
|
|
34
34
|
return @examples if @examples.present?
|
35
35
|
public_methods = @preview.public_instance_methods(false)
|
36
36
|
public_method_objects = @preview_inspector&.methods&.filter { |m| public_methods.include?(m.name) }
|
37
|
-
examples = public_method_objects
|
37
|
+
examples = (public_method_objects || []).map { |m| PreviewExample.new(m.name.to_s, self) }
|
38
38
|
sorted = Lookbook.config.sort_examples ? examples.sort_by(&:label) : examples
|
39
39
|
@examples = []
|
40
40
|
if @preview_inspector&.groups&.any?
|
@@ -49,7 +49,7 @@ module Lookbook
|
|
49
49
|
else
|
50
50
|
@examples = sorted
|
51
51
|
end
|
52
|
-
@examples
|
52
|
+
@examples = @examples.compact
|
53
53
|
end
|
54
54
|
|
55
55
|
def default_example
|
@@ -68,7 +68,7 @@ module Lookbook
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def preview_paths
|
71
|
-
ViewComponent::
|
71
|
+
ViewComponent::Base.preview_paths
|
72
72
|
end
|
73
73
|
|
74
74
|
def parent_collections_names
|
@@ -88,19 +88,78 @@ module Lookbook
|
|
88
88
|
end
|
89
89
|
|
90
90
|
class << self
|
91
|
+
def find(path)
|
92
|
+
all.find { |p| p.lookup_path == path }
|
93
|
+
end
|
94
|
+
|
95
|
+
def exists?(path)
|
96
|
+
!!find(path)
|
97
|
+
end
|
98
|
+
|
91
99
|
def all
|
92
|
-
previews =
|
100
|
+
previews = load_previews.map do |p|
|
101
|
+
new(p)
|
102
|
+
rescue
|
103
|
+
Rails.logger.error "[lookbook] error instantiating preview\n#{exception.full_message}"
|
104
|
+
end
|
93
105
|
|
94
|
-
sorted_previews = previews.sort_by { |preview| [preview.position, preview.label] }
|
106
|
+
sorted_previews = previews.compact.sort_by { |preview| [preview.position, preview.label] }
|
95
107
|
PreviewCollection.new(sorted_previews)
|
96
108
|
end
|
97
109
|
|
98
|
-
def
|
99
|
-
|
110
|
+
def errors
|
111
|
+
@errors || []
|
100
112
|
end
|
101
113
|
|
102
|
-
def
|
103
|
-
|
114
|
+
def reload
|
115
|
+
load_previews
|
116
|
+
end
|
117
|
+
|
118
|
+
protected
|
119
|
+
|
120
|
+
def reset_files_data
|
121
|
+
@loaded_files = []
|
122
|
+
@errors = []
|
123
|
+
end
|
124
|
+
|
125
|
+
def load_previews
|
126
|
+
reset_files_data if @loaded_files.nil?
|
127
|
+
require_preview_files if @errors.any?
|
128
|
+
|
129
|
+
preview_classes = ViewComponent::Preview.descendants
|
130
|
+
if preview_files.size > preview_classes.size
|
131
|
+
require_preview_files
|
132
|
+
end
|
133
|
+
|
134
|
+
ViewComponent::Preview.descendants.filter { |klass| @loaded_files.include? "#{klass.name.underscore}.rb" }
|
135
|
+
end
|
136
|
+
|
137
|
+
def require_preview_files
|
138
|
+
reset_files_data
|
139
|
+
preview_files.each do |file|
|
140
|
+
require_dependency(file[:path])
|
141
|
+
@loaded_files.push(file[:rel_path])
|
142
|
+
rescue => exception
|
143
|
+
Rails.logger.error "[lookbook] preview error\n#{exception.full_message}\n"
|
144
|
+
@errors.push(Lookbook::Error.new(exception, {
|
145
|
+
title: "Preview #{exception.class}",
|
146
|
+
file_name: file[:rel_path],
|
147
|
+
file_path: file[:path]
|
148
|
+
}))
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def preview_files
|
153
|
+
files = Array(Lookbook.config.preview_paths).map do |preview_path|
|
154
|
+
Dir["#{preview_path}/**/*_preview.rb"].map do |path|
|
155
|
+
{
|
156
|
+
path: path,
|
157
|
+
base_path: preview_path,
|
158
|
+
rel_path: Pathname(path).relative_path_from(preview_path).to_s
|
159
|
+
}
|
160
|
+
end
|
161
|
+
end
|
162
|
+
files.flatten
|
104
163
|
end
|
105
164
|
end
|
106
165
|
|