Merge pull request #26 from tmshv/fix/api-pages-breadcrumbs
API pages breadcrumbs
This commit is contained in:
8 files changed
+130
-46
No files matched your search
Generated
+62
@@ -4,6 +4,16 @@
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/connect": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/bson": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.1.tgz",
|
||||
@@ -19,6 +29,42 @@
|
||||
"integrity": "sha512-MNl+rT5UmZeilaPxAVs6YaPC2m6aA8rofviZbhbxpPpl61uKodfdQVsBtgJGTqGizEf02oW3tsVe7FYB8kK14A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/connect": {
|
||||
"version": "3.4.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz",
|
||||
"integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/express": {
|
||||
"version": "4.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz",
|
||||
"integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "*",
|
||||
"@types/serve-static": "*"
|
||||
}
|
||||
},
|
||||
"@types/express-serve-static-core": {
|
||||
"version": "4.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz",
|
||||
"integrity": "sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
|
||||
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mongodb": {
|
||||
"version": "3.3.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.3.16.tgz",
|
||||
@@ -35,6 +81,22 @@
|
||||
"integrity": "sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/range-parser": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
|
||||
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/serve-static": {
|
||||
"version": "1.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
|
||||
"integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express-serve-static-core": "*",
|
||||
"@types/mime": "*"
|
||||
}
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/mongodb": "^3.3.16",
|
||||
"@types/node": "^13.7.1",
|
||||
"ts-node": "^8.6.2",
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { getPathWaterfall } from '../lib/url'
|
||||
import { Breadcrumb } from '../types'
|
||||
import Page from '../core/Page'
|
||||
|
||||
export async function getItems(path: string): Promise<Breadcrumb> {
|
||||
const bc = getPathWaterfall(path)
|
||||
const parts = await Promise.all(bc.map(async href => {
|
||||
const resource = await Page.findByUrl(href)
|
||||
|
||||
if (!resource) {
|
||||
return null
|
||||
}
|
||||
|
||||
const name = resource.title
|
||||
|
||||
return {
|
||||
name,
|
||||
href,
|
||||
}
|
||||
}))
|
||||
|
||||
return parts.filter(Boolean)
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
import { Request, Response } from 'express'
|
||||
import Page from '../core/Page'
|
||||
import { getPathWithNoTrailingSlash } from '../lib/url'
|
||||
import { dropTrailingSlash, getPathWaterfall } from '../lib/url'
|
||||
import { encodePage } from '../factory/page'
|
||||
import * as breadcrumb from './breadcrumb'
|
||||
|
||||
export async function getItem(req: Request, res: Response) {
|
||||
const id = req.query.page
|
||||
const page = getPathWithNoTrailingSlash(id)
|
||||
const page = dropTrailingSlash(req.query.page)
|
||||
const breadcrumbs = await breadcrumb.getItems(page)
|
||||
const resource = await Page.findByUrl(page)
|
||||
|
||||
|
||||
if (resource) {
|
||||
res.json(encodePage(resource))
|
||||
res.json(encodePage(resource, breadcrumbs))
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { encodeImage } from './image'
|
||||
import Page from '../core/Page'
|
||||
import { Breadcrumb } from '../types'
|
||||
|
||||
export function encodePage(page) {
|
||||
export function encodePage(page: Page, breadcrumb: Breadcrumb) {
|
||||
return {
|
||||
id: page.id,
|
||||
title: page.title,
|
||||
@@ -8,5 +10,6 @@ export function encodePage(page) {
|
||||
data: page.data,
|
||||
preview: encodeImage(page.preview),
|
||||
tokens: page.tokens,
|
||||
breadcrumb,
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
function getPathWithNoTrailingSlash(path='') {
|
||||
path = path.charAt(path.length - 1) === '/'
|
||||
? path.substr(0, path.length - 1)
|
||||
: path
|
||||
return path.length === 0
|
||||
? '/'
|
||||
: path
|
||||
}
|
||||
|
||||
function isEqualPath(a, b, ignoreTrailingSlash = false) {
|
||||
return ignoreTrailingSlash
|
||||
? getPathWithNoTrailingSlash(a) === getPathWithNoTrailingSlash(b)
|
||||
: a === b
|
||||
}
|
||||
|
||||
function isMatchPathPattern(pattern, path, ignoreTrailingSlash = false) {
|
||||
const test = ignoreTrailingSlash
|
||||
? getPathWithNoTrailingSlash(path)
|
||||
: path
|
||||
return pattern.test(test)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* /article/* -> ^\/article\/[/\w\d-_]+\/?$
|
||||
*
|
||||
* @param url
|
||||
* @return {RegExp}
|
||||
*/
|
||||
function urlToPattern(url) {
|
||||
const u = url.replace('/*', '[/\\w\\d-_]+')
|
||||
const pattern = `^${u}/?$`
|
||||
|
||||
return new RegExp(pattern)
|
||||
}
|
||||
|
||||
exports.getPathWithNoTrailingSlash = getPathWithNoTrailingSlash
|
||||
exports.isMatchPathPattern = isMatchPathPattern
|
||||
exports.urlToPattern = urlToPattern
|
||||
exports.isEqualPath = isEqualPath
|
||||
@@ -0,0 +1,28 @@
|
||||
export function dropTrailingSlash(path: string) {
|
||||
if (path === '/' || path === '') {
|
||||
return '/'
|
||||
}
|
||||
|
||||
const lastChar = path.charAt(path.length - 1)
|
||||
if (lastChar === '/') {
|
||||
return path.substr(0, path.length - 1)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
export function getPathWaterfall(path: string): string[] {
|
||||
const parts = path
|
||||
.split('/')
|
||||
.filter(Boolean)
|
||||
.reduce((acc, part, index, parts) => {
|
||||
const i = index + 1
|
||||
const item = `/${parts.slice(0, i).join('/')}`
|
||||
return [
|
||||
...acc,
|
||||
item
|
||||
]
|
||||
}, [])
|
||||
|
||||
return ['/', ...parts]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export type BreadcrumbPart = {
|
||||
name: string
|
||||
href: string
|
||||
}
|
||||
|
||||
export type Breadcrumb = BreadcrumbPart[]
|
||||
Reference in new issue
Block a user