lookbook 0.6.1 → 0.7.2.beta.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +233 -5
  3. data/app/assets/lookbook/css/app.css +15 -3
  4. data/app/assets/lookbook/js/app.js +8 -0
  5. data/app/assets/lookbook/js/components/app.js +55 -0
  6. data/app/assets/lookbook/js/components/embed.js +89 -0
  7. data/app/assets/lookbook/js/components/filter.js +13 -2
  8. data/app/assets/lookbook/js/components/nav-group.js +3 -6
  9. data/app/assets/lookbook/js/components/nav-item.js +3 -1
  10. data/app/assets/lookbook/js/components/nav.js +8 -15
  11. data/app/assets/lookbook/js/components/page.js +19 -41
  12. data/app/assets/lookbook/js/components/sidebar.js +16 -1
  13. data/app/assets/lookbook/js/components/splitter.js +1 -1
  14. data/app/assets/lookbook/js/components/tabs.js +3 -1
  15. data/app/assets/lookbook/js/embed.js +1 -0
  16. data/app/assets/lookbook/js/lib/split.js +0 -6
  17. data/app/assets/lookbook/js/stores/layout.js +3 -0
  18. data/app/assets/lookbook/js/stores/pages.js +5 -0
  19. data/app/assets/lookbook/js/stores/sidebar.js +2 -1
  20. data/app/controllers/lookbook/application_controller.rb +28 -0
  21. data/app/controllers/lookbook/page_controller.rb +20 -0
  22. data/app/controllers/lookbook/pages_controller.rb +47 -0
  23. data/app/controllers/lookbook/{app_controller.rb → previews_controller.rb} +39 -70
  24. data/app/helpers/lookbook/application_helper.rb +8 -61
  25. data/app/helpers/lookbook/component_helper.rb +40 -0
  26. data/app/helpers/lookbook/output_helper.rb +15 -0
  27. data/app/helpers/lookbook/page_helper.rb +46 -0
  28. data/app/helpers/lookbook/preview_helper.rb +1 -1
  29. data/app/views/layouts/lookbook/application.html.erb +30 -0
  30. data/app/views/layouts/lookbook/basic.html.erb +7 -0
  31. data/app/views/layouts/lookbook/preview.html.erb +5 -1
  32. data/app/views/layouts/lookbook/skeleton.html.erb +28 -0
  33. data/app/views/lookbook/components/_branding.html.erb +8 -0
  34. data/app/views/lookbook/components/_code.html.erb +2 -2
  35. data/app/views/lookbook/components/_copy_button.html.erb +11 -0
  36. data/app/views/lookbook/components/_drawer.html.erb +4 -16
  37. data/app/views/lookbook/components/_embed.html.erb +39 -0
  38. data/app/views/lookbook/components/_errors.html.erb +13 -0
  39. data/app/views/lookbook/components/_filter.html.erb +8 -5
  40. data/app/views/lookbook/components/_header.html.erb +2 -4
  41. data/app/views/lookbook/components/_icon.html.erb +2 -2
  42. data/app/views/lookbook/components/_nav.html.erb +7 -8
  43. data/app/views/lookbook/components/_nav_group.html.erb +1 -1
  44. data/app/views/lookbook/components/_nav_item.html.erb +4 -3
  45. data/app/views/lookbook/components/_nav_page.html.erb +22 -0
  46. data/app/views/lookbook/components/_nav_preview.html.erb +1 -1
  47. data/app/views/lookbook/components/_not_found.html.erb +11 -0
  48. data/app/views/lookbook/components/_param.html.erb +1 -1
  49. data/app/views/lookbook/components/_preview.html.erb +17 -11
  50. data/app/views/lookbook/components/_sidebar.html.erb +69 -0
  51. data/app/views/lookbook/error.html.erb +31 -1
  52. data/app/views/lookbook/pages/not_found.html.erb +15 -0
  53. data/app/views/lookbook/pages/show.html.erb +71 -0
  54. data/app/views/lookbook/previews/error.html.erb +1 -0
  55. data/app/views/lookbook/{inputs → previews/inputs}/_select.html.erb +0 -0
  56. data/app/views/lookbook/{inputs → previews/inputs}/_text.html.erb +0 -0
  57. data/app/views/lookbook/{inputs → previews/inputs}/_textarea.html.erb +0 -0
  58. data/app/views/lookbook/{inputs → previews/inputs}/_toggle.html.erb +0 -0
  59. data/app/views/lookbook/{not_found.html.erb → previews/not_found.html.erb} +2 -2
  60. data/app/views/lookbook/{panels → previews/panels}/_notes.html.erb +2 -2
  61. data/app/views/lookbook/{panels → previews/panels}/_output.html.erb +0 -0
  62. data/app/views/lookbook/{panels → previews/panels}/_params.html.erb +0 -0
  63. data/app/views/lookbook/{panels → previews/panels}/_preview.html.erb +0 -0
  64. data/app/views/lookbook/{panels → previews/panels}/_source.html.erb +0 -0
  65. data/app/views/lookbook/{show.html.erb → previews/show.html.erb} +0 -0
  66. data/config/routes.rb +9 -4
  67. data/lib/lookbook/code_formatter.rb +22 -1
  68. data/lib/lookbook/code_inspector.rb +73 -0
  69. data/lib/lookbook/collection.rb +110 -8
  70. data/lib/lookbook/engine.rb +63 -37
  71. data/lib/lookbook/error.rb +52 -0
  72. data/lib/lookbook/features.rb +1 -1
  73. data/lib/lookbook/markdown.rb +31 -0
  74. data/lib/lookbook/page.rb +158 -0
  75. data/lib/lookbook/page_collection.rb +11 -0
  76. data/lib/lookbook/parser.rb +2 -0
  77. data/lib/lookbook/preview.rb +111 -57
  78. data/lib/lookbook/preview_collection.rb +15 -0
  79. data/lib/lookbook/preview_example.rb +27 -29
  80. data/lib/lookbook/preview_group.rb +12 -6
  81. data/lib/lookbook/utils.rb +74 -0
  82. data/lib/lookbook/version.rb +1 -1
  83. data/lib/lookbook.rb +19 -1
  84. data/public/lookbook-assets/css/app.css +1 -1
  85. data/public/lookbook-assets/css/app.css.map +1 -1
  86. data/public/lookbook-assets/js/app.js +1 -1
  87. data/public/lookbook-assets/js/app.js.map +1 -1
  88. data/public/lookbook-assets/js/embed.js +2 -0
  89. data/public/lookbook-assets/js/embed.js.map +1 -0
  90. metadata +48 -19
  91. data/app/assets/lookbook/js/lib/utils.js +0 -3
  92. data/app/views/layouts/lookbook/app.html.erb +0 -60
  93. data/lib/lookbook/taggable.rb +0 -46
