-
Notifications
You must be signed in to change notification settings - Fork 2
/
webb_spa.lua
241 lines (198 loc) · 5.93 KB
/
webb_spa.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
--[==[
webb | single-page apps | server-side API
Written by Cosmin Apreutesei. Public Domain.
CONFIG
config('page_title_suffix') suffix for <title>
config('js_mode') 'bundle' | 'ref' | 'separate'
config('css_mode') 'bundle' | 'ref' | 'separate'
API
cssfile(file) add one or more css files to all.css
jsfile(file) add one or more js files to all.js
fontfile(file) add one or more font files to the preload list
css(s) add css code to inline.css
js(s) add js code to inline.js
page_title([title], [body]) -> s set/infer page title
spa(t) single-page app html action
t.head: s content for <head>
t.body: s content for <body>
t.title: s content for <title> (optional)
t.client_action: t|f should run client-side action or not
INTERNAL API
action['config.js'] expose required config() values
action['strings.js'] load strings.<lang>.js
action['all.js'] output of jsfile() calls
action['all.css'] output of cssfile() calls
action['inline.js'] output of js() calls
action['inline.css'] output of css() calls
config'aliases' for href() and find_action()
config'page_title_suffix' for pagetitle()
config'facebook_app_id' for webb.auth.facebook.js
config'google_client_id' for webb.auth.google.js
config'analytics_ua' for webb.analytics.js
LOADS
glue.js
divs.js
webb_spa.js
purify.js
mustache.js
]==]
require'webb'
require'webb_action'
local format = string.format
--pass required config values to the client
action['config.js'] = function()
local cjson = require'cjson'
--initialize some required config values with defaults.
config('lang', 'en')
config('root_action', 'en')
config('page_title_suffix', ' - '..host())
local function C(name)
if config(name) == nil then return end
out('config('
..cjson.encode(name)..', '
..cjson.encode(config(name))..')\n')
end
C'app_name'
C'lang'
C'aliases'
C'root_action'
C'templates_action'
C'page_title_suffix'
C'facebook_app_id'
C'analytics_ua'
C'google_client_id'
end
action['strings.js'] = function()
local t = S_texts(lang(), 'js')
if not next(t) then return end
out'assign(S_texts, '; out_json(t); out')'
end
--simple API to add js and css snippets and files from server-side code
local function sepbuffer(sep)
local buf = stringbuffer()
return function(s)
if s then
buf(s)
buf(sep)
else
return buf()
end
end
end
cssfile = sepbuffer'\n'
wwwfile['all.css.cat'] = function()
return cssfile() .. ' inline.css' --append inline code at the end
end
jsfile = sepbuffer'\n'
wwwfile['all.js.cat'] = function()
return jsfile() .. ' inline.js' --append inline code at the end
end
local fontfiles = {}
function fontfile(file)
for file in file:gmatch'[^%s]+' do
table.insert(fontfiles, file)
end
end
css = sepbuffer'\n'
wwwfile['inline.css'] = function()
return css()
end
js = sepbuffer';\n'
wwwfile['inline.js'] = function()
return js()
end
html = sepbuffer'\n'
jsfile[[
glue.js
divs.js
webb_spa.js
config.js // dynamic config
strings.js // strings in current language
purify.js
mustache.js
]]
--format js and css refs as separate refs or as a single ref based on a .cat action.
--NOTE: with `embed` mode, urls in css files must be absolute paths!
local function jslist(cataction, mode)
if mode == 'bundle' then
out(format(' <script src="%s"></script>', href('/'..cataction)))
elseif mode == 'embed' then
out'<script>'
outcatlist(cataction..'.cat')
out'</script>\n'
elseif mode == 'separate' then
for i,file in ipairs(catlist_files(wwwfile(cataction..'.cat'))) do
out(format('\t<script src="%s"></script>\n', href('/'..file)))
end
else
assert(false)
end
end
local function csslist(cataction, mode)
if mode == 'bundle' then
out(format('\t<link rel="stylesheet" type="text/css" href="/%s">', href(cataction)))
elseif mode == 'embed' then
out'<style>'
outcatlist(cataction..'.cat')
out'</style>\n'
elseif mode == 'separate' then
for i,file in ipairs(catlist_files(wwwfile(cataction..'.cat'))) do
out(format('\t<link rel="stylesheet" type="text/css" href="%s">\n', href('/'..file)))
end
else
assert(false)
end
end
local function preloadlist()
for i,file in ipairs(fontfiles) do
out(format('\t<link rel="preload" href="%s" as="font" crossorigin>\n', href('/'..file)))
end
end
--main template gluing it all together
local spa_template = [[
<!DOCTYPE html>
<html lang="{{lang}}">
<head>
<meta charset=utf-8>
<title>{{title}}{{title_suffix}}</title>
{{#favicon}}<link rel="icon" href="{{favicon}}">{{/favicon}}
{{{preload}}}
{{{all_css}}}
{{{all_js}}}
{{{head}}}
<script>
var client_action = {{client_action}}
</{{undefined}}script>
</head>
<body {{body_attrs}} class="{{body_classes}}">
<div style="display: none;">{{{templates}}}</div>
{{{body}}}
</body>
</html>
]]
function page_title(title, body)
return title
--infer it from the name of the action
or args(1):gsub('[-_]', ' ')
end
function spa(p)
local t = {}
t.lang = lang()
t.body = filter_lang(html(), lang())
t.body_classes = p.body_classes
t.body_attrs = p.body_attrs
t.head = p.head
t.title = page_title(p.title, t.body)
t.title_suffix = config('page_title_suffix', ' - '..host())
t.favicon = p.favicon or config'favicon'
t.client_action = p.client_action or false
t.all_js = record(jslist , 'all.js' , p.js_mode or config('js_mode' , 'separate'))
t.all_css = record(csslist, 'all.css', p.css_mode or config('css_mode', 'separate'))
t.preload = record(preloadlist)
local buf = stringbuffer()
for _,name in ipairs(template()) do
buf(mustache_wrap(template(name), name))
end
t.templates = buf()
out(render_string(spa_template, t))
end