jekyll-awesome-nav 0.1.0 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 01fa08c8a44630c6314fb330db8db6de0977bc66adb8e2c48dd5f127ede6ff8d
4
- data.tar.gz: 29964bc8684fc64593bb76b9385ec19706b578aef5ee780155be2f518c50873b
3
+ metadata.gz: 2cfc859de2a4762ba5046338aa7bc12049da70bfb2d9ac27d727eace95322f11
4
+ data.tar.gz: 0f14a593b81ec5882b65db61b82e93d8bd8f9a3656a8d730cd0f4175a2da65e3
5
5
  SHA512:
6
- metadata.gz: d191dc308b3c7b7b2431417f12c4cdfe59b7acb4c2461364beec68a481909b81dad07724405ee2a2a1644203b6bf13705e86f60af8e98b0895363b3a202a5cb1
7
- data.tar.gz: ef4ed52b48b7595d0994ff1f19ba64d70d4e81998ea1ded6b14b395f165a11a0ea5efe43bff3de73ebd8c7e35a48a5e06e7ec1054ff2407becfc838e7ceae6ae
6
+ metadata.gz: 39eccdb01ce4ecd9c828f2c66b6997b6ef769ec199ffb6a94a206fa6f449c615f84edf7928290029adde02b97bdc24a9050ae70654047915181c26bd81c72a49
7
+ data.tar.gz: '008767e37db8c06b2b2c77ffa4a1cc9f437653a33684a90994d7e7da854b836b5c39687ed3065d0adcd1e6692f757a1e9346cec259e85725d5853495112f33bb'
@@ -1,5 +1,5 @@
1
1
  # Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
2
- _commit: aac269f
2
+ _commit: afa589e
3
3
  _src_path: gh:athackst/ci
4
4
  automerge_mode: poll
5
5
  bump_script_path: ''
@@ -15,35 +15,52 @@ module Jekyll
15
15
  pages = PageSet.new(site, config)
16
16
  return if pages.empty?
17
17
 
18
+ result = build_navigation_result(site, config, pages)
19
+ assign_page_navigation(pages, result)
20
+ assign_site_navigation(site, result)
21
+ end
22
+
23
+ private
24
+
25
+ def build_navigation_result(site, config, pages)
18
26
  tree = TreeBuilder.new(pages: pages, root_dir: config.root_dir).build
19
27
  nav_map = NavFileLoader.new(site: site, config: config).load