@@ -7,14 +7,14 @@
7
7
  <h6 class="text-[11px] text-gray-500 italic inline-block uppercase tracking-wide mb-4">
8
8
  <%= item[:label] %>
9
9
  </h6>
10
- <div class="prose prose-sm">
10
+ <div class="prose prose-sm prose-a:text-indigo-900">
11
11
  <%= markdown(item[:notes]) %>
12
12
  </div>
13
13
  </div>
14
14
  <% end %>
15
15
  </div>
16
16
  <% else %>
17
- <div class="p-4 prose prose-sm">
17
+ <div class="p-4 prose prose-sm prose-a:text-indigo-900">
18
18
  <% if items.any? %>
19
19
  <%= markdown(items.first[:notes]) %>
20
20
  <% else %>
data/config/routes.rb CHANGED
@@ -1,10 +1,15 @@
1
1
  Lookbook::Engine.routes.draw do
2
2
  if Lookbook.config.auto_refresh
3
- mount Lookbook::Engine.websocket => Lookbook::Engine.cable.mount_path
3
+ mount Lookbook::Engine.websocket => Lookbook.config.cable.mount_path
4
4
  end
5
5
 
6
- root to: "app#index", as: :home
6
+ root to: "application#index", as: :home
7
7
 
8
- get "/preview/*path", to: "app#preview", as: :preview
9
- get "/*path", to: "app#show", as: :show
8
+ if Lookbook::Features.enabled?(:pages)
9
+ get "/#{Lookbook.config.page_route}", to: "pages#index", as: :page_index
10
+ get "/#{Lookbook.config.page_route}/*path", to: "pages#show", as: :page
11
+ end
12
+
13
+ get "/preview/*path", to: "previews#preview", as: :preview
14
+ get "/*path", to: "previews#show", as: :show
10
15
  end
@@ -1,5 +1,26 @@
1
+ require "rouge"
2
+ require "htmlbeautifier"
3
+
1
4
  module Lookbook
