guides_style_18f 0.3.0 → 0.4.0

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
  SHA1:
3
- metadata.gz: b6425173ee18142282aaba8e6b3793be82673cc7
4
- data.tar.gz: 5d279c26bb50028d66123be800494747c84ebabc
3
+ metadata.gz: 7c8d5fbef0f481d350fa56a00fb2913d58d78cf7
4
+ data.tar.gz: e23bc6918f8c88349835ff681bf110a556f64547
5
5
  SHA512:
6
- metadata.gz: 24c45ba00326245afb55fb835220cae61b8a24d6772f1cc97896831a42324b7160c24cb350aa0ea88f1b25286497e9446169970327356dc5f5bf02213fae4e6d
7
- data.tar.gz: 6dcfea31eeb9326ad81008502205f828488c43fee20e6c8bb3016e14b6e46091dfa56d7f1b63edbaa1bb96451c4e62965f4301cc9f7896617fce03489d002dcf
6
+ metadata.gz: 45ca1f1854395173e91cfd614555e1afb3a568d463a1fea19e070de56ac113707d685bbc2831b0d42840bce43d7edf5764ab382a148efc64e79bdec857172436
7
+ data.tar.gz: bf04bff3b13442dc3ab69648f8efd181cca82d192d9cded13c03f94a0afa2530ee15965eaf0f7621388fff8cd84f6ef1cb41fef60d7bc1e27ac3002204388c0b
@@ -1,6 +1,7 @@
1
1
  # @author Mike Bland (michael.bland@gsa.gov)
2
2
 
3
3
  require 'guides_style_18f/assets'
4
+ require 'guides_style_18f/breadcrumbs'
4
5
  require 'guides_style_18f/generator'
5
6
  require 'guides_style_18f/includes'
6
7
  require 'guides_style_18f/layouts'
