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
38 changes: 36 additions & 2 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -947,13 +947,30 @@ export class BamLearningApp {
this.renderLesson();
const updatedButton = document.querySelector("[data-toggle-complete]");
updatedButton?.focus();
this.announce(wasCompleted ? "학습 완료 표시를 해제했습니다." : "학습을 완료로 표시했습니다.");
this.announce(
wasCompleted ? "학습 완료 표시를 해제했습니다." : "학습을 완료로 표시했습니다.",
);
} catch {
this.announce("진도를 저장하지 못했습니다. 브라우저 저장 공간 설정을 확인해 주세요.");
}
return;
}

const modelAnswerButton = event.target.closest("[data-toggle-model-answer]");
if (modelAnswerButton) {
const modelAnswer = document.querySelector("#lesson-model-answer");
if (!modelAnswer) return;

const isExpanded = modelAnswerButton.getAttribute("aria-expanded") === "true";
modelAnswer.hidden = isExpanded;
modelAnswerButton.setAttribute("aria-expanded", String(!isExpanded));
modelAnswerButton.textContent = isExpanded ? "면접 답변 보기" : "면접 답변 숨기기";

if (isExpanded) modelAnswerButton.focus();
else modelAnswer.focus();
return;
}

const copyButton = event.target.closest("[data-copy-code]");
if (copyButton) {
const codeElement = copyButton.closest(".code-card")?.querySelector("code");
Expand Down Expand Up @@ -2616,6 +2633,21 @@ export class BamLearningApp {
languageCodingTestCollection,
);
const supportsLanguageFeatures = course.categoryId === "language";
const normalizedMarkdown = String(this.currentMarkdown).replaceAll("\r\n", "\n");
const objectivesMatch = normalizedMarkdown.match(
/(?:^|\n)(## 학습 목표\n[\s\S]*?)(?=\n##\s|$)/,
);
const objectivesMarkdown = objectivesMatch?.[1] ?? "";
let lessonBodyMarkdown = objectivesMatch
? normalizedMarkdown.replace(objectivesMatch[0], "\n")
: normalizedMarkdown;
const modelAnswerMatch = lessonBodyMarkdown.match(
/(?:^|\n)(## 면접 답변 예시\n[\s\S]*?)(?=\n##\s|$)/,
);
const modelAnswerMarkdown = modelAnswerMatch?.[1] ?? "";
if (modelAnswerMatch) {
lessonBodyMarkdown = lessonBodyMarkdown.replace(modelAnswerMatch[0], "\n");
}

const mainContent = `
<main class="main-area" id="lesson-content" tabindex="-1">
Expand All @@ -2630,14 +2662,16 @@ export class BamLearningApp {
</div>
<h1>${escapeHtml(lesson.title)}</h1>
<p class="lesson-summary">${escapeHtml(lesson.summary)}</p>
${objectivesMarkdown ? `<section class="lesson-objectives" aria-labelledby="학습-목표">${renderMarkdown(objectivesMarkdown)}</section>` : ""}
<div class="essential-question">
<span aria-hidden="true">?</span>
<div><small>오늘의 핵심 질문</small><strong>${escapeHtml(lesson.essentialQuestion)}</strong></div>
</div>
</header>

<article class="lesson-body">
${renderMarkdown(this.currentMarkdown, { skipFirstHeading: true })}
${renderMarkdown(lessonBodyMarkdown, { skipFirstHeading: true })}
${modelAnswerMarkdown ? `<div class="lesson-model-answer-disclosure"><p id="lesson-model-answer-help">답을 보기 전에 먼저 확인 문제를 자신의 말로 설명해 보세요.</p><button class="button button--secondary lesson-model-answer-toggle" type="button" data-toggle-model-answer aria-controls="lesson-model-answer" aria-describedby="lesson-model-answer-help" aria-expanded="false">면접 답변 보기</button></div><section class="lesson-model-answer" id="lesson-model-answer" role="region" tabindex="-1" aria-labelledby="면접-답변-예시" hidden>${renderMarkdown(modelAnswerMarkdown)}</section>` : ""}
</article>

<section class="completion-card${isCompleted ? " is-complete" : ""}" aria-labelledby="completion-title">
Expand Down
75 changes: 71 additions & 4 deletions styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,35 @@ a.language-nav-link:hover {
line-height: 1.75;
}

.lesson-objectives {
margin-top: 26px;
}

.lesson-objectives h2 {
margin: 0 0 12px;
color: var(--color-text);
font-size: 18px;
line-height: 1.35;
}

.lesson-objectives ul {
display: grid;
gap: 8px;
padding-left: 1.35rem;
margin: 0;
color: var(--color-text-soft);
}

.lesson-objectives li {
padding-left: 4px;
overflow-wrap: anywhere;
}

.lesson-objectives li::marker {
color: var(--color-primary-strong);
font-weight: 800;
}

.essential-question {
display: flex;
gap: 13px;
Expand Down Expand Up @@ -609,6 +638,34 @@ a.language-nav-link:hover {
color: var(--color-text-soft);
}

.lesson-model-answer {
scroll-margin-top: 80px;
}

.lesson-model-answer-disclosure {
display: flex;
gap: 18px;
align-items: center;
justify-content: space-between;
padding-top: 24px;
margin-top: 26px;
border-top: 1px solid var(--color-border);
}

.lesson-model-answer-disclosure p {
margin: 0;
}

.lesson-model-answer-toggle {
min-height: 44px;
flex: 0 0 auto;
}

.lesson-model-answer:focus {
outline: 2px solid var(--color-primary-strong);
outline-offset: 8px;
}

.lesson-body h2,
.lesson-body h3,
.lesson-body h4 {
Expand Down Expand Up @@ -664,11 +721,12 @@ a.language-nav-link:hover {
color: var(--color-text);
}

.lesson-body :not(pre) > code {
.lesson-body :not(pre) > code,
.lesson-objectives :not(pre) > code {
padding: 0.13em 0.38em;
border: 1px solid transparent;
border-radius: 5px;
background: #292929;
background: var(--color-surface-raised);
font-family: var(--font-mono);
font-size: 0.88em;
overflow-wrap: anywhere;
Expand Down Expand Up @@ -1387,7 +1445,7 @@ a.language-nav-link:hover {
padding: 0.12em 0.34em;
border: 1px solid transparent;
border-radius: 0.35rem;
background: #292929;
background: var(--color-surface-raised);
font-family: var(--font-mono);
font-size: 0.9em;
overflow-wrap: anywhere;
Expand Down Expand Up @@ -1911,7 +1969,7 @@ a.language-nav-link:hover {
padding: 0.08em 0.32em;
border: 1px solid transparent;
border-radius: 5px;
background: #292929;
background: var(--color-surface-raised);
font-family: var(--font-mono);
font-size: 0.9em;
overflow-wrap: anywhere;
Expand Down Expand Up @@ -4601,6 +4659,15 @@ a.language-nav-link:hover {
font-size: 21px;
}

.lesson-model-answer-disclosure {
align-items: stretch;
flex-direction: column;
}

.lesson-model-answer-toggle {
width: 100%;
}

.code-card {
margin-right: -8px;
margin-left: -8px;
Expand Down
13 changes: 9 additions & 4 deletions tests/accessibility.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,31 +35,36 @@ test("주요 버튼의 기본·hover 색 대비가 일반 텍스트 AA 기준을
test("긴 교안 제목과 인라인 코드가 320px 문서 폭을 늘리지 않도록 줄바꿈한다", async () => {
const css = await readFile(new URL("../styles/app.css", import.meta.url), "utf8");
const lessonTitleRule = css.match(/\.lesson-hero h1\s*\{([^}]*)\}/);
const inlineCodeRule = css.match(/\.lesson-body :not\(pre\) > code\s*\{([^}]*)\}/);
const inlineCodeRule = css.match(
/\.lesson-body :not\(pre\) > code,\s*\.lesson-objectives :not\(pre\) > code\s*\{([^}]*)\}/,
);

assert.ok(lessonTitleRule);
assert.ok(inlineCodeRule);
assert.match(lessonTitleRule[1], /overflow-wrap:\s*anywhere/);
assert.match(inlineCodeRule[1], /overflow-wrap:\s*anywhere/);
assert.match(inlineCodeRule[1], /border:\s*1px solid transparent/);
assert.match(inlineCodeRule[1], /background:\s*#292929/);
assert.match(inlineCodeRule[1], /background:\s*var\(--color-surface-raised\)/);
});

test("인라인 코드는 참고 UI의 byte 톤을 쓰고 오류 제목은 물결 밑줄로 구분한다", async () => {
const css = await readFile(new URL("../styles/app.css", import.meta.url), "utf8");
const tokens = await readFile(new URL("../styles/tokens.css", import.meta.url), "utf8");
const sharedInlineRule = css.match(/#lesson-content :not\(pre\) > code\s*\{([^}]*)\}/)?.[1] ?? "";
const quizInlineRule = css.match(/\.quiz-card :not\(pre\) > code\s*\{([^}]*)\}/)?.[1] ?? "";
const questInlineRule = css.match(/\.quest-prose code\s*\{([^}]*)\}/)?.[1] ?? "";
const raisedSurface = tokens.match(/--color-surface-raised:\s*(#[\da-f]{6})\s*;/i)?.[1];
const diagnosticRule = css.match(
/\.quiz-answer-summary\.is-incorrect h3,[\s\S]*?\.web-project-result-item\.is-engine_error strong\s*\{([^}]*)\}/,
)?.[1] ?? "";

for (const rule of [quizInlineRule, questInlineRule]) {
assert.match(rule, /border:\s*1px solid transparent/);
assert.match(rule, /background:\s*#292929/);
assert.match(rule, /background:\s*var\(--color-surface-raised\)/);
}
assert.match(sharedInlineRule, /color:\s*#e26b60/);
assert.ok(contrastRatio("#e26b60", "#292929") >= 4.5);
assert.ok(raisedSurface);
assert.ok(contrastRatio("#e26b60", raisedSurface) >= 4.5);
assert.match(diagnosticRule, /text-decoration-color:\s*var\(--color-danger\)/);
assert.match(diagnosticRule, /text-decoration-line:\s*underline/);
assert.match(diagnosticRule, /text-decoration-style:\s*wavy/);
Expand Down
Loading
Loading