guides_style_18f 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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>