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,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"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": {
|
"@types/bson": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.1.tgz",
|
||||||
@@ -19,6 +29,42 @@
|
|||||||
"integrity": "sha512-MNl+rT5UmZeilaPxAVs6YaPC2m6aA8rofviZbhbxpPpl61uKodfdQVsBtgJGTqGizEf02oW3tsVe7FYB8kK14A==",
|
"integrity": "sha512-MNl+rT5UmZeilaPxAVs6YaPC2m6aA8rofviZbhbxpPpl61uKodfdQVsBtgJGTqGizEf02oW3tsVe7FYB8kK14A==",
|
||||||
"dev": true
|
"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": {
|
"@types/mongodb": {
|
||||||
"version": "3.3.16",
|
"version": "3.3.16",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.3.16.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.3.16.tgz",
|
||||||
@@ -35,6 +81,22 @@
|
|||||||
"integrity": "sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA==",
|
"integrity": "sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA==",
|
||||||
"dev": true
|
"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": {
|
"accepts": {
|
||||||
"version": "1.3.7",
|
"version": "1.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/classnames": "^2.2.9",
|
"@types/classnames": "^2.2.9",
|
||||||
|
"@types/express": "^4.17.2",
|
||||||
"@types/mongodb": "^3.3.16",
|
"@types/mongodb": "^3.3.16",
|
||||||
"@types/node": "^13.7.1",
|
"@types/node": "^13.7.1",
|
||||||
"ts-node": "^8.6.2",
|
"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 { Request, Response } from 'express'
|
||||||
import Page from '../core/Page'
|
import Page from '../core/Page'
|
||||||
import { getPathWithNoTrailingSlash } from '../lib/url'
|
import { dropTrailingSlash, getPathWaterfall } from '../lib/url'
|
||||||
import { encodePage } from '../factory/page'
|
import { encodePage } from '../factory/page'
|
||||||
|
import * as breadcrumb from './breadcrumb'
|
||||||
|
|
||||||
export async function getItem(req: Request, res: Response) {
|
export async function getItem(req: Request, res: Response) {
|
||||||
const id = req.query.page
|
const page = dropTrailingSlash(req.query.page)
|
||||||
const page = getPathWithNoTrailingSlash(id)
|
const breadcrumbs = await breadcrumb.getItems(page)
|
||||||
const resource = await Page.findByUrl(page)
|
const resource = await Page.findByUrl(page)
|
||||||
|
|
||||||
if (resource) {
|
if (resource) {
|
||||||
res.json(encodePage(resource))
|
res.json(encodePage(resource, breadcrumbs))
|
||||||
} else {
|
} else {
|
||||||
res.status(404)
|
res.status(404)
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { encodeImage } from './image'
|
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 {
|
return {
|
||||||
id: page.id,
|
id: page.id,
|
||||||
title: page.title,
|
title: page.title,
|
||||||
@@ -8,5 +10,6 @@ export function encodePage(page) {
|
|||||||
data: page.data,
|
data: page.data,
|
||||||
preview: encodeImage(page.preview),
|
preview: encodeImage(page.preview),
|
||||||
tokens: page.tokens,
|
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