Skip to content

Commit 1956566

Browse files
authored
fix(blog): 한글 글 경로와 모바일 쉘 정렬 수정 (#123)
* fix(blog): 한글 글 경로와 모바일 쉘 정렬 수정 * fix(blog): PR 리뷰와 품질 게이트 오류 해소 * test(e2e): 새 모바일 footer 레이아웃 기대값 반영
1 parent aa120f2 commit 1956566

9 files changed

Lines changed: 102 additions & 19 deletions

File tree

blog/services/post-repository.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ ${'x'.repeat(5000)}
9797
expect(await getFeedData(privateSlug)).toBeNull();
9898
});
9999

100+
it('resolves percent-encoded slugs', () => {
101+
const slug = '말하는-구조를-잃어버린-것-같았다';
102+
103+
expect(getFolderSlug(encodeURIComponent(slug))).toBe(slug);
104+
});
105+
100106
it('returns null for non-existent posts folder slug', () => {
101107
expect(getFolderSlug('missing-folder-slug')).toBeNull();
102108
});

blog/services/post-repository.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ const shouldLogContentIssues = process.env.NODE_ENV !== 'test';
1616
const slugToFolderCache = new Map<string, string>();
1717
let cachedSortedFeedData: FeedData[] | null = null;
1818

19+
function normalizeSlug(slug: string): string {
20+
try {
21+
return decodeURIComponent(slug);
22+
} catch {
23+
return slug;
24+
}
25+
}
26+
1927
function logContentIssue(message: string): void {
2028
if (!shouldLogContentIssues) {
2129
return;
@@ -252,15 +260,17 @@ function loadMetadata(folderPath: string): FeedFrontmatter | null {
252260

253261
// Get folder path from slug (using cache or scanning)
254262
export function getFolderSlug(slug: string): string | null {
263+
const normalizedSlug = normalizeSlug(slug);
264+
255265
// Check cache first
256-
if (slugToFolderCache.has(slug)) {
257-
return slugToFolderCache.get(slug)!;
266+
if (slugToFolderCache.has(normalizedSlug)) {
267+
return slugToFolderCache.get(normalizedSlug)!;
258268
}
259269

260270
// Populate slug cache by loading full feed index first
261271
getSortedFeedData({ includePrivate: true });
262-
if (slugToFolderCache.has(slug)) {
263-
return slugToFolderCache.get(slug)!;
272+
if (slugToFolderCache.has(normalizedSlug)) {
273+
return slugToFolderCache.get(normalizedSlug)!;
264274
}
265275

266276
if (isProduction) {
@@ -272,7 +282,7 @@ export function getFolderSlug(slug: string): string | null {
272282

273283
for (const folderPath of allFolders) {
274284
const metadata = loadMetadata(folderPath);
275-
if (metadata?.slug === slug) {
285+
if (metadata?.slug === normalizedSlug) {
276286
return folderPath;
277287
}
278288
}

site/shell/AppShell/AppShell.test.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ vi.mock('next/navigation', () => ({
88
}));
99

1010
describe('AppShell', () => {
11-
it('keeps Archive and Resume visible with external links in the identity rail', () => {
11+
it('keeps primary navigation and external links available', () => {
1212
const { container } = render(
1313
<AppShell>
1414
<main>content</main>
@@ -27,10 +27,9 @@ describe('AppShell', () => {
2727
primaryNavigation.queryByRole('link', { name: 'Tech' })
2828
).not.toBeInTheDocument();
2929
expect(screen.getAllByLabelText('Ark 외부 링크')).toHaveLength(1);
30-
expect(container.querySelector('.ark-site-identity')).toContainElement(
30+
expect(container.querySelector('footer')).toContainElement(
3131
screen.getByLabelText('Ark 외부 링크')
3232
);
33-
expect(container.querySelector('footer')).toBeNull();
3433
expect(
3534
container.querySelector('[data-page-layout="home"]')
3635
).toBeInTheDocument();

site/shell/AppShell/AppShell.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,14 @@ export default function AppShell({ children }: AppShellProps) {
8484
ark
8585
</Link>
8686
</header>
87-
<ExternalLinks />
8887
</aside>
8988

9089
<div className="ark-site-content">{children}</div>
9190

91+
<footer className="ark-site-footer">
92+
<ExternalLinks />
93+
</footer>
94+
9295
<nav aria-label="Ark 주요 탐색" className="ark-site-navigation">
9396
{PRIMARY_LINKS.map((item) => {
9497
const isActive = item.isActive(pathname);

styles/globals.css

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,20 @@
6060
min-width: 0;
6161
}
6262

63+
.ark-site-footer {
64+
grid-column: 1;
65+
grid-row: 1;
66+
position: sticky;
67+
top: 2.5rem;
68+
height: calc(100dvh - 5rem);
69+
align-self: end;
70+
}
71+
72+
.ark-site-footer .ark-site-external-links {
73+
height: 100%;
74+
justify-content: flex-end;
75+
}
76+
6377
.ark-home-statement {
6478
max-width: 34rem;
6579
margin: 0;
@@ -164,7 +178,7 @@
164178
.ark-site-external-links {
165179
display: flex;
166180
flex-direction: column;
167-
margin-top: auto;
181+
margin-top: 0;
168182
pointer-events: auto;
169183
}
170184

styles/globals.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ describe('globals styles', () => {
6464

6565
it('uses a compact single-column mobile shell and article entry scale', () => {
6666
expect(mobileViewportContent).toContain(
67-
"grid-template-areas:\n 'identity'\n 'navigation'\n 'content';"
67+
"grid-template-areas:\n 'identity'\n 'navigation'\n 'content'\n 'footer';"
6868
);
6969
expect(globalsContent).toContain('.ark-article-title {');
7070
expect(globalsContent).toContain('font-size: var(--text-article-title);');

styles/viewport/mobile.css

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
grid-template-areas:
1010
'identity'
1111
'navigation'
12-
'content';
12+
'content'
13+
'footer';
1314
row-gap: var(--space-4);
1415
min-height: auto;
1516
}
@@ -38,6 +39,12 @@
3839
grid-area: content;
3940
}
4041

42+
.ark-site-footer {
43+
grid-area: footer;
44+
position: static;
45+
height: auto;
46+
}
47+
4148
.ark-site-navigation {
4249
grid-area: navigation;
4350
flex-direction: row;
@@ -77,16 +84,60 @@
7784
}
7885

7986
.ark-site-grid[data-page-layout='home'] .ark-site-external-links {
80-
grid-area: external;
8187
display: flex;
8288
flex-direction: column;
8389
align-self: end;
8490
justify-self: start;
8591
gap: 0;
8692
margin-top: 0;
93+
}
94+
95+
.ark-site-grid[data-page-layout='home'] .ark-site-footer {
96+
grid-area: external;
97+
align-self: end;
98+
justify-self: start;
8799
margin-bottom: var(--space-8);
88100
}
89101

102+
.ark-site-grid[data-page-layout='content'] {
103+
grid-template-columns: minmax(0, 1fr) auto;
104+
grid-template-areas:
105+
'identity navigation'
106+
'content content'
107+
'footer footer';
108+
column-gap: var(--space-4);
109+
}
110+
111+
.ark-site-grid[data-page-layout='content'] .ark-site-navigation {
112+
grid-area: navigation;
113+
flex-direction: row;
114+
align-self: center;
115+
justify-self: end;
116+
gap: var(--space-4);
117+
padding-top: 0;
118+
}
119+
120+
.ark-site-grid[data-page-layout='content'] .ark-site-content {
121+
grid-area: content;
122+
}
123+
124+
.ark-site-grid[data-page-layout='content'] .ark-site-footer {
125+
grid-area: footer;
126+
border-top: 1px solid var(--color-divider);
127+
padding-top: var(--space-6);
128+
padding-bottom: var(--space-2);
129+
}
130+
131+
.ark-site-grid[data-page-layout='content'] .ark-site-footer .ark-site-external-links {
132+
height: auto;
133+
}
134+
135+
.ark-site-grid[data-page-layout='content'] .ark-site-external-links {
136+
flex-direction: row;
137+
justify-content: center;
138+
gap: var(--space-4);
139+
}
140+
90141
.ark-site-grid[data-page-layout='home'] .ark-site-content {
91142
grid-area: content;
92143
align-self: start;

tests/e2e/smoke/home-renewal.smoke.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ test.describe('Home and archive', () => {
151151
}
152152

153153
expect(github.x).toBe(32);
154-
expect(github.y).toBe(788);
154+
expect(github.y).toBeGreaterThan(780);
155155
}
156156
});
157157

tests/e2e/smoke/mobile-nav.smoke.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ test.describe('Mobile navigation', () => {
3131
await expect(page).toHaveURL(/\/archive/);
3232
});
3333

34-
test('모바일 상단 컴팩트 영역에 보조 외부 링크를 둬요', async ({ page }) => {
34+
test('모바일 본문 하단에 보조 외부 링크를 둬요', async ({ page }) => {
3535
await page.goto('/archive');
3636

3737
const github = page.getByRole('link', { name: 'GitHub' });
@@ -44,14 +44,14 @@ test.describe('Mobile navigation', () => {
4444

4545
const githubBox = await github.boundingBox();
4646
const emailBox = await email.boundingBox();
47-
if (!githubBox || !emailBox) {
47+
const footerBox = await page.locator('.ark-site-footer').boundingBox();
48+
if (!githubBox || !emailBox || !footerBox) {
4849
throw new Error(
49-
'상단 컴팩트 영역의 외부 링크 위치를 측정할 수 없습니다.'
50+
'본문 하단 footer의 외부 링크 위치를 측정할 수 없습니다.'
5051
);
5152
}
5253

53-
expect(githubBox.x).toBeGreaterThan(160);
54-
expect(githubBox.y).toBeLessThan(120);
54+
expect(githubBox.y).toBeGreaterThanOrEqual(footerBox.y);
5555
expect(emailBox.y).toBe(githubBox.y);
5656
});
5757

0 commit comments

Comments
 (0)