Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/components/Activities.astro
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
import Sentences from './Sentences.astro';
import { activityGroups } from '../data/activities';
---
<section id="activities" class="activities shell">
<div class="section-heading"><h2>그 밖의 활동</h2></div>
{activityGroups.map(group=><div class="activity-group"><h3 class="eyebrow">{group.label}</h3><ul>{group.items.map(item=><li class:list={[{ 'is-emphasis': item.emphasis }]}><span class="mono activity-period">{item.period}</span><div><strong>{item.url ? <a href={item.url}>{item.title} <span aria-hidden="true">↗</span></a> : item.title}</strong>{item.meta && <span class="activity-meta">{item.meta}</span>}{item.detail && <p>{item.detail}</p>}</div></li>)}</ul></div>)}
{activityGroups.map(group=><div class="activity-group"><h3 class="eyebrow">{group.label}</h3><ul>{group.items.map(item=><li class:list={[{ 'is-emphasis': item.emphasis }]}><span class="mono activity-period">{item.period}</span><div><strong>{item.url ? <a href={item.url}>{item.title} <span aria-hidden="true">↗</span></a> : item.title}</strong>{item.meta && <span class="activity-meta">{item.meta}</span>}{item.detail && <p><Sentences text={item.detail}/></p>}</div></li>)}</ul></div>)}
</section>
3 changes: 2 additions & 1 deletion src/components/CaseStudy.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
import Sentences from './Sentences.astro';
import {render, type CollectionEntry} from 'astro:content';
import DiagramFigure from './DiagramFigure.astro';
import DecisionBlock from './DecisionBlock.astro';
Expand All @@ -8,7 +9,7 @@ const {Content}=await render(entry);
const d=entry.data;
---
<article class="case-study" id={entry.id}>
<header class="case-header"><span class="eyebrow">CASE 0{d.order}</span><h2>{d.title}</h2><p class="case-summary">{d.summary}</p></header>
<header class="case-header"><span class="eyebrow">CASE 0{d.order}</span><h2>{d.title}</h2><p class="case-summary"><Sentences text={d.summary}/></p></header>
<DecisionBlock role={d.role} decision={d.decision} tradeoff={d.tradeoff}/>
<DiagramFigure diagram={d.diagram} caption={d.diagramCaption} title={d.title} prefix={`${print?'print':'detail'}-${entry.id}`} print={print}/>
<div class="prose"><Content /></div>
Expand Down
3 changes: 2 additions & 1 deletion src/components/DecisionBlock.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import Sentences from './Sentences.astro';
type Props = {readonly role:string;readonly decision:string;readonly tradeoff:string};
const {role,decision,tradeoff}=Astro.props;
---
<dl class="decision-block"><div><dt>내가 맡은 범위</dt><dd>{role}</dd></div><div><dt>선택과 이유</dt><dd>{decision}</dd></div><div><dt>트레이드오프</dt><dd>{tradeoff}</dd></div></dl>
<dl class="decision-block"><div><dt>내가 맡은 범위</dt><dd><Sentences text={role}/></dd></div><div><dt>선택과 이유</dt><dd><Sentences text={decision}/></dd></div><div><dt>트레이드오프</dt><dd><Sentences text={tradeoff}/></dd></div></dl>
5 changes: 3 additions & 2 deletions src/components/DiagramFigure.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
import Sentences from './Sentences.astro';
import {pathTo} from '../lib/paths';
type Props = {readonly diagram: string; readonly caption: string; readonly title: string; readonly prefix: string; readonly print?: boolean};
const {diagram,caption,title,prefix,print=false}=Astro.props;
Expand All @@ -8,9 +9,9 @@ const id=`${prefix}-${diagram}`;
<figure class="diagram-figure">
<div class="figure-toolbar"><span class="eyebrow">SYSTEM FLOW</span>{!print && <a class="diagram-open" href={source} data-dialog={id} aria-haspopup="dialog">크게 보기 <span aria-hidden="true">↗</span></a>}</div>
<div class="diagram-frame"><img src={source} alt={`${title} 흐름도. ${caption}`} loading="eager" /></div>
<figcaption>{caption}</figcaption>
<figcaption><Sentences text={caption}/></figcaption>
</figure>
{!print && <dialog id={id} aria-label={`${title} 다이어그램 확대`} class="diagram-dialog"><div class="dialog-top"><strong>{title}</strong><button type="button" class="dialog-close" autofocus>닫기 ×</button></div><div class="dialog-body"><img src={source} alt={caption} /></div><p>{caption}</p></dialog>}
{!print && <dialog id={id} aria-label={`${title} 다이어그램 확대`} class="diagram-dialog"><div class="dialog-top"><strong>{title}</strong><button type="button" class="dialog-close" autofocus>닫기 ×</button></div><div class="dialog-body"><img src={source} alt={caption} /></div><p><Sentences text={caption}/></p></dialog>}
<script>
for (const link of document.querySelectorAll<HTMLAnchorElement>('.diagram-open')) {
const id=link.dataset['dialog'];
Expand Down
3 changes: 2 additions & 1 deletion src/components/ProjectCard.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
import Sentences from './Sentences.astro';
import type { CollectionEntry } from 'astro:content';
import { editionPath, type Edition } from '../lib/editions';
type Props = {readonly project: CollectionEntry<'projects'>; readonly number: string; readonly edition: Edition};
Expand All @@ -8,6 +9,6 @@ const p=project.data;
<a class="project-card" href={editionPath(edition, `projects/${project.id}/`)}>
<div class="card-label"><span class="eyebrow">{p.category}</span><span class="mono">/{number}</span></div>
<div class="project-name">{p.name}<span class="project-arrow" aria-hidden="true">↗</span></div>
<h3>{p.tagline}</h3><p>{p.description}</p>
<h3>{p.tagline}</h3><p><Sentences text={p.description}/></p>
<div class="card-bottom"><span>{p.role}<small>{p.focus}</small></span><span class="case-link">사례 보기 <span aria-hidden="true">→</span></span></div>
</a>
5 changes: 3 additions & 2 deletions src/components/ProjectHeader.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
import Sentences from './Sentences.astro';
import type { CollectionEntry } from 'astro:content';
type Props = {readonly project: CollectionEntry<'projects'>; readonly number: string};
const {project, number} = Astro.props;
Expand All @@ -7,8 +8,8 @@ const p=project.data;
<header class="project-header">
<div class="eyebrow">PROJECT {number} / {p.category}</div>
<h1>{p.name}<span>{p.tagline}</span></h1>
<p class="project-description">{p.description}</p>
<p class="project-description"><Sentences text={p.description}/></p>
<dl class="project-meta"><div><dt>ROLE</dt><dd>{p.role}</dd></div><div><dt>PERIOD</dt><dd>{p.period}</dd></div><div><dt>STACK</dt><dd>{p.stack.join(' · ')}</dd></div></dl>
<p class="project-scope">{p.scope} 사례의 테스트 건수와 측정값은 당시 PR·개발 기록 기준입니다.</p>
<p class="project-scope"><Sentences text={`${p.scope} 사례의 테스트 건수와 측정값은 당시 PR·개발 기록 기준입니다.`}/></p>
{p.links.length>0 && <p class="project-links">{p.links.map(link=><a class="text-link" href={link.url}>{link.label} <span aria-hidden="true">↗</span></a>)}</p>}
</header>
6 changes: 6 additions & 0 deletions src/components/Sentences.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
// Renders each sentence of a short text field on its own line.
type Props = { readonly text: string };
const sentences = Astro.props.text.split(/(?<=[.!?])\s+(?=\S)/);
---
{sentences.map(sentence => <span class="sentence">{sentence}</span>)}
3 changes: 3 additions & 0 deletions src/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ img {
.projects-section {
padding-top: var(--space-8);
}
.sentence {
display: block;
}
.section-heading {
display: flex;
align-items: center;
Expand Down
11 changes: 11 additions & 0 deletions tests/portfolio.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,14 @@ test('activities section lists other work on both editions and in print', async
await expect(page.locator('#activities .is-emphasis')).toContainText('파트장');
}
});

test('multi-sentence descriptions break into one line per sentence', async ({ page }) => {
await page.goto('./');
const kokkok = page.locator('#activities li', { hasText: 'KOKKOK' }).locator('p .sentence');
await expect(kokkok).toHaveCount(2);
await expect(kokkok.first()).toHaveText('친구들과 예약 일정을 공유하고 준비 상태부터 성공 여부까지 같이 확인하는 앱.');
const tops = await kokkok.evaluateAll(spans => spans.map(span => span.getBoundingClientRect().top));
expect(tops[1]).toBeGreaterThan(tops[0]);
await page.goto('projects/gguk/');
await expect(page.locator('.project-scope .sentence').last()).toHaveText('사례의 테스트 건수와 측정값은 당시 PR·개발 기록 기준입니다.');
});
Loading