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.
@@ -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
 
@@ -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)
@@ -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
- 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.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(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,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 ?? `${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')
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)
@@ -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.15",
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
@@ -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