20
- resolved_tree = NavResolver.new(root_dir: config.root_dir, nav_map: nav_map).apply(tree, config.root_dir)
21
- result = NavigationResult.new(
28
+ resolved_tree = NavResolver.new(
29
+ root_dir: config.root_dir,
30
+ nav_map: nav_map,
31
+ root_page: pages.root_page
32
+ ).apply(tree, config.root_dir)
33
+
34
+ NavigationResult.new(
22
35
  tree: resolved_tree,
23
36
  root_dir: config.root_dir,
24
37
  root_page: pages.root_page,
25
38
  nav_map: nav_map
26
39
  )
40
+ end
27
41
 
42
+ def assign_page_navigation(pages, result)
28
43
  pages.each do |page|
29
44
  page_dir = Utils.source_dir_for(page)
30
45
  nav_dir = result.nav_dir_for(page_dir)
31
46
  page_url = Utils.normalize_url(page.url)
47
+ page_entry = result.nav_entry_for(page_url)
48
+
32
49
  page.data["awesome_nav"] = deep_copy(result.serialized_tree)
33
50
  page.data["awesome_nav_local"] = deep_copy(result.local_nav_for(nav_dir))
34
51
  page.data["awesome_nav_dir"] = nav_dir
35
52
  page.data["breadcrumbs"] = result.breadcrumbs_for(page)
36
- page.data["awesome_nav_previous"] = deep_copy(result.nav_entry_for(page_url)&.fetch("previous", nil))
37
- page.data["awesome_nav_next"] = deep_copy(result.nav_entry_for(page_url)&.fetch("next", nil))
53
+ page.data["awesome_nav_previous"] = deep_copy(page_entry&.fetch("previous", nil))
54
+ page.data["awesome_nav_next"] = deep_copy(page_entry&.fetch("next", nil))
38
55
  end
56
+ end
39
57
 
58
+ def assign_site_navigation(site, result)
40
59
  site.config["awesome_nav_tree"] = deep_copy(result.serialized_tree)
41
60
  site.config["awesome_nav_local_map"] = deep_copy(result.serialized_local_nav_map)
42
61
  site.config["awesome_nav_files"] = deep_copy(result.serialized_nav_files)
43
62
  end
44
63
 
45
- private
46
-
47
64
  def deep_copy(value)
48
65
  Marshal.load(Marshal.dump(value))
49
66
  end
@@ -2,12 +2,16 @@
2
2
 
3
3
  module Jekyll
4
4
  module AwesomeNav
5
+ # This class centralizes override resolution, matching, and unmatched append
6
+ # behavior, so a narrow class-length suppression keeps the logic together.
7
+ # rubocop:disable Metrics/ClassLength
5
8
  class NavResolver
6
9
  ResolutionContext = Struct.new(:append_unmatched, :sort_options, :ignore_patterns, keyword_init: true)
7
10
 
8
- def initialize(root_dir:, nav_map:)
11
+ def initialize(root_dir:, nav_map:, root_page: nil)
9
12
  @root_dir = root_dir
10
13
  @nav_map = nav_map
14
+ @root_page = root_page
11
15
  @generated_by_dir = {}
12
16
  @generated_by_path = {}
13
17
  end
@@ -71,7 +75,10 @@ module Jekyll
71
75
  end
72
76
 
73
77
  def same_dir_wrapper?(items, current_dir)
74
- items.length == 1 && items.first.section? && Utils.normalize_dir(items.first.dir) == current_dir
78
+ items.length == 1 &&
79
+ items.first.section? &&
80
+ !items.first.dir.nil? &&
81
+ Utils.normalize_dir(items.first.dir) == current_dir
75
82
  end
76
83
 
77
84
  def raw_same_dir_wrapper?(current_dir)
@@ -149,7 +156,13 @@ module Jekyll
149
156
  return [] unless generated
150
157
  return [] if hidden?(generated)
151
158
 
152
- return [section_page_node(generated, item.title)] if generated.section? && Utils.normalize_dir(generated.dir) == current_dir
159
+ if generated.section? &&
160
+ (Utils.normalize_dir(generated.dir) == current_dir || references_section_index?(item, current_dir, generated))
161
+ node = section_page_node(generated, item.title)
162
+ mark_matched(node, matched)
163
+ mark_section_consumed(generated, matched)
164
+ return [node]
165
+ end
153
166
 
154
167
  node = generated.deep_dup
155
168
  node.title = item.title if item.title
@@ -229,7 +242,10 @@ module Jekyll
229
242
  next if ignored?(item, current_dir, context.ignore_patterns)
230
243
 
231
244
  node = item.deep_dup
232
- with_resolved_children(node, Utils.normalize_dir(node.dir), context)
245
+ pruned = prune_matched_descendants(node, matched, context, current_dir)
246
+ next if pruned.nil?
247
+
248
+ with_resolved_children(pruned, Utils.normalize_dir(pruned.dir), context)
233
249
  end
234
250
  end
235
251
 
@@ -240,6 +256,9 @@ module Jekyll
240
256
 
241
257
  section = @generated_by_dir[candidate]
242
258
  return section if section
259
+
260
+ root = root_page_node_for(candidate)
261
+ return root if root
243
262
  end
244
263
 
245
264
  nil
@@ -337,10 +356,38 @@ module Jekyll
337
356
  matched[node_key(item)]
338
357
  end
339
358
 
359
+ def prune_matched_descendants(item, matched, context, current_dir)
360
+ return nil if matched?(item, matched)
361
+ return item unless item.section?
362
+
363
+ child_dir = Utils.normalize_dir(item.dir)
364
+ pruned_children = item.children.filter_map do |child|
365
+ next if hidden?(child)
366
+ next if ignored?(child, child_dir.empty? ? current_dir : child_dir, context.ignore_patterns)
367
+
368
+ prune_matched_descendants(child.deep_dup, matched, context, child_dir.empty? ? current_dir : child_dir)
369
+ end
370
+
371
+ return nil if pruned_children.empty? && item.url.nil?
372
+
373
+ Node.section(
374
+ dir: item.dir,
375
+ title: item.title,
376
+ url: item.url,
377
+ children: pruned_children,
378
+ path: item.path,
379
+ filename: item.filename
380
+ )
381
+ end
382
+
340
383
  def node_key(item)
341
384
  item.section? ? "dir:#{Utils.normalize_dir(item.dir)}" : "path:#{Utils.normalize_dir(item.path || item.url)}"
342
385
  end
343
386
 
387
+ def mark_section_consumed(item, matched)
388
+ matched["dir:#{Utils.normalize_dir(item.dir)}"] = true
389
+ end
390
+
344
391
  def child_dir_for_item(parent_dir, item)
345
392
  return Utils.normalize_dir(item.dir) if item.dir
346
393
  return nil unless item.url
@@ -365,6 +412,31 @@ module Jekyll
365
412
  segments = path.split("/")
366
413
  File.extname(segments.last).empty? ? segments.join("/") : segments[0...-1].join("/")
367
414
  end
415
+
416
+ def root_page_node_for(candidate)
417
+ return nil unless @root_page
418
+
419
+ normalized = Utils.normalize_dir(candidate)
420
+ root_index_path = Utils.normalize_dir(File.join(@root_dir, "index.md"))
421
+ root_readme_path = Utils.normalize_dir(File.join(@root_dir, "README.md"))
422
+ root_readme_lower_path = Utils.normalize_dir(File.join(@root_dir, "readme.md"))
423
+ return nil unless [@root_dir, root_index_path, "index.md", root_readme_path, root_readme_lower_path].include?(normalized)
424
+
425
+ Node.page(
426
+ dir: @root_dir,
427
+ title: Utils.page_title(@root_page, File.basename(@root_page.path, File.extname(@root_page.path))),
428
+ url: Utils.normalize_url(@root_page.url),
429
+ path: Utils.source_path_for(@root_page),
430
+ filename: File.basename(Utils.source_path_for(@root_page))
431
+ )
432
+ end
433
+
434
+ def references_section_index?(item, current_dir, generated)
435
+ return false unless generated.path
436
+
437
+ candidates_for(item.target, current_dir).include?(Utils.normalize_dir(generated.path))
438
+ end
368
439
  end
440
+ # rubocop:enable Metrics/ClassLength
369
441
  end
370
442
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Jekyll
4
4
  module AwesomeNav
5
- VERSION = "0.1.0"
5
+ VERSION = "0.1.2"
6
6
 
7
7
  def self.warn_if_unstamped_version(output = $stderr)
8
8
  return unless VERSION == "0.0.0"
@@ -3,5 +3,6 @@ nav:
3
3
  - Install Guide: install.md
4
4
  - Layout Integration: layouts.md
5
5
  - Configuration: config.md
6
+ - .nav.yml Reference: nav-file.md
6
7
  - Navigation Overrides: overrides.md
7
8
  - Generated Data: data.md
@@ -11,5 +11,6 @@ The `docs/guides/.nav.yml` file replaces this subtree, which means the guides ca
11
11
  - [Install Guide]({{ "/docs/guides/install/" | relative_url }})
12
12
  - [Layout Integration]({{ "/docs/guides/layouts/" | relative_url }})
13
13
  - [Configuration]({{ "/docs/guides/config/" | relative_url }})
14
+ - [.nav.yml Reference]({{ "/docs/guides/nav-file/" | relative_url }})
14
15
  - [Navigation Overrides]({{ "/docs/guides/overrides/" | relative_url }})
15
16
  - [Generated Data]({{ "/docs/guides/data/" | relative_url }})
@@ -0,0 +1,260 @@
1
+ ---
2
+ title: .nav.yml Reference
3
+ ---
4
+
5
+ `.nav.yml` lets a directory replace its generated navigation subtree with a hand-authored one.
6
+
7
+ Put the file in the directory you want to control:
8
+
9
+ ```text
10
+ docs/
11
+ guides/
12
+ .nav.yml
13
+ index.md
14
+ install.md
15
+ config.md
16
+ ```
17
+
18
+ The override applies only to that directory subtree. Parent sections and sibling sections keep using their own generated or overridden navigation.
19
+
20
+ ## Minimal file
21
+
22
+ The smallest useful `.nav.yml` defines a `nav:` array:
23
+
24
+ ```yaml
25
+ nav:
26
+ - Guides Home: index.md
27
+ - Install: install.md
28
+ - Configuration: config.md
29
+ ```
30
+
31
+ Each item is either:
32
+
33
+ - a string path like `install.md`
34
+ - a titled path mapping like `Install: install.md`
35
+ - a titled section with nested children
36
+ - a glob entry such as `"*.md"`
37
+
38
+ ## Top-level options
39
+
40
+ These keys are supported at the top level of `.nav.yml`:
41
+
42
+ | Option | Type | Description |
43
+ | --- | --- | --- |
44
+ | `nav` | array | Manual nav entries for this directory. |
45
+ | `append_unmatched` | boolean | Appends generated items that were not matched by `nav`. |
46
+ | `ignore` | string or array | Excludes generated items from globs and `append_unmatched`. |
47
+ | `sort` | mapping | Sorts generated batches from globs and `append_unmatched`. |
48
+ | `hide` | boolean | Hides this directory from generated nav, manual directory references, and child nav processing. |
49
+
50
+ Options-only files are valid, so this works when you only want to hide a directory:
51
+
52
+ ```yaml
53
+ hide: true
54
+ ```
55
+
56
+ ## `nav` items
57
+
58
+ ### String paths
59
+
60
+ Use a string when you want the generated page or section title:
61
+
62
+ ```yaml
63
+ nav:
64
+ - index.md
65
+ - install.md
66
+ - guides
67
+ ```
68
+
69
+ - File paths insert a page.
70
+ - Directory paths insert that directory's generated section.
71
+ - External URLs are allowed and stay as-is.
72
+
73
+ ### Titled paths
74
+
75
+ Use a mapping to rename an item in navigation:
76
+
77
+ ```yaml
78
+ nav:
79
+ - Guides Hub: index.md
80
+ - Install Guide: install.md
81
+ - Project Website: https://example.com
82
+ ```
83
+
84
+ ### Manual sections
85
+
86
+ Use a titled list to create a group:
87
+
88
+ ```yaml
89
+ nav:
90
+ - Main:
91
+ - getting-started.md
92
+ - Guides: guides
93
+ - More:
94
+ - API Reference: ../reference.md
95
+ - Project Website: https://example.com
96
+ ```
97
+
98
+ Manual sections are grouping nodes. They do not get their own URL unless you point them at a page instead of a child list.
99
+
100
+ ## Path resolution
101
+
102
+ Paths are resolved in this order:
103
+
104
+ 1. Relative to the directory that contains the `.nav.yml`
105
+ 2. Relative to `awesome_nav.root`
106
+
107
+ That means a file in `docs/guides/.nav.yml` can usually use short local paths like `install.md`, while a shared page can still be referenced from the root, such as `reference.md`.
108
+
109
+ ## Globs
110
+
111
+ Globs pull in generated items without listing each one manually:
112
+
113
+ ```yaml
114
+ nav:
115
+ - getting-started.md
116
+ - "*.md"
117
+ - "*/"
118
+ - "archive/**/*.md"
119
+ ```
120
+
121
+ Useful patterns:
122
+
123
+ - `"*.md"` matches pages in the current directory
124
+ - `"*/"` matches child sections only
125
+ - `"**/*.md"` matches pages recursively
126
+
127
+ Recursive globs insert matches as a flat list at that position. They do not preserve nested folder structure.
128
+
129
+ You can also write a glob entry explicitly:
130
+
131
+ ```yaml
132
+ nav:
133
+ - glob: "*.md"
134
+ ```
135
+
136
+ ## `append_unmatched`
137
+
138
+ By default, a manual `nav:` array is a complete replacement for the local generated subtree. Items you leave out stay out.
139
+
140
+ Set `append_unmatched: true` when you want to hand-place a few items and then append the rest of the generated local items afterward:
141
+
142
+ ```yaml
143
+ append_unmatched: true
144
+ nav:
145
+ - getting-started.md
146
+ - guides
147
+ ```
148
+
149
+ Child `.nav.yml` files inherit the nearest parent `append_unmatched` setting unless they set their own value.
150
+
151
+ ## `ignore`
152
+
153
+ `ignore` filters generated items from:
154
+
155
+ - glob matches
156
+ - appended unmatched items
157
+
158
+ It does not block a page you list explicitly in `nav:`.
159
+
160
+ ```yaml
161
+ ignore:
162
+ - "*.hidden.md"
163
+ - "drafts/"
164
+ nav:
165
+ - visible.md
166
+ - "*.md"
167
+ ```
168
+
169
+ Here, `visible.md` still appears even if it also matches an ignore pattern, because it was added manually.
170
+
171
+ ## `sort`
172
+
173
+ `sort` controls the order of generated batches only:
174
+
175
+ - glob expansions
176
+ - `append_unmatched` output
177
+
178
+ Manual entries stay in the exact order you wrote them.
179
+
180
+ ```yaml
181
+ sort:
182
+ direction: desc
183
+ type: natural
184
+ by: filename
185
+ sections: last
186
+ ignore_case: true
187
+ nav:
188
+ - intro.md
189
+ - "*.md"
190
+ ```
191
+
192
+ Supported sort fields:
193
+
194
+ | Key | Values | Default |
195
+ | --- | --- | --- |
196
+ | `direction` | `asc`, `desc` | `asc` |
197
+ | `type` | `alphabetical`, `natural` | `alphabetical` |
198
+ | `by` | `path`, `filename`, `title` | `path` |
199
+ | `sections` | `first`, `last` | `first` |
200
+ | `ignore_case` | `true`, `false` | `true` |
201
+
202
+ Per-glob sort settings are not supported. Sorting is configured once for the whole file.
203
+
204
+ ## `hide`
205
+
206
+ Use `hide: true` in a directory's own `.nav.yml` when that subtree should disappear from navigation entirely:
207
+
208
+ ```yaml
209
+ hide: true
210
+ ```
211
+
212
+ When a directory is hidden:
213
+
214
+ - it is skipped in generated navigation
215
+ - manual references to that directory do not insert it
216
+ - child `.nav.yml` files under that directory are not used
217
+
218
+ ## Common patterns
219
+
220
+ ### Curated local section
221
+
222
+ ```yaml
223
+ nav:
224
+ - Guides Home: index.md
225
+ - Install: install.md
226
+ - Configuration: config.md
227
+ ```
228
+
229
+ ### Manual intro, generated remainder
230
+
231
+ ```yaml
232
+ append_unmatched: true
233
+ nav:
234
+ - index.md
235
+ - getting-started.md
236
+ ```
237
+
238
+ ### Rename a section and include its generated children
239
+
240
+ ```yaml
241
+ nav:
242
+ - User Guides: guides
243
+ ```
244
+
245
+ ### Group links under headings
246
+
247
+ ```yaml
248
+ nav:
249
+ - Learn:
250
+ - getting-started.md
251
+ - guides
252
+ - External:
253
+ - API Docs: https://example.com/api
254
+ ```
255
+
256
+ ## Notes
257
+
258
+ - `.nav.yml` replaces the local subtree. It does not merge with generated items unless you opt into `append_unmatched`.
259
+ - Duplicate items are automatically deduplicated when a manual entry and a glob point at the same generated source.
260
+ - Paths must resolve to pages or directories already known to Jekyll under the configured root.
@@ -22,6 +22,8 @@ Use `.nav.yml` when a section needs:
22
22
  - shorter or clearer labels
23
23
  - links that do not map one-to-one with filenames
24
24
  - a curated subset of pages
25
+ - generated batches mixed with manual items
26
+ - hidden sections or filtered globs
25
27
 
26
28
  ## What gets replaced
27
29
 
@@ -40,3 +42,15 @@ nav:
40
42
  ```
41
43
 
42
44
  Use Markdown source paths. The plugin resolves them through Jekyll pages and then uses the final page URL.
45
+
46
+ ## More options
47
+
48
+ `.nav.yml` also supports:
49
+
50
+ - glob entries like `"*.md"` and `"*/"`
51
+ - `append_unmatched: true` to append generated items you did not list manually
52
+ - `ignore:` to filter generated matches
53
+ - `sort:` to order generated batches
54
+ - `hide: true` to remove a subtree entirely
55
+
56
+ The full syntax and option reference lives in [.nav.yml Reference]({{ "/docs/guides/nav-file/" | relative_url }}).
data/site/docs/index.md CHANGED
@@ -11,6 +11,7 @@ The plugin does not require collections. It reads pages under the configured `aw
11
11
  - [Getting Started]({{ "/docs/getting-started/" | relative_url }}) shows the minimum install and config.
12
12
  - [Layout Integration]({{ "/docs/guides/layouts/" | relative_url }}) shows how to render sidebars, breadcrumbs, and previous/next links.
13
13
  - [Configuration]({{ "/docs/guides/config/" | relative_url }}) explains the available plugin settings.
14
+ - [.nav.yml Reference]({{ "/docs/guides/nav-file/" | relative_url }}) documents override syntax, options, and common patterns.
14
15
  - [Navigation Overrides]({{ "/docs/guides/overrides/" | relative_url }}) shows how local `.nav.yml` files customize sections.
15
16
  - [Generated Data]({{ "/docs/guides/data/" | relative_url }}) lists the page variables available to themes and layouts.
16
17
 
@@ -26,6 +27,7 @@ docs/
26
27
  ├── index.md
27
28
  ├── install.md
28
29
  ├── layouts.md
30
+ ├── nav-file.md
29
31
  ├── overrides.md
30
32
  ├── data.md
31
33
  ├── config.md
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-awesome-nav
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Allison Thackston
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-07 00:00:00.000000000 Z
11
+ date: 2026-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -76,6 +76,7 @@ files:
76
76
  - site/docs/guides/index.md
77
77
  - site/docs/guides/install.md
78
78
  - site/docs/guides/layouts.md
79
+ - site/docs/guides/nav-file.md
79
80
  - site/docs/guides/overrides.md
80
81
  - site/docs/index.md
81
82
  - site/index.md