waibu-mpa 1.0.13 → 1.0.15
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.
- package/bajo/method/build-url.js +4 -1
- package/bajoI18N/resource/en-US.json +2 -0
- package/bajoI18N/resource/id.json +2 -0
- package/lib/build-locals.js +6 -0
- package/lib/class/component.js +2 -2
- package/lib/class/factory/page-end.js +5 -6
- package/lib/class/view-engine.js +29 -3
- package/lib/error.js +2 -0
- package/lib/get-cached-result.js +1 -1
- package/lib/not-found.js +3 -2
- package/package.json +1 -1
- package/waibuMpa/partial/404.html +6 -0
- package/waibuMpa/partial/500.html +10 -0
- package/waibuMpa/partial/address.html +8 -0
- package/waibuMpa/template/404.html +0 -1
- package/waibuMpa/template/500.html +0 -4
- package/waibuMpa/template/wmpa.js +21 -0
package/bajo/method/build-url.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
function buildUrl ({ exclude = [], prefix = '?', base, url = '', params = {} }) {
|
|
1
|
+
function buildUrl ({ exclude = [], prefix = '?', base, url = '', params = {}, prettyUrl }) {
|
|
2
2
|
const { qs } = this.app.waibu
|
|
3
3
|
const { forOwn, omit, isEmpty } = this.app.bajo.lib._
|
|
4
4
|
const qsKey = this.app.waibu.config.qsKey
|
|
@@ -13,6 +13,8 @@ function buildUrl ({ exclude = [], prefix = '?', base, url = '', params = {} })
|
|
|
13
13
|
const key = qsKey[k] ?? k
|
|
14
14
|
query[key] = v
|
|
15
15
|
})
|
|
16
|
+
const id = query.id
|
|
17
|
+
if (prettyUrl) delete query.id
|
|
16
18
|
query = prefix + qs.stringify(omit(query, exclude))
|
|
17
19
|
if (!isEmpty(hash)) hash = '#' + hash
|
|
18
20
|
if (!base) return path + query + hash
|
|
@@ -21,6 +23,7 @@ function buildUrl ({ exclude = [], prefix = '?', base, url = '', params = {} })
|
|
|
21
23
|
parts.pop()
|
|
22
24
|
parts.push(base)
|
|
23
25
|
}
|
|
26
|
+
if (prettyUrl && id) parts.push(id)
|
|
24
27
|
return parts.join('/') + query + hash
|
|
25
28
|
}
|
|
26
29
|
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
"Success": "Sukses",
|
|
23
23
|
"Warning": "Peringatan",
|
|
24
24
|
"Danger": "Bahaya",
|
|
25
|
+
"404Message": "Maaf, halaman yang Anda inginkan tidak ditemukan. Silahkan kontak Admin Anda segera!",
|
|
26
|
+
"500Message": "Maaf, ada kesalahan internal server. Silahkan kontak Admin Anda segera!",
|
|
25
27
|
"field": {
|
|
26
28
|
"session": "Sesi",
|
|
27
29
|
"expires": "Kadaluarsa"
|
package/lib/build-locals.js
CHANGED
|
@@ -15,6 +15,7 @@ async function buildLocals ({ template, params = {}, opts = {} } = {}) {
|
|
|
15
15
|
const { getAppTitle } = this.app.waibuMpa
|
|
16
16
|
const { set, merge, pick, get, isEmpty, find } = this.app.bajo.lib._
|
|
17
17
|
const { req, reply } = opts
|
|
18
|
+
|
|
18
19
|
params.page = merge(params.page ?? {}, { ns: req.ns, appTitle: getAppTitle(req.ns) })
|
|
19
20
|
set(params, 'menu.homes', buildHomesMenu.call(this))
|
|
20
21
|
|
|
@@ -24,9 +25,14 @@ async function buildLocals ({ template, params = {}, opts = {} } = {}) {
|
|
|
24
25
|
const routeOpts = get(req, 'routeOptions.config', {})
|
|
25
26
|
const _meta = { theme, iconset, site, user, lang, darkMode, routeOpts }
|
|
26
27
|
merge(_meta, pick(req, ['url', 'params', 'query']))
|
|
28
|
+
_meta.env = this.app.bajo.config.env
|
|
27
29
|
_meta.url = _meta.url.split('?')[0].split('#')[0]
|
|
28
30
|
_meta.route = get(req, 'routeOptions.url')
|
|
29
31
|
_meta.template = template
|
|
32
|
+
if (params.error) {
|
|
33
|
+
_meta.statusCode = params.error.statusCode ?? 200
|
|
34
|
+
_meta.errorMessage = params.error.message
|
|
35
|
+
}
|
|
30
36
|
if (req.flash && !opts.partial) _meta.flash = reply.flash()
|
|
31
37
|
const merged = merge({}, params, { _meta })
|
|
32
38
|
await runHook(`${this.name}:afterBuildLocals`, merged, req)
|
package/lib/class/component.js
CHANGED
|
@@ -154,10 +154,10 @@ class Component {
|
|
|
154
154
|
return items.join('\n')
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
buildUrl ({ exclude, prefix, base, url, params = {} }) {
|
|
157
|
+
buildUrl ({ exclude, prefix, base, url, params = {}, prettyUrl }) {
|
|
158
158
|
const { buildUrl } = this.plugin.app.waibuMpa
|
|
159
159
|
url = url ?? this.req.referer ?? this.req.url
|
|
160
|
-
return buildUrl({ exclude, prefix, base, url, params })
|
|
160
|
+
return buildUrl({ exclude, prefix, base, url, params, prettyUrl })
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
async buildSentence (sentence, params = {}, extra = {}) {
|
|
@@ -20,9 +20,8 @@ class PageEnd extends Factory {
|
|
|
20
20
|
let tc = ''
|
|
21
21
|
if (!this.params.attr.noToastContainer && factory.toastStack && factory.toast) {
|
|
22
22
|
const toasts = []
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const details = get(err, 'details', [])
|
|
23
|
+
if (get(locals, 'error')) {
|
|
24
|
+
const details = get(locals.error, 'details', [])
|
|
26
25
|
if (details.length > 0) {
|
|
27
26
|
const list = [`<ul class="m-0 ${details.length === 1 ? 'list-unstyled' : ''}">`]
|
|
28
27
|
for (const d of details) {
|
|
@@ -34,12 +33,12 @@ class PageEnd extends Factory {
|
|
|
34
33
|
const attr = {
|
|
35
34
|
border: 'side:all width:0',
|
|
36
35
|
text: 'background:danger',
|
|
37
|
-
title: req.t(
|
|
36
|
+
title: req.t(locals.error.message)
|
|
38
37
|
}
|
|
39
38
|
toasts.push(await this.component.buildTag({ tag: 'toast', attr, html: list.join('\n') }))
|
|
40
|
-
} else {
|
|
39
|
+
} else if (!(locals._meta.statusCode === 404 || locals._meta.statusCode >= 500)) {
|
|
41
40
|
const attr = { border: 'side:all width:0', text: 'background:danger' }
|
|
42
|
-
toasts.push(await this.component.buildTag({ tag: 'toast', attr, html: this.component.req.t(
|
|
41
|
+
toasts.push(await this.component.buildTag({ tag: 'toast', attr, html: this.component.req.t(locals.error.message) }))
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
const notifications = get(locals, '_meta.flash.notify', [])
|
package/lib/class/view-engine.js
CHANGED
|
@@ -9,6 +9,7 @@ class ViewEngine {
|
|
|
9
9
|
this.fileExts = typeof fileExts === 'string' ? [fileExts] : fileExts
|
|
10
10
|
this.cacheMaxAge = plugin.app.waibuMpa.config.viewEngine.cacheMaxAge
|
|
11
11
|
this.md = get(plugin, 'app.bajoMarkdown')
|
|
12
|
+
this.history = {}
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
_applySetting (opts = {}) {
|
|
@@ -20,9 +21,23 @@ class ViewEngine {
|
|
|
20
21
|
opts.theme = get(setting, 'theme', req.theme)
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
_clearHistory () {
|
|
25
|
+
const { omit } = this.plugin.app.bajo.lib._
|
|
26
|
+
const maxAge = 60
|
|
27
|
+
const now = Date.now()
|
|
28
|
+
const omitted = []
|
|
29
|
+
for (const reqId in this.history) {
|
|
30
|
+
const history = this.history[reqId]
|
|
31
|
+
if ((history.ts + (maxAge * 1000)) < now) omitted.push(reqId)
|
|
32
|
+
}
|
|
33
|
+
this.history = omit(this.history, omitted)
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
async render (tpl, locals = {}, opts = {}) {
|
|
24
|
-
|
|
37
|
+
this._clearHistory()
|
|
38
|
+
const { trim, isEmpty, last } = this.plugin.app.bajo.lib._
|
|
25
39
|
const { fs } = this.plugin.app.bajo.lib
|
|
40
|
+
const { req } = opts
|
|
26
41
|
const mpa = this.plugin.app.waibuMpa
|
|
27
42
|
const { ns, subSubNs, path, qs } = mpa.getResource(tpl)
|
|
28
43
|
this._applySetting(opts)
|
|
@@ -34,12 +49,23 @@ class ViewEngine {
|
|
|
34
49
|
resp = mpa.resolvePartial(`${ns}.partial${subSubNs ? ('.' + subSubNs) : ''}:${path}`, opts)
|
|
35
50
|
}
|
|
36
51
|
const file = resp.file
|
|
52
|
+
// prevent looping
|
|
53
|
+
if (this.history[req.id]) {
|
|
54
|
+
if (last(this.history[req.id].file) === file) throw this.plugin.error('Template looping detected: %s => %s', tpl, file)
|
|
55
|
+
this.history[req.id].file.push(file)
|
|
56
|
+
} else {
|
|
57
|
+
this.history[req.id] = {
|
|
58
|
+
ts: Date.now(),
|
|
59
|
+
file: [file]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
37
62
|
let content = trim(fs.readFileSync(file, 'utf8'))
|
|
38
63
|
if (isEmpty(content) && tpl.includes('.template')) {
|
|
39
64
|
content = '<c:include resource="' + tpl.replace('.template', '.partial') + '" />'
|
|
40
65
|
}
|
|
41
66
|
opts.ext = _path.extname(file)
|
|
42
67
|
opts.fn = true
|
|
68
|
+
opts.tpl = tpl
|
|
43
69
|
return await this.write(content, locals, opts)
|
|
44
70
|
}
|
|
45
71
|
|
|
@@ -66,7 +92,7 @@ class ViewEngine {
|
|
|
66
92
|
let layout
|
|
67
93
|
if (!opts.partial) {
|
|
68
94
|
locals.page = merge(locals.page, parseObject(parsed.frontMatter, { parseValue: true, i18n: req.i18n, ns: reqNs }))
|
|
69
|
-
layout = locals.page.layout ?? qs.layout ?? opts.layout ?? `${
|
|
95
|
+
layout = locals.page.layout ?? qs.layout ?? opts.layout ?? (locals.page.ns ? `${locals.page.ns}.layout:/default.html` : 'main.layout:/default.html')
|
|
70
96
|
const ext = _path.extname(layout)
|
|
71
97
|
const { file } = mpa.resolveLayout(layout, opts)
|
|
72
98
|
let layoutContent = fs.readFileSync(file, 'utf8')
|
|
@@ -89,7 +115,7 @@ class ViewEngine {
|
|
|
89
115
|
locals.page.fullTitle = locals.fullTitle ?? (locals.page.title ? `${locals.page.title} - ${req.t(locals.page.appTitle)}` : req.t(locals.page.appTitle))
|
|
90
116
|
}
|
|
91
117
|
// if (['.js'].includes(opts.ext)) return content // TODO: clash with lodash template
|
|
92
|
-
return await getCachedResult.call(this.plugin, content, locals, { req, ttl: this.cacheMaxAge, fn: opts.fn })
|
|
118
|
+
return await getCachedResult.call(this.plugin, content, locals, { req, ttl: this.cacheMaxAge, fn: opts.fn, tpl: opts.tpl })
|
|
93
119
|
}
|
|
94
120
|
}
|
|
95
121
|
|
package/lib/error.js
CHANGED
|
@@ -4,6 +4,7 @@ const extHandler = async function (err, req, reply) {
|
|
|
4
4
|
const { getPluginFile } = this.app.bajo
|
|
5
5
|
const { fs } = this.app.bajo.lib
|
|
6
6
|
const { template } = this.app.bajo.lib._
|
|
7
|
+
err.statusCode = err.statusCode ?? 500
|
|
7
8
|
|
|
8
9
|
if (err.message.toLowerCase() === 'notfound' || err.statusCode === 404) {
|
|
9
10
|
return await handler.call(this, req, reply)
|
|
@@ -12,6 +13,7 @@ const extHandler = async function (err, req, reply) {
|
|
|
12
13
|
try {
|
|
13
14
|
result = await reply.view('waibuMpa.template:/500.html', { error: err })
|
|
14
15
|
} catch (err) {
|
|
16
|
+
console.log(err)
|
|
15
17
|
const file = getPluginFile(`${this.name}:/waibuMpa/template/_500.html`)
|
|
16
18
|
const content = fs.readFileSync(file)
|
|
17
19
|
const compiled = template(content)
|
package/lib/get-cached-result.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import crypto from 'crypto'
|
|
2
2
|
|
|
3
|
-
async function getCachedResult (content, params, { req, ttl = 0, fn = false, keyFn } = {}) {
|
|
3
|
+
async function getCachedResult (content, params, { req, ttl = 0, fn = false, keyFn, tpl } = {}) {
|
|
4
4
|
const _ = this.app.bajo.lib._
|
|
5
5
|
const { template, get } = _
|
|
6
6
|
const cache = this.app.bajoCache
|
package/lib/not-found.js
CHANGED
|
@@ -30,9 +30,10 @@ export async function handler (req, reply) {
|
|
|
30
30
|
if (fn) redirectTo = await fn(req)
|
|
31
31
|
if (redirectTo) return reply.redirectTo(redirectTo)
|
|
32
32
|
}
|
|
33
|
-
const
|
|
33
|
+
const error = this.error('Route \'%s (%s)\' not found', req.url, req.method)
|
|
34
|
+
error.statusCode = 404
|
|
34
35
|
reply.code(404)
|
|
35
|
-
if (reply.view) return await reply.view('waibuMpa.template:/404.html', {
|
|
36
|
+
if (reply.view) return await reply.view('waibuMpa.template:/404.html', { error })
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
async function notFound (ctx) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<c:container margin="y-4">
|
|
2
|
+
<c:div text="align:center">
|
|
3
|
+
<c:heading type="6-display" t:content="Internal Server Error" />
|
|
4
|
+
<% if (_meta.env === 'dev') { %>
|
|
5
|
+
<%= _meta.errorMessage %>
|
|
6
|
+
<% } else { %>
|
|
7
|
+
<c:t>500Message</c:t>
|
|
8
|
+
<% } %>
|
|
9
|
+
</c:div>
|
|
10
|
+
</c:container>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<% const type = attr.type ?? 'site' %>
|
|
2
|
+
<address>
|
|
3
|
+
<c:strong text="nowrap"><%= type === 'user' ? (_meta[type].firstName + ' ' + _meta[type].lastName) : _meta[type].orgName %></c:strong><br />
|
|
4
|
+
<%= _meta[type].address1 %><br/>
|
|
5
|
+
<%= _meta[type].address2 %><br />
|
|
6
|
+
<c:span text="nowrap"><%= _meta[type].city %> <%= _meta[type].zipCode %> - <%= _meta[type].provinceState %></c:span><br />
|
|
7
|
+
<%= _meta[type].country %><br />
|
|
8
|
+
</address>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
404
|
|
@@ -407,9 +407,30 @@ class Wmpa {
|
|
|
407
407
|
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
|
|
408
408
|
}).join(''))
|
|
409
409
|
}
|
|
410
|
+
|
|
411
|
+
getTableDataset (selector, ignoreHeader = true) {
|
|
412
|
+
const table = document.querySelector(selector)
|
|
413
|
+
if (!table) return []
|
|
414
|
+
const data = []
|
|
415
|
+
let row
|
|
416
|
+
for (let i = ignoreHeader ? 1 : 0; row = table.rows[i]; i++) {
|
|
417
|
+
let col
|
|
418
|
+
const d = {}
|
|
419
|
+
for (let j = 0; col = row.cells[j]; j++) {
|
|
420
|
+
if (!col.dataset.key) continue
|
|
421
|
+
let value = col.dataset.value
|
|
422
|
+
if (['integer', 'float'].includes(col.dataset.type)) value = Number(value)
|
|
423
|
+
else if (col.dataset.type === 'boolean') value = value === 'true'
|
|
424
|
+
d[col.dataset.key] = value
|
|
425
|
+
}
|
|
426
|
+
data.push(d)
|
|
427
|
+
}
|
|
428
|
+
return data
|
|
429
|
+
}
|
|
410
430
|
}
|
|
411
431
|
|
|
412
432
|
const wmpa = new Wmpa() // eslint-disable-line no-unused-vars
|
|
433
|
+
window.wmpa = wmpa
|
|
413
434
|
if (window._ && window._.VERSION) {
|
|
414
435
|
window._.templateSettings.evaluate = /\{\%(.+?)\%\}/g
|
|
415
436
|
window._.templateSettings.interpolate = /\{\%=(.+?)\%\}/g
|