fenton-jekyll-boilerplate 0.0.4 → 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 +4 -4
- data/_data/language.yaml +3 -0
- metadata +2 -29
- data/_authors/steve-fenton.md +0 -21
- data/_plugins/article_paging.rb +0 -223
- data/_plugins/breadcrumbs.rb +0 -109
- data/_plugins/liquid_language.rb +0 -38
- data/_plugins/liquid_regex.rb +0 -17
- data/_plugins/markdown.rb +0 -17
- data/assets/img/2022/09/surface-accessories-700.webp +0 -0
- data/assets/img/2022/09/surface-accessories.webp +0 -0
- data/assets/img/authors/steve-fenton.webp +0 -0
- data/assets/img/jekyll-and-hyde.webp +0 -0
- data/assets/img/lighthouse-scores.webp +0 -0
- data/assets/js/main.js +0 -18
- data/assets/js/modules/animation.js +0 -41
- data/assets/js/modules/click-blocks.js +0 -35
- data/assets/js/modules/events.js +0 -19
- data/assets/js/modules/focus.js +0 -76
- data/assets/js/modules/nav-expand.js +0 -51
- data/assets/js/modules/nav-mobile.js +0 -104
- data/assets/js/modules/nav-sticky.js +0 -54
- data/assets/js/modules/query.js +0 -42
- data/assets/js/modules/resizing.js +0 -43
- data/assets/js/modules/string.js +0 -49
- data/assets/js/search.js +0 -154
- data/assets/svg/down.svg +0 -38
- data/favicon.ico +0 -0
- data/robots.txt +0 -5
- data/sitemap.xml +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e62bb1bc58c9c86331a322e70bb7d8b16ce251dcc572d89361617e9f994bf53
|
4
|
+
data.tar.gz: 1d25e7531752be6c2690eb81843a24b1dce2a343381df86546e9bf6a0465cd40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37d7589c85c4853b70fff3699c32400a1ce2d7a1374804270bac303be382bedb22c7403ffaad665b631206812b7142fdd1d15479db92c4d185204981fafaeae5
|
7
|
+
data.tar.gz: 14a32ca59b666e353ec58d6274d02eda8eace7f4910564009eadf3a663aac6885bbb8adff82f4df3b1c6e2bcda39419fabf9146503c4d456de036a0bcb8a927d
|
data/_data/language.yaml
CHANGED
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.
|
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-
|
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,27 +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
|
-
- sitemap.xml
|
100
73
|
homepage: https://jekyll.stevefenton.co.uk/
|
101
74
|
licenses:
|
102
75
|
- Apache-2.0
|
data/_authors/steve-fenton.md
DELETED
@@ -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.
|
data/_plugins/article_paging.rb
DELETED
@@ -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
|
data/_plugins/breadcrumbs.rb
DELETED
@@ -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
|
data/_plugins/liquid_language.rb
DELETED
@@ -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)
|
data/_plugins/liquid_regex.rb
DELETED
@@ -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
|
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 };
|
data/assets/js/modules/events.js
DELETED
@@ -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 };
|
data/assets/js/modules/focus.js
DELETED
@@ -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 };
|
data/assets/js/modules/query.js
DELETED
@@ -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 };
|
data/assets/js/modules/string.js
DELETED
@@ -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
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>
|