@@ -0,0 +1,28 @@
1
+ require 'jekyll'
2
+ require 'safe_yaml'
3
+
4
+ module GuidesStyle18F
5
+ class Breadcrumbs
6
+ def self.generate(site, docs)
7
+ breadcrumbs = create_breadcrumbs(site)
8
+ docs.each do |page|
9
+ page.data['breadcrumbs'] = breadcrumbs[page.permalink || page.url]
10
+ end
11
+ end
12
+
13
+ def self.create_breadcrumbs(site)
14
+ (site.config['navigation'] || []).flat_map do |nav|
15
+ Breadcrumbs.generate_breadcrumbs(nav, '/', [])
16
+ end.to_h
17
+ end
18
+
19
+ def self.generate_breadcrumbs(nav, parent_url, parents)
20
+ url = parent_url + (nav['url'] || '')
21
+ crumbs = parents + [{ 'url' => url, 'text' => nav['text'] }]
22
+ child_crumbs = (nav['children'] || []).flat_map do |child|
23
+ generate_breadcrumbs(child, url, crumbs)
24
+ end
25
+ [[url, crumbs]] + child_crumbs
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,63 @@
1
+ module GuidesStyle18F
2
+ class GeneratedNodes
3
+ # Params:
4
+ # url_to_nav: Mapping from original document URL to "nav item" objects,
5
+ # i.e. { 'text' => '...', 'url' => '...', 'internal' => true }
6
+ # nav_data: Array of nav item objects contained in `url_to_nav` after
7
+ # applying updates, possibly containing "orphan" items marked with an
8
+ # `:orphan_url` property
9
+ #
10
+ # Returns:
11
+ # nav_data with orphans properly nested within automatically-generated
12
+ # parent nodes marked with `'generated' => true`
13
+ def self.create_homes_for_orphans(url_to_nav, nav_data)
14
+ orphans = nav_data.select { |nav| nav[:orphan_url] }
15
+ orphans.each { |nav| create_home_for_orphan(nav, nav_data, url_to_nav) }
16
+ nav_data.reject! { |nav| nav[:orphan_url] }
17
+ prune_childless_parents(nav_data)
18
+ end
19
+
20
+ def self.create_home_for_orphan(nav, nav_data, url_to_nav)
21
+ parents = nav[:orphan_url].split('/')[1..-1]
22
+ nav['url'] = parents.pop + '/'
23
+ child_url = '/'
24
+ immediate_parent = parents.reduce(nil) do |parent, child|
25
+ child_url = child_url + child + '/'
26
+ find_or_create_node(nav_data, child_url, parent, child, url_to_nav)
27
+ end
28
+ assign_orphan_to_home(nav, immediate_parent, url_to_nav)
29
+ end
30
+
31
+ def self.find_or_create_node(nav_data, child_url, parent, child, url_to_nav)
32
+ child_nav = url_to_nav[child_url]
33
+ if child_nav.nil?
34
+ child_nav = generated_node(child)
35
+ url_to_nav[child_url] = child_nav
36
+ (parent.nil? ? nav_data : (parent['children'] ||= [])) << child_nav
37
+ end
38
+ child_nav
39
+ end
40
+
41
+ def self.generated_node(parent_slug)
42
+ { 'text' => parent_slug.split('-').join(' ').capitalize,
43
+ 'url' => parent_slug + '/',
44
+ 'internal' => true,
45
+ 'generated' => true,
46
+ }
47
+ end
48
+
49
+ def self.assign_orphan_to_home(nav, immediate_parent, url_to_nav)
50
+ nav_copy = {}.merge(nav)
51
+ url_to_nav[nav_copy.delete(:orphan_url)] = nav_copy
52
+ (immediate_parent['children'] ||= []) << nav_copy
53
+ end
54
+
55
+ def self.prune_childless_parents(nav_data)
56
+ (nav_data || []).reject! do |nav|
57
+ children = (nav['children'] || [])
58
+ prune_childless_parents(children)
59
+ nav['generated'] && children.empty?
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,38 @@
1
+ require_relative './layouts'
2
+
3
+ module GuidesStyle18F
4
+ class GeneratedPages
5
+ DEFAULT_LAYOUT = 'guides_style_18f_generated_home_redirect'
6
+
7
+ def self.generate_pages_from_navigation_data(site)
8
+ layout = site.config['generate_nodes']
9
+ return if layout.nil? || layout == false
10
+ layout = DEFAULT_LAYOUT if layout == true
11
+ nav_data = site.config['navigation']
12
+ generate_pages_from_generated_nodes(site, layout, nav_data, '/')
13
+ end
14
+
15
+ def self.generate_pages_from_generated_nodes(
16
+ site, layout, nav_data, parent_url)
17
+ (nav_data || []).select { |nav| nav['generated'] }.each do |nav|
18
+ site.pages << GeneratedPage.new(site, layout, nav, parent_url)
19
+ children = nav['children']
20
+ next_url = parent_url + nav['url']
21
+ generate_pages_from_generated_nodes(site, layout, children, next_url)
22
+ end
23
+ end
24
+ end
25
+
26
+ class GeneratedPage < ::Jekyll::Page
27
+ def initialize(site, layout, nav, parent_url)
28
+ @site = site
29
+ @name = 'index.html'
30
+
31
+ process(@name)
32
+ @data = {}
33
+ data['title'] = nav['text']
34
+ data['permalink'] = parent_url + nav['url']
35
+ data['layout'] = layout
36
+ end
37
+ end
38
+ end
@@ -1,12 +1,23 @@
1
1
  # @author Mike Bland (michael.bland@gsa.gov)
2
2
 
3
+ require_relative './assets'
4
+ require_relative './breadcrumbs'
5
+ require_relative './generated_pages'
6
+ require_relative './layouts'
7
+ require_relative './namespace_flattener'
8
+
3
9
  require 'jekyll'
4
10
 
5
11
  module GuidesStyle18F
6
12
  class Generator < ::Jekyll::Generator
7
13
  def generate(site)
8
- Layouts.register site
9
- Assets.copy_to_site site
14
+ Layouts.register(site)
15
+ Assets.copy_to_site(site)
16
+ GeneratedPages.generate_pages_from_navigation_data(site)
17
+ pages = site.collections['pages']
18
+ docs = (pages.nil? ? [] : pages.docs) + site.pages
19
+ Breadcrumbs.generate(site, docs)
20
+ NamespaceFlattener.flatten_url_namespace(site, docs)
10
21
  end
11
22
  end
12
23
  end
