fenton-jekyll-boilerplate 0.0.3 → 0.0.5

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: f8a2c828a5f914d7e9e56e8245a39a2b77f44e3f22924f7cf0919ae62b005acf
4
- data.tar.gz: 7e78f2f84bfcc15c4784aeabe255e04b4a8643bdfb83ac8c9fc7aacdd90de83c
3
+ metadata.gz: 8e62bb1bc58c9c86331a322e70bb7d8b16ce251dcc572d89361617e9f994bf53
4
+ data.tar.gz: 1d25e7531752be6c2690eb81843a24b1dce2a343381df86546e9bf6a0465cd40
5
5
  SHA512:
6
- metadata.gz: 1ac5c044c8156b95777ce7851a020eaeb72c7dc929769f05d911b301718889866bcad742da1dfa92cafe3184fa527b462ceb59f65dcd0161742b09d3d4f3bd1a
7
- data.tar.gz: a826ee42744b3bddc4e1ceda2335b413586fe407d42c08ef3cfcbedd03fb8bf80f50ca8e02187f68d3addb353320f97a99bf709d002ca1b15da7331b1021e3bc
6
+ metadata.gz: 37d7589c85c4853b70fff3699c32400a1ce2d7a1374804270bac303be382bedb22c7403ffaad665b631206812b7142fdd1d15479db92c4d185204981fafaeae5
7
+ data.tar.gz: 14a32ca59b666e353ec58d6274d02eda8eace7f4910564009eadf3a663aac6885bbb8adff82f4df3b1c6e2bcda39419fabf9146503c4d456de036a0bcb8a927d
data/_data/language.yaml CHANGED
@@ -3,6 +3,9 @@
3
3
  # template:
4
4
  # term:
5
5
  # en: Translation
6
+ #
7
+ # If you are running Jekyll v4.3.0 or higher, you don't need this file in your site
8
+ # as it will be read from the theme's _data folder
6
9
 
7
10
  skiplinks:
8
11
  skip_to_navigation:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fenton-jekyll-boilerplate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Fenton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-13 00:00:00.000000000 Z
11
+ date: 2022-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -47,7 +47,6 @@ extra_rdoc_files: []
47
47
  files:
48
48
  - LICENSE
49
49
  - README.md
50
- - _authors/steve-fenton.md
51
50
  - _data/language.yaml
52
51
  - _includes/analytics.html
53
52
  - _includes/breadcrumbs.html
@@ -62,11 +61,6 @@ files:
62
61
  - _layouts/page.html
63
62
  - _layouts/post.html
64
63
  - _layouts/search.html
65
- - _plugins/article_paging.rb
66
- - _plugins/breadcrumbs.rb
67
- - _plugins/liquid_language.rb
68
- - _plugins/liquid_regex.rb
69
- - _plugins/markdown.rb
70
64
  - assets/css/code.css
71
65
  - assets/css/main.css
72
66
  - assets/css/vars.css
@@ -76,28 +70,6 @@ files:
76
70
  - assets/icons/favicon-16x16.png
77
71
  - assets/icons/favicon-32x32.png
78
72
  - assets/icons/favicon.ico
79
- - assets/img/2022/09/surface-accessories-700.webp
80
- - assets/img/2022/09/surface-accessories.webp
81
- - assets/img/authors/steve-fenton.webp
82
- - assets/img/jekyll-and-hyde.webp
83
- - assets/img/lighthouse-scores.webp
84
- - assets/js/main.js
85
- - assets/js/modules/animation.js
86
- - assets/js/modules/click-blocks.js
87
- - assets/js/modules/events.js
88
- - assets/js/modules/focus.js
89
- - assets/js/modules/nav-expand.js
90
- - assets/js/modules/nav-mobile.js
91
- - assets/js/modules/nav-sticky.js
92
- - assets/js/modules/query.js
93
- - assets/js/modules/resizing.js
94
- - assets/js/modules/string.js
95
- - assets/js/search.js
96
- - assets/svg/down.svg
97
- - favicon.ico
98
- - robots.txt
99
- - search.json
100
- - sitemap.xml
101
73
  homepage: https://jekyll.stevefenton.co.uk/
102
74
  licenses:
103
75
  - Apache-2.0
