guides_style_18f 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +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>
|