commit 1ab3b5257016688a0ae46d0d1f4a434dda8763c3
parent 3d70e10ff3815e60e5f336da1aee80456b560f61
Author: Yongbin Kim <iam@yongbin.kim>
Date: Sun, 29 Jan 2023 04:03:06 +0900
feat: 헤더 브랜딩에 현재 위키 이름 표시하도록 수정
Signed-off-by: Yongbin Kim <iam@yongbin.kim>
Diffstat:
6 files changed, 94 insertions(+), 6 deletions(-)
diff --git a/components/layout/Navbar.module.css b/components/layout/Navbar.module.css
@@ -1 +1 @@
-.navbar{padding:.5rem 0;z-index:10;background-color:#fafdfa;color:#191c1b}.navbar .item:hover{background-color:rgba(25,28,27,.1)}.navbar.is-primary{background-color:#58fbda;color:#00201a}.navbar.is-primary .item:hover{background-color:rgba(0,32,26,.1)}.navbar.is-secondary{background-color:#f5d9ff;color:#30004a}.navbar.is-secondary .item:hover{background-color:rgba(48,0,74,.1)}.navbar.is-tertiary{background-color:#c7e7ff;color:#001e2e}.navbar.is-tertiary .item:hover{background-color:rgba(0,30,46,.1)}.navbar.is-error{background-color:#ffdad6;color:#410002}.navbar.is-error .item:hover{background-color:rgba(65,0,2,.1)}@media(prefers-color-scheme: dark){.navbar{background-color:#191c1b;color:#e1e3e0}.navbar .item:hover{background-color:rgba(225,227,224,.1)}.navbar.is-primary{background-color:#005143;color:#58fbda}.navbar.is-primary .item:hover{background-color:rgba(88,251,218,.1)}.navbar.is-secondary{background-color:#61317e;color:#f5d9ff}.navbar.is-secondary .item:hover{background-color:rgba(245,217,255,.1)}.navbar.is-tertiary{background-color:#2a4a5f;color:#c7e7ff}.navbar.is-tertiary .item:hover{background-color:rgba(199,231,255,.1)}.navbar.is-error{background-color:#93000a;color:#ffdad6}.navbar.is-error .item:hover{background-color:rgba(255,218,214,.1)}}.navbar>.container{display:flex;padding:0 1rem;align-items:stretch;min-height:2.5rem}.brand,.menu{display:flex;align-items:stretch}.brand{flex-shrink:0;line-height:1.5rem;font-size:1rem;letter-spacing:.009375rem;font-weight:500}.brand .navbar-item{font-weight:bold}.menu{flex:1;justify-content:flex-end}.menu.is-centered{justify-content:center;margin-right:0}.item{display:flex;align-items:center;padding:0 1rem;line-height:2.5rem;border-radius:9999px}.item[href]{color:inherit}.item[href],.item[href]:hover{text-decoration:none}.item.is-dropdown{position:relative;cursor:default}.item.is-dropdown .dropdown{position:absolute;top:100%;z-index:10;min-width:10rem;margin-top:.5rem;padding:.5rem 0;box-shadow:0 14px 28px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.22);border-radius:.5rem;opacity:0;visibility:hidden;transform:translateY(-0.5rem);transition:transform 100ms ease-in-out,opacity 150ms,visibility 150ms;box-shadow:0px 4px 4px 0px rgba(0, 0, 0, 0.2),0px 8px 12px 6px rgba(0, 0, 0, 0.1);background-color:#fafdfa;color:#191c1b;background-color:#f1f6f2}.menu .item.is-dropdown .dropdown{right:0}.brand .item.is-dropdown .dropdown{left:0}@media(prefers-color-scheme: dark){.item.is-dropdown .dropdown{background-color:#191c1b;color:#e1e3e0;background-color:#242a28}}.item.is-dropdown .dropdown hr{margin:.5rem 0;border:0;border-top:1px solid rgba(63,73,70,.24)}@media(prefers-color-scheme: dark){.item.is-dropdown .dropdown hr{border-top:1px solid rgba(191,201,196,.24)}}.item.is-dropdown .dropdown .item{padding:0 1rem;line-height:2.5rem;border-radius:0}.item.is-dropdown:hover .dropdown{opacity:1;visibility:visible;transform:translateY(0)}.dropdown-label{display:flex;align-items:center;padding:.5rem 1rem;line-height:2.5rem;border-radius:9999px;cursor:default;line-height:1rem;font-size:.75rem;letter-spacing:.0416666667rem;font-weight:500}/*# sourceMappingURL=Navbar.module.css.map */
+.navbar{padding:.5rem 0;z-index:10;background-color:#fafdfa;color:#191c1b}.navbar .item:hover{background-color:rgba(25,28,27,.1)}.navbar.is-primary{background-color:#58fbda;color:#00201a}.navbar.is-primary .item:hover{background-color:rgba(0,32,26,.1)}.navbar.is-secondary{background-color:#f5d9ff;color:#30004a}.navbar.is-secondary .item:hover{background-color:rgba(48,0,74,.1)}.navbar.is-tertiary{background-color:#c7e7ff;color:#001e2e}.navbar.is-tertiary .item:hover{background-color:rgba(0,30,46,.1)}.navbar.is-error{background-color:#ffdad6;color:#410002}.navbar.is-error .item:hover{background-color:rgba(65,0,2,.1)}@media(prefers-color-scheme: dark){.navbar{background-color:#191c1b;color:#e1e3e0}.navbar .item:hover{background-color:rgba(225,227,224,.1)}.navbar.is-primary{background-color:#005143;color:#58fbda}.navbar.is-primary .item:hover{background-color:rgba(88,251,218,.1)}.navbar.is-secondary{background-color:#61317e;color:#f5d9ff}.navbar.is-secondary .item:hover{background-color:rgba(245,217,255,.1)}.navbar.is-tertiary{background-color:#2a4a5f;color:#c7e7ff}.navbar.is-tertiary .item:hover{background-color:rgba(199,231,255,.1)}.navbar.is-error{background-color:#93000a;color:#ffdad6}.navbar.is-error .item:hover{background-color:rgba(255,218,214,.1)}}.navbar>.container{display:flex;padding:0 1rem;align-items:stretch;min-height:2.5rem}.brand,.menu{display:flex;align-items:stretch}.brand{flex-shrink:0;line-height:1.5rem;font-size:1rem;letter-spacing:.009375rem;font-weight:500}.brand .navbar-item{font-weight:bold}.menu{flex:1;justify-content:flex-end}.menu.is-centered{justify-content:center;margin-right:0}.item{display:flex;align-items:center;padding:0 1rem;line-height:2.5rem;border-radius:9999px}.item[href]{color:inherit}.item[href],.item[href]:hover{text-decoration:none}.item.is-dropdown{position:relative;cursor:default}.item.is-dropdown .dropdown{position:absolute;top:100%;z-index:10;min-width:10rem;margin-top:.5rem;padding:.5rem 0;box-shadow:0 14px 28px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.22);border-radius:.5rem;opacity:0;visibility:hidden;transform:translateY(-0.5rem);transition:transform 100ms ease-in-out,opacity 150ms,visibility 150ms;box-shadow:0px 4px 4px 0px rgba(0, 0, 0, 0.2),0px 8px 12px 6px rgba(0, 0, 0, 0.1);background-color:#fafdfa;color:#191c1b;background-color:#f1f6f2}.menu .item.is-dropdown .dropdown{right:0}.brand .item.is-dropdown .dropdown{left:0}@media(prefers-color-scheme: dark){.item.is-dropdown .dropdown{background-color:#191c1b;color:#e1e3e0;background-color:#242a28}}.item.is-dropdown .dropdown hr{margin:.5rem 0;border:0;border-top:1px solid rgba(63,73,70,.24)}@media(prefers-color-scheme: dark){.item.is-dropdown .dropdown hr{border-top:1px solid rgba(191,201,196,.24)}}.item.is-dropdown .dropdown .item{padding:0 1rem;line-height:2.5rem;border-radius:0}.item.is-dropdown:hover .dropdown{opacity:1;visibility:visible;transform:translateY(0)}.dropdown-label{display:flex;align-items:center;padding:.5rem 1rem;line-height:2.5rem;border-radius:9999px;cursor:default;line-height:1rem;font-size:.75rem;letter-spacing:.0416666667rem;font-weight:500}.branding-divider{width:1rem;line-height:2.5rem;text-align:center}.branding-divider::before{content:"|"}/*# sourceMappingURL=Navbar.module.css.map */
diff --git a/components/layout/Navbar.module.css.map b/components/layout/Navbar.module.css.map
@@ -1 +1 @@
-{"version":3,"sourceRoot":"","sources":["Navbar.module.scss","../../styles/core/_colors.scss","../../styles/core/_typography.scss","../../styles/core/_elevate.scss"],"names":[],"mappings":"AA0BA,QACE,gBACA,WAVA,yBACA,cAEA,oBACE,mCAYE,mBAhBJ,yBACA,cAEA,+BACE,kCAYE,qBAhBJ,yBACA,cAEA,iCACE,kCAYE,oBAhBJ,yBACA,cAEA,gCACE,kCAYE,iBAhBJ,yBACA,cAEA,6BACE,iCC0GF,mCDtGF,QARE,yBACA,cAEA,oBACE,sCAYE,mBAhBJ,yBACA,cAEA,+BACE,qCAYE,qBAhBJ,yBACA,cAEA,iCACE,sCAYE,oBAhBJ,yBACA,cAEA,gCACE,sCAYE,iBAhBJ,yBACA,cAEA,6BACE,uCAkBF,mBACE,aACA,eACA,oBACA,WAnCU,OA2Cd,aAEE,aACA,oBAGF,OACE,cE2DE,mBACA,eACA,0BACA,gBF3DF,oBACE,iBAIJ,MACE,OACA,yBAEA,kBACE,uBACA,eAQJ,MACE,aACA,mBACA,QA1Ea,OA2Eb,YA5EY,OA6EZ,cA3EY,OA6EZ,YACE,cAEA,8BACE,qBASN,kBACE,kBACA,eAEA,4BACE,kBACA,SACA,WACA,UAhGa,MAiGb,iBACA,gBACA,WAzGK,wDA0GL,cAnGc,MAoGd,UACA,kBACA,8BACA,sEGvDF,kFHoEI,yBACA,cG/EJ,yBHmEE,kCACE,QAGF,mCACE,OCIJ,mCDvBA,4BAyBI,yBACA,cG/EJ,0BHoFE,+BACE,eACA,SAEE,wCCZN,mCDQE,+BAII,4CAIJ,kCACE,QAvIS,OAwIT,YAzIQ,OA0IR,gBASF,kCACE,UACA,mBACA,wBAKN,gBACE,aACA,mBACA,QAxJuB,WAyJvB,YA/JY,OAgKZ,cA9JY,OA+JZ,eEpDE,iBACA,iBACA,8BACA","file":"Navbar.module.css"}
-\ No newline at end of file
+{"version":3,"sourceRoot":"","sources":["Navbar.module.scss","../../styles/core/_colors.scss","../../styles/core/_typography.scss","../../styles/core/_elevate.scss","../../styles/core/_vars.scss"],"names":[],"mappings":"AA0BA,QACE,gBACA,WAVA,yBACA,cAEA,oBACE,mCAYE,mBAhBJ,yBACA,cAEA,+BACE,kCAYE,qBAhBJ,yBACA,cAEA,iCACE,kCAYE,oBAhBJ,yBACA,cAEA,gCACE,kCAYE,iBAhBJ,yBACA,cAEA,6BACE,iCC0GF,mCDtGF,QARE,yBACA,cAEA,oBACE,sCAYE,mBAhBJ,yBACA,cAEA,+BACE,qCAYE,qBAhBJ,yBACA,cAEA,iCACE,sCAYE,oBAhBJ,yBACA,cAEA,gCACE,sCAYE,iBAhBJ,yBACA,cAEA,6BACE,uCAkBF,mBACE,aACA,eACA,oBACA,WAnCU,OA2Cd,aAEE,aACA,oBAGF,OACE,cE2DE,mBACA,eACA,0BACA,gBF3DF,oBACE,iBAIJ,MACE,OACA,yBAEA,kBACE,uBACA,eAQJ,MACE,aACA,mBACA,QA1Ea,OA2Eb,YA5EY,OA6EZ,cA3EY,OA6EZ,YACE,cAEA,8BACE,qBASN,kBACE,kBACA,eAEA,4BACE,kBACA,SACA,WACA,UAhGa,MAiGb,iBACA,gBACA,WAzGK,wDA0GL,cAnGc,MAoGd,UACA,kBACA,8BACA,sEGvDF,kFHoEI,yBACA,cG/EJ,yBHmEE,kCACE,QAGF,mCACE,OCIJ,mCDvBA,4BAyBI,yBACA,cG/EJ,0BHoFE,+BACE,eACA,SAEE,wCCZN,mCDQE,+BAII,4CAIJ,kCACE,QAvIS,OAwIT,YAzIQ,OA0IR,gBASF,kCACE,UACA,mBACA,wBAKN,gBACE,aACA,mBACA,QAxJuB,WAyJvB,YA/JY,OAgKZ,cA9JY,OA+JZ,eEpDE,iBACA,iBACA,8BACA,gBF0DJ,kBACE,MIpLI,KJqLJ,YA5KY,OA6KZ,kBAEA,0BACE","file":"Navbar.module.css"}
+\ No newline at end of file
diff --git a/components/layout/Navbar.module.scss b/components/layout/Navbar.module.scss
@@ -172,3 +172,17 @@ $dropdown-label-padding: 0.5rem vars.$gap;
@include typography.apply('label-medium');
}
+
+// ----------------------------------------
+// Branding Divider
+// ----------------------------------------
+
+.branding-divider {
+ width: vars.$gap;
+ line-height: $item-height;
+ text-align: center;
+
+ &::before {
+ content: '|'
+ }
+}
diff --git a/components/layout/Navbar.tsx b/components/layout/Navbar.tsx
@@ -1,7 +1,10 @@
import Logo from '@/components/elements/Logo'
import Container from '@/components/layout/Container'
+import { WikiInfo } from '@/lib/models/wiki_info'
+import { getSlugAndPath } from '@/lib/utils/wiki'
import Link from 'next/link'
-import { ReactNode } from 'react'
+import { useRouter } from 'next/router'
+import { ReactNode, useEffect, useMemo, useState } from 'react'
import styles from './Navbar.module.css'
interface NavbarProps {
@@ -9,14 +12,48 @@ interface NavbarProps {
}
export default function Navbar (props: NavbarProps) {
+ const router = useRouter()
+ const [wikiName, setWikiName] = useState<string | null>(null)
+
+ const [slug] = getSlugAndPath(router)
+
+ useEffect(() => {
+ if (slug == null) {
+ setWikiName(null)
+ return
+ }
+
+ fetch(`/api/wiki/${slug}`, {
+ method: 'GET'
+ }).then<{ wiki: WikiInfo }, null>((res) => {
+ return res.json()
+ }, (err) => {
+ console.error('failed to get current wiki information', err)
+ return null
+ }).then((data) => {
+ if (data == null) {
+ return
+ }
+
+ setWikiName(data.wiki.title)
+ })
+ }, [slug])
+
return (
<div className={styles.navbar}>
<Container className={styles.container}>
<nav className={styles.brand}>
- {/* TODO: Show current wiki name here */}
<Link href="/" className={styles.item}>
<Logo />
</Link>
+ {slug != null && (
+ <>
+ <div className={styles['branding-divider']}/>
+ <Link href={`/wiki/${slug}`} className={styles.item}>
+ {wikiName}
+ </Link>
+ </>
+ )}
</nav>
<nav className={styles.menu}>
{props.children}
diff --git a/lib/utils/wiki.ts b/lib/utils/wiki.ts
@@ -7,9 +7,12 @@ export const getSlugAndPath = (
context: GetServerSidePropsContext | NextApiRequest | NextRouter,
): [slug: string | null, path: string | null] => {
const { slug, path } = context.query
- if (typeof slug !== 'string' || path == null || !Array.isArray(path)) {
+ if (typeof slug !== 'string') {
return [null, null]
}
+ if (path == null || !Array.isArray(path)) {
+ return [slug, null]
+ }
return [slug, path.join('')]
}
diff --git a/pages/api/wiki/[slug]/index.ts b/pages/api/wiki/[slug]/index.ts
@@ -0,0 +1,34 @@
+import { ApiError, ERR_NOT_FOUND } from '@/lib/apierror'
+import { getWiki, getWikiViaSlug, WikiInfo } from '@/lib/models/wiki_info'
+import { ACL_ACTION_READ, resolveACL } from '@/lib/security/acl'
+import { authenticationFromCookies } from '@/lib/security/token'
+import { getSlugAndPath } from '@/lib/utils/wiki'
+import { NextApiRequest, NextApiResponse } from 'next'
+
+export default async function handler (
+ req: NextApiRequest,
+ res: NextApiResponse<ApiError | { wiki: WikiInfo }>,
+) {
+ const [slug] = getSlugAndPath(req)
+ if (slug == null) {
+ res.status(404).json(ERR_NOT_FOUND)
+ return
+ }
+
+ const token = authenticationFromCookies(req.cookies)
+
+ const wiki = await getWikiViaSlug([slug])
+ if (wiki == null) {
+ res.status(404).json(ERR_NOT_FOUND)
+ return
+ }
+
+ if (!resolveACL(token, wiki.acl, ACL_ACTION_READ)) {
+ res.status(404).json(ERR_NOT_FOUND)
+ return
+ }
+
+ res.status(200).json({
+ wiki: wiki,
+ })
+}