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,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module AwesomeNav
5
+ class SortOptions
6
+ DEFAULTS = {
7
+ "direction" => "asc",
8
+ "type" => "alphabetical",
9
+ "by" => "path",
10
+ "sections" => "first",
11
+ "ignore_case" => true
12
+ }.freeze
13
+
14
+ def self.from(value)
15
+ new(value)
16
+ end
17
+
18
+ def initialize(value)
19
+ raise Error, "sort must be a mapping" unless value.nil? || value.is_a?(Hash)
20
+
21
+ @data = DEFAULTS.merge(value || {})
22
+ end
23
+
24
+ def sort(items)
25
+ sorted = Array(items).sort_by { |item| sort_key(item) }
26
+ direction == "desc" ? sorted.reverse : sorted
27
+ end
28
+
29
+ private
30
+
31
+ def sort_key(item)
32
+ [
33
+ section_rank(item),
34
+ value_key(value_for(item))
35
+ ]
36
+ end
37
+
38
+ def section_rank(item)
39
+ case sections
40
+ when "first"
41
+ item.section? ? 0 : 1
42
+ when "last"
43
+ item.section? ? 1 : 0
44
+ else
45
+ 0
46
+ end
47
+ end
48
+
49
+ def value_key(value)
50
+ value = value.to_s
51
+ value = value.downcase if ignore_case?
52
+ type == "natural" ? natural_key(value) : value
53
+ end
54
+
55
+ def natural_key(value)
56
+ value.split(/(\d+)/).map { |part| part.match?(/\A\d+\z/) ? part.to_i : part }
57
+ end
58
+
59
+ def value_for(item)
60
+ case by
61
+ when "filename"
62
+ item.filename || File.basename(item.path.to_s)
63
+ when "title"
64
+ item.title
65
+ else
66
+ item.path || item.dir || item.title
67
+ end
68
+ end
69
+
70
+ def direction
71
+ @data["direction"].to_s
72
+ end
73
+
74
+ def type
75
+ @data["type"].to_s
76
+ end
77
+
78
+ def by
79
+ @data["by"].to_s
80
+ end
81
+
82
+ def sections
83
+ @data["sections"].to_s
84
+ end
85
+
86
+ def ignore_case?
87
+ @data["ignore_case"] != false
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module AwesomeNav
5
+ class TreeBuilder
6
+ def initialize(pages:, root_dir:)
7
+ @pages = pages
8
+ @root_dir = root_dir
9
+ end
10
+
11
+ def build
12
+ root = Node.section(dir: @root_dir)
13
+ @pages.each { |page| add_page(root, page) }
14
+ root.children.sort_by! { |child| sort_key(child) }
15
+ sort_sections(root.children)
16
+ end
17
+
18
+ private
19
+
20
+ def add_page(root, page)
21
+ page_path = Utils.source_path_for(page)
22
+ dir = Utils.source_dir_for(page)
23
+ relative_dir = Utils.relative_to_root(dir, @root_dir)
24
+ current = root
25
+ current_dir = @root_dir
26
+
27
+ Utils.path_segments(relative_dir).each do |segment|
28
+ current_dir = [current_dir, segment].reject(&:empty?).join("/")
29
+ child = current.children.find { |node| node.section? && Utils.last_segment(node.dir) == segment }
30
+ unless child
31
+ child = Node.section(dir: current_dir, title: Utils.titleize(segment))
32
+ current.children << child
33
+ end
34
+ current = child
35
+ end
36
+
37
+ basename = File.basename(page.path, File.extname(page.path))
38
+ title = Utils.page_title(page, basename)
39
+ url = Utils.normalize_url(page.url)
40
+
41
+ if basename == "index"
42
+ current.title = title
43
+ current.url = url
44
+ current.path = page_path
45
+ current.filename = File.basename(page_path)
46
+ else
47
+ current.children << Node.page(
48
+ dir: [dir, basename].reject(&:empty?).join("/"),
49
+ title: title,
50
+ url: url,
51
+ path: page_path,
52
+ filename: File.basename(page_path)
53
+ )
54
+ end
55
+ end
56
+
57
+ def sort_sections(items)
58
+ items.each do |child|
59
+ next unless child.section?
60
+
61
+ child.children.sort_by! { |nested| sort_key(nested) }
62
+ sort_sections(child.children)
63
+ end
64
+ end
65
+
66
+ def sort_key(child)
67
+ [
68
+ child.section? ? 0 : 1,
69
+ child.path.to_s.downcase,
70
+ child.title.to_s.downcase
71
+ ]
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module AwesomeNav
5
+ module Utils
6
+ module_function
7
+
8
+ def normalize_dir(dir)
9
+ dir.to_s.strip.sub(%r{\A/+}, "").sub(%r{/+\z}, "")
10
+ end
11
+
12
+ def path_segments(dir)
13
+ normalize_dir(dir).split("/").reject(&:empty?)
14
+ end
15
+
16
+ def parent_dir(dir)
17
+ segments = path_segments(dir)
18
+ return "" if segments.length <= 1
19
+
20
+ segments[0...-1].join("/")
21
+ end
22
+
23
+ def last_segment(path)
24
+ path_segments(path).last.to_s
25
+ end
26
+
27
+ def relative_to_root(dir, root_dir)
28
+ return "" if dir == root_dir
29
+ return dir.delete_prefix("#{root_dir}/") if dir.start_with?("#{root_dir}/")
30
+
31
+ dir
32
+ end
33
+
34
+ def relative_dir(source, absolute_dir)
35
+ absolute_dir
36
+ .sub(%r{\A#{Regexp.escape(source)}/?}, "")
37
+ .sub(%r{\A/+}, "")
38
+ .sub(%r{/+\z}, "")
39
+ end
40
+
41
+ def external_url?(value)
42
+ value.to_s.match?(%r{\A([a-z][a-z0-9+\-.]*:)?//}i)
43
+ end
44
+
45
+ def normalize_url(url)
46
+ value = url.to_s.strip
47
+ return "/" if value.empty?
48
+ return value if external_url?(value)
49
+
50
+ value = "/#{value}" unless value.start_with?("/")
51
+ value = value.sub(/index\.html\z/, "")
52
+ value = value.gsub(%r{/+}, "/")
53
+ value = "#{value}/" if File.extname(value).empty? && !value.end_with?("/")
54
+ value
55
+ end
56
+
57
+ def titleize(value)
58
+ value.to_s
59
+ .tr("_-", " ")
60
+ .split
61
+ .map { |part| part[0] ? "#{part[0].upcase}#{part[1..]}" : part }
62
+ .join(" ")
63
+ end
64
+
65
+ def index_page?(page)
66
+ File.basename(page.path, File.extname(page.path)) == "index"
67
+ end
68
+
69
+ def source_dir_for(page)
70
+ source_dir_for_path(source_path_for(page))
71
+ end
72
+
73
+ def source_dir_for_path(path)
74
+ dir = File.dirname(path.to_s)
75
+ normalize_dir(dir == "." ? "" : dir)
76
+ end
77
+
78
+ def source_path_for(page)
79
+ path =
80
+ if page.respond_to?(:relative_path) && !page.relative_path.nil?
81
+ page.relative_path
82
+ else
83
+ page.path
84
+ end
85
+
86
+ normalize_dir(path)
87
+ end
88
+
89
+ def page_title(page, basename)
90
+ page.data["nav_title"] || page.data["title"] || titleize(basename)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module AwesomeNav
5
+ VERSION = "0.0.1"
6
+
7
+ def self.warn_if_unstamped_version(output = $stderr)
8
+ return unless VERSION == "0.0.0"
9
+ return if @unstamped_version_warning_emitted
10
+
11
+ output.puts(
12
+ "Warning: jekyll-awesome-nav is using unstamped source version 0.0.0. " \
13
+ "Release builds should stamp lib/jekyll/awesome_nav/version.rb before packaging."
14
+ )
15
+ @unstamped_version_warning_emitted = true
16
+ end
17
+ private_class_method :warn_if_unstamped_version
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jekyll"
4
+
5
+ require_relative "awesome_nav/utils"
6
+ require_relative "awesome_nav/config"
7
+ require_relative "awesome_nav/node"
8
+ require_relative "awesome_nav/sort_options"
9
+ require_relative "awesome_nav/nav_file_options"
10
+ require_relative "awesome_nav/nav_file"
11
+ require_relative "awesome_nav/page_set"
12
+ require_relative "awesome_nav/tree_builder"
13
+ require_relative "awesome_nav/nav_file_loader"
14
+ require_relative "awesome_nav/nav_resolver"
15
+ require_relative "awesome_nav/serializer"
16
+ require_relative "awesome_nav/navigation_result"
17
+ require_relative "awesome_nav/version"
18
+ require_relative "awesome_nav/generator"
19
+
20
+ module Jekyll
21
+ module AwesomeNav
22
+ class Error < StandardError; end
23
+ end
24
+ end
25
+
26
+ Jekyll::AwesomeNav.send(:warn_if_unstamped_version)
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "jekyll/awesome_nav"
data/site/_config.yml ADDED
@@ -0,0 +1,33 @@
1
+ theme: jekyll-theme-profile
2
+ title: Jekyll Awesome Nav
3
+ description: Folder-based navigation for Jekyll with automatic generation and local subtree overrides.
4
+ repository: PrimerPages/jekyll-awesome-nav
5
+ permalink: pretty
6
+ style: appbar
7
+ repo_info: true
8
+ user_metadata: false
9
+ profile_link: false
10
+ nav:
11
+ - name: Overview
12
+ url: /
13
+ - name: Docs Home
14
+ url: /docs/
15
+ - name: Getting Started
16
+ url: /docs/getting-started/
17
+ plugins:
18
+ - jekyll-seo-tag
19
+ - jekyll-awesome-nav
20
+ awesome_nav:
21
+ enabled: true
22
+ root: docs
23
+ nav_filename: .nav.yml
24
+ markdown: kramdown
25
+ defaults:
26
+ - scope:
27
+ path: ""
28
+ values:
29
+ layout: page
30
+ - scope:
31
+ path: "docs"
32
+ values:
33
+ layout: docs
@@ -0,0 +1,15 @@
1
+ {% for item in include.items %}
2
+ <li>
3
+ <a
4
+ href="{{ item.url | relative_url }}"
5
+ {% if item.url == page.url %}aria-current="page"{% endif %}
6
+ >
7
+ {{ item.title }}
8
+ </a>
9
+ {% if item.children %}
10
+ <ul>
11
+ {% include awesome-nav-demo-tree.html items=item.children %}
12
+ </ul>
13
+ {% endif %}
14
+ </li>
15
+ {% endfor %}
@@ -0,0 +1,19 @@
1
+ {% for item in include.items %}
2
+ <li>
3
+ {% if item.url %}
4
+ <a
5
+ href="{{ item.url | relative_url }}"
6
+ {% if item.url == page.url %}aria-current="page"{% endif %}
7
+ >
8
+ {{ item.title }}
9
+ </a>
10
+ {% else %}
11
+ <span>{{ item.title }}</span>
12
+ {% endif %}
13
+ {% if item.children %}
14
+ <ul>
15
+ {% include awesome-nav-tree.html items=item.children %}
16
+ </ul>
17
+ {% endif %}
18
+ </li>
19
+ {% endfor %}
@@ -0,0 +1,128 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+ <style>
5
+ .awesome-nav-docs {
6
+ display: grid;
7
+ gap: 2rem;
8
+ grid-template-columns: minmax(14rem, 18rem) minmax(0, 1fr);
9
+ max-width: 72rem;
10
+ margin: 0 auto;
11
+ padding: 2rem 1rem;
12
+ }
13
+
14
+ .awesome-nav-docs__sidebar {
15
+ align-self: start;
16
+ position: sticky;
17
+ top: 1rem;
18
+ }
19
+
20
+ .awesome-nav-docs__sidebar ul {
21
+ list-style: none;
22
+ margin: 0;
23
+ padding-left: 0;
24
+ }
25
+
26
+ .awesome-nav-docs__sidebar ul ul {
27
+ margin-top: 0.25rem;
28
+ padding-left: 1rem;
29
+ }
30
+
31
+ .awesome-nav-docs__sidebar li {
32
+ margin: 0.25rem 0;
33
+ }
34
+
35
+ .awesome-nav-docs__sidebar a,
36
+ .awesome-nav-docs__sidebar span {
37
+ display: block;
38
+ padding: 0.25rem 0;
39
+ }
40
+
41
+ .awesome-nav-docs__sidebar a[aria-current="page"] {
42
+ font-weight: 700;
43
+ }
44
+
45
+ .awesome-nav-docs__breadcrumbs ol {
46
+ display: flex;
47
+ flex-wrap: wrap;
48
+ gap: 0.25rem;
49
+ list-style: none;
50
+ margin: 0 0 1rem;
51
+ padding: 0;
52
+ }
53
+
54
+ .awesome-nav-docs__breadcrumbs li {
55
+ margin: 0.25rem;
56
+ }
57
+
58
+ .awesome-nav-docs__breadcrumbs li:not(:last-child)::after {
59
+ content: "/";
60
+ margin-left: 0.25rem;
61
+ }
62
+
63
+ .awesome-nav-docs__pager {
64
+ border-top: 1px solid var(--borderColor-default, #d0d7de);
65
+ display: flex;
66
+ justify-content: space-between;
67
+ gap: 1rem;
68
+ margin-top: 3rem;
69
+ padding-top: 1rem;
70
+ }
71
+
72
+ @media (max-width: 48rem) {
73
+ .awesome-nav-docs {
74
+ grid-template-columns: 1fr;
75
+ }
76
+
77
+ .awesome-nav-docs__sidebar {
78
+ position: static;
79
+ }
80
+ }
81
+ </style>
82
+
83
+ <div class="awesome-nav-docs">
84
+ <aside class="awesome-nav-docs__sidebar">
85
+ <h2>Docs</h2>
86
+ {% if page.awesome_nav %}
87
+ <nav aria-label="Documentation">
88
+ <ul>
89
+ {% include awesome-nav-tree.html items=page.awesome_nav %}
90
+ </ul>
91
+ </nav>
92
+ {% endif %}
93
+ </aside>
94
+
95
+ <main class="markdown-body">
96
+ {% if page.breadcrumbs %}
97
+ <nav class="awesome-nav-docs__breadcrumbs" aria-label="Breadcrumbs">
98
+ <ol>
99
+ {% for item in page.breadcrumbs %}
100
+ <li>
101
+ <a href="{{ item.url | relative_url }}">{{ item.title }}</a>
102
+ </li>
103
+ {% endfor %}
104
+ </ol>
105
+ </nav>
106
+ {% endif %}
107
+
108
+ <h1>{{ page.title }}</h1>
109
+ {{ content }}
110
+
111
+ <nav class="awesome-nav-docs__pager" aria-label="Pagination">
112
+ <div>
113
+ {% if page.awesome_nav_previous %}
114
+ <a href="{{ page.awesome_nav_previous.url | relative_url }}">
115
+ Previous: {{ page.awesome_nav_previous.title }}
116
+ </a>
117
+ {% endif %}
118
+ </div>
119
+ <div>
120
+ {% if page.awesome_nav_next %}
121
+ <a href="{{ page.awesome_nav_next.url | relative_url }}">
122
+ Next: {{ page.awesome_nav_next.title }}
123
+ </a>
124
+ {% endif %}
125
+ </div>
126
+ </nav>
127
+ </main>
128
+ </div>
@@ -0,0 +1,68 @@
1
+ ---
2
+ title: Getting Started
3
+ ---
4
+
5
+ Add the gem to your Jekyll site and enable it in `_config.yml`.
6
+
7
+ ## Install the gem
8
+
9
+ ```ruby
10
+ gem "jekyll-awesome-nav"
11
+ ```
12
+
13
+ Then install your bundle:
14
+
15
+ ```bash
16
+ bundle install
17
+ ```
18
+
19
+ ## Enable the plugin
20
+
21
+ ```yaml
22
+ plugins:
23
+ - jekyll-awesome-nav
24
+
25
+ awesome_nav:
26
+ enabled: true
27
+ root: docs
28
+ nav_filename: .nav.yml
29
+ ```
30
+
31
+ ## Add docs pages
32
+
33
+ Create Markdown files under the configured root:
34
+
35
+ ```text
36
+ docs/
37
+ ├── index.md
38
+ ├── getting-started.md
39
+ └── guides/
40
+ ├── index.md
41
+ └── install.md
42
+ ```
43
+
44
+ Each page should have a title:
45
+
46
+ ```markdown
47
+ ---
48
+ title: Install
49
+ ---
50
+
51
+ Install instructions go here.
52
+ ```
53
+
54
+ The plugin builds navigation from these pages during the normal Jekyll build. Your layout can then render `page.awesome_nav`, `page.breadcrumbs`, and the previous/next links.
55
+
56
+ ## Render it
57
+
58
+ If your theme already supports `jekyll-awesome-nav`, choose that docs layout for pages under your docs root. This site uses its docs layout through defaults:
59
+
60
+ ```yaml
61
+ defaults:
62
+ - scope:
63
+ path: "docs"
64
+ values:
65
+ layout: docs
66
+ ```
67
+
68
+ If your theme does not support it yet, use the examples in [Layout Integration]({{ "/docs/guides/layouts/" | relative_url }}) to wire the data into your own layout.
@@ -0,0 +1,7 @@
1
+ nav:
2
+ - Guides:
3
+ - Install Guide: install.md
4
+ - Layout Integration: layouts.md
5
+ - Configuration: config.md
6
+ - Navigation Overrides: overrides.md
7
+ - Generated Data: data.md
@@ -0,0 +1,37 @@
1
+ ---
2
+ title: Configuration
3
+ ---
4
+
5
+ Configure the plugin in `_config.yml`.
6
+
7
+ ```yaml
8
+ awesome_nav:
9
+ enabled: true
10
+ root: docs
11
+ nav_filename: .nav.yml
12
+ ```
13
+
14
+ ## Options
15
+
16
+ | Option | Default | Description |
17
+ | --- | --- | --- |
18
+ | `enabled` | `true` | Turns generation on or off. |
19
+ | `root` | `docs` | Folder that contains your documentation pages. |
20
+ | `nav_filename` | `.nav.yml` | Filename used for local subtree overrides. |
21
+
22
+ ## Page titles
23
+
24
+ Generated items use page front matter when it is available:
25
+
26
+ ```yaml
27
+ ---
28
+ title: Configuration
29
+ nav_title: Config
30
+ ---
31
+ ```
32
+
33
+ Use `title` for the page heading and `nav_title` when the navigation label should be shorter.
34
+
35
+ ## URLs
36
+
37
+ Jekyll controls the final page URL. The plugin reads those URLs from generated pages, so settings like `permalink: pretty` work normally.
@@ -0,0 +1,40 @@
1
+ ---
2
+ title: Generated Data
3
+ ---
4
+
5
+ The plugin writes navigation data onto each page under the configured docs root.
6
+
7
+ ## Page variables
8
+
9
+ | Variable | Description |
10
+ | --- | --- |
11
+ | `page.awesome_nav` | Full resolved docs tree. |
12
+ | `page.awesome_nav_local` | Navigation items for the current directory context. |
13
+ | `page.awesome_nav_dir` | Directory that supplied the current local nav context. |
14
+ | `page.breadcrumbs` | Breadcrumb entries for the current page. |
15
+ | `page.awesome_nav_previous` | Previous page in the resolved navigation order. |
16
+ | `page.awesome_nav_next` | Next page in the resolved navigation order. |
17
+
18
+ ## Tree item shape
19
+
20
+ Navigation entries are hashes with a title, URL, and optional children:
21
+
22
+ ```yaml
23
+ title: Install Guide
24
+ url: /docs/guides/install/
25
+ children: []
26
+ ```
27
+
28
+ Layouts should treat `children` as optional because leaf pages do not need nested items.
29
+
30
+ ## Site variables
31
+
32
+ The plugin also writes resolved data into `site.config` for advanced theme use:
33
+
34
+ | Variable | Description |
35
+ | --- | --- |
36
+ | `site.awesome_nav_tree` | Full resolved tree. |
37
+ | `site.awesome_nav_local_map` | Local navigation lookup by directory. |
38
+ | `site.awesome_nav_files` | Loaded `.nav.yml` data. |
39
+
40
+ Most layouts should prefer the `page.*` variables because they are already scoped to the current page.
@@ -0,0 +1,15 @@
1
+ ---
2
+ title: Guides
3
+ ---
4
+
5
+ The guides section demonstrates local control over one part of the docs tree.
6
+
7
+ The `docs/guides/.nav.yml` file replaces this subtree, which means the guides can be ordered and titled independently of filenames.
8
+
9
+ ## Guides
10
+
11
+ - [Install Guide]({{ "/docs/guides/install/" | relative_url }})
12
+ - [Layout Integration]({{ "/docs/guides/layouts/" | relative_url }})
13
+ - [Configuration]({{ "/docs/guides/config/" | relative_url }})
14
+ - [Navigation Overrides]({{ "/docs/guides/overrides/" | relative_url }})
15
+ - [Generated Data]({{ "/docs/guides/data/" | relative_url }})