2
- class CodeFormatter < Rouge::Formatters::HTML
5
+ module CodeFormatter
6
+ class << self
7
+ def highlight(source, language, opts = {})
8
+ source&.gsub!("&gt;", "<")&.gsub!("&lt;", ">")
9
+ language ||= "ruby"
10
+ formatter = Formatter.new(opts)
11
+ lexer = Rouge::Lexer.find(language.to_s) || Rouge::Lexer.find("plaintext")
12
+ formatter.format(lexer.lex(source)).html_safe
13
+ end
14
+
15
+ def beautify(source, language = "html")
16
+ source = source.strip
17
+ result = language.downcase == "html" ? HtmlBeautifier.beautify(source) : source
18
+ result.strip.html_safe
19
+ end
20
+ end
21
+ end
22
+
23
+ class Formatter < Rouge::Formatters::HTML
3
24
  def initialize(opts = {})
4
25
  @opts = opts
5
26
  end
@@ -0,0 +1,73 @@
1
+ module Lookbook
2
+ class CodeInspector
3
+ include Utils
4
+
5
+ attr_reader :code_object
6
+ delegate :groups, :source, to: :@code_object, allow_nil: true
7
+
8
+ def initialize(taggable_object_path)
9
+ @code_object = Lookbook::Engine.parser.get_code_object(taggable_object_path)
10
+ end
11
+
12
+ def hidden?
13
+ if code_object&.tag(:hidden)
14
+ code_object.tag(:hidden).text.strip != "false"
15
+ end
16
+ end
17
+
18
+ def id
19
+ if code_object&.tag(:id)&.text&.present?
20
+ generate_id(code_object&.tag(:id)&.text)
21
+ end
22
+ end
23
+
24
+ def label
25
+ code_object&.tag(:label)&.text
26
+ end
27
+
28
+ def notes
29
+ if code_object&.docstring
30
+ code_object.docstring.to_s.strip
31
+ end
32
+ end
33
+
34
+ def group
35
+ code_object&.group
36
+ end
37
+
38
+ def position
39
+ code_object&.tag(:position)&.text&.to_i || 10000
40
+ end
41
+
42
+ def display_params
43
+ display_params = {}.with_indifferent_access
44
+ if code_object&.tags(:display).present?
45
+ code_object.tags(:display).each do |tag|
46
+ parts = tag.text.strip.match(/^([^\s]*)\s?(.*)$/)
47
+ if parts.present?
48
+ begin
49
+ display_params[parts[1]] = YAML.safe_load(parts[2] || "~")
50
+ rescue SyntaxError => err
51
+ Lookbook.logger.error("\n👀 [Lookbook] Invalid JSON in @display tag.\n👀 [Lookbook] (#{err})\n")
52
+ end
53
+ end
54
+ end
55
+ end
56
+ display_params
57
+ end
58
+
59
+ def parameter_defaults
60
+ code_object&.parameters&.map { |str| Params.parse_method_param_str(str) }&.compact&.to_h
61
+ end
62
+
63
+ def params
64
+ code_object&.tags("param")&.map do |param|
65
+ Lookbook::Params.build_param(param, parameter_defaults[param.name])
66
+ end
67
+ end
68
+
69
+ def methods
70
+ code_object&.meths
71
+ end
72
+ end
73
+ end
@@ -1,33 +1,56 @@
1
1
  module Lookbook
2
2
  class Collection
3
+ include Utils
4
+ include Enumerable
5
+
3
6
  attr_reader :path
7
+ delegate :size, :each, to: :items
4
8
 
5
- def initialize(path = "")
9
+ def initialize(path = "", items = [])
10
+ if path.is_a?(Array)
11
+ @items = path
12
+ path = ""
13
+ else
14
+ @items = items
15
+ end
6
16
  @path = path.delete_prefix("/").delete_suffix("/")
7
- @items = []
8
17
  end
9
18
 
10
19
  def id
11
- (@path || "root").underscore.tr("_", "-")
20
+ generate_id(lookup_path || "root")
12
21
  end
13
22
 
14
23
  def name
15
- @path.present? ? @path.split("/").last : "root"
24
+ @name ||= remove_position_prefix(basename)
16
25
  end
17
26
 
18
27
  def label
19
28
  name&.titleize
20
29
  end
21
30
 
31
+ def lookup_path
32
+ @lookup_path ||= to_lookup_path(@path)
33
+ end
34
+
35
+ def position
36
+ @position ||= parse_position_prefix(basename).first
37
+ end
38
+
22
39
  def hierarchy_depth
23
40
  @path ? @path.split("/").size : 0
24
41
  end
25
42
 
