jekyll-awesome-nav 0.0.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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.copier-answers.ci.yml +12 -0
  3. data/.devcontainer/devcontainer.json +35 -0
  4. data/.devcontainer/post-create.sh +19 -0
  5. data/.rubocop.yml +43 -0
  6. data/.ruby-version +1 -0
  7. data/.vscode/tasks.json +70 -0
  8. data/AGENTS.md +287 -0
  9. data/CODE_OF_CONDUCT.md +84 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +176 -0
  12. data/Rakefile +11 -0
  13. data/jekyll-awesome-nav.gemspec +35 -0
  14. data/lib/jekyll/awesome_nav/config.rb +31 -0
  15. data/lib/jekyll/awesome_nav/generator.rb +50 -0
  16. data/lib/jekyll/awesome_nav/nav_file.rb +14 -0
  17. data/lib/jekyll/awesome_nav/nav_file_loader.rb +140 -0
  18. data/lib/jekyll/awesome_nav/nav_file_options.rb +69 -0
  19. data/lib/jekyll/awesome_nav/nav_resolver.rb +370 -0
  20. data/lib/jekyll/awesome_nav/navigation_result.rb +150 -0
  21. data/lib/jekyll/awesome_nav/node.rb +64 -0
  22. data/lib/jekyll/awesome_nav/page_set.rb +31 -0
  23. data/lib/jekyll/awesome_nav/serializer.rb +26 -0
  24. data/lib/jekyll/awesome_nav/sort_options.rb +91 -0
  25. data/lib/jekyll/awesome_nav/tree_builder.rb +75 -0
  26. data/lib/jekyll/awesome_nav/utils.rb +94 -0
  27. data/lib/jekyll/awesome_nav/version.rb +19 -0
  28. data/lib/jekyll/awesome_nav.rb +26 -0
  29. data/lib/jekyll-awesome-nav.rb +3 -0
  30. data/site/_config.yml +33 -0
  31. data/site/_includes/awesome-nav-demo-tree.html +15 -0
  32. data/site/_includes/awesome-nav-tree.html +19 -0
  33. data/site/_layouts/awesome_nav_demo.html +128 -0
  34. data/site/docs/getting-started.md +68 -0
  35. data/site/docs/guides/.nav.yml +7 -0
  36. data/site/docs/guides/config.md +37 -0
  37. data/site/docs/guides/data.md +40 -0
  38. data/site/docs/guides/index.md +15 -0
  39. data/site/docs/guides/install.md +53 -0
  40. data/site/docs/guides/layouts.md +116 -0
  41. data/site/docs/guides/overrides.md +42 -0
  42. data/site/docs/index.md +35 -0
  43. data/site/index.md +66 -0
  44. metadata +111 -0