@@ -0,0 +1,8 @@
1
+ <nav class="nav-main">
2
+ <div class="wrapper">
3
+ <ol class="breadcrumbs">
4
+ {% for breadcrumb in page.breadcrumbs %}<li>{% if forloop.last %}{{ breadcrumb.text }}{% else %}<a href="{{ site.baseurl}}{{ breadcrumb.url }}">{{ breadcrumb.text }}</a>{% endif %}</li>
5
+ {% endfor %}
6
+ </div>
7
+ </div>
8
+ </nav>
@@ -7,11 +7,14 @@ module GuidesStyle18F
7
7
  # We have to essentially recreate the ::Jekyll::Layout constructor to loosen
8
8
  # the default restriction that layouts be included in the site source.
9
9
  class Layouts < ::Jekyll::Layout
10
+ LAYOUTS_DIR = File.join(File.dirname(__FILE__), 'layouts')
11
+ SEARCH_RESULTS_LAYOUT = 'search-results'
12
+
10
13
  private_class_method :new
11
14
 
12
- def initialize(site, layout_file)
15
+ def initialize(site, subdir, layout_file)
13
16
  @site = site
14
- @base = File.join File.dirname(__FILE__), 'layouts'
17
+ @base = File.join(LAYOUTS_DIR, subdir)
15
18
  @name = "#{layout_file}.html"
16
19
  @path = File.join @base, @name
17
20
  parse_content_and_data File.join(@base, name)
@@ -20,7 +23,7 @@ module GuidesStyle18F
20
23
 
21
24
  def parse_content_and_data(file_path)
22
25
  self.data = {}
23
- self.content = File.read file_path
26
+ self.content = File.read(file_path)
24
27
 
25
28
  front_matter_pattern = /^(---\n.*)---\n/m
26
29
  front_matter_match = front_matter_pattern.match content
@@ -32,7 +35,18 @@ module GuidesStyle18F
32
35
  private :parse_content_and_data
33
36
 
34
37
  def self.register(site)
35
- site.layouts['guides_style_18f_default'] = new site, 'default'
38
+ site.layouts['guides_style_18f_default'] = new(site, '', 'default')
39
+ site.layouts['guides_style_18f_generated_home_redirect'] = new(
40
+ site, 'generated', 'home-redirect')
41
+ register_search_results_layout(site)
42
+ end
43
+
44
+ def self.register_search_results_layout(site)
45
+ layouts_dir = File.join(site.source, site.config['layouts_dir'])
46
+ if !File.exist?(File.join(layouts_dir, "#{SEARCH_RESULTS_LAYOUT}.html"))
47
+ site.layouts[SEARCH_RESULTS_LAYOUT] = new(
48
+ site, '', SEARCH_RESULTS_LAYOUT)
49
+ end
36
50
  end
37
51
  end
38
52
  end
@@ -0,0 +1,6 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head><meta http-equiv="refresh" content="0;URL='{{ site.baseurl }}/#{{ page.title | slugify }}'">
4
+ </head>
5
+ <body></body>
6
+ </html>
@@ -0,0 +1,4 @@
1
+ ---
2
+ layout: guides_style_18f_default
3
+ ---
4
+ {% jekyll_pages_api_search_results %}
@@ -0,0 +1,39 @@
1
+ module GuidesStyle18F
2
+ class NamespaceFlattener
3
+ def self.flatten_url_namespace(site, docs)
4
+ flatten_urls(docs) if site.config['flat_namespace']
5
+ end
6
+
7
+ def self.flatten_urls(docs)
8
+ flat_to_orig = {}
9
+ docs.each { |page| flatten_page_urls(page, flat_to_orig) }
10
+ check_for_collisions(flat_to_orig)
11
+ end
12
+
13
+ def self.flatten_page_urls(page, flat_to_orig)
14
+ orig_url = page.permalink || page.url
15
+ flattened_url = flat_url(orig_url)
16
+ (flat_to_orig[flattened_url] ||= []) << orig_url
17
+ page.data['permalink'] = flattened_url
18
+ (page.data['breadcrumbs'] || []).each do |crumb|
19
+ crumb['url'] = flat_url(crumb['url'])
20
+ end
21
+ end
22
+
23
+ def self.flat_url(url)
24
+ url == '/' ? url : "/#{url.split('/')[1..-1].last}/"
25
+ end
26
+
27
+ def self.check_for_collisions(flat_to_orig)
28
+ collisions = flat_to_orig.map do |flattened, orig|
29
+ [flattened, orig] if orig.size != 1
30
+ end.compact
31
+
32
+ return if collisions.empty?
33
+
34
+ messages = collisions.map { |flat, orig| "#{flat}: #{orig.join(', ')}" }
35
+ fail(StandardError, "collisions in flattened namespace between\n " +
36
+ messages.join("\n "))
37
+ end
38
+ end
39
+ end
@@ -1,5 +1,7 @@
1
1
  # @author Mike Bland (michael.bland@gsa.gov)
