waibu-mpa 1.0.13 → 1.0.14

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.
@@ -1,4 +1,6 @@
1
1
  {
2
+ "404Message": "Page not found, sorry!",
3
+ "500Message": "Internal server error, sorry!",
2
4
  "field": {
3
5
  "session": "Session",
4
6
  "expires": "Expires"
@@ -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"
@@ -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)
@@ -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
- const err = get(locals, 'error')
24
- if (err) {
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(err.message)
36
+ title: req.t(locals.err.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(err.message) }))
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', [])
@@ -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
- const { trim, isEmpty } = this.plugin.app.bajo.lib._
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,9 +92,11 @@ 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 ?? `${reqNs}.layout:/default.html`
95
+ layout = locals.page.layout ?? qs.layout ?? opts.layout ?? (locals.page.ns ? `${locals.page.ns}.layout:/default.html` : 'main.layout:/default.html')
96
+ console.log('---', locals.page.layout, qs.layout, opts.layout, reqNs, layout)
70
97
  const ext = _path.extname(layout)
71
98
  const { file } = mpa.resolveLayout(layout, opts)
99
+ console.log('---', layout, file)
72
100
  let layoutContent = fs.readFileSync(file, 'utf8')
73
101
  const layoutParsed = await this.md.parse(layoutContent, merge({ readFile: false, parseContent: false, i18n: req.i18n }, mpa.config.markdown))
74
102
  layoutContent = ext === '.md' ? this.md.parseContent(layoutParsed.content, mdOpts) : layoutParsed.content
@@ -89,7 +117,7 @@ class ViewEngine {
89
117
  locals.page.fullTitle = locals.fullTitle ?? (locals.page.title ? `${locals.page.title} - ${req.t(locals.page.appTitle)}` : req.t(locals.page.appTitle))
90
118
  }
91
119
  // 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 })
120
+ return await getCachedResult.call(this.plugin, content, locals, { req, ttl: this.cacheMaxAge, fn: opts.fn, tpl: opts.tpl })
93
121
  }
94
122
  }
95
123
 
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)
@@ -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 message = this.print.write('Route \'%s (%s)\' not found', req.url, req.method)
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', { message })
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waibu-mpa",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "description": "MPA support for Waibu Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,6 @@
1
+ <c:container margin="y-4">
2
+ <c:div text="align:center">
3
+ <c:heading type="6-display" t:content="Not Found" />
4
+ <c:p t:content="404Message" />
5
+ </c:div>
6
+ </c:container>
@@ -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
@@ -1,4 +0,0 @@
1
- ---
2
- t:title: Error
3
- ---
4
- 500