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.
- checksums.yaml +7 -0
- data/.copier-answers.ci.yml +12 -0
- data/.devcontainer/devcontainer.json +35 -0
- data/.devcontainer/post-create.sh +19 -0
- data/.rubocop.yml +43 -0
- data/.ruby-version +1 -0
- data/.vscode/tasks.json +70 -0
- data/AGENTS.md +287 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +176 -0
- data/Rakefile +11 -0
- data/jekyll-awesome-nav.gemspec +35 -0
- data/lib/jekyll/awesome_nav/config.rb +31 -0
- data/lib/jekyll/awesome_nav/generator.rb +50 -0
- data/lib/jekyll/awesome_nav/nav_file.rb +14 -0
- data/lib/jekyll/awesome_nav/nav_file_loader.rb +140 -0
- data/lib/jekyll/awesome_nav/nav_file_options.rb +69 -0
- data/lib/jekyll/awesome_nav/nav_resolver.rb +370 -0
- data/lib/jekyll/awesome_nav/navigation_result.rb +150 -0
- data/lib/jekyll/awesome_nav/node.rb +64 -0
- data/lib/jekyll/awesome_nav/page_set.rb +31 -0
- data/lib/jekyll/awesome_nav/serializer.rb +26 -0
- data/lib/jekyll/awesome_nav/sort_options.rb +91 -0
- data/lib/jekyll/awesome_nav/tree_builder.rb +75 -0
- data/lib/jekyll/awesome_nav/utils.rb +94 -0
- data/lib/jekyll/awesome_nav/version.rb +19 -0
- data/lib/jekyll/awesome_nav.rb +26 -0
- data/lib/jekyll-awesome-nav.rb +3 -0
- data/site/_config.yml +33 -0
- data/site/_includes/awesome-nav-demo-tree.html +15 -0
- data/site/_includes/awesome-nav-tree.html +19 -0
- data/site/_layouts/awesome_nav_demo.html +128 -0
- data/site/docs/getting-started.md +68 -0
- data/site/docs/guides/.nav.yml +7 -0
- data/site/docs/guides/config.md +37 -0
- data/site/docs/guides/data.md +40 -0
- data/site/docs/guides/index.md +15 -0
- data/site/docs/guides/install.md +53 -0
- data/site/docs/guides/layouts.md +116 -0
- data/site/docs/guides/overrides.md +42 -0
- data/site/docs/index.md +35 -0
- data/site/index.md +66 -0
- 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
|