lookbook 0.8.2 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b2895b167e1e2dc38f33927d4dfab553e0fe58e11b3558330d95eb139011c25
4
- data.tar.gz: c7c62607dc8634d4883fde5a4495d5bfcf7c2fb30edbb37ba4ab228bf0735cd2
3
+ metadata.gz: e7dfafad463df617a30ac6baff60536568771276737340b18f094bb4ad29b90a
4
+ data.tar.gz: 24221d0a990d8181f7fec06bd2acc24d21599b33778b25591054a1d279317d6c
5
5
  SHA512:
6
- metadata.gz: 510e98f6284e5d374347612e4c724bb5745b09cf82945443d2f37c6a54dde996298c31598ba2a5188ee325fb7ee550857bf66b1f9ecf260fb0ea4db0963f50b1
7
- data.tar.gz: 2d41d201fd170a206b91638cc6eb51d638ade8f8d8c30206ce5e4ecbb0e53023c68379fc93428b161e4b5efeb28ead5b4d6908d9e689b493b58175f0016ab4a9
6
+ metadata.gz: a84d78ab4e4bd953f0f3b012a6cf8056efe57f37656ed9ba9839ed99c65cf824a510b1286eede9a4b3640cd9ee25575f0c58fd6998752169303fa5a29df10ec8
7
+ data.tar.gz: 0d516c5a151d34929594abb832827783fa8ceba7a95c2bbea7113a094a1320a5e8b394cab441f551cf6f9d0d4c715a72f00d14a414684e6995362344bc19de08
data/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  ---
14
14
 
15
15
  <div align="center">
16
- <a href="#installing">Installing</a> • <a href="#previews">Previews</a> • <a href="#pages">Pages</a> • <a href="#deployment">Deployment</a> • <a href="#config">Config</a>
16
+ <a href="#installing">Installing</a> • <a href="#previews">Previews</a> • <a href="#pages">Pages</a> • <a href="#deployment">Deployment</a> • <a href="#config">Config</a>
17
17
  </div>
18
18
 
19
19
  ---
@@ -481,10 +481,10 @@ end
481
481
  If you need to add more long-form documentation to live alongside your component previews you can do so using Lookbook's markdown-powered `pages` system.
482
482
 
