From 2a258be3daf91852ffd3ce5b5889ae0702f732f9 Mon Sep 17 00:00:00 2001 From: chpark Date: Thu, 30 Apr 2026 12:08:35 +0900 Subject: [PATCH] PHP-parity: anon mb_level=1 + mega-menu separator-grouped columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Board access (gnuboard common.php parity): - gnuboard sets default mb_level=1 for anonymous visitors (common.php:675) - React was treating anon as level 0, so bo_read_level=1 boards were blocking anonymous reads when PHP allowed them. Fix: checkBoardAccess() now coerces userLevel<1 to 1 before comparison - Verified externally: anon /free /review /humor → 200 (read=1), /notice → 307 (read=2) Mega-menu (포인트게임 layout fix): - gnuboard's g5_eyoom_menu uses leaf separator labels like "─ 스포츠 ─" / "─ 미니게임 ─" / "─ 슬롯/릴 ─" with href='#' to visually group sub-items. - MegaPanel now recognises these as section breaks: each separator starts a new column with the cleaned label as the column title, and following leaves attach to that section until the next separator. - Fallback: items that have actual children still render as titled groups. - Result: 포인트게임 hover now lays out as 포인트 바카라 (loose) | 스포츠 | 미니게임 | 슬롯/릴 | (loose tail) instead of one giant column + scattered group headers. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../apps/web/src/components/Chrome/Header.tsx | 35 +++++++++++++------ next-app/apps/web/src/lib/legacy-board.ts | 13 +++++-- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/next-app/apps/web/src/components/Chrome/Header.tsx b/next-app/apps/web/src/components/Chrome/Header.tsx index 901db73..18958e2 100644 --- a/next-app/apps/web/src/components/Chrome/Header.tsx +++ b/next-app/apps/web/src/components/Chrome/Header.tsx @@ -164,23 +164,36 @@ function IconLink({ href, Icon, emoji, label, badge }: { href: string; Icon?: Re /** Group submenu items: items with children render as section headers with their kids inline below. */ function MegaPanel({ items }: { items: MenuItem[] }) { - // Group leaves into a "MAIN" section then add each titled child group as its own column. - const looseLeaves: MenuItem[] = []; - const groupSections: { title: string; href?: string; links: MenuItem[] }[] = []; + // Two grouping strategies — pick whichever gnuboard's data hints at: + // (a) child-grouped: items with `children` become titled section columns + // (b) separator-grouped: a leaf with href='#' or label like "─ X ─" / "── X ──" + // starts a new section with that label as the column title (used by the + // gnuboard 슬생 data, which flattens 포인트게임 sub-menu) + const sections: { title?: string; href?: string; links: MenuItem[] }[] = []; + let cur: { title?: string; href?: string; links: MenuItem[] } = { links: [] }; + function flush() { if (cur.links.length > 0 || cur.title) sections.push(cur); cur = { links: [] }; } + + const isSeparator = (it: MenuItem) => { + const lab = (it.label || '').replace(/[─\-=ㅡ]/g, '').trim(); + return (it.href === '#' || !it.href) && lab.length <= 8 && (it.label || '').match(/[─\-=ㅡ]/); + }; + for (const it of items) { if (it.children && it.children.length > 0) { - groupSections.push({ title: it.label, href: it.href, links: it.children }); + flush(); + sections.push({ title: it.label, href: it.href, links: it.children }); + } else if (isSeparator(it)) { + flush(); + const label = (it.label || '').replace(/[─\-=ㅡ\s]/g, '').trim(); + cur = { title: label || it.label, links: [] }; } else { - looseLeaves.push(it); + cur.links.push(it); } } - // Always show loose leaves first as a single "전체" column, then group columns. - const sections: { title?: string; href?: string; links: MenuItem[] }[] = []; - if (looseLeaves.length > 0) sections.push({ links: looseLeaves }); - sections.push(...groupSections); + flush(); - // Layout: 1 col when 1-2 sections, 2 col when 3, 3 col when 4+ but never more than 4 cols. - const colCount = sections.length <= 2 ? 1 : sections.length === 3 ? 2 : Math.min(4, sections.length); + // Layout: pick column count by total sections; cap 4 + const colCount = sections.length <= 1 ? 1 : sections.length === 2 ? 2 : sections.length === 3 ? 3 : Math.min(4, sections.length); const colClass = colCount === 1 ? 'grid-cols-1' : colCount === 2 ? 'grid-cols-2' : colCount === 3 ? 'grid-cols-3' : 'grid-cols-4'; return ( diff --git a/next-app/apps/web/src/lib/legacy-board.ts b/next-app/apps/web/src/lib/legacy-board.ts index 1ed66ee..c42ae93 100644 --- a/next-app/apps/web/src/lib/legacy-board.ts +++ b/next-app/apps/web/src/lib/legacy-board.ts @@ -76,17 +76,24 @@ export async function getBoardMeta(slug: string): Promise= required) return { ok: true }; + // Anonymous visitors are level 1 in gnuboard common.php — treat 0 as 1 + const effective = userLevel >= 1 ? userLevel : 1; + if (effective >= required) return { ok: true }; return { ok: false, reason: `이 게시판은 레벨 ${required} 이상만 ${action === 'read' ? '열람' : action === 'write' ? '작성' : '댓글 작성'} 가능합니다`, - needsLogin: userLevel === 0, + needsLogin: userLevel === 0 && required > 1, required, }; }