26
- def items(sorted = true)
27
- sorted ? @items.sort_by(&:label) : @items
43
+ def items
44
+ @items.sort_by { |item| [item.hierarchy_depth, item.position, item.label] }
45
+ end
46
+
47
+ def visible_items
48
+ reject { |i| i.hidden? }
28
49
  end
29
50
 
30
51
  def add(item)
52
+ @ordered_entities = nil
53
+ @tree = nil
31
54
  if item.is_a?(String)
32
55
  item = Collection.new([@path, item].join("/"))
33
56
  end
@@ -37,15 +60,94 @@ module Lookbook
37
60
 
38
61
  def get(name)
39
62
  name = name.underscore
40
- @items.find { |item| item.name.underscore == name }
63
+ items.find { |item| item.name.underscore == name }
41
64
  end
42
65
 
43
66
  def get_or_create(name)
44
- get(name).presence || add(name)
67
+ get(remove_position_prefix(name)).presence || add(name)
68
+ end
69
+
70
+ def find(lookup = nil, &block)
71
+ if lookup
72
+ lookup.is_a?(Symbol) ? find_by_id(lookup.to_s.tr("_", "-")) : find_by_path(lookup)
73
+ elsif block
74
+ items.find(&block)
75
+ end
76
+ end
77
+
78
+ def find_by_id(id)
79
+ items.find { |i| i.id == id }
80
+ end
81
+
82
+ def find_by_path(path)
83
+ items.find { |i| i.lookup_path == path }
84
+ end
85
+
86
+ def find_parent(child)
87
+ parent_path = child.lookup_path.split("/").pop.join("/")
88
+ find_by_path(parent_path)
89
+ end
90
+
91
+ def ordered_entities
92
+ return @ordered_entities if @ordered_entities.present?
93
+ entities = []
94
+ as_tree.items.each do |item|
95
+ entities.append(item.is_a?(Collection) ? item.ordered_entities : item)
96
+ end
97
+ @ordered_entities ||= entities.flatten
98
+ end
99
+
100
+ def find_first
101
+ ordered_entities.first
102
+ end
103
+
104
+ def find_next(item)
105
+ index = ordered_entities.find_index { |i| i.lookup_path == item.lookup_path }
106
+ ordered_entities[index + 1] unless index.nil?
107
+ end
108
+
109
+ def find_previous(item)
110
+ index = ordered_entities.find_index { |i| i.lookup_path == item.lookup_path }
111
+ ordered_entities[index - 1] if !index.nil? && index > 0
112
+ end
113
+
114
+ def as_tree(filter_hidden: true)
115
+ return self if hierarchy_depth > 0
116
+ return @tree if @tree.present?
117
+ @tree = self.class.new
118
+ candidates = filter_hidden ? visible_items : items
119
+ candidates.each do |item|
120
+ current = @tree
121
+ if item.hierarchy_depth == 1
122
+ current.add(item)
123
+ else
124
+ item.parent_collections_names.each.with_index(1) do |name, i|
125
+ target = current.get_or_create(name)
126
+ if item.hierarchy_depth == i + 1
127
+ target.add(item)
128
+ else
129
+ current = target
130
+ end
131
+ end
132
+ end
133
+ end
134
+ @tree
45
135
  end
46
136
 
47
137
  def type
48
138
  :collection
49
139
  end
140
+
141
+ protected
142
+
143
+ def basename
144
+ @path.present? ? @path.split("/").last : ""
145
+ end
146
+
147
+ class << self
148
+ def describe_as
149
+ "items"
150
+ end
151
+ end
50
152
  end
51
153
  end
@@ -24,11 +24,13 @@ module Lookbook
24
24
  config.lookbook = ActiveSupport::OrderedOptions.new
25
25
  config.lookbook.listen_paths ||= []
26
26
  config.lookbook.preview_paths ||= []
27
+ config.lookbook.page_paths ||= ["test/components/docs"]
27
28
 
28
- initializer "view_component.set_configs" do |app|
29
- options = app.config.lookbook
30
- vc_options = app.config.view_component
29
+ initializer "view_component.set_configs" do
30
+ options = config.lookbook
31
+ vc_options = config.view_component
31
32
 
33
+ options.project_name ||= options.project_name == false ? nil : options.project_name || "Lookbook"
32
34
  options.auto_refresh = true if options.auto_refresh.nil?
33
35
  options.sort_examples = false if options.sort_examples.nil?
34
36
  options.debug = false unless options.debug == true