483
483
  > ⚠️ This feature is currently flagged as an **experimental** feature which requires [feature opt-in](#experimental-features) to use. Its API and implementation may change before it is released.
484
- >
484
+ >
485
485
  > To enable support for pages in your project, add `config.lookbook.experimental_features = [:pages]` into your application configuration file.
486
486
 
487
- ### Pages demo
487
+ ### Pages demo
488
488
 
489
489
  For an example of some pages in Lookbook, check out the [example pages](https://lookbook-demo-app.herokuapp.com/lookbook) in the Lookbook demo app and the associated [page files](https://github.com/allmarkedup/lookbook-demo/tree/main/test/components/docs) in the demo repo.
490
490
 
@@ -496,7 +496,7 @@ Pages must have either a `.html.erb` or a `.md.erb` file extension. All pages a
496
496
 
497
497
  Pages can optionally make use of a **YAML frontmatter block** to customise the behaviour and content of the page itself.
498
498
 
499
- An example page might look like this:
499
+ An example page might look like this:
500
500
 
501
501
  ```markdown
502
502
  ---
@@ -510,7 +510,7 @@ contents will be run through a Markdown parser/renderer before display.
510
510
  Fenced code blocks are fully supported and will be highlighted appropriately.
511
511
 
512
512
  ERB can be used in here.
513
- The template will be rendered **before** being parsed as Markdown.
513
+ The template will be rendered **before** being parsed as Markdown.
514
514
 
515
515
  You can can access data about the page using the `@page` variable.
516
516
  The title of this page is "<%= @page.title %>".
@@ -656,6 +656,35 @@ The default language is `ruby`. To highlight a different language you need to sp
656
656
 
657
657
  > Lookbook uses [Rouge](https://github.com/rouge-ruby/rouge) for syntax highlighting. You can find a [full list of supported languages here](https://github.com/rouge-ruby/rouge/blob/master/docs/Languages.md).
658
658
 
659
+ ### Tabs
660
+
661
+ You can break up your page's content into tabs. If your avatar page is named `01_avatar.md.erb`, by declaring a page named `01_avatar[web].md.erb` it will create a **web** tab on the page. Tabs like normal Pages can contain embedded previews and code examples.
662
+
663
+ ```
664
+ test/components/docs/
665
+ ├── 01_avatar.md.erb
666
+ ├── 01_avatar[design].md.erb
667
+ ├── 01_avatar[mobile].md.erb
668
+ ├── 01_avatar[web].md.erb
669
+ ```
670
+
671
+ By declaring the `label` frontmatter you can change the label shown on the tab:
672
+
673
+ ```
674
+ ---
675
+ label: Website
676
+ ---
677
+ ```
678
+
679
+ If you want the tabs in a different order, you can use the `position` frontmatter:
680
+
681
+ ```
682
+ ---
683
+ label: Web
684
+ position: 1
685
+ ---
686
+ ```
687
+
659
688
  ---
660
689
 
661
690
  ### Pages configuration
@@ -743,10 +772,10 @@ If for some reason you wish to enable file watching or runtime preview annotatio
743
772
  # config/environments/production.rb
744
773
 
745
774
  # enable file-change listening
746
- config.lookbook.listen = true
775
+ config.lookbook.listen = true
747
776
 
748
777
  # enable runtime preview parsing
749
- config.lookbook.runtime_parsing = true
778
+ config.lookbook.runtime_parsing = true
750
779
  ```
751
780
 
752
781
  <h2 id="config">General Configuration</h2>
@@ -778,7 +807,7 @@ config.lookbook.listen_paths << Rails.root.join('app/other/directory')
778
807
  If you want to change the favicon used by the Lookbook UI, you can provide a path to your own (or a data-uri string) using the `ui_favicon` option:
779
808
 
780
809
  ```ruby
781
- config.lookbook.ui_favicon = "/path/to/my/favicon.png"
810
+ config.lookbook.ui_favicon = "/path/to/my/favicon.png"
782
811
  ```
783
812
 
784
813
  > To disable the favicon entirely, set the value to `false`.
@@ -788,7 +817,7 @@ config.lookbook.ui_favicon = "/path/to/my/favicon.png"
788
817
  Specify a project name to display in the top left of the UI (instead of the default "Lookbook"):
789
818
 
790
819
  ```ruby
791
- config.lookbook.project_name = "My Project"
820
+ config.lookbook.project_name = "My Project"
792
821
  ```
793
822
 
794
823
  > If you don't want to display a project name at all, set the value to `false`.
@@ -58,7 +58,7 @@
58
58
  }
59
59
 
60
60
  @layer components {
61
- .unsectioned > #nav > ul > li > div {
61
+ .unsectioned > nav > ul > li > div {
62
62
  @apply py-1 border-b border-gray-300;
63
63
  }
64
64
 
@@ -20,6 +20,7 @@ import copy from "./components/copy";
20
20
  import code from "./components/code";
21
21
  import sizes from "./components/sizes";
22
22
  import embed from "./components/embed";
23
+ import pageTabs from "./components/page-tabs";
23
24
 
24
25
  import initFilterStore from "./stores/filter";
25
26
  import initLayoutStore from "./stores/layout";
@@ -61,6 +62,7 @@ Alpine.data("tabs", tabs);
61
62
  Alpine.data("navItem", navItem);
62
63
  Alpine.data("navGroup", navGroup);
63
64
  Alpine.data("embed", embed);
65
+ Alpine.data("pageTabs", pageTabs);
64
66
 
65
67
  // Init
66
68
 
@@ -0,0 +1,9 @@
1
+ export default function pageTabs() {
2
+ return {
3
+ active: "",
4
+
5
+ changeTab(tabName) {
6
+ this.active = tabName;
7
+ },
8
+ };
9
+ }
@@ -14,7 +14,7 @@ module Lookbook
14
14
  if feature_enabled? :pages
15
15
  landing = Lookbook.pages.find(&:landing) || Lookbook.pages.first
16
16
  if landing.present?
17
- redirect_to page_path(landing.lookup_path)
17
+ redirect_to lookbook_page_path(landing.lookup_path)
18
18
  end
19
19
  end
20
20
  end
@@ -1,5 +1,7 @@
1
1
  module Lookbook
2
2
  class PagesController < ApplicationController
3
+ helper_method :page_controller
4
+
3
5
  def self.controller_path
4
6
  "lookbook/pages"
5
7
  end
@@ -7,7 +9,7 @@ module Lookbook
7
9
  def index
8
10
  landing = Lookbook.pages.find(&:landing) || Lookbook.pages.first
9
11
  if landing.present?
10
- redirect_to page_path landing.lookup_path
12
+ redirect_to lookbook_page_path landing.lookup_path
11
13
  else
12
14
  @title = "Not found"
13
15
  render "not_found"
@@ -28,8 +28,8 @@ module Lookbook
28
28
  begin
29
29
  set_params
30
30
  @examples = examples_data
31
- @drawer_panels = drawer_panels.filter { |name, panel| panel[:show] }
32
- @preview_panels = preview_panels.filter { |name, panel| panel[:show] }
31
+ @drawer_panels = drawer_panels.select { |name, panel| panel[:show] }
32
+ @preview_panels = preview_panels.select { |name, panel| panel[:show] }
33
33
  rescue => exception
34
34
  render_in_layout "lookbook/error", error: prettify_error(exception)
35
35
  end
@@ -45,11 +45,11 @@ module Lookbook
45
45
  if @example
46
46
  @preview = @example.preview
47
47
  if params[:path] == @preview&.lookup_path
48
- redirect_to show_path "#{params[:path]}/#{@preview.default_example.name}"
48
+ redirect_to lookbook_inspect_path "#{params[:path]}/#{@preview.default_example.name}"
49
49
  end
50
50
  else
51
51
  first_example = Lookbook.previews.find(params[:path])&.examples&.first
52
- redirect_to show_path(first_example.lookup_path) if first_example
52
+ redirect_to lookbook_inspect_path(first_example.lookup_path) if first_example
53
53
  end
54
54
  end
55
55
 
@@ -133,7 +133,7 @@ module Lookbook
133
133
  template: "lookbook/previews/panels/notes",
134
134
  hotkey: "n",
135
135
  show: true,
136
- disabled: @examples.filter { |e| e[:notes].present? }.none?
136
+ disabled: @examples.select { |e| e[:notes].present? }.none?
137
137
  },
138
138
  params: {
139
139
  label: "Params",
@@ -11,9 +11,9 @@ module Lookbook
11
11
  def landing_path
12
12
  landing = feature_enabled?(:pages) ? Lookbook.pages.find(&:landing) || Lookbook.pages.first : nil
13
13
  if landing.present?
14
- page_path(landing.lookup_path)
14
+ lookbook_page_path landing.lookup_path
15
15
  else
16
- home_path
16
+ lookbook_home_path
17
17
  end
18
18
  end
19
19
  end
@@ -4,7 +4,7 @@ module Lookbook
4
4
 
5
5
  def page_path(id)
6
6
  page = id.is_a?(Page) ? id : Lookbook.pages.find(id)
7
- lookbook.page_path page.lookup_path
7
+ lookbook_page_path page.lookup_path
8
8
  end
9
9
 
10
10
  def embed(*args, params: {}, type: :preview, **opts)
@@ -1,14 +1,14 @@
1
- <div id="<%= id %>" x-data="embed" class="not-prose embed border border-gray-300 rounded-md overflow-hidden" @page:morphed.window="recaclulateIframeHeight">
1
+ <div id="<%= id %>" x-data="embed" class="not-prose embed border border-gray-300 rounded-md overflow-hidden" @page:morphed.window="recaclulateIframeHeight" @tab-switch.window="recaclulateIframeHeight">
2
2
  <header class="px-3 py-2 flex items-center text-xs text-gray-400 bg-white border-b border-gray-300">
3
3
  <div class="text-gray-500">
4
4
  <%= example.preview.label %> (<%= example.label %>)
5
5
  </div>
6
6
  <div class="ml-auto flex items-center space-x-3">
7
- <a href="<%= lookbook.show_path(example.path, params) %>" class="" x-tooltip.theme.lookbook="`View in Inspector`">
7
+ <a href="<%= lookbook_inspect_path(example.path, params) %>" class="" x-tooltip.theme.lookbook="`View in Inspector`">
8
8
  <%= icon "eye", size: 4, class: "hover:text-indigo-800" %>
9
9
  </a>
10
10
  <a
11
- href="<%= lookbook.preview_path(example.path, params) %>"
11
+ href="<%= lookbook_preview_path(example.path, params) %>"
12
12
  target="_blank"
13
13
  class="-top-px relative"
14
14
  x-tooltip.theme.lookbook="`Open in new window`">
@@ -21,7 +21,7 @@
21
21
  <iframe seamless
22
22
  id="<%= id %>-iframe"
23
23
  x-ref="iframe"
24
- src="<%= lookbook.preview_path(example.path, params.merge(lookbook_embed: true)) %>"
24
+ src="<%= lookbook_preview_path(example.path, params.merge(lookbook_embed: true)) %>"
25
25
  class="min-w-full h-full w-px"
26
26
  :class="{ 'pointer-events-none': reflowing }"
27
27
  @load="recaclulateIframeHeight">
@@ -1,6 +1,6 @@
1
- <nav id="nav" x-data="nav(<%= filterable %>)">
1
+ <nav id="nav-<%= items.class.describe_as %>" x-data="nav(<%= filterable %>)">
2
2
  <% if items.any? %>
3
- <ul x-ref="items" id="nav-<%= items.class.describe_as %>">
3
+ <ul x-ref="items" id="nav-<%= items.class.describe_as %>-items">
4
4
  <% items.each do |item| %>
5
5
  <%= component "nav_#{item.type}", node: item %>
6
6
  <% end %>
@@ -1,5 +1,5 @@
1
1
  <%
2
- path = show_path(item.lookup_path)
2
+ path = lookbook_inspect_path(item.lookup_path)
3
3
  display ||= :item
4
4
  label ||= item.label
5
5
  item_icon = display == :node ? "layers" : "eye"
@@ -1,5 +1,5 @@
1
1
  <%
2
- path = page_path(node.lookup_path)
2
+ path = lookbook_page_path(node.lookup_path)
3
3
  depth = node.hierarchy_depth + 1
4
4
  %>
5
5
  <li key="nav-page-<%= node.id %>-item">
@@ -1,6 +1,6 @@
1
1
  <% examples = node.examples.reject(&:hidden?) %>
2
2
  <% if examples.many? %>
3
- <%= component "nav_group", node: node, path: show_path(examples.first.path) do %>
3
+ <%= component "nav_group", node: node, path: lookbook_inspect_path(examples.first.path) do %>
4
4
  <% examples.each do |example| %>
5
5
  <%= component "nav_item", item: example, depth: example.hierarchy_depth + 1 %>
6
6
  <% end %>
@@ -47,7 +47,7 @@
47
47
  <%= icon "refresh-cw", size: 3.5, class: "hover:text-indigo-800" %>
48
48
  </button>
49
49
  <a
50
- href="<%= preview_path %>"
50
+ href="<%= lookbook_preview_path %>"
51
51
  target="_blank"
52
52
  x-tooltip.theme.lookbook="`Open in new window`"
53
53
  data-hotkey="w">
@@ -43,6 +43,32 @@
43
43
  <div id="page-main-<%= @page.id %>" data-morph-strategy="replace">
44
44
  <div class="prose max-w-none prose-a:text-indigo-900">
45
45
  <%= @page_content %>
46
+
47
+ <% if @page.tabs.any? %>
48
+ <div
49
+ id="page-tabs"
50
+ x-data="pageTabs"
51
+ x-init="active = '<%= @page.tabs.first.tab %>'"
52
+ class="">
53
+ <nav class="flex border-b border-gray-300">
54
+ <% @page.tabs.each do |tab| %>
55
+ <button
56
+ class="px-5 py-2 font-bold border-b-2 hover:border-gray-500"
57
+ :class="active === '<%= tab.tab %>' ? '!border-gray-500 !cursor-default' : 'border-gray-50'"
58
+ x-on:click="changeTab('<%= tab.tab %>');$dispatch('tab-switch')">
59
+ <%= tab.label %>
60
+ </button>
61
+ <% end %>
62
+ </nav>
63
+
64
+ <% @page.tabs.each do |tab| %>
65
+ <div id="tab-<%= tab.tab %>" x-show="active === '<%= tab.tab %>'">
66
+ <%= page_controller.render_page(tab) %>
67
+ </div>
68
+ <% end %>
69
+ </div>
70
+ <% end %>
71
+
46
72
  </div>
47
73
  </div>
48
74
  <% if @page.footer? %>
@@ -1 +1 @@
1
- <iframe id="error-iframe" src="<%= url_for lookbook.preview_path %>" frameborder="0" class=" h-screen w-full" seamless></iframe>
1
+ <iframe id="error-iframe" src="<%= url_for lookbook_preview_path %>" frameborder="0" class=" h-screen w-full" seamless></iframe>
@@ -1,4 +1,4 @@
1
- <% items = examples.filter { |example| example[:notes].present? } %>
1
+ <% items = examples.select { |example| example[:notes].present? } %>
2
2
  <div class="text-gray-600 bg-gray-50 h-full overflow-auto" data-morph-strategy="replace">
3
3
  <% if items.many? %>
4
4
  <div class="divide-y divide-dashed divide-gray-300">
@@ -22,4 +22,4 @@
22
22
  <% end %>
23
23
  </div>
24
24
  <% end %>
25
- </div>
25
+ </div>
@@ -9,7 +9,7 @@
9
9
  <iframe seamless
10
10
  class="h-full w-full border border-gray-300"
11
11
  :class="{ 'pointer-events-none': $store.layout.reflowing }"
12
- src="<%= lookbook.preview_path(request.query_parameters.merge(lookbook_timestamp: Time.now)) %>"
12
+ src="<%= lookbook_preview_path(request.query_parameters.merge(lookbook_timestamp: Time.now)) %>"
13
13
  <% if config.preview_srcdoc %>srcdoc="<%== srcdoc %>"<% end %>
14
14
  frameborder="0"
15
15
  x-data="sizes"
data/config/routes.rb CHANGED
@@ -3,13 +3,13 @@ Lookbook::Engine.routes.draw do
3
3
  mount Lookbook::Engine.websocket => Lookbook.config.cable_mount_path
4
4
  end
5
5
 
6
- root to: "application#index", as: :home
6
+ root to: "application#index", as: :lookbook_home
7
7
 
8
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
9
+ get "/#{Lookbook.config.page_route}", to: "pages#index", as: :lookbook_page_index
10
+ get "/#{Lookbook.config.page_route}/*path", to: "pages#show", as: :lookbook_page
11
11
  end
12
12
 
13
- get "/preview/*path", to: "previews#preview", as: :preview
14
- get "/*path", to: "previews#show", as: :show
13
+ get "/preview/*path", to: "previews#preview", as: :lookbook_preview
14
+ get "/*path", to: "previews#show", as: :lookbook_inspect
15
15
  end
@@ -53,7 +53,7 @@ module Lookbook
53
53
  options.listen_paths = options.listen_paths.map(&:to_s)
54
54
  options.listen_paths += options.preview_paths
55
55
  options.listen_paths << (vc_options.view_component_path || Rails.root.join("app/components"))
56
- options.listen_paths.filter! { |path| Dir.exist? path }
56
+ options.listen_paths.select! { |path| Dir.exist? path }
57
57
 
58
58
  options.cable_mount_path ||= "/lookbook-cable"
59
59
  options.cable_logger ||= Rails.logger
@@ -95,7 +95,7 @@ module Lookbook
95
95
  @preview_listener.start
96
96
 
97
97
  if Lookbook::Features.enabled?(:pages)
98
- @page_listener = Listen.to(*config.lookbook.page_paths.filter { |dir| Dir.exist? dir }, only: /\.(html.*|md.*)$/) do |modified, added, removed|
98
+ @page_listener = Listen.to(*config.lookbook.page_paths.select { |dir| Dir.exist? dir }, only: /\.(html.*|md.*)$/) do |modified, added, removed|
99
99
  Lookbook::Engine.websocket&.broadcast("reload", {
100
100
  modified: modified,
101
101
  removed: removed,
data/lib/lookbook/page.rb CHANGED
@@ -16,17 +16,22 @@ module Lookbook
16
16
  ]
17
17
 
18
18
  attr_reader :errors
19
+ attr_accessor :tabs
19
20
 
20
21
  def initialize(path, base_path)
21
22
  @pathname = Pathname.new path
22
- @base_path = base_path
23
+ @base_path = Pathname.new base_path
23
24
  @options = nil
24
25
  @errors = []
26
+ @tabs = []
25
27
  end
26
28
 
27
29
  def path
28
30
  rel_path = @pathname.relative_path_from(@base_path)
29
- (rel_path.dirname.to_s == "." ? name : "#{rel_path.dirname}/#{name}")
31
+
32
+ _path = (rel_path.dirname.to_s == "." ? name : "#{rel_path.dirname}/#{name}")
33
+ _path.gsub!("[#{tab}]", "") if tab?
34
+ _path
30
35
  end
31
36
 
32
37
  def lookup_path
@@ -78,7 +83,16 @@ module Lookbook
78
83
  end
79
84
 
80
85
  def type
81
- :page
86
+ tab? ? :tab : :page
87
+ end
88
+
89
+ def tab
90
+ matches = full_path.to_s.match(%r{\[(?<tab>\w+)\]})
91
+ matches ? remove_position_prefix(matches[:tab]) : nil
92
+ end
93
+
94
+ def tab?
95
+ tab.present?
82
96
  end
83
97
 
84
98
  def method_missing(method_name, *args, &block)
@@ -114,7 +128,7 @@ module Lookbook
114
128
  end
115
129
  @options = Lookbook.config.page_options.deep_merge(frontmatter).with_indifferent_access
116
130
  @options[:id] = @options[:id] ? generate_id(@options[:id]) : generate_id(lookup_path)
117
- @options[:label] ||= name.titleize
131
+ @options[:label] ||= (tab? ? tab : name).titleize
118
132
  @options[:title] ||= @options[:label]
119
133
  @options[:hidden] ||= false
120
134
  @options[:landing] ||= false
@@ -143,17 +157,29 @@ module Lookbook
143
157
  end
144
158
 
145
159
  def all
146
- pages = Array(page_paths).map do |dir|
147
- Dir["#{dir}/**/*.html.*", "#{dir}/**/*.md.*"].sort.map do |page|
148
- Lookbook::Page.new(page, dir)
149
- end
160
+ pages, tabs =
161
+ Array(page_paths).flat_map do |dir|
162
+ Dir["#{dir}/**/*.html.*", "#{dir}/**/*.md.*"].sort.map do |page|
163
+ page = Lookbook::Page.new(page, dir)
164
+ end
165
+ end.partition { |page| page.type == :page }
166
+
167
+ sorted_pages = pages
168
+ .uniq { |page| page.path }
169
+ .sort_by { |page| [page.position, page.label] }
170
+
171
+ page_dict = sorted_pages.index_by(&:path)
172
+ sorted_tabs = tabs.sort_by { |tab| [tab.position, tab.label] }
173
+
174
+ sorted_tabs.each do |tab|
175
+ page_dict[tab.path].tabs << tab
150
176
  end
151
- sorted_pages = pages.flatten.uniq { |p| p.path }.sort_by { |page| [page.position, page.label] }
177
+
152
178
  PageCollection.new(sorted_pages)
153
179
  end
154
180
 
155
181
  def page_paths
156
- Lookbook.config.page_paths.filter { |dir| Dir.exist? dir }
182
+ Lookbook.config.page_paths.select { |dir| Dir.exist? dir }
157
183
  end
158
184
  end
159
185
  end
@@ -33,7 +33,7 @@ module Lookbook
33
33
  def examples
34
34
  return @examples if @examples.present?
35
35
  public_methods = @preview.public_instance_methods(false)
36
- public_method_objects = @preview_inspector&.methods&.filter { |m| public_methods.include?(m.name) }
36
+ public_method_objects = @preview_inspector&.methods&.select { |m| public_methods.include?(m.name) }
37
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 = []
@@ -6,7 +6,7 @@ module Lookbook
6
6
  protected
7
7
 
8
8
  def generate_id(*args)
9
- parts = args.map { |arg| arg.to_s.parameterize.underscore }
9
+ parts = args.map { |arg| arg.to_s.force_encoding("UTF-8").parameterize.underscore }
10
10
  parts.join("-").tr("/", "-").tr("_", "-").delete_prefix("-").delete_suffix("-").gsub("--", "-")
11
11
  end
12
12
 
@@ -1,3 +1,3 @@
1
1
  module Lookbook
2
- VERSION = "0.8.2"
2
+ VERSION = "0.9.1"
3
3
  end