2
2
 
3
+ require_relative './generated_nodes'
4
+
3
5
  require 'jekyll'
4
6
  require 'safe_yaml'
5
7
 
@@ -99,7 +101,7 @@ module GuidesStyle18F
99
101
 
100
102
  def self.missing_property_errors(data)
101
103
  properties = %w(title permalink)
102
- properties.map { |p| "no `#{p}:` property" unless data[p] }.compact
104
+ properties.map { |p| "no `#{p}:` property" if data[p].nil? }.compact
103
105
  end
104
106
 
105
107
  def self.permalink_errors(data)
@@ -122,19 +124,22 @@ module GuidesStyle18F
122
124
  config_data = SafeYAML.load_file config_path, safe: true
123
125
  return unless config_data
124
126
  nav_data = config_data['navigation'] || []
125
- NavigationMenu.update_navigation_data(nav_data, basedir)
126
- NavigationMenu.write_navigation_data_to_config_file(config_path, nav_data)
127
+ NavigationMenu.update_navigation_data(nav_data, basedir, config_data)
128
+ NavigationMenuWriter.write_navigation_data_to_config_file(
129
+ config_path, nav_data)
127
130
  end
128
131
 
129
- private
130
-
131
132
  module NavigationMenu
132
- def self.update_navigation_data(nav_data, basedir)
133
+ def self.update_navigation_data(nav_data, basedir, config_data)
133
134
  original = map_nav_items_by_url('/', nav_data).to_h
134
135
  updated = updated_nav_data(basedir)
135
136
  remove_stale_nav_entries(nav_data, original, updated)
136
137
  updated.map { |url, nav| apply_nav_update(url, nav, nav_data, original) }
137
- check_for_orphaned_items(nav_data)
138
+ if config_data['generate_nodes']
139
+ GeneratedNodes.create_homes_for_orphans(original, nav_data)
140
+ else
141
+ check_for_orphaned_items(nav_data)
142
+ end
138
143
  end
139
144
 
140
145
  def self.map_nav_items_by_url(parent_url, nav_data)
@@ -168,7 +173,9 @@ module GuidesStyle18F
168
173
  def self.remove_stale_nav_entries(nav_data, original, updated)
169
174
  # Remove old entries whose pages have been deleted
170
175
  original.each do |url, nav|
171
- nav['delete'] = true if !updated.member?(url) && nav['internal']
176
+ if !updated.member?(url) && nav['internal'] && !nav['generated']
177
+ nav['delete'] = true
178
+ end
172
179
  end
173
180
  original.delete_if { |_url, nav| nav['delete'] }
174
181
  nav_data.delete_if { |nav| nav['delete'] }
@@ -188,6 +195,7 @@ module GuidesStyle18F
188
195
  apply_new_nav_item(url, nav, nav_data, original)
189
196
  else
190
197
  orig['text'] = nav['text']
198
+ orig.delete('generated')
191
199
  end
192
200
  end
193
201
 
@@ -197,20 +205,22 @@ module GuidesStyle18F
197
205
  if parent_url == '/'
198
206
  nav_data << (original[url] = nav)
199
207
  elsif parent.nil?
200
- nav_data << { orphan_url: url }
208
+ nav_data << nav.merge(orphan_url: url)
201
209
  else
202
210
  (parent['children'] ||= []) << nav
203
211
  end
204
212
  end
205
213
 
206
214
  def self.check_for_orphaned_items(nav_data)
207
- orphans = nav_data.map { |nav| nav[:orphan_url] }.compact
208
- unless orphans.empty?
215
+ orphan_urls = nav_data.map { |nav| nav[:orphan_url] }.compact
216
+ unless orphan_urls.empty?
209
217
  fail(StandardError, "Parent pages missing for the following:\n " +
210
- orphans.join("\n "))
218
+ orphan_urls.join("\n "))
211
219
  end
212
220
  end
