waibu-mpa 2.15.2 → 2.17.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/bajo/hook.js +41 -0
- package/extend/bajoTemplate/template/wmpa.js +3 -5
- package/extend/waibuMpa/route/component/render.js +14 -2
- package/extend/waibuMpa/route/wmpa.js +1 -0
- package/index.js +7 -11
- package/lib/build-page/inject-elements/meta.js +13 -1
- package/lib/build-page/inject-elements.js +1 -1
- package/lib/build-page.js +1 -1
- package/lib/build-routes.js +8 -1
- package/lib/class/component.js +2 -2
- package/lib/class/view-engine.js +1 -0
- package/lib/decorate.js +9 -2
- package/package.json +1 -1
- package/wiki/CHANGES.md +10 -0
- package/extend/bajo/hook/waibu-mpa.theme@after-inject-scripts.js +0 -5
- package/extend/bajo/hook/waibu-mpa@pre-parsing.js +0 -33
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
async function checkLang (req, reply) {
|
|
2
|
+
if (!req.session) return
|
|
3
|
+
if (req.langDetector) {
|
|
4
|
+
req.session.lang = req.lang
|
|
5
|
+
return
|
|
6
|
+
}
|
|
7
|
+
if (req.session.lang) req.lang = req.session.lang
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function checkDark (req, reply) {
|
|
11
|
+
const { isSet } = this.app.lib.aneka
|
|
12
|
+
const key = this.config.darkMode.qsKey
|
|
13
|
+
const value = this.config.darkMode.set ?? req.query[key]
|
|
14
|
+
if (isSet(value)) {
|
|
15
|
+
req.darkMode = value
|
|
16
|
+
if (req.session) req.session.darkMode = req.darkMode
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
if (isSet(req.session.darkMode)) req.darkMode = req.session.darkMode
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function hook () {
|
|
23
|
+
return [{
|
|
24
|
+
level: 9,
|
|
25
|
+
name: 'waibuMpa:preParsing',
|
|
26
|
+
handler: async function (req, reply) {
|
|
27
|
+
const { importModule } = this.app.bajo
|
|
28
|
+
const attachIntl = await importModule('waibu:/lib/webapp-scope/attach-intl.js')
|
|
29
|
+
await attachIntl.call(this, this.config.intl.detectors, req, reply)
|
|
30
|
+
await checkLang.call(this, req, reply)
|
|
31
|
+
await checkDark.call(this, req, reply)
|
|
32
|
+
}
|
|
33
|
+
}, {
|
|
34
|
+
name: 'waibuMpa.theme:afterInjectScripts',
|
|
35
|
+
handler: async function ({ items }) {
|
|
36
|
+
// items.push(`${this.ns}.virtual:/json2csv/json2csv.js`)
|
|
37
|
+
}
|
|
38
|
+
}]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default hook
|
|
@@ -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,24 @@
|
|
|
1
1
|
const component = {
|
|
2
2
|
method: 'POST',
|
|
3
|
+
noCacheReq: true,
|
|
3
4
|
handler: async function (req, reply) {
|
|
5
|
+
const { merge, get } = this.app.lib._
|
|
4
6
|
req.referer = req.headers['x-referer']
|
|
7
|
+
const pageId = req.headers['x-req-id']
|
|
5
8
|
const { ext = '.html' } = req.body
|
|
9
|
+
let params = req.query
|
|
6
10
|
reply.header('Content-Type', `text/html; charset=${this.config.page.charset}`)
|
|
7
11
|
reply.header('Content-Language', req.lang)
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
if (pageId) {
|
|
13
|
+
try {
|
|
14
|
+
const locals = await this.app.cache.load(`${this.ns}.req:/${pageId}-locals.json`, this.config.reqTtlDur)
|
|
15
|
+
params = merge({}, locals ?? {}, params)
|
|
16
|
+
} catch (err) {}
|
|
17
|
+
}
|
|
18
|
+
const theme = get(req, 'headers.x-theme', get(params, '_meta.theme.name'))
|
|
19
|
+
const iconset = get(req, 'headers.x-iconset', get(params, '_meta.iconset.name'))
|
|
20
|
+
const opts = { pageId, req, reply, partial: true, ext, theme, iconset }
|
|
21
|
+
return this.renderString(req.body, params, opts)
|
|
10
22
|
}
|
|
11
23
|
}
|
|
12
24
|
|
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,8 @@ async function factory (pkgName) {
|
|
|
136
137
|
},
|
|
137
138
|
compress: false,
|
|
138
139
|
rateLimit: false,
|
|
139
|
-
disabled: []
|
|
140
|
+
disabled: [],
|
|
141
|
+
reqTtlDur: '1d'
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
this.configDev = {
|
|
@@ -495,23 +497,17 @@ async function factory (pkgName) {
|
|
|
495
497
|
return output
|
|
496
498
|
}
|
|
497
499
|
|
|
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 })
|
|
500
|
+
renderString = async (text, locals = {}, opts = {}) => {
|
|
502
501
|
const ve = this.getViewEngine(opts.ext)
|
|
503
502
|
return await ve.renderString(text, locals, opts)
|
|
504
503
|
}
|
|
505
504
|
|
|
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 })
|
|
505
|
+
render = async (tpl, locals = {}, opts = {}) => {
|
|
510
506
|
const ext = path.extname(tpl)
|
|
511
507
|
if (['.json', '.js', '.css'].includes(ext)) opts.partial = true
|
|
512
508
|
opts.ext = ext
|
|
513
|
-
const
|
|
514
|
-
return await
|
|
509
|
+
const ve = this.getViewEngine(ext)
|
|
510
|
+
return await ve.render(tpl, locals, opts)
|
|
515
511
|
}
|
|
516
512
|
|
|
517
513
|
stripHtmlTags = (html, options = {}) => {
|
|
@@ -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
|
|
@@ -38,7 +38,7 @@ async function injectElements (options) {
|
|
|
38
38
|
}
|
|
39
39
|
options.inlineScript = rsc.inlineScript
|
|
40
40
|
options.inlineCss = rsc.inlineCss
|
|
41
|
-
await runHook(`${this.ns}:
|
|
41
|
+
await runHook(`${this.ns}.injectElement:beforeBuildPage`, options)
|
|
42
42
|
await injectMeta.call(this, options)
|
|
43
43
|
await injectLink.call(this, options)
|
|
44
44
|
await injectCss.call(this, options)
|
package/lib/build-page.js
CHANGED
|
@@ -33,7 +33,7 @@ async function buildPage ({ text, locals = {}, opts = {} } = {}) {
|
|
|
33
33
|
const ns = req.ns
|
|
34
34
|
let $ = cheerio.load(text, this.config.cheerio.loadOptions, false)
|
|
35
35
|
if (partial) $('c\\:page-start, c\\:page-end').remove()
|
|
36
|
-
await runHook(`${this.ns}
|
|
36
|
+
await runHook(`${this.ns}${partial ? '.partial' : ''}:beforeBuildPage`, { $, theme, iconset, req, reply, locals, ns, text })
|
|
37
37
|
await attrsMutation.call(this, { $, el: $.root(), req })
|
|
38
38
|
const cmp = await theme.createComponent({ $, iconset, req, reply, locals: cloneDeep(locals), scriptBlock: reqAsset[req.id].scriptBlock, styleBlock: reqAsset[req.id].styleBlock })
|
|
39
39
|
await replaceTag.call(this, { el: $.root(), cmp, opts })
|
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) {
|
|
@@ -48,6 +54,7 @@ export async function build ({ files, pathPrefix, dir, ns, cfg, parent, urlPrefi
|
|
|
48
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
|
@@ -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
package/lib/decorate.js
CHANGED
|
@@ -19,8 +19,10 @@ async function isCacheable (req, cachedUrls) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
async function decorate () {
|
|
22
|
+
const { importModule } = 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
27
|
const { outmatch } = this.app.lib
|
|
26
28
|
const { routePath } = this.app.waibu
|
|
@@ -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,11 @@ 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
|
+
await me.app.cache.save(`${me.ns}.req:/${opts.req.id}-locals.json`, locals, me.config.reqTtlDur)
|
|
69
|
+
}
|
|
70
|
+
const result = await me.render(tpl, locals, opts)
|
|
64
71
|
if (ttl > 0) await setCache({ key, value: result, ttl })
|
|
65
72
|
if (this.request.session) {
|
|
66
73
|
ext = path.extname(this.request.url)
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-05-28
|
|
4
|
+
|
|
5
|
+
- [2.17.0] Change hooks to be written in one ```hook.js``` file
|
|
6
|
+
- [2.17.0] Change model schemas to be written in one ```model.js``` file
|
|
7
|
+
|
|
8
|
+
## 2026-05-25
|
|
9
|
+
|
|
10
|
+
- [2.16.0] Add theme & iconset auto detection mechanism
|
|
11
|
+
- [2.16.0] Add page expiration & reload through ```routeOptions.config.refreshDur``` or ```config.page.refreshDur```
|
|
12
|
+
|
|
3
13
|
## 2026-05-22
|
|
4
14
|
|
|
5
15
|
- [2.15.2] Bug fix in ```error-handler.js```
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
async function checkLang (req, reply) {
|
|
2
|
-
if (!req.session) return
|
|
3
|
-
if (req.langDetector) {
|
|
4
|
-
req.session.lang = req.lang
|
|
5
|
-
return
|
|
6
|
-
}
|
|
7
|
-
if (req.session.lang) req.lang = req.session.lang
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
async function checkDark (req, reply) {
|
|
11
|
-
const { isSet } = this.app.lib.aneka
|
|
12
|
-
const key = this.config.darkMode.qsKey
|
|
13
|
-
const value = this.config.darkMode.set ?? req.query[key]
|
|
14
|
-
if (isSet(value)) {
|
|
15
|
-
req.darkMode = value
|
|
16
|
-
if (req.session) req.session.darkMode = req.darkMode
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
if (isSet(req.session.darkMode)) req.darkMode = req.session.darkMode
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const waibuMpaPreParsing = {
|
|
23
|
-
level: 9,
|
|
24
|
-
handler: async function (req, reply) {
|
|
25
|
-
const { importModule } = this.app.bajo
|
|
26
|
-
const attachIntl = await importModule('waibu:/lib/webapp-scope/attach-intl.js')
|
|
27
|
-
await attachIntl.call(this, this.config.intl.detectors, req, reply)
|
|
28
|
-
await checkLang.call(this, req, reply)
|
|
29
|
-
await checkDark.call(this, req, reply)
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default waibuMpaPreParsing
|