guides_style_18f 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/guides_style_18f.rb +1 -0
- data/lib/guides_style_18f/breadcrumbs.rb +28 -0
- data/lib/guides_style_18f/generated_nodes.rb +63 -0
- data/lib/guides_style_18f/generated_pages.rb +38 -0
- data/lib/guides_style_18f/generator.rb +13 -2
- data/lib/guides_style_18f/includes/breadcrumbs.html +8 -0
- data/lib/guides_style_18f/layouts.rb +18 -4
- data/lib/guides_style_18f/layouts/generated/home-redirect.html +6 -0
- data/lib/guides_style_18f/layouts/search-results.html +4 -0
- data/lib/guides_style_18f/namespace_flattener.rb +39 -0
- data/lib/guides_style_18f/navigation.rb +22 -12
- data/lib/guides_style_18f/sass/_guides_style_18f_main.scss +0 -31
- data/lib/guides_style_18f/version.rb +1 -1
- metadata +9 -4
- data/assets/png/search.png +0 -0
- data/assets/svg/search.svg +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c8d5fbef0f481d350fa56a00fb2913d58d78cf7
|
4
|
+
data.tar.gz: e23bc6918f8c88349835ff681bf110a556f64547
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45ca1f1854395173e91cfd614555e1afb3a568d463a1fea19e070de56ac113707d685bbc2831b0d42840bce43d7edf5764ab382a148efc64e79bdec857172436
|
7
|
+
data.tar.gz: bf04bff3b13442dc3ab69648f8efd181cca82d192d9cded13c03f94a0afa2530ee15965eaf0f7621388fff8cd84f6ef1cb41fef60d7bc1e27ac3002204388c0b
|
data/lib/guides_style_18f.rb
CHANGED
@@ -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
|
9
|
-
Assets.copy_to_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
|
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
|
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
|
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,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"
|
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
|
-
|
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
|
-
|
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
|
-
|
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 <<
|
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
|
-
|
208
|
-
unless
|
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
|
-
|
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
|
}
|
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.
|
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-
|
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
|
data/assets/png/search.png
DELETED
Binary file
|
data/assets/svg/search.svg
DELETED
@@ -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>
|