waibu-mpa 2.15.1 → 2.16.0
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/extend/bajoTemplate/template/wmpa.js +3 -5
- package/extend/waibuMpa/route/component/render.js +17 -2
- package/extend/waibuMpa/route/wmpa.js +1 -0
- package/index.js +29 -11
- package/lib/build-page/inject-elements/meta.js +13 -1
- package/lib/build-routes.js +9 -2
- package/lib/class/component.js +3 -3
- package/lib/class/view-engine.js +1 -0
- package/lib/class/widget/page-end.js +1 -1
- package/lib/class/widget.js +2 -1
- package/lib/decorate.js +12 -3
- package/lib/error-handler.js +4 -6
- package/package.json +1 -1
- package/wiki/CHANGES.md +10 -0
|
@@ -8,8 +8,6 @@ class Wmpa {
|
|
|
8
8
|
this.prefixMain = '<%= prefix.main %>'
|
|
9
9
|
this.accessTokenUrl = '<%= accessTokenUrl %>'
|
|
10
10
|
this.renderUrl = '<%= renderUrl %>'
|
|
11
|
-
this.theme = '<%= _meta.theme.name %>'
|
|
12
|
-
this.iconset = '<%= _meta.iconset.name %>'
|
|
13
11
|
this.apiPrefix = '<%= api.prefix %>'
|
|
14
12
|
this.apiExt = '<%= api.ext %>'
|
|
15
13
|
this.apiHeaderKey = '<%= api.headerKey %>'
|
|
@@ -59,6 +57,7 @@ class Wmpa {
|
|
|
59
57
|
}
|
|
60
58
|
|
|
61
59
|
init = () => {
|
|
60
|
+
this.reqId = document.querySelector('meta[name="req-id"]').getAttribute('content')
|
|
62
61
|
window.addEventListener('load', evt => {
|
|
63
62
|
if (window.hljs) window.hljs.highlightAll()
|
|
64
63
|
})
|
|
@@ -103,7 +102,7 @@ class Wmpa {
|
|
|
103
102
|
fetchRender = async (body, qs = {}) => {
|
|
104
103
|
if (_.isArray(body)) body = body.join('\n')
|
|
105
104
|
let url = this.renderUrl + '?'
|
|
106
|
-
_.forOwn(
|
|
105
|
+
_.forOwn(qs, (v, k) => {
|
|
107
106
|
url += '&' + k + '=' + v
|
|
108
107
|
})
|
|
109
108
|
const opts = {
|
|
@@ -111,8 +110,7 @@ class Wmpa {
|
|
|
111
110
|
headers: {
|
|
112
111
|
'Content-Type': 'text/plain',
|
|
113
112
|
'X-Referer': window.location.href,
|
|
114
|
-
'X-
|
|
115
|
-
'X-Iconset': this.iconset
|
|
113
|
+
'X-Req-Id': this.reqId
|
|
116
114
|
},
|
|
117
115
|
body
|
|
118
116
|
}
|
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
const component = {
|
|
2
2
|
method: 'POST',
|
|
3
|
+
noCacheReq: true,
|
|
3
4
|
handler: async function (req, reply) {
|
|
5
|
+
const { getPluginDataDir } = this.app.bajo
|
|
6
|
+
const { fs } = this.app.lib
|
|
7
|
+
const { merge, get } = this.app.lib._
|
|
4
8
|
req.referer = req.headers['x-referer']
|
|
9
|
+
const pageId = req.headers['x-req-id']
|
|
5
10
|
const { ext = '.html' } = req.body
|
|
11
|
+
let params = req.query
|
|
6
12
|
reply.header('Content-Type', `text/html; charset=${this.config.page.charset}`)
|
|
7
13
|
reply.header('Content-Language', req.lang)
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
if (pageId) {
|
|
15
|
+
try {
|
|
16
|
+
const file = `${getPluginDataDir(this.ns)}/cache/req/${pageId}/locals.json`
|
|
17
|
+
const locals = JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
18
|
+
params = merge({}, locals, params)
|
|
19
|
+
} catch (err) {}
|
|
20
|
+
}
|
|
21
|
+
const theme = get(req, 'headers.x-theme', get(params, '_meta.theme.name'))
|
|
22
|
+
const iconset = get(req, 'headers.x-iconset', get(params, '_meta.iconset.name'))
|
|
23
|
+
const opts = { pageId, req, reply, partial: true, ext, theme, iconset }
|
|
24
|
+
return this.renderString(req.body, params, opts)
|
|
10
25
|
}
|
|
11
26
|
}
|
|
12
27
|
|
package/index.js
CHANGED
|
@@ -43,6 +43,7 @@ async function factory (pkgName) {
|
|
|
43
43
|
ttlDur: 0,
|
|
44
44
|
urls: []
|
|
45
45
|
},
|
|
46
|
+
refreshDur: '1d',
|
|
46
47
|
insertWarning: false,
|
|
47
48
|
usePluginTitle: false,
|
|
48
49
|
scriptsAtEndOfBody: true
|
|
@@ -136,7 +137,11 @@ async function factory (pkgName) {
|
|
|
136
137
|
},
|
|
137
138
|
compress: false,
|
|
138
139
|
rateLimit: false,
|
|
139
|
-
disabled: []
|
|
140
|
+
disabled: [],
|
|
141
|
+
purgeCache: {
|
|
142
|
+
intvDur: '1m',
|
|
143
|
+
reqTtlDur: '1d'
|
|
144
|
+
}
|
|
140
145
|
}
|
|
141
146
|
|
|
142
147
|
this.configDev = {
|
|
@@ -166,6 +171,13 @@ async function factory (pkgName) {
|
|
|
166
171
|
await widgetFactory.call(this)
|
|
167
172
|
}
|
|
168
173
|
|
|
174
|
+
start = async () => {
|
|
175
|
+
this.purgeCache()
|
|
176
|
+
setInterval(() => {
|
|
177
|
+
this.purgeCache()
|
|
178
|
+
}, this.config.purgeCache.intvDur)
|
|
179
|
+
}
|
|
180
|
+
|
|
169
181
|
buildUrl = ({ exclude = [], prefix = '?', base, url = '', params = {}, prettyUrl }) => {
|
|
170
182
|
const { parseObject } = this.app.lib
|
|
171
183
|
const { qs } = this.app.waibu
|
|
@@ -495,23 +507,17 @@ async function factory (pkgName) {
|
|
|
495
507
|
return output
|
|
496
508
|
}
|
|
497
509
|
|
|
498
|
-
renderString = async (text,
|
|
499
|
-
const { importModule } = this.app.bajo
|
|
500
|
-
const buildLocals = await importModule('waibu:/lib/build-locals.js')
|
|
501
|
-
const locals = await buildLocals.call(this, { tpl: null, params, opts })
|
|
510
|
+
renderString = async (text, locals = {}, opts = {}) => {
|
|
502
511
|
const ve = this.getViewEngine(opts.ext)
|
|
503
512
|
return await ve.renderString(text, locals, opts)
|
|
504
513
|
}
|
|
505
514
|
|
|
506
|
-
render = async (tpl,
|
|
507
|
-
const { importModule } = this.app.bajo
|
|
508
|
-
const buildLocals = await importModule('waibu:/lib/build-locals.js')
|
|
509
|
-
const locals = await buildLocals.call(this, { tpl, params, opts })
|
|
515
|
+
render = async (tpl, locals = {}, opts = {}) => {
|
|
510
516
|
const ext = path.extname(tpl)
|
|
511
517
|
if (['.json', '.js', '.css'].includes(ext)) opts.partial = true
|
|
512
518
|
opts.ext = ext
|
|
513
|
-
const
|
|
514
|
-
return await
|
|
519
|
+
const ve = this.getViewEngine(ext)
|
|
520
|
+
return await ve.render(tpl, locals, opts)
|
|
515
521
|
}
|
|
516
522
|
|
|
517
523
|
stripHtmlTags = (html, options = {}) => {
|
|
@@ -634,6 +640,18 @@ async function factory (pkgName) {
|
|
|
634
640
|
if (!iconset) return iconset
|
|
635
641
|
return nameOnly ? iconset.name : iconset
|
|
636
642
|
}
|
|
643
|
+
|
|
644
|
+
purgeCache = async () => {
|
|
645
|
+
const { getPluginDataDir } = this.app.bajo
|
|
646
|
+
const { fastGlob, fs } = this.app.lib
|
|
647
|
+
const dirs = await fastGlob(`${getPluginDataDir(this.ns)}/cache/req/*`, { onlyDirectories: true })
|
|
648
|
+
for (const dir of dirs) {
|
|
649
|
+
try {
|
|
650
|
+
const { mtimeMs } = await fs.stat(dir)
|
|
651
|
+
if (Date.now() - mtimeMs > this.config.purgeCache.reqTtlDur) await fs.remove(dir)
|
|
652
|
+
} catch (err) {}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
637
655
|
}
|
|
638
656
|
|
|
639
657
|
return WaibuMpa
|
|
@@ -11,7 +11,19 @@ async function meta (options) {
|
|
|
11
11
|
const { sprintf } = this.app.lib
|
|
12
12
|
const { $, theme, req, locals } = options ?? {}
|
|
13
13
|
const { page = {} } = locals
|
|
14
|
-
let items = [
|
|
14
|
+
let items = [{
|
|
15
|
+
tag: 'meta',
|
|
16
|
+
name: 'req-id',
|
|
17
|
+
content: req.id
|
|
18
|
+
}]
|
|
19
|
+
const refreshDur = get(req, 'routeOptions.config.refreshDur', get(this, 'config.page.refreshDur', 0))
|
|
20
|
+
if (refreshDur > 0) {
|
|
21
|
+
items.push({
|
|
22
|
+
tag: 'meta',
|
|
23
|
+
httpEquiv: 'Refresh',
|
|
24
|
+
content: Math.floor(refreshDur / 60) + ''
|
|
25
|
+
})
|
|
26
|
+
}
|
|
15
27
|
// meta
|
|
16
28
|
for (const attr of keys(omit(locals.page, [...omitted]))) {
|
|
17
29
|
if (!page[attr]) continue
|
package/lib/build-routes.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
|
|
3
3
|
export async function build ({ files, pathPrefix, dir, ns, cfg, parent, urlPrefix, subRoute }) {
|
|
4
|
-
const { defaultsDeep } = this.app.lib.aneka
|
|
4
|
+
const { defaultsDeep, parseObject } = this.app.lib.aneka
|
|
5
5
|
const { importModule, readJson } = this.app.bajo
|
|
6
6
|
const { isFunction, isPlainObject, pick, last, camelCase, omit } = this.app.lib._
|
|
7
7
|
const { titleize } = this.app.lib.aneka
|
|
@@ -20,6 +20,12 @@ export async function build ({ files, pathPrefix, dir, ns, cfg, parent, urlPrefi
|
|
|
20
20
|
if (isFunction(mod)) mod = [{ handler: mod }]
|
|
21
21
|
else if (isPlainObject(mod)) mod = [mod]
|
|
22
22
|
for (let m of mod) {
|
|
23
|
+
const mhandler = m.handler // parseObject has problem with function
|
|
24
|
+
const murl = m.url
|
|
25
|
+
m = parseObject(omit(m, ['handler', 'url'], { parseValue: true }))
|
|
26
|
+
m.handler = mhandler
|
|
27
|
+
m.url = murl
|
|
28
|
+
|
|
23
29
|
m.url = m.url ?? url
|
|
24
30
|
if (isFunction(m.url)) m.url = await m.url.call(this)
|
|
25
31
|
if (m.redirect) {
|
|
@@ -45,9 +51,10 @@ export async function build ({ files, pathPrefix, dir, ns, cfg, parent, urlPrefi
|
|
|
45
51
|
m.config.prefix = getPluginPrefix(ns)
|
|
46
52
|
m.config.pathSrc = m.url
|
|
47
53
|
m.config.webApp = parent ?? ns
|
|
48
|
-
m.config.
|
|
54
|
+
m.config.xSite = m.xSite
|
|
49
55
|
m.config.ns = ns
|
|
50
56
|
m.config.subNs = ''
|
|
57
|
+
m.config.noCacheReq = m.noCacheReq
|
|
51
58
|
m.config.title = m.title ?? camelCase(last(m.url.split('/')))
|
|
52
59
|
m.config.subRoute = subRoute
|
|
53
60
|
if (m.cache === true) m.cache = omit(me.config.page.cache, ['urls'])
|
package/lib/class/component.js
CHANGED
|
@@ -105,7 +105,7 @@ async function componentFactory () {
|
|
|
105
105
|
await this.iconAttr(params, method)
|
|
106
106
|
await this.beforeBuildTag(method, params)
|
|
107
107
|
const Widget = await this.getWidget(method)
|
|
108
|
-
const widget = new Widget({ component: this, params })
|
|
108
|
+
const widget = new Widget({ component: this, params, options: opts })
|
|
109
109
|
const resp = await widget.build()
|
|
110
110
|
if (resp === false) {
|
|
111
111
|
return false
|
|
@@ -280,7 +280,7 @@ async function componentFactory () {
|
|
|
280
280
|
* @returns {Promise<string>} The built sentence.
|
|
281
281
|
*/
|
|
282
282
|
buildSentence = async (sentence, params = {}, extra = {}) => {
|
|
283
|
-
const { get } = this.app.lib._
|
|
283
|
+
const { get, merge } = this.app.lib._
|
|
284
284
|
if (Array.isArray(sentence)) sentence = sentence.join(' ')
|
|
285
285
|
const { minify, renderString } = this.app.waibuMpa
|
|
286
286
|
if (extra.wrapped) sentence = '<w>' + sentence + '</w>'
|
|
@@ -292,7 +292,7 @@ async function componentFactory () {
|
|
|
292
292
|
theme: get(this, 'theme.name', 'default'),
|
|
293
293
|
iconset: get(this, 'iconset.name', 'default')
|
|
294
294
|
}
|
|
295
|
-
let html = await renderString(sentence, params, opts)
|
|
295
|
+
let html = await renderString(sentence, merge({}, this.locals, params), opts)
|
|
296
296
|
if (extra.wrapped) html = html.slice(3, html.length - 4)
|
|
297
297
|
if (extra.minify) html = await minify(html)
|
|
298
298
|
return html
|
package/lib/class/view-engine.js
CHANGED
|
@@ -23,7 +23,7 @@ async function pageEndFactory () {
|
|
|
23
23
|
let tc = ''
|
|
24
24
|
if (!this.params.attr.noToastContainer && widget.toastStack && widget.toast) {
|
|
25
25
|
const toasts = []
|
|
26
|
-
if (get(locals, 'error')) {
|
|
26
|
+
if (get(locals, 'error') && get(locals, '_meta.flash.notify')) {
|
|
27
27
|
const details = get(locals.error, 'details', [])
|
|
28
28
|
if (details.length > 0) {
|
|
29
29
|
const list = [`<ul class="m-0 ${details.length === 1 ? 'list-unstyled' : ''}">`]
|
package/lib/class/widget.js
CHANGED
|
@@ -10,7 +10,7 @@ async function widgetFactory () {
|
|
|
10
10
|
static inlineScript = null
|
|
11
11
|
static inlineCss = null
|
|
12
12
|
|
|
13
|
-
constructor ({ component = {}, params = {} } = {}) {
|
|
13
|
+
constructor ({ component = {}, params = {}, options = {} } = {}) {
|
|
14
14
|
super(component.plugin)
|
|
15
15
|
const names = kebabCase(this.constructor.name).split('-')
|
|
16
16
|
const alias = names.length > 1 ? names[0] : 'wbs'
|
|
@@ -21,6 +21,7 @@ async function widgetFactory () {
|
|
|
21
21
|
this.component = component
|
|
22
22
|
this.params = params
|
|
23
23
|
this.block = {}
|
|
24
|
+
this.options = options
|
|
24
25
|
this.setting = this._parseBase64Attr(this.params.attr.setting)
|
|
25
26
|
this.formData = get(this, `component.locals.${this.params.attr.keyLocals ?? 'form'}`, {})
|
|
26
27
|
this.oldData = get(this, `component.locals.${this.params.attr.keyOldData ?? 'oldData'}`, {})
|
package/lib/decorate.js
CHANGED
|
@@ -19,10 +19,12 @@ async function isCacheable (req, cachedUrls) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
async function decorate () {
|
|
22
|
+
const { importModule, getPluginDataDir } = this.app.bajo
|
|
23
|
+
const buildLocals = await importModule('waibu:/lib/build-locals.js')
|
|
22
24
|
const { importPkg } = this.app.bajo
|
|
23
|
-
const { isString, cloneDeep, isEmpty } = this.app.lib._
|
|
25
|
+
const { isString, cloneDeep, isEmpty, get } = this.app.lib._
|
|
24
26
|
const { get: getCache, set: setCache } = this.app.bajoCache ?? {}
|
|
25
|
-
const { outmatch } = this.app.lib
|
|
27
|
+
const { outmatch, fs } = this.app.lib
|
|
26
28
|
const { routePath } = this.app.waibu
|
|
27
29
|
const mime = await importPkg('waibu:mime')
|
|
28
30
|
const cfg = this.config
|
|
@@ -46,6 +48,7 @@ async function decorate () {
|
|
|
46
48
|
mimeType += `; charset=${cfg.page.charset}`
|
|
47
49
|
this.header('Content-Type', mimeType)
|
|
48
50
|
this.header('Content-Language', this.request.lang)
|
|
51
|
+
this.header('X-Req-Id', this.request.id)
|
|
49
52
|
opts.req = this.request
|
|
50
53
|
opts.reply = this
|
|
51
54
|
for (const item of ['theme', 'iconset']) {
|
|
@@ -60,7 +63,13 @@ async function decorate () {
|
|
|
60
63
|
return cached
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
|
-
const
|
|
66
|
+
const locals = await buildLocals.call(me, { tpl, params, opts })
|
|
67
|
+
if (!get(this.request, 'routeOptions.config.noCacheReq')) {
|
|
68
|
+
const dir = `${getPluginDataDir(me.ns)}/cache/req/${opts.req.id}`
|
|
69
|
+
fs.ensureDirSync(dir)
|
|
70
|
+
fs.writeFileSync(`${dir}/locals.json`, JSON.stringify(locals, null, 2), 'utf8')
|
|
71
|
+
}
|
|
72
|
+
const result = await me.render(tpl, locals, opts)
|
|
64
73
|
if (ttl > 0) await setCache({ key, value: result, ttl })
|
|
65
74
|
if (this.request.session) {
|
|
66
75
|
ext = path.extname(this.request.url)
|
package/lib/error-handler.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import notFoundHandler from './not-found-handler.js'
|
|
2
2
|
|
|
3
3
|
async function errorHandler (err, req, reply) {
|
|
4
|
-
const {
|
|
5
|
-
const { resolveTemplate, compile } = this.app.bajoTemplate
|
|
4
|
+
const { resolveTemplate } = this.app.bajoTemplate
|
|
6
5
|
err.statusCode = err.statusCode ?? 500
|
|
7
6
|
reply.code(err.statusCode)
|
|
8
7
|
reply.header('Content-Type', `text/html; charset=${this.config.page.charset}`)
|
|
@@ -16,12 +15,11 @@ async function errorHandler (err, req, reply) {
|
|
|
16
15
|
// let result
|
|
17
16
|
let tpl = `${ns}.template:/${err.statusCode ?? 500}.html`
|
|
18
17
|
try {
|
|
19
|
-
|
|
18
|
+
resolveTemplate(tpl)
|
|
20
19
|
} catch (err) {
|
|
21
|
-
tpl =
|
|
20
|
+
tpl = `${this.ns}.template:/500.html`
|
|
22
21
|
}
|
|
23
|
-
|
|
24
|
-
return await compile(content, { error: err })
|
|
22
|
+
return reply.view(tpl, { error: err }, { noFlash: true })
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
export default errorHandler
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-05-25
|
|
4
|
+
|
|
5
|
+
- [2.16.0] Add theme & iconset auto detection mechanism
|
|
6
|
+
- [2.16.0] Add page expiration & reload through ```routeOptions.config.refreshDur``` or ```config.page.refreshDur```
|
|
7
|
+
|
|
8
|
+
## 2026-05-22
|
|
9
|
+
|
|
10
|
+
- [2.15.2] Bug fix in ```error-handler.js```
|
|
11
|
+
- [2.15.2] Bug fix in ```component.buildTag()```
|
|
12
|
+
|
|
3
13
|
## 2026-05-16
|
|
4
14
|
|
|
5
15
|
- [2.15.1] Bug fix in ```component.js```
|