@@ -36,6 +38,13 @@ module Lookbook
36
38
  options.preview_paths = options.preview_paths.map(&:to_s)
37
39
  options.preview_paths += vc_options.preview_paths
38
40
 
41
+ options.page_paths = options.page_paths.map(&:to_s)
42
+ options.page_controller = "Lookbook::PageController" if options.page_controller.nil?
43
+ options.page_route ||= "pages"
44
+ options.page_options ||= {}.with_indifferent_access
45
+
46
+ options.markdown_options = Markdown::DEFAULT_OPTIONS.merge(options.markdown_options || {})
47
+
39
48
  options.preview_controller = vc_options.preview_controller if options.preview_controller.nil?
40
49
  options.preview_srcdoc = false if options.preview_srcdoc.nil?
41
50
  options.preview_display_params ||= {}.with_indifferent_access
@@ -43,16 +52,14 @@ module Lookbook
43
52
  options.listen_paths = options.listen_paths.map(&:to_s)
44
53
  options.listen_paths += options.preview_paths
45
54
  options.listen_paths << (vc_options.view_component_path || Rails.root.join("app/components"))
55
+ options.listen_paths.filter! { |path| Dir.exist? path }
46
56
 
47
- options.experimental_features = false unless options.experimental_features.present?
48
- end
57
+ options.cable = ActionCable::Server::Configuration.new
58
+ options.cable.cable = {adapter: "async"}.with_indifferent_access
59
+ options.cable.mount_path ||= "/lookbook-cable"
60
+ options.cable.connection_class = -> { Lookbook::Connection }
49
61
 
50
- initializer "lookbook.cable.config" do |app|
51
- if app.config.lookbook.auto_refresh
52
- Lookbook::Engine.cable.cable = {adapter: "async"}.with_indifferent_access
53
- Lookbook::Engine.cable.mount_path = "/cable"
54
- Lookbook::Engine.cable.connection_class = -> { Lookbook::Connection }
55
- end
62
+ options.experimental_features = false unless options.experimental_features.present?
56
63
  end
57
64
 
58
65
  initializer "lookbook.parser.tags" do
@@ -68,57 +75,76 @@ module Lookbook
68
75
 
69
76
  initializer "lookbook.logging" do
70
77
  if config.lookbook.debug == true
71
- Lookbook::Engine.cable.logger ||= Rails.logger
78
+ config.lookbook.cable.logger ||= Rails.logger
72
79
  else
73
- Lookbook::Engine.cable.logger = Lookbook::NullLogger.new
80
+ config.lookbook.cable.logger = Lookbook::NullLogger.new
74
81
  config.action_view.logger = Lookbook::NullLogger.new
75
82
  end
76
83
  end
77
84
 
78
- initializer "lookbook.preview.extend" do
79
- ActiveSupport.on_load(:view_component) do
80
- ViewComponent::Preview.extend Lookbook::Preview
81
- end
82
- end
83
-
84
- config.after_initialize do |app|
85
- @listener = Listen.to(*app.config.lookbook.listen_paths, only: /\.(rb|html.*)$/) do |modified, added, removed|
86
- parser.parse
87
- if app.config.lookbook.auto_refresh
88
- if (modified.any? || removed.any?) && added.none?
85
+ config.after_initialize do
86
+ @preview_listener = Listen.to(*config.lookbook.listen_paths, only: /\.(rb|html.*)$/) do |modified, added, removed|
87
+ if Lookbook::Preview.errors.any?
88
+ Lookbook::Preview.reload
89
+ end
90
+ begin
91
+ parser.parse
92
+ rescue
93
+ end
94
+ if Lookbook::Engine.websocket
95
+ if modified.any? || removed.any? || added.none?
89
96
  Lookbook::Engine.websocket.broadcast("reload", {
90
97
  modified: modified,
91
- removed: removed
98
+ removed: removed,
99
+ added: added
92
100
  })
93
101
  end
94
102
  end
95
103
  end
96
- @listener.start
104
+ @preview_listener.start
105
+
106
+ if Lookbook::Features.enabled?(:pages)
107
+ @page_listener = Listen.to(*config.lookbook.page_paths.filter { |dir| Dir.exist? dir }, only: /\.(html.*|md.*)$/) do |modified, added, removed|
108
+ if Lookbook::Engine.websocket
109
+ if modified.any? || removed.any? || added.any?
110
+ Lookbook::Engine.websocket.broadcast("reload", {
111
+ modified: modified,
112
+ removed: removed,
113
+ added: added
114
+ })
115
+ end
116
+ end
117
+ end
118
+ @page_listener.start
119
+ end
120
+
97
121
  parser.parse
