weaver 0.8.0 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Rakefile +1 -1
- data/bin/console +3 -3
- data/exe/weaver +112 -122
- data/lib/weaver.rb +24 -2654
- data/lib/weaver/element_types/accordion.rb +122 -0
- data/lib/weaver/element_types/action.rb +38 -0
- data/lib/weaver/element_types/code.rb +81 -0
- data/lib/weaver/element_types/dynamic_table.rb +324 -0
- data/lib/weaver/element_types/dynamic_table_cell.rb +16 -0
- data/lib/weaver/element_types/form.rb +47 -0
- data/lib/weaver/element_types/form_elements.rb +297 -0
- data/lib/weaver/element_types/javascript_object.rb +28 -0
- data/lib/weaver/element_types/modal_dialog.rb +68 -0
- data/lib/weaver/element_types/panel.rb +124 -0
- data/lib/weaver/element_types/row.rb +117 -0
- data/lib/weaver/element_types/tabs.rb +75 -0
- data/lib/weaver/element_types/textfield_javascript.rb +58 -0
- data/lib/weaver/elements.rb +406 -0
- data/lib/weaver/page_types/center_page.rb +25 -0
- data/lib/weaver/page_types/empty_page.rb +26 -0
- data/lib/weaver/page_types/nav_page.rb +39 -0
- data/lib/weaver/page_types/nonnav_page.rb +35 -0
- data/lib/weaver/page_types/page.rb +234 -0
- data/lib/weaver/page_types/raw_page.rb +17 -0
- data/lib/weaver/page_types/sidenav_page.rb +119 -0
- data/lib/weaver/page_types/structured_page.rb +19 -0
- data/lib/weaver/page_types/topnav_page.rb +167 -0
- data/lib/weaver/version.rb +1 -1
- data/lib/weaver/weave.rb +44 -0
- data/weaver.gemspec +27 -24
- metadata +37 -13
@@ -0,0 +1,25 @@
|
|
1
|
+
module Weaver
|
2
|
+
# Page where all content is centered
|
3
|
+
class CenterPage < Page
|
4
|
+
def initialize(title, global_settings, options, &block)
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def generate(level)
|
9
|
+
elem = Elements.new(self, {})
|
10
|
+
elem.instance_eval(&@block)
|
11
|
+
|
12
|
+
@body_class = 'gray-bg'
|
13
|
+
@content = <<-CONTENT
|
14
|
+
<div class="middle-box text-center animated fadeInDown">
|
15
|
+
<div>
|
16
|
+
#{elem.generate}
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
CONTENT
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Weaver::Weave.register_page_type(:center_page, Weaver::CenterPage)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Weaver
|
2
|
+
# Empty page
|
3
|
+
class EmptyPage < Page
|
4
|
+
def initialize(title, global_settings, options, &block)
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def generate(level)
|
9
|
+
elem = Elements.new(self, {})
|
10
|
+
elem.instance_eval(&@block)
|
11
|
+
@body_class = 'gray-bg'
|
12
|
+
@content = <<-CONTENT
|
13
|
+
<div id="wrapper">
|
14
|
+
<div class="wrapper-content">
|
15
|
+
<div class="container">
|
16
|
+
#{elem.generate}
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
CONTENT
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Weaver::Weave.register_page_type(:empty_page, Weaver::EmptyPage)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'weaver/page_types/structured_page'
|
2
|
+
module Weaver
|
3
|
+
# Pages with navigation menus
|
4
|
+
class NavPage < StructuredPage
|
5
|
+
def initialize(title, global_settings, options, &block)
|
6
|
+
super
|
7
|
+
@menu = Menu.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def menu(&block)
|
11
|
+
@menu.instance_eval(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def brand(text, link = '/')
|
15
|
+
@brand = text
|
16
|
+
@brand_link = link
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Menu used in nav pages
|
21
|
+
class Menu
|
22
|
+
attr_accessor :items
|
23
|
+
def initialize
|
24
|
+
@items = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def nav(name, icon = :question, url = nil, options = {}, &block)
|
28
|
+
if url && !block
|
29
|
+
@items << { name: name, link: url, icon: icon, options: options }
|
30
|
+
elsif block
|
31
|
+
menu = Menu.new
|
32
|
+
menu.instance_eval(&block)
|
33
|
+
@items << { name: name, menu: menu, icon: icon, options: options }
|
34
|
+
else
|
35
|
+
@items << { name: name, link: '#', icon: icon, options: options }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'weaver/page_types/structured_page'
|
2
|
+
module Weaver
|
3
|
+
# Page with no navigation bar
|
4
|
+
class NonNavPage < StructuredPage
|
5
|
+
def initialize(title, global_settings, options, &block)
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def generate(level)
|
10
|
+
instance_eval &@block
|
11
|
+
rows = @rows.map do |row|
|
12
|
+
<<-ENDROW
|
13
|
+
<div class="row #{row.extra_classes}">
|
14
|
+
#{row.generate}
|
15
|
+
</div>
|
16
|
+
ENDROW
|
17
|
+
end.join
|
18
|
+
|
19
|
+
@body_class = 'gray-bg'
|
20
|
+
|
21
|
+
@content = <<-CONTENT
|
22
|
+
<div id="wrapper">
|
23
|
+
<div class="wrapper-content">
|
24
|
+
<div class="container">
|
25
|
+
#{rows}
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
CONTENT
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Weaver::Weave.register_page_type(:nonnav_page, Weaver::NonNavPage)
|
@@ -0,0 +1,234 @@
|
|
1
|
+
module Weaver
|
2
|
+
# Base page class
|
3
|
+
class Page
|
4
|
+
attr_accessor :scripts, :onload_scripts
|
5
|
+
|
6
|
+
def initialize(title, global_settings, options, &block)
|
7
|
+
@title = title
|
8
|
+
@content = ''
|
9
|
+
@body_class = nil
|
10
|
+
@anchors = {}
|
11
|
+
@global_settings = global_settings
|
12
|
+
@options = options
|
13
|
+
@scripts = []
|
14
|
+
@top_content = ''
|
15
|
+
|
16
|
+
@scripts_once = {}
|
17
|
+
@onload_scripts = []
|
18
|
+
|
19
|
+
@requested_scripts = {}
|
20
|
+
@requested_css = {}
|
21
|
+
|
22
|
+
@background = Elements.new(self, @anchors)
|
23
|
+
|
24
|
+
@block = Proc.new &block
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_anchor(name)
|
28
|
+
@anchors[name] = [] unless @anchors[name]
|
29
|
+
|
30
|
+
anchor_array = @anchors[name]
|
31
|
+
|
32
|
+
anchor_name = "#{name}#{anchor_array.length}"
|
33
|
+
anchor_array << anchor_name
|
34
|
+
|
35
|
+
anchor_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def root
|
39
|
+
@global_settings[:root]
|
40
|
+
end
|
41
|
+
|
42
|
+
def request_js(path)
|
43
|
+
@requested_scripts[path] = true
|
44
|
+
end
|
45
|
+
|
46
|
+
def request_css(path)
|
47
|
+
@requested_css[path] = true
|
48
|
+
end
|
49
|
+
|
50
|
+
def on_page_load(script)
|
51
|
+
@onload_scripts << script
|
52
|
+
end
|
53
|
+
|
54
|
+
def write_script_once(script)
|
55
|
+
@scripts_once[script] = true
|
56
|
+
end
|
57
|
+
|
58
|
+
def background(&block)
|
59
|
+
@background.instance_eval(&block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def top(&block)
|
63
|
+
elem = Elements.new(self, @anchors)
|
64
|
+
elem.instance_eval(&block)
|
65
|
+
|
66
|
+
@top_content = elem.generate
|
67
|
+
end
|
68
|
+
|
69
|
+
def generate(back_folders, options = {})
|
70
|
+
if @options[:cache_file]
|
71
|
+
expired = @options[:cache_expired]
|
72
|
+
cache_exist = File.exist?("cache/cachedpage#{@options[:cache_file]}")
|
73
|
+
|
74
|
+
if cache_exist && !expired
|
75
|
+
puts "Weaver Hit cache for file: #{@options[:cache_file]}"
|
76
|
+
puts "- expired: #{expired}"
|
77
|
+
puts "- cache_exist: #{cache_exist}"
|
78
|
+
return File.read("cache/cachedpage#{@options[:cache_file]}")
|
79
|
+
end
|
80
|
+
puts "Weaver Miss cache for file: #{@options[:cache_file]}"
|
81
|
+
puts "- expired: #{expired}"
|
82
|
+
puts "- cache_exist: #{cache_exist}"
|
83
|
+
end
|
84
|
+
|
85
|
+
scripts = @scripts.join("\n")
|
86
|
+
|
87
|
+
mod = '../' * back_folders
|
88
|
+
|
89
|
+
style = <<-ENDSTYLE
|
90
|
+
<link href="#{mod}css/style.css" rel="stylesheet">
|
91
|
+
ENDSTYLE
|
92
|
+
|
93
|
+
style = '' if options[:style] == :empty
|
94
|
+
|
95
|
+
body_tag = '<body>'
|
96
|
+
|
97
|
+
body_tag = "<body class='#{@body_class}'>" if @body_class
|
98
|
+
|
99
|
+
loading_bar = ''
|
100
|
+
loading_bar = "<script src='#{mod}js/plugins/pace/pace.min.js'></script>" if @loading_bar_visible
|
101
|
+
|
102
|
+
extra_scripts = @requested_scripts.map do |key, _value|
|
103
|
+
<<-SCRIPT_DECL
|
104
|
+
<script src="#{mod}#{key}"></script>
|
105
|
+
SCRIPT_DECL
|
106
|
+
end.join "\n"
|
107
|
+
|
108
|
+
extra_css = @requested_css.map do |key, _value|
|
109
|
+
<<-STYLESHEET_DECL
|
110
|
+
<link href="#{mod}#{key}" rel="stylesheet">
|
111
|
+
STYLESHEET_DECL
|
112
|
+
end.join "\n"
|
113
|
+
|
114
|
+
extra_one_time_scripts = @scripts_once.map do |key, _value|
|
115
|
+
<<-SCRIPT_DECL
|
116
|
+
#{key}
|
117
|
+
SCRIPT_DECL
|
118
|
+
end.join "\n"
|
119
|
+
|
120
|
+
onload_scripts = @onload_scripts.map do |value|
|
121
|
+
<<-SCRIPT_DECL
|
122
|
+
#{value}
|
123
|
+
SCRIPT_DECL
|
124
|
+
end.join "\n"
|
125
|
+
|
126
|
+
extra_stuff = ''
|
127
|
+
|
128
|
+
if @global_settings[:mixpanel_token]
|
129
|
+
extra_stuff += '<!-- start Mixpanel --><script type="text/javascript">(function(e,a){if(!a.__SV){var b=window;try{var c,l,i,j=b.location,g=j.hash;c=function(a,b){return(l=a.match(RegExp(b+"=([^&]*)")))?l[1]:null};g&&c(g,"state")&&(i=JSON.parse(decodeURIComponent(c(g,"state"))),"mpeditor"===i.action&&(b.sessionStorage.setItem("_mpcehash",g),history.replaceState(i.desiredHash||"",e.title,j.pathname+j.search)))}catch(m){}var k,h;window.mixpanel=a;a._i=[];a.init=function(b,c,f){function e(b,a){var c=a.split(".");2==c.length&&(b=b[c[0]],a=c[1]);b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments,
|
130
|
+
0)))}}var d=a;"undefined"!==typeof f?d=a[f]=[]:f="mixpanel";d.people=d.people||[];d.toString=function(b){var a="mixpanel";"mixpanel"!==f&&(a+="."+f);b||(a+=" (stub)");return a};d.people.toString=function(){return d.toString(1)+".people (stub)"};k="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset opt_in_tracking opt_out_tracking has_opted_in_tracking has_opted_out_tracking clear_opt_in_out_tracking people.set people.set_once people.unset people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
|
131
|
+
for(h=0;h<k.length;h++)e(d,k[h]);a._i.push([b,c,f])};a.__SV=1.2;b=e.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn4.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn4.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn4.mxpnl.com/libs/mixpanel-2-latest.min.js";c=e.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)}})(document,window.mixpanel||[]);
|
132
|
+
mixpanel.init("' + @global_settings[:mixpanel_token] + '");</script><!-- end Mixpanel -->'
|
133
|
+
end
|
134
|
+
|
135
|
+
if @global_settings[:google_tracking_code]
|
136
|
+
extra_stuff += <<-GOOGLE
|
137
|
+
<!-- Global site tag (gtag.js) - Google Analytics -->
|
138
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=#{@global_settings[:google_tracking_code]}"></script>
|
139
|
+
<script>
|
140
|
+
window.dataLayer = window.dataLayer || [];
|
141
|
+
function gtag(){dataLayer.push(arguments);}
|
142
|
+
gtag('js', new Date());
|
143
|
+
|
144
|
+
gtag('config', '#{@global_settings[:google_tracking_code]}');
|
145
|
+
</script>
|
146
|
+
GOOGLE
|
147
|
+
end
|
148
|
+
|
149
|
+
result = <<-SKELETON
|
150
|
+
<!DOCTYPE html>
|
151
|
+
<html>
|
152
|
+
<!-- Generated using weaver: https://github.com/davidsiaw/weaver -->
|
153
|
+
<head>
|
154
|
+
|
155
|
+
<meta charset="utf-8">
|
156
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
157
|
+
|
158
|
+
<title>#{@title}</title>
|
159
|
+
|
160
|
+
<link href="#{mod}css/bootstrap.min.css" rel="stylesheet">
|
161
|
+
<link href="#{mod}font-awesome/css/font-awesome.css" rel="stylesheet">
|
162
|
+
<link href="#{mod}css/plugins/iCheck/custom.css" rel="stylesheet">
|
163
|
+
|
164
|
+
#{style}
|
165
|
+
|
166
|
+
<link href="#{mod}css/animate.css" rel="stylesheet">
|
167
|
+
|
168
|
+
#{extra_css}
|
169
|
+
|
170
|
+
#{extra_stuff}
|
171
|
+
|
172
|
+
</head>
|
173
|
+
|
174
|
+
#{body_tag}
|
175
|
+
|
176
|
+
<div id="background" style="z-index: -999; position:absolute; left:0px; right:0px; width:100%; height:100%">
|
177
|
+
#{@background.generate}
|
178
|
+
</div>
|
179
|
+
|
180
|
+
<div id="content" style="z-index: 0">
|
181
|
+
#{@top_content}
|
182
|
+
#{@content}
|
183
|
+
</div>
|
184
|
+
|
185
|
+
<!-- Mainly scripts -->
|
186
|
+
<script src="#{mod}js/jquery-3.1.1.min.js"></script>
|
187
|
+
<script src="#{mod}js/jquery-ui-1.10.4.min.js"></script>
|
188
|
+
<script src="#{mod}js/bootstrap.min.js"></script>
|
189
|
+
<script src="#{mod}js/plugins/metisMenu/jquery.metisMenu.js"></script>
|
190
|
+
<script src="#{mod}js/plugins/slimscroll/jquery.slimscroll.min.js"></script>
|
191
|
+
<script type="text/x-mathjax-config">
|
192
|
+
MathJax.Hub.Config({
|
193
|
+
asciimath2jax: {
|
194
|
+
delimiters: [['$$$MATH$$$','$$$ENDMATH$$$']]
|
195
|
+
}
|
196
|
+
});
|
197
|
+
</script>
|
198
|
+
<script src="#{mod}js/MathJax/MathJax.js?config=AM_HTMLorMML-full" async></script>
|
199
|
+
|
200
|
+
#{extra_scripts}
|
201
|
+
|
202
|
+
|
203
|
+
<!-- Custom and plugin javascript -->
|
204
|
+
<script src="#{mod}js/weaver.js"></script>
|
205
|
+
#{loading_bar}
|
206
|
+
|
207
|
+
<script>
|
208
|
+
#{scripts}
|
209
|
+
#{extra_one_time_scripts}
|
210
|
+
|
211
|
+
$( document ).ready(function() {
|
212
|
+
|
213
|
+
#{onload_scripts}
|
214
|
+
|
215
|
+
});
|
216
|
+
</script>
|
217
|
+
|
218
|
+
|
219
|
+
|
220
|
+
</body>
|
221
|
+
|
222
|
+
</html>
|
223
|
+
|
224
|
+
SKELETON
|
225
|
+
|
226
|
+
if @options[:cache_file]
|
227
|
+
FileUtils.mkdir_p 'cache'
|
228
|
+
File.write("cache/cachedpage#{@options[:cache_file]}", result)
|
229
|
+
end
|
230
|
+
|
231
|
+
result
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Weaver
|
2
|
+
# Raw oage with no html
|
3
|
+
class RawPage < Page
|
4
|
+
def initialize(title, global_settings, options, &block)
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def generate(_back_folders, _options = {})
|
9
|
+
elem = Elements.new(self, {})
|
10
|
+
elem.instance_eval(&@block)
|
11
|
+
|
12
|
+
elem.generate
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Weaver::Weave.register_page_type(:raw_page, Weaver::RawPage)
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'weaver/page_types/nav_page'
|
2
|
+
module Weaver
|
3
|
+
class SideNavPage < NavPage
|
4
|
+
def initialize(title, global_settings, options, &block)
|
5
|
+
super
|
6
|
+
@body_class = "#{@body_class} fixed-sidebar"
|
7
|
+
end
|
8
|
+
|
9
|
+
def keep_icons_when_hidden
|
10
|
+
arr = @body_class.to_s.split(' ')
|
11
|
+
@body_class = arr.reject { |x| x == 'fixed-sidebar' }.join(' ')
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate(level)
|
15
|
+
instance_eval &@block
|
16
|
+
rows = @rows.map do |row|
|
17
|
+
<<-ENDROW
|
18
|
+
<div class="row #{row.extra_classes}" style="#{row.style}">
|
19
|
+
#{row.generate}
|
20
|
+
</div>
|
21
|
+
ENDROW
|
22
|
+
end.join
|
23
|
+
|
24
|
+
menu = @menu
|
25
|
+
|
26
|
+
navigation = Elements.new(self, @anchors)
|
27
|
+
navigation.instance_eval do
|
28
|
+
menu.items.each do |item|
|
29
|
+
li item[:options] do
|
30
|
+
if item.key? :menu
|
31
|
+
|
32
|
+
hyperlink '/#' do
|
33
|
+
icon item[:icon]
|
34
|
+
span class: 'nav-label' do
|
35
|
+
text item[:name]
|
36
|
+
end
|
37
|
+
span class: 'fa arrow' do
|
38
|
+
text ''
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
open = ''
|
43
|
+
open = 'collapse in' if item[:options][:open]
|
44
|
+
ul class: "nav nav-second-level #{open}" do
|
45
|
+
item[:menu].items.each do |inneritem|
|
46
|
+
li inneritem[:options] do
|
47
|
+
if inneritem.key?(:menu)
|
48
|
+
raise 'Second level menu not supported'
|
49
|
+
else
|
50
|
+
hyperlink (inneritem[:link]).to_s do
|
51
|
+
icon inneritem[:icon]
|
52
|
+
span class: 'nav-label' do
|
53
|
+
text inneritem[:name]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
elsif
|
61
|
+
hyperlink (item[:link]).to_s do
|
62
|
+
icon item[:icon]
|
63
|
+
span class: 'nav-label' do
|
64
|
+
text item[:name]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
brand_content = ''
|
73
|
+
|
74
|
+
if @brand
|
75
|
+
brand_content = <<-BRAND_CONTENT
|
76
|
+
|
77
|
+
<li>
|
78
|
+
<a href="#{root}"><i class="fa fa-home"></i> <span class="nav-label">#{@brand}</span> <span class="label label-primary pull-right"></span></a>
|
79
|
+
</li>
|
80
|
+
BRAND_CONTENT
|
81
|
+
end
|
82
|
+
|
83
|
+
@loading_bar_visible = true
|
84
|
+
@content =
|
85
|
+
<<-ENDBODY
|
86
|
+
<div id="wrapper">
|
87
|
+
|
88
|
+
<nav class="navbar-default navbar-static-side" role="navigation">
|
89
|
+
<div class="sidebar-collapse">
|
90
|
+
<ul class="nav" id="side-menu">
|
91
|
+
|
92
|
+
#{brand_content}
|
93
|
+
#{navigation.generate}
|
94
|
+
|
95
|
+
</ul>
|
96
|
+
</div>
|
97
|
+
</nav>
|
98
|
+
<div id="page-wrapper" class="gray-bg">
|
99
|
+
<div class="row border-bottom">
|
100
|
+
<nav class="navbar navbar-static-top " role="navigation" style="margin-bottom: 0">
|
101
|
+
<div class="navbar-header">
|
102
|
+
<a class="navbar-minimalize minimalize-styl-2 btn btn-primary " href="#"><i class="fa fa-bars"></i> </a>
|
103
|
+
</div>
|
104
|
+
<ul class="nav navbar-top-links navbar-right">
|
105
|
+
<!-- NAV RIGHT -->
|
106
|
+
</ul>
|
107
|
+
</nav>
|
108
|
+
</div>
|
109
|
+
#{rows}
|
110
|
+
</div>
|
111
|
+
</div>
|
112
|
+
ENDBODY
|
113
|
+
|
114
|
+
super
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
Weaver::Weave.register_page_type(:sidenav_page, Weaver::SideNavPage)
|