waibu-mpa 2.2.0 → 2.3.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/waibu/boot.js +2 -0
- package/extend/waibuMpa/iconset.js +1 -1
- package/index.js +3 -2
- package/lib/build-page/inject-elements/css.js +3 -3
- package/lib/build-page/inject-elements/link.js +4 -2
- package/lib/build-page/inject-elements/meta.js +12 -28
- package/lib/build-page/inject-elements/script.js +3 -2
- package/lib/build-page/inject-elements.js +3 -3
- package/lib/class/theme.js +1 -1
- package/lib/error-handler.js +2 -2
- package/lib/load-resource.js +11 -9
- package/lib/not-found-handler.js +20 -0
- package/package.json +1 -1
- package/wiki/CHANGES.md +9 -0
- package/lib/not-found.js +0 -58
package/extend/waibu/boot.js
CHANGED
|
@@ -6,10 +6,12 @@ import collectIconsets from '../../lib/collect-iconsets.js'
|
|
|
6
6
|
import handleSession from '../../lib/session/setup.js'
|
|
7
7
|
import subApp from '../../lib/sub-app.js'
|
|
8
8
|
import errorHandler from '../../lib/error-handler.js'
|
|
9
|
+
import notFoundHandler from '../../lib/not-found-handler.js'
|
|
9
10
|
|
|
10
11
|
const boot = {
|
|
11
12
|
level: 10,
|
|
12
13
|
errorHandler,
|
|
14
|
+
notFoundHandler,
|
|
13
15
|
handler: async function (prefix) {
|
|
14
16
|
const { importPkg, importModule } = this.app.bajo
|
|
15
17
|
const bodyParser = await importPkg('waibu:@fastify/formbody')
|
package/index.js
CHANGED
|
@@ -40,7 +40,8 @@ async function factory (pkgName) {
|
|
|
40
40
|
charset: 'utf-8',
|
|
41
41
|
cacheMaxAge: 0,
|
|
42
42
|
insertWarning: false,
|
|
43
|
-
usePluginTitle: false
|
|
43
|
+
usePluginTitle: false,
|
|
44
|
+
scriptsAtEndOfBody: true
|
|
44
45
|
},
|
|
45
46
|
darkMode: {
|
|
46
47
|
set: null,
|
|
@@ -231,7 +232,7 @@ async function factory (pkgName) {
|
|
|
231
232
|
|
|
232
233
|
getSessionId = (rawCookie, secure) => {
|
|
233
234
|
const cookieName = this.config.session.cookieName
|
|
234
|
-
return this.
|
|
235
|
+
return this.webAppCtx.parseCookie(rawCookie)[cookieName]
|
|
235
236
|
}
|
|
236
237
|
|
|
237
238
|
getViewEngine = (ext) => {
|
|
@@ -7,10 +7,10 @@ export function printLink (link) {
|
|
|
7
7
|
|
|
8
8
|
async function css (options) {
|
|
9
9
|
const { $ } = options ?? {}
|
|
10
|
-
const inline = await collectInline.call(this, 'inlineCss', options)
|
|
11
|
-
if (inline.length > 0) $('head').prepend(`<style>\n${inline.join('\n')}\n</style>`)
|
|
12
10
|
const regular = await collectRegular.call(this, 'css', printLink, options)
|
|
13
|
-
if (regular.length > 0) $('head').
|
|
11
|
+
if (regular.length > 0) $('head').append(regular.join('\n'))
|
|
12
|
+
const inline = await collectInline.call(this, 'inlineCss', options)
|
|
13
|
+
if (inline.length > 0) $('head').append(`<style>\n${inline.join('\n')}\n</style>`)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export default css
|
|
@@ -4,13 +4,15 @@ export function printLink (link) {
|
|
|
4
4
|
const { routePath } = this.app.waibu
|
|
5
5
|
const { isString } = this.app.lib._
|
|
6
6
|
const item = isString(link) ? { href: link, rel: 'stylesheet', type: 'text/css' } : link
|
|
7
|
-
|
|
7
|
+
if (item.href) item.href = routePath(item.href)
|
|
8
|
+
const attrs = this.stringifyAttribs(item)
|
|
9
|
+
return `<link ${attrs} />`
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
async function link (options) {
|
|
11
13
|
const { $ } = options ?? {}
|
|
12
14
|
const regular = await collectRegular.call(this, 'links', printLink, options)
|
|
13
|
-
if (regular.length > 0) $('head').
|
|
15
|
+
if (regular.length > 0) $('head').append(regular.join('\n'))
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export default link
|
|
@@ -7,14 +7,13 @@ const names = ['description', 'keywords', 'robots', 'viewport', 'author', 'publi
|
|
|
7
7
|
|
|
8
8
|
async function meta (options) {
|
|
9
9
|
const { runHook, importPkg } = this.app.bajo
|
|
10
|
-
const { map, uniq, isArray,
|
|
10
|
+
const { map, uniq, isArray, keys, omit, get, isFunction } = this.app.lib._
|
|
11
11
|
const { sprintf } = this.app.lib
|
|
12
12
|
const { $, theme, req, locals } = options ?? {}
|
|
13
13
|
const { page = {} } = locals
|
|
14
14
|
let items = []
|
|
15
15
|
// meta
|
|
16
|
-
const
|
|
17
|
-
for (const attr of keys(omit(locals.page, [...links, ...omitted]))) {
|
|
16
|
+
for (const attr of keys(omit(locals.page, [...omitted]))) {
|
|
18
17
|
if (!page[attr]) continue
|
|
19
18
|
if (!names.includes(attr)) continue
|
|
20
19
|
items.push({
|
|
@@ -22,28 +21,19 @@ async function meta (options) {
|
|
|
22
21
|
content: isArray(page[attr]) ? page[attr].join(', ') : page[attr]
|
|
23
22
|
})
|
|
24
23
|
}
|
|
25
|
-
// links
|
|
26
|
-
for (const attr of links) {
|
|
27
|
-
if (!page[attr]) continue
|
|
28
|
-
const all = isArray(page[attr]) ? page[attr] : page[attr] = [page[attr]]
|
|
29
|
-
for (const a of all) {
|
|
30
|
-
const [href, ...params] = a.split('|')
|
|
31
|
-
const item = {
|
|
32
|
-
tag: 'link',
|
|
33
|
-
rel: kebabCase(attr),
|
|
34
|
-
href
|
|
35
|
-
}
|
|
36
|
-
for (const p of params ?? []) {
|
|
37
|
-
item[p] = true
|
|
38
|
-
}
|
|
39
|
-
items.push(item)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
24
|
if (this.config.theme && this.config.theme.autoInsert.meta) {
|
|
43
25
|
await runHook(`${this.ns}.${theme.name}:beforeInjectMeta`, { meta: theme.meta, items, req })
|
|
44
26
|
items.push(...(theme.meta ?? []))
|
|
45
27
|
await runHook(`${this.ns}.${theme.name}:afterInjectMeta`, { meta: theme.meta, items, req })
|
|
46
28
|
}
|
|
29
|
+
// title
|
|
30
|
+
const formatter = get(this, 'app.waibuMpa.pageTitleFormat', '%s : %s')
|
|
31
|
+
const title = page.fullTitle ?? page.title
|
|
32
|
+
let pageTitle
|
|
33
|
+
if (isFunction(formatter)) pageTitle = await formatter.call(this, locals)
|
|
34
|
+
else pageTitle = sprintf(formatter, title, this.app.waibuMpa.getAppTitle(req.lang))
|
|
35
|
+
$('head').append(`<title>${pageTitle}</title>`)
|
|
36
|
+
// favicon
|
|
47
37
|
const favicon = this.app.waibu.config.favicon
|
|
48
38
|
if (favicon) {
|
|
49
39
|
const mime = await importPkg('waibu:mime')
|
|
@@ -51,20 +41,14 @@ async function meta (options) {
|
|
|
51
41
|
const type = mime.getType(ext)
|
|
52
42
|
items.push({ tag: 'link', href: `/favicon${ext}`, rel: 'icon', type })
|
|
53
43
|
}
|
|
44
|
+
// meta
|
|
54
45
|
items = map(items, m => {
|
|
55
46
|
const tag = m.tag ?? 'meta'
|
|
56
47
|
delete m.tag
|
|
57
48
|
const attrs = this.stringifyAttribs(m)
|
|
58
49
|
return `<${tag} ${attrs} />`
|
|
59
50
|
})
|
|
60
|
-
$('head').
|
|
61
|
-
// title
|
|
62
|
-
const formatter = get(this, 'app.waibuMpa.pageTitleFormat', '%s : %s')
|
|
63
|
-
const title = page.fullTitle ?? page.title
|
|
64
|
-
let pageTitle
|
|
65
|
-
if (isFunction(formatter)) pageTitle = await formatter.call(this, locals)
|
|
66
|
-
else pageTitle = sprintf(formatter, title, this.app.waibuMpa.getAppTitle(req.lang))
|
|
67
|
-
$('head').append(`<title>${pageTitle}</title>`)
|
|
51
|
+
$('head').append(uniq(items).join('\n'))
|
|
68
52
|
}
|
|
69
53
|
|
|
70
54
|
export default meta
|
|
@@ -64,12 +64,13 @@ export async function collectInline (type, options = {}) {
|
|
|
64
64
|
async function script (options = {}) {
|
|
65
65
|
const { $, cmp, locals } = options
|
|
66
66
|
const { render } = this.app.bajoTemplate
|
|
67
|
+
const parent = this.config.page.scriptsAtEndOfBody ? 'body' : 'head'
|
|
67
68
|
const regular = await collectRegular.call(this, 'scripts', printScript, options)
|
|
68
|
-
if (regular.length > 0) $(
|
|
69
|
+
if (regular.length > 0) $(parent).append(regular.join('\n'))
|
|
69
70
|
const inline = await collectInline.call(this, 'inlineScript', options)
|
|
70
71
|
if (inline.length > 0) cmp.scriptBlock.root.push(...inline)
|
|
71
72
|
const script = await render(locals.page.scriptBlock, { block: cmp.scriptBlock }, { default: 'bajoTemplate.partial:/script-block.html' })
|
|
72
|
-
$(
|
|
73
|
+
$(parent).append(script)
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
export default script
|
|
@@ -14,7 +14,7 @@ async function injectElements (options) {
|
|
|
14
14
|
let Builder = get(cmp, `widget.${tag}`)
|
|
15
15
|
if (!isFunction(Builder)) continue
|
|
16
16
|
if (!isClass(Builder)) Builder = await Builder.call(cmp)
|
|
17
|
-
for (const key of ['
|
|
17
|
+
for (const key of ['links', 'scripts', 'css']) {
|
|
18
18
|
let item = Builder[key] ?? []
|
|
19
19
|
if (isString(item)) item = [item]
|
|
20
20
|
if (isFunction(item)) item = await item.call(cmp, req)
|
|
@@ -32,15 +32,15 @@ async function injectElements (options) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
for (const key of ['
|
|
35
|
+
for (const key of ['links', 'scripts', 'css']) {
|
|
36
36
|
options[key] = rsc[key] ?? []
|
|
37
37
|
}
|
|
38
38
|
options.inlineScript = rsc.inlineScript
|
|
39
39
|
options.inlineCss = rsc.inlineCss
|
|
40
40
|
await runHook(`${this.ns}:beforeBuildPageInjectElement`, options)
|
|
41
|
+
await injectMeta.call(this, options)
|
|
41
42
|
await injectLink.call(this, options)
|
|
42
43
|
await injectCss.call(this, options)
|
|
43
|
-
await injectMeta.call(this, options)
|
|
44
44
|
await injectScript.call(this, options)
|
|
45
45
|
}
|
|
46
46
|
|
package/lib/class/theme.js
CHANGED
|
@@ -5,9 +5,9 @@ async function themeFactory () {
|
|
|
5
5
|
this.name = name
|
|
6
6
|
this.css = []
|
|
7
7
|
this.meta = []
|
|
8
|
+
this.links = []
|
|
8
9
|
this.moveToEnd = ''
|
|
9
10
|
this.scipts = undefined
|
|
10
|
-
this.links = undefined
|
|
11
11
|
this.inlineCss = undefined
|
|
12
12
|
this.inlineScript = undefined
|
|
13
13
|
this.framework = undefined
|
package/lib/error-handler.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import notFoundHandler from './not-found-handler.js'
|
|
2
2
|
|
|
3
3
|
async function errorHandler (err, req, reply) {
|
|
4
4
|
const { getPluginFile } = this.app.bajo
|
|
@@ -9,7 +9,7 @@ async function errorHandler (err, req, reply) {
|
|
|
9
9
|
reply.code(err.statusCode)
|
|
10
10
|
|
|
11
11
|
if (err.message === '_notFound' || err.statusCode === 404) {
|
|
12
|
-
return await
|
|
12
|
+
return await notFoundHandler.call(this, err, req, reply)
|
|
13
13
|
}
|
|
14
14
|
if (err.noContent) return ''
|
|
15
15
|
const ns = err.ns ?? this.ns
|
package/lib/load-resource.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
async function loadResource (mod = [], item) {
|
|
2
2
|
const { breakNsPath, readConfig } = this.app.bajo
|
|
3
|
-
const { isArray, isEmpty } = this.app.lib._
|
|
3
|
+
const { isString, isArray, isEmpty } = this.app.lib._
|
|
4
4
|
|
|
5
5
|
if (isEmpty(mod[item])) return []
|
|
6
6
|
if (!isArray(mod[item])) mod[item] = [mod[item]]
|
|
7
7
|
const items = []
|
|
8
8
|
const extItems = []
|
|
9
9
|
for (const i in mod[item]) {
|
|
10
|
-
if (mod[item][i]
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
if (isString(mod[item][i])) {
|
|
11
|
+
if (mod[item][i].startsWith('/')) items.push(mod.css[i])
|
|
12
|
+
else {
|
|
13
|
+
let name = mod[item][i]
|
|
14
|
+
if (['$', '^'].includes(name[0])) name = name.slice(1)
|
|
15
|
+
const { ns, path, subNs } = breakNsPath(name, undefined, false)
|
|
16
|
+
if (subNs === 'load') extItems.push({ ns, path })
|
|
17
|
+
else items.push(mod[item][i])
|
|
18
|
+
}
|
|
19
|
+
} else items.push(mod[item][i])
|
|
18
20
|
}
|
|
19
21
|
for (const c of extItems) {
|
|
20
22
|
let emod = await readConfig(`${c.ns}:${c.path}`, { ns: c.ns })
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
async function notFoundHandler (err, req, reply) {
|
|
2
|
+
const welcome = req.url.split('?')[0] === '/'
|
|
3
|
+
const { resolveTemplate } = this.app.bajoTemplate
|
|
4
|
+
const msg = req.t('routeNotFound%s%s', req.url, req.method)
|
|
5
|
+
const error = err ?? this.error(msg)
|
|
6
|
+
if (err) error.message = msg
|
|
7
|
+
error.statusCode = 404
|
|
8
|
+
reply.code(404)
|
|
9
|
+
if (error.noContent) return ''
|
|
10
|
+
const ns = error.ns ?? this.ns
|
|
11
|
+
let tpl = welcome ? `${this.ns}.template:/welcome.html` : `${ns}.template:/404.html`
|
|
12
|
+
try {
|
|
13
|
+
await resolveTemplate(tpl)
|
|
14
|
+
} catch (err) {
|
|
15
|
+
tpl = `${this.ns}.template:/404.html`
|
|
16
|
+
}
|
|
17
|
+
if (reply.view) return await reply.view(tpl, { error })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default notFoundHandler
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-02-09
|
|
4
|
+
|
|
5
|
+
- [2.3.0] Add ```config.page.scriptsAtEndOfBody``` to put scripts at the end of body or not. Defaults to ```true```
|
|
6
|
+
- [2.3.0] Add not found & error handlers
|
|
7
|
+
- [2.3.0] Bug fix on old context
|
|
8
|
+
- [2.3.0] Bug fix on ```loadResource()```
|
|
9
|
+
- [2.3.0] Bug fix on ```<link />``` injection
|
|
10
|
+
- [2.3.0] Bug fix on order of metas, links & scripts
|
|
11
|
+
|
|
3
12
|
## 2026-02-05
|
|
4
13
|
|
|
5
14
|
- [2.1.11] Bug fix on rendering ```preconnect```
|
package/lib/not-found.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
function redirSvc (req) {
|
|
2
|
-
const { trim, find, get } = this.app.lib._
|
|
3
|
-
const { outmatch } = this.app.lib
|
|
4
|
-
let match = false
|
|
5
|
-
let [prefix, ...args] = trim(req.url, '/').split('/')
|
|
6
|
-
args = '/' + args.join('/')
|
|
7
|
-
let plugin = find(this.app.getAllNs(), p => {
|
|
8
|
-
return get(this, `app.${p}.config.waibu.prefix`) === prefix
|
|
9
|
-
})
|
|
10
|
-
if (!plugin) {
|
|
11
|
-
plugin = 'main'
|
|
12
|
-
args = `/${prefix}`
|
|
13
|
-
}
|
|
14
|
-
const items = get(this, `app.${plugin}.config.waibuMpa.redirect`, {})
|
|
15
|
-
for (const k in items) {
|
|
16
|
-
const isMatch = outmatch(k)
|
|
17
|
-
if (isMatch(args)) {
|
|
18
|
-
match = items[k]
|
|
19
|
-
break
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return match
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function handler (req, reply, err) {
|
|
26
|
-
const { getMethod } = this.app.bajo
|
|
27
|
-
const { resolveTemplate } = this.app.bajoTemplate
|
|
28
|
-
let redirectTo = await redirSvc.call(this, req, reply)
|
|
29
|
-
if (redirectTo !== false) {
|
|
30
|
-
const fn = getMethod(redirectTo, false)
|
|
31
|
-
if (fn) redirectTo = await fn(req)
|
|
32
|
-
if (redirectTo) return reply.redirectTo(redirectTo)
|
|
33
|
-
}
|
|
34
|
-
const welcome = req.url.split('?')[0] === '/'
|
|
35
|
-
const msg = req.t('routeNotFound%s%s', req.url, req.method)
|
|
36
|
-
const error = err ?? this.error(msg)
|
|
37
|
-
if (err) error.message = msg
|
|
38
|
-
error.statusCode = 404
|
|
39
|
-
reply.code(404)
|
|
40
|
-
if (error.noContent) return ''
|
|
41
|
-
const ns = error.ns ?? this.ns
|
|
42
|
-
let tpl = welcome ? `${this.ns}.template:/welcome.html` : `${ns}.template:/404.html`
|
|
43
|
-
try {
|
|
44
|
-
await resolveTemplate(tpl)
|
|
45
|
-
} catch (err) {
|
|
46
|
-
tpl = `${this.ns}.template:/404.html`
|
|
47
|
-
}
|
|
48
|
-
if (reply.view) return await reply.view(tpl, { error })
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function notFound (ctx) {
|
|
52
|
-
const me = this
|
|
53
|
-
await ctx.setNotFoundHandler(async function (req, reply) {
|
|
54
|
-
return await handler.call(me, req, reply)
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export default notFound
|