@@ -1,21 +0,0 @@
1
- ---
2
- username: steve-fenton
3
- name: Steve Fenton
4
- location: UK
5
- url_full: https://www.stevefenton.co.uk/
6
- url_short: stevefenton.co.uk
7
- bio: Steve is the creator of Jekyll Boilerplate
8
- picture: assets/img/authors/steve-fenton.webp
9
- twitter: _stevefenton
10
- date: 2022-09-12
11
- nav-sitemap: true
12
- nav-search: true
13
- ---
14
-
15
- This is some information about Steve Fenton.
16
-
17
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sit amet elit sodales, egestas odio eu, interdum nunc. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In elit odio, dapibus nec finibus sit amet, aliquam a tortor.
18
-
19
- Sed ultricies sollicitudin ipsum at bibendum. Nulla euismod erat arcu, vel convallis risus sagittis non. Fusce scelerisque arcu nec blandit dictum. In vel ultricies nisi. Vivamus pharetra, nibh sit amet semper tristique, leo sapien scelerisque risus, a pharetra tortor lectus at nisl.
20
-
21
- Vestibulum dui mauris, mattis mattis hendrerit non, aliquam non sem. Nullam orci arcu, viverra vel quam sed, dapibus porta dui. Etiam venenatis ipsum eget mi sodales lobortis. Donec varius non metus eget congue. Suspendisse quis mauris vitae lectus porttitor placerat at quis sapien.
@@ -1,223 +0,0 @@
1
- module Paginate
2
- class Pagination < Jekyll::Generator
3
- # This generator is safe from arbitrary code execution.
4
- safe true
5
-
6
- # This generator should be passive with regard to its execution
7
- priority :lowest
8
-
9
- # Generate paginated pages if necessary.
10
- #
11
- # site - The Site.
12
- #
13
- # Returns nothing.
14
- def generate(site)
15
- if Pager.pagination_enabled?(site)
16
- if template = self.class.template_page(site)
17
- paginate(site, template)
18
- else
19
- Jekyll.logger.warn "Pagination:", "Pagination is enabled, but I couldn't find " +
20
- "an index.html page to use as the pagination template. Skipping pagination."
21
- end
22
- end
23
- end
24
-
25
- # Paginates the blog's posts. Renders the index.html file into paginated
26
- # directories, e.g.: page2/index.html, page3/index.html, etc and adds more
27
- # site-wide data.
28
- #
29
- # site - The Site.
30
- # page - The index.html Page that requires pagination.
31
- #
32
- # {"paginator" => { "page" => <Number>,
33
- # "per_page" => <Number>,
34
- # "posts" => [<Post>],
35
- # "total_posts" => <Number>,
36
- # "total_pages" => <Number>,
37
- # "previous_page" => <Number>,
38
- # "next_page" => <Number> }}
39
- def paginate(site, page)
40
- all_posts = site.site_payload['site']['posts'].reject { |post| post['hidden'] }
41
- pages = Pager.calculate_pages(all_posts, site.config['page_size'].to_i)
42
- (1..pages).each do |num_page|
43
- pager = Pager.new(site, num_page, all_posts, pages)
44
- if num_page > 1
45
- newpage = Jekyll::Page.new(site, site.source, page.dir, page.name)
46
- newpage.pager = pager
47
- newpage.dir = Pager.paginate_path(site, num_page)
48
- site.pages << newpage
49
- else
50
- page.pager = pager
51
- end
52
- end
53
- end
54
-
55
- # Static: Fetch the URL of the template page. Used to determine the
56
- # path to the first pager in the series.
57
- #
58
- # site - the Jekyll::Site object
59
- #
60
- # Returns the url of the template page
61
- def self.first_page_url(site)
62
- if page = Pagination.template_page(site)
63
- page.url
64
- else
65
- nil
66
- end
67
- end
68
-
69
- # Public: Find the Jekyll::Page which will act as the pager template
70
- #
71
- # site - the Jekyll::Site object
72
- #
73
- # Returns the Jekyll::Page which will act as the pager template
74
- def self.template_page(site)
75
- site.pages.select do |page|
76
- Pager.pagination_candidate?(site.config, page)
77
- end.sort do |one, two|
78
- two.path.size <=> one.path.size
79
- end.first
80
- end
81
-
82
- end
83
- end
84
-
85
- module Paginate
86
- class Pager
87
- attr_reader :page, :per_page, :posts, :total_posts, :total_pages,
88
- :previous_page, :previous_page_path, :next_page, :next_page_path
89
-
90
- # Calculate the number of pages.
91
- #
92
- # all_posts - The Array of all Posts.
93
- # per_page - The Integer of entries per page.
94
- #
95
- # Returns the Integer number of pages.
96
- def self.calculate_pages(all_posts, per_page)
97
- (all_posts.size.to_f / per_page.to_i).ceil
98
- end
99
-
100
- # Determine if pagination is enabled the site.
101
- #
102
- # site - the Jekyll::Site object
103
- #
104
- # Returns true if pagination is enabled, false otherwise.
105
- def self.pagination_enabled?(site)
106
- !site.config['page_size'].nil? &&
107
- site.pages.size > 0
108
- end
109
-
110
- # Static: Determine if a page is a possible candidate to be a template page.
111
- # Page's name must be `index.html` and exist in any of the directories
112
- # between the site source and `paginate_path`.
113
- #
114
- # config - the site configuration hash
115
- # page - the Jekyll::Page about which we're inquiring
116
- #
117
- # Returns true if the
118
- def self.pagination_candidate?(config, page)
119
- page_dir = File.dirname(File.expand_path(remove_leading_slash(page.path), config['source']))
120
- paginate_path = remove_leading_slash(config['paginate_path'])
121
- paginate_path = File.expand_path(paginate_path, config['source'])
122
- page.name == 'index.html' &&
123
- in_hierarchy(config['source'], page_dir, File.dirname(paginate_path))
124
- end
125
-
126
- # Determine if the subdirectories of the two paths are the same relative to source
127
- #
128
- # source - the site source
129
- # page_dir - the directory of the Jekyll::Page
130
- # paginate_path - the absolute paginate path (from root of FS)
131
- #
132
- # Returns whether the subdirectories are the same relative to source
133
- def self.in_hierarchy(source, page_dir, paginate_path)
134
- return false if paginate_path == File.dirname(paginate_path)
135
- return false if paginate_path == Pathname.new(source).parent
136
- page_dir == paginate_path ||
137
- in_hierarchy(source, page_dir, File.dirname(paginate_path))
138
- end
139
-
140
- # Static: Return the pagination path of the page
141
- #
142
- # site - the Jekyll::Site object
143
- # num_page - the pagination page number
144
- #
145
- # Returns the pagination path as a string
146
- def self.paginate_path(site, num_page)
147
- return nil if num_page.nil?
148
- return Pagination.first_page_url(site) if num_page <= 1
149
- format = site.config['paginate_path']
150
- if format.include?(":num")
151
- format = format.sub(':num', num_page.to_s)
152
- else
153
- raise ArgumentError.new("Invalid pagination path: '#{format}'. It must include ':num'.")
154
- end
155
- ensure_leading_slash(format)
156
- end
157
-
158
- # Static: Return a String version of the input which has a leading slash.
159
- # If the input already has a forward slash in position zero, it will be
160
- # returned unchanged.
161
- #
162
- # path - a String path
163
- #
164
- # Returns the path with a leading slash
165
- def self.ensure_leading_slash(path)
166
- path[0..0] == "/" ? path : "/#{path}"
167
- end
168
-
169
- # Static: Return a String version of the input without a leading slash.
170
- #
171
- # path - a String path
172
- #
173
- # Returns the input without the leading slash
174
- def self.remove_leading_slash(path)
175
- ensure_leading_slash(path)[1..-1]
176
- end
177
-
178
- # Initialize a new Pager.
179
- #
180
- # site - the Jekyll::Site object
181
- # page - The Integer page number.
182
- # all_posts - The Array of all the site's Posts.
183
- # num_pages - The Integer number of pages or nil if you'd like the number
184
- # of pages calculated.
185
- def initialize(site, page, all_posts, num_pages = nil)
186
- @page = page
187
- @per_page = site.config['page_size'].to_i
188
- @total_pages = num_pages || Pager.calculate_pages(all_posts, @per_page)
189
-
190
- if @page > @total_pages
191
- raise RuntimeError, "page number can't be greater than total pages: #{@page} > #{@total_pages}"
192
- end
193
-
194
- init = (@page - 1) * @per_page
195
- offset = (init + @per_page - 1) >= all_posts.size ? all_posts.size : (init + @per_page - 1)
196
-
197
- @total_posts = all_posts.size
198
- @posts = all_posts[init..offset]
199
- @previous_page = @page != 1 ? @page - 1 : nil
200
- @previous_page_path = Pager.paginate_path(site, @previous_page)
201
- @next_page = @page != @total_pages ? @page + 1 : nil
202
- @next_page_path = Pager.paginate_path(site, @next_page)
203
- end
204
-
205
- # Convert this Pager's data to a Hash suitable for use by Liquid.
206
- #
207
- # Returns the Hash representation of this Pager.
208
- def to_liquid
209
- {
210
- 'page' => page,
211
- 'per_page' => per_page,
212
- 'posts' => posts,
213
- 'total_posts' => total_posts,
214
- 'total_pages' => total_pages,
215
- 'previous_page' => previous_page,
216
- 'previous_page_path' => previous_page_path,
217
- 'next_page' => next_page,
218
- 'next_page_path' => next_page_path
219
- }
220
- end
221
-
222
- end
223
- end
@@ -1,109 +0,0 @@
1
- module Jekyll
2
- module Breadcrumbs
3
- class BreadcrumbItem < Liquid::Drop
4
- extend Forwardable
5
-
6
- def initialize(side)
7
- @side = side
8
- end
9
-
10
- def position
11
- @side[:position]
12
- end
13
-
14
- def title
15
- @side[:title]
16
- end
17
-
18
- def url
19
- @side[:url]
20
- end
21
-
22
- def rootimage
23
- @side[:root_image]
24
- end
25
-
26
- end
27
- end
28
- end
29
-
30
- module Jekyll
31
- module Breadcrumbs
32
- @@config = {}
33
- @@siteAddress = ""
34
- @@sideAddresses = {}
35
-
36
- def self.clearAddressCache
37
- @@sideAddresses = {}
38
- end
39
-
40
- def self.loadAddressCache(site)
41
- clearAddressCache
42
- site.documents.each { |page| addAddressItem(page.url, page['nav-title'] || page['title'] || '') } # collection files including posts
43
- site.pages.each { |page| addAddressItem(page.url, page['nav-title'] || page['title'] || '') } # pages
44
- site.posts.docs.each { |page| addAddressItem(page.url, page['nav-title'] || page['title'] || '') } # posts
45
- end
46
-
47
- def self.addAddressItem(url, title)
48
- key = createAddressCacheKey(url)
49
- @@sideAddresses[key] = {:url => url, :title => title}
50
- end
51
-
52
- def self.findAddressItem(path)
53
- key = createAddressCacheKey(path)
54
- @@sideAddresses[key] if key
55
- end
56
-
57
- def self.createAddressCacheKey(path)
58
- path.chomp("/").empty? ? "/" : path.chomp("/")
59
- end
60
-
61
- def self.buildSideBreadcrumbs(side, payload)
62
- payload["breadcrumbs"] = []
63
- return if side.url == @@siteAddress && root_hide === true
64
-
65
- drop = Jekyll::Breadcrumbs::BreadcrumbItem
66
- position = 0
67
-
68
- path = side.url.chomp("/").split(/(?=\/)/)
69
- -1.upto(path.size - 1) do |int|
70
- joined_path = int == -1 ? "" : path[0..int].join
71
- item = findAddressItem(joined_path)
72
- if item
73
- position += 1
74
- item[:position] = position
75
- item[:root_image] = root_image
76
- payload["breadcrumbs"] << drop.new(item)
77
- end
78
- end
79
- end
80
-
81
- # Config
82
- def self.loadConfig(site)
83
- config = site.config["breadcrumbs"] || {"root" => {"hide" => false, "image" => false}}
84
- root = config["root"]
85
- @@config[:root_hide] = root["hide"] || false
86
- @@config[:root_image] = root["image"] || false
87
-
88
- @@siteAddress = site.config["baseurl"] || "/"
89
- @@siteAddress = "/" if @@siteAddress.empty?
90
- end
91
-
92
- def self.root_hide
93
- @@config[:root_hide]
94
- end
95
-
96
- def self.root_image
97
- @@config[:root_image]
98
- end
99
- end
100
- end
101
-
102
- Jekyll::Hooks.register :site, :pre_render do |site, payload|
103
- Jekyll::Breadcrumbs::loadConfig(site)
104
- Jekyll::Breadcrumbs::loadAddressCache(site)
105
- end
106
-
107
- Jekyll::Hooks.register [:pages, :documents], :pre_render do |side, payload|
108
- Jekyll::Breadcrumbs::buildSideBreadcrumbs(side, payload)
109
- end
@@ -1,38 +0,0 @@
1
- require 'liquid'
2
-
3
- module Jekyll
4
- module Language
5
-
6
- @@lang = nil
7
-
8
- # Supplies translated text
9
- #
10
- # Usage: {{ 'author' | t: 'recent_articles' }}
11
- def t(section, item)
12
- site = @context.registers[:site]
13
-
14
- if @@lang == nil
15
- # Access this fewer times by keeping it as a module variable
16
- @@lang = Jekyll.configuration({})['language']
17
- end
18
-
19
- # Find text in the site language (for example 'fr-be')
20
- text = site.data['language'][section][item][@@lang]
21
-
22
- # Fall back to a more general version of the language (for example 'fr')
23
- if text == nil and @@lang.include? '-'
24
- fallback_lang = @@lang.split('-')[0];
25
- text = site.data['language'][section][item][fallback_lang]
26
- end
27
-
28
- if text == nil
29
- # Fallback to English text
30
- text = site.data['language'][section][item]['en']
31
- end
32
-
33
- return text
34
- end
35
- end
36
- end
37
-
38
- Liquid::Template.register_filter(Jekyll::Language)
@@ -1,17 +0,0 @@
1
- require 'liquid'
2
-
3
- module Jekyll
4
- module RegexReplace
5
- def regex_replace(str, regex_search, value_replace)
6
- regex = /#{regex_search}/
7
- return str.gsub(regex, value_replace)
8
- end
9
-
10
- def regex_replace_once(str, regex_search, value_replace)
11
- regex = /#{regex_search}/
12
- return str.sub(regex, value_replace)
13
- end
14
- end
15
- end
16
-
17
- Liquid::Template.register_filter(Jekyll::RegexReplace)
data/_plugins/markdown.rb DELETED
@@ -1,17 +0,0 @@
1
- Jekyll::Hooks.register :posts, :post_init do |item|
2
- item.content = item.content
3
- &.gsub(/^:::([a-z \-_]+)/, '<div class="\1" markdown="1">')
4
- &.gsub(/^:::/, '</div>')
5
- end
6
-
7
- Jekyll::Hooks.register :pages, :post_init do |item|
8
- item.content = item.content
9
- &.gsub(/^:::([a-z \-_]+)/, '<div class="\1" markdown="1">')
10
- &.gsub(/^:::/, '</div>')
11
- end
12
-
13
- Jekyll::Hooks.register :documents, :post_init do |item|
14
- item.content = item.content
15
- &.gsub(/^:::([a-z \-_]+)/, '<div class="\1" markdown="1">')
16
- &.gsub(/^:::/, '</div>')
17
- end
Binary file
Binary file
Binary file
data/assets/js/main.js DELETED
@@ -1,18 +0,0 @@
1
- // @ts-check
2
-
3
- import { setNavigationTree, setNavigationItem } from './modules/nav-expand.js';
4
- import { setClickableBlocks } from './modules/click-blocks.js';
5
- import { addResizedEvent } from './modules/resizing.js';
6
- import { addStickyNavigation } from './modules/nav-sticky.js';
7
- import { addMobileNavigation } from './modules/nav-mobile.js';
8
- import { addIntersectionObserver } from './modules/animation.js';
9
-
10
- setNavigationTree('details.sub-nav');
11
- setNavigationItem('.site-nav a', 'current-item')
12
- setClickableBlocks('data-destination');
13
-
14
- var resizedEventName = addResizedEvent();
15
-
16
- addStickyNavigation('.site-header', '.site-nav', '.site-nav > ul', resizedEventName);
17
- addMobileNavigation('.navigation-icon', '.site-nav', resizedEventName);
18
- addIntersectionObserver('.post-list .list-item, main img, main .note, main blockquote');
@@ -1,41 +0,0 @@
1
- // @ts-check
2
-
3
- import { qsa } from './query.js';
4
-
5
- /**
6
- * Assists animation by setting "--shown" CSS property
7
- *
8
- * When an item is visible in the viewport, it will have --shown: 1
9
- * Otherwise it will be --shown: 0
10
- * This allows CSS transitions and calculated properties to animate elements
11
- *
12
- * Example
13
- * transition: all 0.2s ease-in;
14
- * scale: calc(0.75 + (var(--shown, 1) * 0.25));
15
- *
16
- * @param {string} listItemQuery
17
- */
18
- function addIntersectionObserver(listItemQuery) {
19
- function handleIntersection(entries, observer) {
20
- for (var entry of entries) {
21
- var value = entry.isIntersecting ? 1 : 0;
22
- entry.target.style.setProperty('--shown', value);
23
- }
24
- }
25
-
26
- var options = {
27
- root: null,
28
- rootMargin: '0px',
29
- threshold: 0
30
- };
31
-
32
- var observer = new IntersectionObserver(handleIntersection, options)
33
-
34
- var items = qsa(listItemQuery);
35
-
36
- for (var i = 0; i < items.length; i++) {
37
- observer.observe(items[i]);
38
- }
39
- }
40
-
41
- export { addIntersectionObserver };
@@ -1,35 +0,0 @@
1
- // @ts-check
2
-
3
- import { qsa } from './query.js';
4
-
5
- /**
6
- * Makes an entire block clickable based on a data-attribute, usually "data-destination"
7
- *
8
- * Example: You have a list of blog posts, including featured images. If you make the title
9
- * clickable, clicks on the image won't open the blog. Adding links to the images means
10
- * keyboard users have to tab twice as much to get through the list.
11
- *
12
- * Use clickable blocks to allow keyboard users to tab through the real links, but still
13
- * capture clicks elsewhere on the block.
14
- *
15
- * @param {string} dataAttributeName
16
- */
17
- function setClickableBlocks(dataAttributeName) {
18
- var listItems = qsa('[' + dataAttributeName + ']');
19
-
20
- for(var i = 0; i < listItems.length; i++) {
21
- var listItem = listItems[i];
22
- listItem.style.cursor = 'pointer';
23
- listItem.addEventListener('click', function (e) {
24
- var location = this.getAttribute(dataAttributeName);
25
-
26
- if (location) {
27
- e.preventDefault();
28
- document.location = location;
29
- return false;
30
- }
31
- });
32
- }
33
- }
34
-
35
- export { setClickableBlocks };
@@ -1,19 +0,0 @@
1
- // @ts-check
2
-
3
- /**
4
- *
5
- * @param {string} name
6
- * @param {{[key: string]: any}} detail
7
- * @param {Document | HTMLElement} [target]
8
- */
9
- function raiseEvent(name, detail, target) {
10
- if (!target) {
11
- target = document;
12
- }
13
-
14
- const event = new CustomEvent(name, { detail: detail});
15
- document.dispatchEvent(event);
16
- console.log('Event Raised', name, detail);
17
- }
18
-
19
- export { raiseEvent };
@@ -1,76 +0,0 @@
1
- // @ts-check
2
-
3
- import { qsa } from './query.js';
4
-
5
- /**
6
- * Gets first, last, and all focusable elements in the target
7
- *
8
- * For the supplied element, finds all the elements that can receive keyboard focus.
9
- *
10
- * Examples: a, button, input, textarea, select, and other valid interactive items
11
- * that haven't been disabled or hidden.
12
- *
13
- * @param {HTMLElement} target element
14
- * @returns {{first: HTMLElement, last: HTMLElement, all: HTMLElement[]}}
15
- */
16
- function getFocusableElement(target) {
17
- var focusElements = Array.from(
18
- qsa('a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])', target)
19
- ).filter(function(el) {
20
- return !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden');
21
- });
22
-
23
- return {
24
- first: focusElements[0],
25
- last: focusElements[focusElements.length -1],
26
- all: focusElements
27
- };
28
- }
29
-
30
- /**
31
- * Mechanism to trap focus
32
- *
33
- * @param {KeyboardEvent} event
34
- * @param {HTMLElement} focusItem
35
- * @returns
36
- */
37
- function trapFocus(event, focusItem) {
38
- switch (event.code.toLowerCase()) {
39
- case 'tab':
40
- event.preventDefault();
41
- focusItem.focus();
42
- return false;
43
- }
44
- }
45
-
46
- /**
47
- * Mechanism to trap focus (TAB)
48
- *
49
- * @param {KeyboardEvent} event
50
- * @param {HTMLElement} focusItem
51
- * @returns
52
- */
53
- function trapFocusForward(event, focusItem) {
54
- if (event.shiftKey) {
55
- return;
56
- }
57
-
58
- trapFocus(event, focusItem);
59
- }
60
-
61
- /**
62
- * Mechanism to trap tab (SHIFT + TAB)
63
- *
64
- * @param {KeyboardEvent} event
65
- * @param {HTMLElement} focusItem
66
- * @returns
67
- */
68
- function trapReverseFocus(event, focusItem) {
69
- if (!event.shiftKey) {
70
- return;
71
- }
72
-
73
- trapFocus(event, focusItem);
74
- }
75
-
76
- export { getFocusableElement, trapFocusForward, trapReverseFocus };
@@ -1,51 +0,0 @@
1
- // @ts-check
2
-
3
- import { qs, qsa } from './query.js';
4
-
5
- /**
6
- * Sets the navigation based on the current page
7
- *
8
- * Example: You have a navigation tree with an "About" page with several child items.
9
- * When the user is on the "About" page, or any child pages, the navigation should
10
- * be automatically expanded so the user can orient themselves within the site.
11
- *
12
- * @param {string} className
13
- */
14
- function setNavigationTree(className) {
15
- var summaries = qsa(className);
16
- var site = document.location.origin;
17
- var location = document.location.pathname;
18
-
19
- for (var i = 0; i < summaries.length; i++) {
20
- var summary = summaries[i];
21
- var anchorElement = /** @type {HTMLAnchorElement} */(qs('a', summary));
22
- var address = anchorElement.href.replace(site, '');
23
-
24
- if (location.startsWith(address)){
25
- summary.setAttribute('open', 'open');
26
- }
27
- }
28
- }
29
-
30
- /**
31
- * Highlights the current navigation item
32
- *
33
- * @param {string} navQuery
34
- * @param {string} selectedClass
35
- */
36
- function setNavigationItem(navQuery, selectedClass) {
37
- var anchors = qsa(navQuery);
38
- var site = document.location.origin;
39
- var location = document.location.pathname;
40
-
41
- for (var j = 0; j < anchors.length; j++) {
42
- var anchor = /** @type {HTMLAnchorElement} */ (anchors[j]);
43
- var href = anchor.href.replace(site, '');
44
-
45
- if (href === location) {
46
- anchor.classList.add(selectedClass);
47
- }
48
- }
49
- }
50
-
51
- export { setNavigationTree, setNavigationItem };
@@ -1,104 +0,0 @@
1
- // @ts-check
2
-
3
- import { qs } from './query.js';
4
- import { getFocusableElement, trapFocusForward, trapReverseFocus } from './focus.js';
5
-
6
- /**
7
- * Provides an overlay with the navigation for mobile users.
8
- *
9
- * Example: You have site navigation on the page, but demote it (closer to the footer) on mobile to avoid
10
- * the content being pushed below the fold. You provide an icon that bookmarks to the
11
- * navigation.
12
- *
13
- * The mobile navigation intercepts the bookmark link and opens the navigation in a modal
14
- * overlay, trapping keyboard focus until the overlay is closed.
15
- *
16
- * @param {string} iconSelector
17
- * @param {string} navigationSelector
18
- */
19
- function addMobileNavigation(iconSelector, navigationSelector, resizedEventName) {
20
- var icon = qs(iconSelector);
21
- var originalIcon = icon.innerHTML;
22
- var overlay = document.createElement('div');
23
- var dataOpen = 'data-open';
24
-
25
- icon.addEventListener('keydown', function(e) {
26
- if (icon.getAttribute(dataOpen) === dataOpen) {
27
- var focusElements = getFocusableElement(overlay);
28
- trapFocusForward(e, focusElements.first);
29
- trapReverseFocus(e, focusElements.last);
30
- }
31
- });
32
-
33
- function handleIconInteraction() {
34
- if (icon.dataset.open == dataOpen) {
35
- closeMobileMenu();
36
- } else {
37
- openMobileMenu();
38
- }
39
- }
40
-
41
- function openMobileMenu(){
42
- document.body.style.overflow = 'hidden';
43
- var navigation = qs(navigationSelector);
44
-
45
- overlay.innerHTML = navigation.outerHTML;
46
- overlay.className = 'overlay';
47
- overlay.style.display = 'block';
48
-
49
- // Modal Accessibility
50
- var title = qs('.site-nav-title', overlay);
51
- title.setAttribute('id', 'modal-title');
52
- title.setAttribute('tabindex', '-1');
53
- overlay.setAttribute('role', 'dialog');
54
- overlay.setAttribute('aria-modal', 'true');
55
- overlay.setAttribute('aria-labelled-by', 'modal-title');
56
-
57
- // Trap Focus to Visible Overlay
58
- var focusElements = getFocusableElement(overlay);
59
-
60
- focusElements.first.addEventListener('keydown', function(e) {
61
- trapReverseFocus(e, icon);
62
- })
63
- focusElements.last.addEventListener('keydown', function(e) {
64
- trapFocusForward(e, icon);
65
- });
66
-
67
- icon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg"
68
- width="40" height="40" viewBox="0 0 24 24" stroke-width="1.5"
69
- fill="none" stroke-linecap="round" stroke-linejoin="round">
70
- <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
71
- <line x1="18" y1="6" x2="6" y2="18" />
72
- <line x1="6" y1="6" x2="18" y2="18" />
73
- </svg>`;
74
-
75
- document.body.appendChild(overlay);
76
- icon.setAttribute(dataOpen, dataOpen);
77
- title.focus();
78
- }
79
-
80
- function closeMobileMenu() {
81
- document.body.style.overflow = 'auto';
82
-
83
- if (icon.getAttribute(dataOpen) === dataOpen) {
84
- overlay.innerHTML = '';
85
- overlay.style.display = 'none';
86
- document.body.removeChild(overlay);
87
- }
88
-
89
- icon.innerHTML = originalIcon;
90
- icon.removeAttribute(dataOpen);
91
- }
92
-
93
- icon.addEventListener('click', function (e) {
94
- e.preventDefault();
95
- handleIconInteraction();
96
- return false;
97
- });
98
-
99
- document.addEventListener(resizedEventName, function () {
100
- closeMobileMenu();
101
- })
102
- }
103
-
104
- export { addMobileNavigation };
@@ -1,54 +0,0 @@
1
- // @ts-check
2
-
3
- import { qs, qsa } from './query.js';
4
-
5
- /**
6
- * Makes an existing navigation element sticky
7
- *
8
- * Example: If the existing navigation is not as tall as the content, the
9
- * navigation will stick to the top, allowing the user to see it as
10
- * they scroll through the article
11
- *
12
- * @param {string} headerSelector
13
- * @param {string} navigationSelector
14
- * @param {string} navigationListSelector
15
- */
16
- function addStickyNavigation(headerSelector, navigationSelector, navigationListSelector, resizedEventName) {
17
- function setNavigationMode() {
18
- var header = qs(headerSelector);
19
- var navigation = qs(navigationSelector);
20
- var navigationList = qs(navigationListSelector);
21
-
22
- var buffer = 50;
23
- var className = 'sticky';
24
-
25
- var dimensions = {
26
- browserHeight: window.innerHeight,
27
- browserWidth: window.innerWidth,
28
- headerHeight: header.clientHeight,
29
- navigationHeight: navigationList.clientHeight
30
- };
31
-
32
- // Only enable sticky mode if the menu will fit vertically
33
- // && where the browser is more than 860px wide
34
- if (dimensions.navigationHeight < ((dimensions.browserHeight - dimensions.headerHeight) - buffer)
35
- && dimensions.browserWidth > 860) {
36
- console.log('Navigation: Sticky Mode');
37
- navigation.classList.add(className)
38
- navigation.style.top = dimensions.headerHeight.toString() + 'px';
39
- } else {
40
- console.log('Navigation: Fixed Mode');
41
- navigation.classList.remove(className);
42
- }
43
- }
44
-
45
- setNavigationMode();
46
-
47
- document.addEventListener(resizedEventName, function(e) {
48
- if (e.detail && e.detail.change && e.detail.change.height != 0) {
49
- setNavigationMode();
50
- }
51
- });
52
- }
53
-
54
- export { addStickyNavigation };
@@ -1,42 +0,0 @@
1
- // @ts-check
2
-
3
- /**
4
- * Utility for query selector
5
- *
6
- * @param {string} query
7
- * @param {HTMLElement | null} [container]
8
- * @returns {HTMLElement}
9
- */
10
- function qs(query, container) {
11
- var target = (container)
12
- ? container
13
- : document;
14
-
15
- /** @type {HTMLElement | null} */
16
- var result = target.querySelector(query);
17
-
18
- if (result) {
19
- return result;
20
- }
21
-
22
- throw new Error(`No element ${query}`);
23
- }
24
-
25
- /**
26
- * Utility for query selector all
27
- *
28
- * @param {string} query
29
- * @param {HTMLElement | null} [container]
30
- * @returns {NodeListOf<any>}
31
- */
32
- function qsa(query, container) {
33
- var target = (container)
34
- ? container
35
- : document;
36
-
37
- /** @type {NodeListOf<HTMLElement>} */
38
- var result = target.querySelectorAll(query);
39
- return result;
40
- }
41
-
42
- export { qs, qsa };
@@ -1,43 +0,0 @@
1
- // @ts-check
2
-
3
- import { raiseEvent } from './events.js';
4
-
5
- var resizeEventName = 'resize';
6
- var resizedEventName = 'resized';
7
-
8
- var width = window.innerWidth;
9
- var height = window.innerHeight;
10
-
11
- /**
12
- * Adds a de-bounced "resized" event, so you can listen to:
13
- * document.addEventListener('resized', <handler>);
14
- *
15
- * @returns {string}
16
- */
17
- function addResizedEvent() {
18
- var debounce = null;
19
-
20
- function resizeEnd(e) {
21
- window.clearTimeout(debounce);
22
- debounce = window.setTimeout(raiseResizeEvent, 500);
23
- }
24
-
25
- function raiseResizeEvent() {
26
- var change = {
27
- width: window.innerWidth - width,
28
- height: window.innerHeight - height
29
- };
30
-
31
- width = window.innerWidth;
32
- height = window.innerHeight;
33
-
34
-
35
- raiseEvent(resizedEventName, { change: change });
36
- }
37
-
38
- window.addEventListener(resizeEventName, resizeEnd);
39
-
40
- return resizedEventName;
41
- }
42
-
43
- export { addResizedEvent };
@@ -1,49 +0,0 @@
1
- // @ts-check
2
-
3
- /**
4
- * Looks for a search within a string
5
- *
6
- * @param {string} string
7
- * @param {string} search
8
- * @returns
9
- */
10
- function contains(string, search) {
11
- return string.indexOf(search) > -1;
12
- }
13
-
14
- /**
15
- * Simplifies a string to plain lower case, removing diacritic characters and hyphens
16
- * This means a search for "co-op" will be found in "COOP" and "Café" will be found in "cafe"
17
- * @param {string} string
18
- * @returns
19
- */
20
- function sanitise(string) {
21
- // @ts-ignore
22
- if (String.prototype.normalize) {
23
- // Reduces diacritic characters to plain characters
24
- string.trim().normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().replace(/-/g, '');
25
- }
26
-
27
- // Some browsers can't normalise strings
28
- return string.trim().toLowerCase().replace(/-/g, '');
29
- }
30
-
31
- /**
32
- * Sets a minimum length for a search
33
- * @param {string} string
34
- * @returns
35
- */
36
- function isLongEnough(string) {
37
- return string.length > 1;
38
- }
39
-
40
- /**
41
- * Splits a sentence into individual search terms
42
- * @param {string} string
43
- * @returns
44
- */
45
- function explode(string) {
46
- return string.split(' ').filter(isLongEnough).map(sanitise);
47
- }
48
-
49
- export { contains, sanitise, explode };
data/assets/js/search.js DELETED
@@ -1,154 +0,0 @@
1
- // @ts-check
2
-
3
- import { qs } from './modules/query.js';
4
- import { raiseEvent } from './modules/events.js';
5
- import { contains, sanitise, explode } from './modules/string.js';
6
-
7
- var haystack = [];
8
- var needles = [];
9
- var currentQuery = null;
10
-
11
- var ready = false;
12
- var scrolled = false;
13
-
14
- /**
15
- * Performs the search
16
- * @param {string} s
17
- * @returns
18
- */
19
- function search(s) {
20
- needles = [];
21
-
22
- // Clean the input
23
- var cleanQuery = sanitise(s);
24
-
25
- if (currentQuery === cleanQuery) {
26
- return;
27
- }
28
-
29
- raiseEvent('searched', { search: s });
30
-
31
- currentQuery = cleanQuery;
32
-
33
- var queryTerms = explode(currentQuery);
34
-
35
- for (var i = 0; i < haystack.length; i++) {
36
- var item = haystack[i];
37
-
38
- item.score = 0;
39
-
40
- var title = sanitise(item.title);
41
- var category = sanitise(item.category);
42
- var tags = sanitise(item.tags);
43
-
44
- for (var j = 0; j < queryTerms.length; j++) {
45
- var term = queryTerms[j];
46
-
47
- if (contains(title, term)) {
48
- item.score = item.score + 10;
49
- }
50
-
51
- if (contains(category, term)) {
52
- item.score = item.score + 5;
53
- }
54
-
55
- if (contains(tags, term)) {
56
- item.score = item.score + 5;
57
- }
58
- }
59
-
60
- if (item.score > 0) {
61
- needles.push(item);
62
- }
63
- }
64
-
65
- needles.sort(function (a, b){
66
- return b.score - a.score;
67
- });
68
-
69
- var results = qs('#site-search-results');
70
-
71
- if (results == null) {
72
- throw new Error('Cannot find #site-search-results');
73
- }
74
-
75
- var ol = document.createElement('ol');
76
- ol.className = 'site-search-results';
77
-
78
- var limit = Math.min(needles.length, 12)
79
-
80
- for (var i = 0; i < limit; i++) {
81
- var needle = needles[i];
82
-
83
- var a = document.createElement('a');
84
- a.innerHTML = needle.title;
85
- a.href = needle.url;
86
-
87
- var li = document.createElement('li');
88
- li.appendChild(a);
89
-
90
- ol.appendChild(li);
91
- }
92
-
93
- var h2 = document.createElement('h2');
94
- h2.innerHTML = needles.length === 0
95
- ? results.dataset.emptytitle || 'No Results'
96
- : results.dataset.title || 'Results';
97
-
98
- results.innerHTML = '';
99
- results.appendChild(h2);
100
- results.appendChild(ol);
101
- }
102
-
103
- var debounceTimer;
104
-
105
- function debounceSearch() {
106
- var input = /** @type {HTMLInputElement} */(qs('#site-search-query'));
107
-
108
- if (input == null) {
109
- throw new Error('Cannot find #site-search-query');
110
- }
111
-
112
- var s = input.value;
113
-
114
- window.clearTimeout(debounceTimer);
115
- debounceTimer = window.setTimeout(function () {
116
- if (ready) {
117
- search(s);
118
- }
119
- }, 400);
120
- }
121
-
122
- fetch('/search.json')
123
- .then(function (response) {
124
- return response.json();
125
- })
126
- .then(function (data) {
127
- haystack = data;
128
- ready = true;
129
-
130
- var siteSearch = qs('#site-search');
131
- var siteSearchQuery = qs('#site-search-query');
132
-
133
- if (siteSearch == null || siteSearchQuery == null) {
134
- throw new Error('Cannot find #site-search or #site-search-query');
135
- }
136
-
137
- siteSearch.addEventListener('submit', function (e) {
138
- e.preventDefault();
139
- debounceSearch();
140
- return false;
141
- });
142
-
143
- siteSearchQuery.addEventListener('keyup', function (e) {
144
- e.preventDefault();
145
- if (!scrolled) {
146
- scrolled = true;
147
- this.scrollIntoView(true);
148
- }
149
- debounceSearch();
150
- return false;
151
- });
152
-
153
- console.log('Search ready');
154
- });
data/assets/svg/down.svg DELETED
@@ -1,38 +0,0 @@
1
- <?xml version="1.0" encoding="iso-8859-1"?>
2
- <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
- <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
- viewBox="0 0 330 330" style="enable-background:new 0 0 330 330;" xml:space="preserve">
5
- <path id="XMLID_225_" d="M325.607,79.393c-5.857-5.857-15.355-5.858-21.213,0.001l-139.39,139.393L25.607,79.393
6
- c-5.857-5.857-15.355-5.858-21.213,0.001c-5.858,5.858-5.858,15.355,0,21.213l150.004,150c2.813,2.813,6.628,4.393,10.606,4.393
7
- s7.794-1.581,10.606-4.394l149.996-150C331.465,94.749,331.465,85.251,325.607,79.393z"/>
8
- <g>
9
- </g>
10
- <g>
11
- </g>
12
- <g>
13
- </g>
14
- <g>
15
- </g>
16
- <g>
17
- </g>
18
- <g>
19
- </g>
20
- <g>
21
- </g>
22
- <g>
23
- </g>
24
- <g>
25
- </g>
26
- <g>
27
- </g>
28
- <g>
29
- </g>
30
- <g>
31
- </g>
32
- <g>
33
- </g>
34
- <g>
35
- </g>
36
- <g>
37
- </g>
38
- </svg>
data/favicon.ico DELETED
Binary file
data/robots.txt DELETED
@@ -1,5 +0,0 @@
1
- ---
2
- layout: null
3
- ---
4
- Sitemap: {{ site.url }}{{ '/sitemap.xml' | prepend: site.baseurl }}
5
- User-agent: *
data/search.json DELETED
@@ -1,36 +0,0 @@
1
- ---
2
- layout: null
3
- ---
4
- {%- assign pages = site.pages | where: 'nav-search', 'true' %}
5
- {%- assign posts = site.posts | where: 'nav-search', 'true' %}
6
- {%- assign authors = site.authors | where: 'nav-search', 'true' %}
7
- [
8
- {% for page in pages %}{
9
- "title" : "{{ page.title | escape }}",
10
- "category" : "{{ page.category }}",
11
- "tags" : "{{ page.tags | join: ' ' }} {{page.keywords}}",
12
- "url" : "{{ site.baseurl }}{{ page.url }}",
13
- "date" : "{{ page.date }}"
14
- }{%- unless forloop.last %},{% endunless %}
15
- {%- endfor %}
16
- {%- if authors.size > 0 %},{% endif %}
17
- {%- for author in authors %}{
18
- "title" : "{{ author.name | escape }}",
19
- "category" : "{{ author.category }}",
20
- "tags" : "{{ page.tags | join: ' ' }} {{page.keywords}}",
21
- "url" : "{{ site.baseurl }}{{ author.url }}",
22
- "date" : "{{ author.date }}"
23
- }{%- unless forloop.last %},{% endunless %}
24
- {%- endfor %}
25
- {%- if posts.size > 0 %},{% endif %}
26
- {%- for post in posts %}{
27
- "title" : "{{ post.title | escape }}",
28
- "category" : "{{ post.category }}",
29
- "tags" : "{{ page.tags | join: ' ' }} {{page.keywords}}",
30
- "url" : "{{ site.baseurl }}{{ post.url }}",
31
- "date" : "{{ post.date }}"
32
- }
33
- {%- unless forloop.last %},{% endunless %}
34
- {%- endfor %}
35
- ]
36
-
data/sitemap.xml DELETED
@@ -1,15 +0,0 @@
1
- ---
2
- layout: null
3
- ---
4
- <?xml version="1.0" encoding="UTF-8"?>
5
- <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
6
- <sitemap>
7
- <loc>{{ site.url }}{{ '/sitemap/pages.xml' | prepend: site.baseurl }}</loc>
8
- </sitemap>
9
- <sitemap>
10
- <loc>{{ site.url }}{{ '/sitemap/posts.xml' | prepend: site.baseurl }}</loc>
11
- </sitemap>
12
- <sitemap>
13
- <loc>{{ site.url }}{{ '/sitemap/authors.xml' | prepend: site.baseurl }}</loc>
14
- </sitemap>
15
- </sitemapindex>