@@ -0,0 +1,370 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module AwesomeNav
5
+ class NavResolver
6
+ ResolutionContext = Struct.new(:append_unmatched, :sort_options, :ignore_patterns, keyword_init: true)
7
+
8
+ def initialize(root_dir:, nav_map:)
9
+ @root_dir = root_dir
10
+ @nav_map = nav_map
11
+ @generated_by_dir = {}
12
+ @generated_by_path = {}
13
+ end
14
+
15
+ def apply(
16
+ items,
17
+ current_dir = @root_dir,
18
+ inherited_append_unmatched: false,
19
+ inherited_sort_options: SortOptions.from(nil),
20
+ inherited_ignore_patterns: []
21
+ )
22
+ index_generated(items)
23
+ generated_items = generated_children_for(current_dir, items)
24
+ nav_file = @nav_map[current_dir]
25
+ options = nav_file&.options || NavFileOptions.new
26
+ override_items = nav_file&.items
27
+ context = ResolutionContext.new(
28
+ append_unmatched: options.append_unmatched_or(inherited_append_unmatched),
29
+ sort_options: options.sort_options_or(inherited_sort_options),
30
+ ignore_patterns: options.ignore_patterns_or(inherited_ignore_patterns)
31
+ )
32
+
33
+ return resolve_generated_items(generated_items, current_dir, context) unless override_items
34
+
35
+ matched = {}
36
+ resolved = expand_override_items(override_items, current_dir, generated_items, context, matched)
37
+ return resolved.first.children if same_dir_wrapper?(resolved, current_dir)
38
+ return resolved unless context.append_unmatched
39
+
40
+ resolved + unmatched_items(generated_items, matched, context, current_dir)
41
+ end
42
+
43
+ def resolved_nav_dir(page_dir)
44
+ current = page_dir
45
+
46
+ loop do
47
+ return current if @nav_map.key?(current)
48
+ return @root_dir if current == @root_dir
49
+ break if current.empty?
50
+
51
+ current = Utils.parent_dir(current)
52
+ end
53
+
54
+ @root_dir
55
+ end
56
+
57
+ private
58
+
59
+ def index_generated(items)
60
+ Array(items).each do |item|
61
+ @generated_by_dir[Utils.normalize_dir(item.dir)] = item if item.section? && item.dir
62
+ @generated_by_path[Utils.normalize_dir(item.path)] = item if item.path
63
+ index_generated(item.children)
64
+ end
65
+ end
66
+
67
+ def generated_children_for(current_dir, fallback_items)
68
+ return fallback_items if current_dir == @root_dir
69
+
70
+ @generated_by_dir.fetch(current_dir, Node.section(dir: current_dir)).children
71
+ end
72
+
73
+ def same_dir_wrapper?(items, current_dir)
74
+ items.length == 1 && items.first.section? && Utils.normalize_dir(items.first.dir) == current_dir
75
+ end
76
+
77
+ def raw_same_dir_wrapper?(current_dir)
78
+ items = Array(@nav_map[current_dir]&.items)
79
+
80
+ same_dir_wrapper?(items, current_dir) ||
81
+ (items.length == 1 && items.first.section? && same_dir_manual_wrapper?(items.first, current_dir))
82
+ end
83
+
84
+ def expand_override_items(items, current_dir, generated_items, context, matched)
85
+ Array(items).flat_map do |item|
86
+ expand_item(item, current_dir, generated_items, context, matched)
87
+ end
88
+ end
89
+
90
+ def expand_item(item, current_dir, generated_items, context, matched)
91
+ return expand_reference(item, current_dir, generated_items, context, matched) if item.reference?
92
+
93
+ applied_item = item.deep_dup
94
+ item_dir = child_dir_for_item(current_dir, applied_item)
95
+ mark_matched(applied_item, matched)
96
+
97
+ if applied_item.section? && item_dir == current_dir
98
+ return [resolve_local_section(applied_item, current_dir, generated_items, context, matched)]
99
+ end
100
+
101
+ if applied_item.section? && item_dir.nil?
102
+ if same_dir_manual_wrapper?(applied_item, current_dir)
103
+ return [resolve_local_section(same_dir_section(applied_item, current_dir), current_dir, generated_items, context, matched)]
104
+ end
105
+
106
+ return [resolve_manual_section(applied_item, current_dir, generated_items, context, matched)]
107
+ end
108
+
109
+ applied_item = with_resolved_children(applied_item, item_dir, context) if item_dir && item_dir != current_dir
110
+
111
+ [applied_item]
112
+ end
113
+
114
+ def resolve_local_section(item, current_dir, generated_items, context, matched)
115
+ child_matched = {}
116
+ children = expand_override_items(item.children, current_dir, generated_items, context, child_matched)
117
+ children += unmatched_items(generated_items, child_matched, context, current_dir) if context.append_unmatched
118
+ child_matched.each_key { |key| matched[key] = true }
119
+
120
+ Node.section(
121
+ dir: item.dir,
122
+ title: item.title,
123
+ url: item.url,
124
+ children: children,
125
+ path: item.path,
126
+ filename: item.filename
127
+ )
128
+ end
129
+
130
+ def resolve_manual_section(item, current_dir, generated_items, context, matched)
131
+ child_matched = {}
132
+ children = expand_override_items(item.children, current_dir, generated_items, context, child_matched)
133
+ child_matched.each_key { |key| matched[key] = true }
134
+
135
+ Node.section(
136
+ dir: item.dir,
137
+ title: item.title,
138
+ url: item.url,
139
+ children: children,
140
+ path: item.path,
141
+ filename: item.filename
142
+ )
143
+ end
144
+
145
+ def expand_reference(item, current_dir, generated_items, context, matched)
146
+ return expand_glob(item, current_dir, generated_items, context, matched) if glob?(item.target)
147
+
148
+ generated = generated_node_for_reference(item, current_dir)
149
+ return [] unless generated
150
+ return [] if hidden?(generated)
151
+
152
+ return [section_page_node(generated, item.title)] if generated.section? && Utils.normalize_dir(generated.dir) == current_dir
153
+
154
+ node = generated.deep_dup
155
+ node.title = item.title if item.title
156
+ mark_matched(node, matched)
157
+ [with_resolved_children(node, Utils.normalize_dir(node.dir), context)]
158
+ end
159
+
160
+ def section_page_node(section, title)
161
+ Node.page(
162
+ dir: section.dir,
163
+ title: title || section.title,
164
+ url: section.url,
165
+ path: section.path,
166
+ filename: section.filename
167
+ )
168
+ end
169
+
170
+ def expand_glob(item, current_dir, generated_items, context, matched)
171
+ glob_matches(item.target, current_dir, generated_items, context).filter_map do |generated|
172
+ next if matched?(generated, matched)
173
+
174
+ node = generated.deep_dup
175
+ mark_matched(node, matched)
176
+ with_resolved_children(node, Utils.normalize_dir(node.dir), context)
177
+ end
178
+ end
179
+
180
+ def with_resolved_children(item, item_dir, context)
181
+ return item unless item.section?
182
+
183
+ children = apply(
184
+ item.children,
185
+ item_dir,
186
+ inherited_append_unmatched: context.append_unmatched,
187
+ inherited_sort_options: context.sort_options,
188
+ inherited_ignore_patterns: context.ignore_patterns
189
+ )
190
+ Node.section(
191
+ dir: item.dir,
192
+ title: item.title,
193
+ url: item.url,
194
+ children: children,
195
+ path: item.path,
196
+ filename: item.filename
197
+ )
198
+ end
199
+
200
+ def resolve_generated_items(items, current_dir, context)
201
+ Array(items).flat_map do |item|
202
+ applied_item = item.deep_dup
203
+ item_dir = child_dir_for_item(current_dir, applied_item)
204
+
205
+ next if hidden?(applied_item)
206
+ next resolve_generated_child(applied_item, item_dir, context) if item_dir && item_dir != current_dir
207
+
208
+ applied_item
209
+ end
210
+ end
211
+
212
+ def resolve_generated_child(item, item_dir, context)
213
+ return with_resolved_children(item, item_dir, context) unless @nav_map.key?(item_dir)
214
+ return with_resolved_children(item, item_dir, context) if raw_same_dir_wrapper?(item_dir)
215
+
216
+ apply(
217
+ item.children,
218
+ item_dir,
219
+ inherited_append_unmatched: context.append_unmatched,
220
+ inherited_sort_options: context.sort_options,
221
+ inherited_ignore_patterns: context.ignore_patterns
222
+ )
223
+ end
224
+
225
+ def unmatched_items(generated_items, matched, context, current_dir)
226
+ context.sort_options.sort(generated_items).filter_map do |item|
227
+ next if matched?(item, matched)
228
+ next if hidden?(item)
229
+ next if ignored?(item, current_dir, context.ignore_patterns)
230
+
231
+ node = item.deep_dup
232
+ with_resolved_children(node, Utils.normalize_dir(node.dir), context)
233
+ end
234
+ end
235
+
236
+ def generated_node_for_reference(item, current_dir)
237
+ candidates_for(item.target, current_dir).each do |candidate|
238
+ page = @generated_by_path[candidate]
239
+ return page if page
240
+
241
+ section = @generated_by_dir[candidate]
242
+ return section if section
243
+ end
244
+
245
+ nil
246
+ end
247
+
248
+ def same_dir_manual_wrapper?(item, current_dir)
249
+ generated = @generated_by_dir[current_dir]
250
+
251
+ generated&.section? && item.title == generated.title
252
+ end
253
+
254
+ def same_dir_section(item, current_dir)
255
+ generated = @generated_by_dir[current_dir]
256
+
257
+ Node.section(
258
+ dir: generated.dir,
259
+ title: item.title,
260
+ url: generated.url,
261
+ children: item.children,
262
+ path: generated.path,
263
+ filename: generated.filename
264
+ )
265
+ end
266
+
267
+ def candidates_for(value, current_dir)
268
+ clean_value = Utils.normalize_dir(value)
269
+ candidates = []
270
+ candidates << Utils.normalize_dir(File.join(current_dir, clean_value)) unless current_dir.empty?
271
+ candidates << Utils.normalize_dir(File.join(@root_dir, clean_value))
272
+ candidates << clean_value
273
+ candidates
274
+ end
275
+
276
+ def glob_matches(pattern, current_dir, generated_items, context)
277
+ recursive = pattern.to_s.include?("**")
278
+ directory_only = pattern.to_s.end_with?("/")
279
+ normalized_pattern = pattern.to_s.delete_suffix("/")
280
+
281
+ pool = recursive ? flatten_generated(generated_items) : Array(generated_items)
282
+ matches = pool.select do |item|
283
+ next false if directory_only && !item.section?
284
+ next false if hidden?(item)
285
+ next false if ignored?(item, current_dir, context.ignore_patterns)
286
+
287
+ File.fnmatch?(normalized_pattern, relative_match_path(item, current_dir), File::FNM_PATHNAME)
288
+ end
289
+ context.sort_options.sort(matches)
290
+ end
291
+
292
+ def hidden?(item)
293
+ item_dir = hidden_dir_for(item)
294
+ nav_file = @nav_map[item_dir]
295
+
296
+ nav_file&.options&.hide?
297
+ end
298
+
299
+ def hidden_dir_for(item)
300
+ item.section? ? Utils.normalize_dir(item.dir) : Utils.source_dir_for_path(item.path)
301
+ end
302
+
303
+ def ignored?(item, current_dir, ignore_patterns)
304
+ match_path = relative_match_path(item, current_dir)
305
+ ignore_patterns.any? do |pattern|
306
+ normalized_pattern = pattern.to_s.delete_suffix("/")
307
+ File.fnmatch?(normalized_pattern, match_path, File::FNM_PATHNAME)
308
+ end
309
+ end
310
+
311
+ def flatten_generated(items)
312
+ Array(items).each_with_object([]) do |item, flattened|
313
+ next if hidden?(item)
314
+
315
+ flattened << item
316
+ flattened.concat(flatten_generated(item.children)) if item.section?
317
+ end
318
+ end
319
+
320
+ def relative_match_path(item, current_dir)
321
+ source = item.section? ? Utils.normalize_dir(item.dir) : Utils.normalize_dir(item.path)
322
+ Utils.relative_to_root(source, current_dir)
323
+ end
324
+
325
+ def glob?(value)
326
+ value.to_s.match?(/[*?\[]/)
327
+ end
328
+
329
+ def mark_matched(item, matched)
330
+ return if item.reference?
331
+
332
+ matched[node_key(item)] = true
333
+ item.children.each { |child| mark_matched(child, matched) } if item.section?
334
+ end
335
+
336
+ def matched?(item, matched)
337
+ matched[node_key(item)]
338
+ end
339
+
340
+ def node_key(item)
341
+ item.section? ? "dir:#{Utils.normalize_dir(item.dir)}" : "path:#{Utils.normalize_dir(item.path || item.url)}"
342
+ end
343
+
344
+ def child_dir_for_item(parent_dir, item)
345
+ return Utils.normalize_dir(item.dir) if item.dir
346
+ return nil unless item.url
347
+
348
+ dir = dir_for_item(item)
349
+ return nil if dir.nil? || dir.empty?
350
+
351
+ if dir == parent_dir || dir.start_with?("#{parent_dir}/")
352
+ Utils.normalize_dir(dir)
353
+ else
354
+ Utils.normalize_dir([parent_dir, Utils.last_segment(dir)].reject(&:empty?).join("/"))
355
+ end
356
+ end
357
+
358
+ def dir_for_item(item)
359
+ return nil unless item.url
360
+ return nil if Utils.external_url?(item.url)
361
+
362
+ path = Utils.normalize_url(item.url).sub(%r{\A/}, "").sub(%r{/\z}, "")
363
+ return "" if path.empty?
364
+
365
+ segments = path.split("/")
366
+ File.extname(segments.last).empty? ? segments.join("/") : segments[0...-1].join("/")
367
+ end
368
+ end
369
+ end
370
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module AwesomeNav
5
+ class NavigationResult
6
+ def initialize(tree:, root_dir:, root_page:, nav_map:)
7
+ @tree = tree
8
+ @root_dir = root_dir
9
+ @root_page = root_page
10
+ @nav_map = nav_map
11
+ end
12
+
13
+ def serialized_tree
14
+ @serialized_tree ||= Serializer.serialize_tree(@tree)
15
+ end
16
+
17
+ def serialized_local_nav_map
18
+ @serialized_local_nav_map ||= Serializer.serialize_map(local_nav_nodes)
19
+ end
20
+
21
+ def serialized_nav_files
22
+ @serialized_nav_files ||= Serializer.serialize_map(@nav_map)
23
+ end
24
+
25
+ def local_nav_for(page_dir)
26
+ serialized_local_nav_map.fetch(page_dir, [])
27
+ end
28
+
29
+ def nav_dir_for(page_dir)
30
+ current = page_dir
31
+
32
+ loop do
33
+ return current if @nav_map.key?(current)
34
+ return @root_dir if current == @root_dir
35
+ break if current.empty?
36
+
37
+ current = Utils.parent_dir(current)
38
+ end
39
+
40
+ @root_dir
41
+ end
42
+
43
+ def breadcrumbs_for(page)
44
+ page_url = Utils.normalize_url(page.url)
45
+ trail = find_trail(serialized_tree, page_url)
46
+ return root_breadcrumb(page) if trail.nil? && Utils.source_dir_for(page) == @root_dir && Utils.index_page?(page)
47
+ return [] unless trail
48
+
49
+ breadcrumbs = trail.filter_map do |item|
50
+ next if item["title"].to_s.empty?
51
+
52
+ crumb = { "title" => item["title"] }
53
+ crumb["url"] = item["url"] if item["url"]
54
+ crumb
55
+ end
56
+
57
+ prepend_root_breadcrumb(breadcrumbs)
58
+ end
59
+
60
+ def nav_entry_for(page_url)
61
+ neighbor_map[page_url]
62
+ end
63
+
64
+ private
65
+
66
+ def local_nav_nodes
67
+ @local_nav_nodes ||= begin
68
+ map = { @root_dir => @tree.map(&:deep_dup) }
69
+ walk_local_nav_map(@tree, map)
70
+ map
71
+ end
72
+ end
73
+
74
+ def walk_local_nav_map(items, map)
75
+ Array(items).each do |item|
76
+ next unless item.section? && item.children.any?
77
+
78
+ dir = Utils.normalize_dir(item.dir)
79
+ next if dir.empty?
80
+
81
+ map[dir] = item.children.map(&:deep_dup)
82
+ walk_local_nav_map(item.children, map)
83
+ end
84
+ end
85
+
86
+ def root_breadcrumb(page)
87
+ title = page.data["nav_title"] || page.data["title"] || Utils.titleize(Utils.last_segment(@root_dir))
88
+ [{ "title" => title, "url" => Utils.normalize_url(page.url) }]
89
+ end
90
+
91
+ def prepend_root_breadcrumb(breadcrumbs)
92
+ root_crumb = {
93
+ "title" => @root_page&.data&.fetch("nav_title",
94
+ nil) || @root_page&.data&.fetch("title", nil) || Utils.titleize(Utils.last_segment(@root_dir)),
95
+ "url" => Utils.normalize_url(@root_page&.url || "/#{@root_dir}/")
96
+ }
97
+
98
+ return breadcrumbs if breadcrumbs.first == root_crumb
99
+
100
+ [root_crumb] + breadcrumbs
101
+ end
102
+
103
+ def neighbor_map
104
+ @neighbor_map ||= begin
105
+ items = []
106
+ if @root_page
107
+ items << {
108
+ "title" => @root_page.data["nav_title"] || @root_page.data["title"] || Utils.titleize(Utils.last_segment(@root_dir)),
109
+ "url" => Utils.normalize_url(@root_page.url)
110
+ }
111
+ end
112
+
113
+ items.concat(flatten_nav_items(serialized_tree))
114
+
115
+ items.each_with_index.with_object({}) do |(item, index), neighbors|
116
+ neighbors[item["url"]] = {
117
+ "previous" => index.positive? ? deep_copy(items[index - 1]) : nil,
118
+ "next" => index < items.length - 1 ? deep_copy(items[index + 1]) : nil
119
+ }
120
+ end
121
+ end
122
+ end
123
+
124
+ def flatten_nav_items(items)
125
+ Array(items).each_with_object([]) do |item, flattened|
126
+ flattened << { "title" => item["title"], "url" => item["url"] } if item["url"]
127
+ flattened.concat(flatten_nav_items(item["children"])) if item["children"].is_a?(Array)
128
+ end
129
+ end
130
+
131
+ def find_trail(items, target_url, trail = [])
132
+ Array(items).each do |item|
133
+ current_trail = trail + [item]
134
+ return current_trail if item["url"] && Utils.normalize_url(item["url"]) == target_url
135
+
136
+ next unless item["children"].is_a?(Array)
137
+
138
+ found = find_trail(item["children"], target_url, current_trail)
139
+ return found if found
140
+ end
141
+
142
+ nil
143
+ end
144
+
145
+ def deep_copy(value)
146
+ Marshal.load(Marshal.dump(value))
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module AwesomeNav
5
+ class Node
6
+ attr_accessor :title, :url, :path, :filename, :target
7
+ attr_reader :type, :dir, :children
8
+
9
+ def self.section(dir:, title: nil, url: nil, children: [], path: nil, filename: nil)
10
+ new(type: :section, dir: dir, title: title, url: url, children: children, path: path, filename: filename)
11
+ end
12
+
13
+ def self.page(dir:, title:, url:, path: nil, filename: nil)
14
+ new(type: :page, dir: dir, title: title, url: url, children: [], path: path, filename: filename)
15
+ end
16
+
17
+ def self.reference(dir:, target:, title: nil)
18
+ node = new(type: :reference, dir: dir, title: title, url: nil, children: [])
19
+ node.target = target
20
+ node
21
+ end
22
+
23
+ def initialize(type:, dir:, title:, url:, children:, path: nil, filename: nil)
24
+ @type = type
25
+ @dir = dir
26
+ @title = title
27
+ @url = url
28
+ @children = Array(children)
29
+ @path = path
30
+ @filename = filename
31
+ end
32
+
33
+ def section?
34
+ type == :section
35
+ end
36
+
37
+ def page?
38
+ type == :page
39
+ end
40
+
41
+ def reference?
42
+ type == :reference
43
+ end
44
+
45
+ def with_children(children)
46
+ node = self.class.new(
47
+ type: type,
48
+ dir: dir,
49
+ title: title,
50
+ url: url,
51
+ children: children,
52
+ path: path,
53
+ filename: filename
54
+ )
55
+ node.target = target
56
+ node
57
+ end
58
+
59
+ def deep_dup
60
+ with_children(children.map(&:deep_dup))
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module AwesomeNav
5
+ class PageSet
6
+ include Enumerable
7
+
8
+ def initialize(site, config)
9
+ @pages = site.pages.select do |page|
10
+ next false if File.basename(page.path) == config.nav_filename
11
+
12
+ dir = Utils.source_dir_for(page)
13
+ dir == config.root_dir || dir.start_with?("#{config.root_dir}/")
14
+ end
15
+ @root_dir = config.root_dir
16
+ end
17
+
18
+ def each(&block)
19
+ @pages.each(&block)
20
+ end
21
+
22
+ def empty?
23
+ @pages.empty?
24
+ end
25
+
26
+ def root_page
27
+ @pages.find { |page| Utils.source_dir_for(page) == @root_dir && Utils.index_page?(page) }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module AwesomeNav
5
+ class Serializer
6
+ def self.serialize_tree(nodes, include_internal: false)
7
+ Array(nodes).map { |node| serialize_node(node, include_internal: include_internal) }
8
+ end
9
+
10
+ def self.serialize_map(map, include_internal: false)
11
+ map.each_with_object({}) do |(key, value), serialized|
12
+ items = value.is_a?(NavFile) ? value.items : value
13
+ serialized[key] = serialize_tree(items, include_internal: include_internal)
14
+ end
15
+ end
16
+
17
+ def self.serialize_node(node, include_internal: false)
18
+ item = { "title" => node.title }
19
+ item["url"] = node.url if node.url
20
+ item["children"] = serialize_tree(node.children, include_internal: include_internal) if node.section? && node.children.any?
21
+ item["__dir"] = node.dir if include_internal && node.dir
22
+ item
23
+ end
24
+ end
25
+ end
26
+ end