221
+ end
213
222
 
223
+ class NavigationMenuWriter
214
224
  def self.write_navigation_data_to_config_file(config_path, nav_data)
215
225
  lines = []
216
226
  in_navigation = false
@@ -366,39 +366,8 @@ header div.wrap div.back-link a:hover{ text-decoration: none; }
366
366
  overflow: hidden;
367
367
  white-space: nowrap;
368
368
  }
369
- header div.search-interface{
370
- width: 30%;
371
- float: right;
372
- }
373
369
  }
374
370
 
375
-
376
- header div.search-interface input{
377
- display: block;
378
- -webkit-box-sizing: border-box;
379
- width: 100%;
380
- padding: 6px 4px;
381
- padding-left: 2em;
382
- font: inherit;
383
- border-radius: 3px;
384
- border: 1px solid #ccc;
385
- background: url(../svg/search.svg) 6px 45% no-repeat;
386
- background-size: 16px;
387
- }
388
-
389
- ul.searchresultspopup{ border-radius: 3px; }
390
- ul.searchresultspopup li{ padding: 0; }
391
- ul.searchresultspopup li a{
392
- padding: 4px;
393
- display: block;
394
- color: inherit;
395
- }
396
- ul.searchresultspopup li.ng-scope.selected a{
397
- background: #333;
398
- color: white;
399
- }
400
-
401
-
402
371
  .main-content img {
403
372
  max-width: 100%;
404
373
  }
@@ -1,5 +1,5 @@
1
1
  # @author Mike Bland (michael.bland@gsa.gov)
2
2
 
3
3
  module GuidesStyle18F
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: guides_style_18f
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Bland
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-28 00:00:00.000000000 Z
11
+ date: 2016-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -194,13 +194,15 @@ files:
194
194
  - assets/js/vendor/anchor.min.js
195
195
  - assets/js/vendor/jquery-1.11.2.min.js
196
196
  - assets/js/vendor/private-eye-1.0.0.js
197
- - assets/png/search.png
198
- - assets/svg/search.svg
199
197
  - lib/guides_style_18f.rb
200
198
  - lib/guides_style_18f/assets.rb
199
+ - lib/guides_style_18f/breadcrumbs.rb
200
+ - lib/guides_style_18f/generated_nodes.rb
201
+ - lib/guides_style_18f/generated_pages.rb
201
202
  - lib/guides_style_18f/generator.rb
202
203
  - lib/guides_style_18f/includes.rb
203
204
  - lib/guides_style_18f/includes/analytics.html
205
+ - lib/guides_style_18f/includes/breadcrumbs.html
204
206
  - lib/guides_style_18f/includes/footer.html
205
207
  - lib/guides_style_18f/includes/header.html
206
208
  - lib/guides_style_18f/includes/scripts.html
@@ -208,6 +210,9 @@ files:
208
210
  - lib/guides_style_18f/includes/sidebar.html
209
211
  - lib/guides_style_18f/layouts.rb
210
212
  - lib/guides_style_18f/layouts/default.html
213
+ - lib/guides_style_18f/layouts/generated/home-redirect.html
214
+ - lib/guides_style_18f/layouts/search-results.html
215
+ - lib/guides_style_18f/namespace_flattener.rb
211
216
  - lib/guides_style_18f/navigation.rb
212
217
  - lib/guides_style_18f/repository.rb
213
218
  - lib/guides_style_18f/sass.rb
Binary file
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <!-- Generated by IcoMoon.io -->
3
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
- <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="26" height="28" viewBox="0 0 26 28">
5
- <path fill="#444" d="M18 13q0-2.891-2.055-4.945t-4.945-2.055-4.945 2.055-2.055 4.945 2.055 4.945 4.945 2.055 4.945-2.055 2.055-4.945zM26 26q0 0.812-0.594 1.406t-1.406 0.594q-0.844 0-1.406-0.594l-5.359-5.344q-2.797 1.937-6.234 1.937-2.234 0-4.273-0.867t-3.516-2.344-2.344-3.516-0.867-4.273 0.867-4.273 2.344-3.516 3.516-2.344 4.273-0.867 4.273 0.867 3.516 2.344 2.344 3.516 0.867 4.273q0 3.437-1.937 6.234l5.359 5.359q0.578 0.578 0.578 1.406z"></path>
6
- </svg>