98
122
  end
99
123
 
100
124
  at_exit do
101
- @listener&.stop
125
+ @preview_listener&.stop
126
+ @page_listener&.stop
102
127
  end
103
128
 
104
129
  class << self
105
130
  def websocket
106
- if Rails.version.to_f >= 6.0
107
- @websocket ||= ActionCable::Server::Base.new(config: Lookbook::Engine.cable)
108
- else
109
- @websocket = ActionCable::Server::Base.new
110
- @websocket.config = Lookbook::Engine.cable
111
-
112
- @websocket
131
+ if config.lookbook.auto_refresh
132
+ @websocket ||= if Rails.version.to_f >= 6.0
133
+ ActionCable::Server::Base.new(config: config.lookbook.cable)
134
+ else
135
+ websocket ||= ActionCable::Server::Base.new
136
+ websocket.config = config.lookbook.cable
137
+ websocket
138
+ end
113
139
  end
114
140
  end
115
141
 
116
142
  def websocket_mount_path
117
- "#{Lookbook::Engine.routes.find_script_name({})}#{cable.mount_path}"
143
+ "#{mounted_path}#{config.lookbook.cable.mount_path}" if websocket
118
144
  end
119
145
 
120
- def cable
121
- @cable ||= ActionCable::Server::Configuration.new
146
+ def mounted_path
147
+ Lookbook::Engine.routes.find_script_name({})
122
148
  end
123
149
 
124
150
  def parser
@@ -0,0 +1,52 @@
1
+ module Lookbook
2
+ class Error < StandardError
3
+ delegate :full_message, :backtrace, :to_s, to: :target
4
+
5
+ def initialize(original = nil, title: nil, message: nil, file_name: false, line_number: false)
6
+ @original = original
7
+ @title = title
8
+ @message = message
9
+ @file_name = file_name
10
+ @line_number = line_number
11
+ super()
12
+ end
13
+
14
+ def title
15
+ @title || target.class.to_s
16
+ end
17
+
18
+ def message
19
+ (@message || target.message).gsub("(<unknown>):", "").strip.upcase_first
20
+ end
21
+
22
+ def file_name
23
+ path = if @file_name == false
24
+ parsed_backtrace[0][0] if parsed_backtrace.any?
25
+ else
26
+ @file_name
27
+ end
28
+ path.delete_prefix("#{Rails.root}/")
29
+ end
30
+
31
+ def line_number
32
+ if @line_number == false
33
+ parsed_backtrace[0][1] if parsed_backtrace.any?
34
+ else
35
+ @line_number
36
+ end
37
+ end
38
+
39
+ def parsed_backtrace
40
+ backtrace.map do |x|
41
+ x =~ /^(.+?):(\d+)(|:in `(.+)')$/
42
+ [$1, $2, $4]
43
+ end
44
+ end
45
+
46
+ protected
47
+
48
+ def target
49
+ @original.presence || self
50
+ end
51
+ end
52
+ end
@@ -1,6 +1,6 @@
1
1
  module Lookbook
2
2
  module Features
3
- EXPERIMENTAL_FEATURES = []
3
+ EXPERIMENTAL_FEATURES = [:pages]
4
4
 
5
5
  def self.experimental_feature?(name)
6
6
  EXPERIMENTAL_FEATURES.include?(name.to_sym)
@@ -0,0 +1,31 @@
1
+ require "redcarpet"
2
+
3
+ module Lookbook
4
+ class Markdown
5
+ DEFAULT_OPTIONS = {
6
+ tables: true,
7
+ fenced_code_blocks: true,
8
+ disable_indented_code_blocks: true,
9
+ strikethrough: true,
10
+ highlight: true,
11
+ with_toc_data: true
12
+ }
13
+
14
+ def self.render(text)
15
+ markdown = Redcarpet::Markdown.new(Renderer, Lookbook.config.markdown_options)
16
+ markdown.render(text).html_safe
17
+ end
18
+
19
+ class Renderer < Redcarpet::Render::HTML
20
+ def block_code(code, language = "ruby")
21
+ line_numbers = language.to_s.end_with? "-numbered"
22
+ ApplicationController.render "lookbook/components/_code", locals: {
23
+ code: code,
24
+ language: language.to_s.chomp("-numbered"),
25
+ line_numbers: line_numbers
26
+ },
27
+ layout: nil
28
+ end
29
+ end
30
+ end
31
+ end