feat: apply feedback updates and streamline online delivery
Publish GitHub Pages output / publish (push) Successful in 34s

This commit is contained in:
2026-09-01 22:21:45 +09:00
parent 98d5575463
commit c0f4060dec
36 changed files with 447 additions and 471 deletions
+1 -74
View File
@@ -1,23 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
import { dev } from '$app/environment';
import updateHistoryMarkdown from '../../../UPDATES.md?raw';
import { renderReleaseMarkdown } from '$lib/release-notes/markdown';
import { currentAppVersion, releaseNotes } from '$lib/release-notes/release-note-store';
import { versionTagIsNewer } from '$lib/release-notes/version';
import { parseUpdateHistoryDocument } from '$lib/update-history-document.ts';
let previewUpdate = false;
const updateHistoryDocument = parseUpdateHistoryDocument(updateHistoryMarkdown);
onMount(() => {
previewUpdate = dev && new URLSearchParams(window.location.search).get('previewUpdate') === '1';
void releaseNotes.load();
});
$: latestRelease = $releaseNotes.releases[0] ?? null;
$: updateAvailable = latestRelease !== null && (previewUpdate || versionTagIsNewer(latestRelease.tagName, currentAppVersion));
function updatedDateTime(value: string | null): string {
if (!value) return '시각 미등록';
const date = new Date(value);
@@ -32,91 +18,32 @@
}).format(date);
}
function publishedDate(value: string): string {
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return new Intl.DateTimeFormat('ko-KR', {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date);
}
</script>
<aside class:available={updateAvailable} class="update-notice" aria-live="polite">
<aside class="update-notice" aria-live="polite">
<div class="notice-header">
<div class="version-summary">
<span class="label">최근 업데이트</span>
<strong><time datetime={updateHistoryDocument.updatedAt ?? undefined}>{updatedDateTime(updateHistoryDocument.updatedAt)}</time></strong>
{#if previewUpdate}
<span class="status"><b>업데이트 알림 미리보기</b></span>
{:else if updateAvailable}
<span class="status"><b>{latestRelease.tagName}</b> 업데이트를 사용할 수 있습니다.</span>
{/if}
</div>
<a class="action" href="/updates">업데이트 내역 확인 <span aria-hidden="true">→</span></a>
</div>
{#if updateAvailable && latestRelease}
<section class="latest-note" aria-labelledby="latest-release-title">
<header>
<div>
<span class="release-tag">{latestRelease.tagName}</span>
<h2 id="latest-release-title">{latestRelease.name}</h2>
</div>
<time datetime={latestRelease.publishedAt}>{publishedDate(latestRelease.publishedAt)}</time>
</header>
{#if latestRelease.body.trim()}
<div class="markdown-body">{@html renderReleaseMarkdown(latestRelease.body)}</div>
{:else}
<p class="no-notes">작성된 릴리즈 노트가 없습니다.</p>
{/if}
</section>
{/if}
</aside>
<style>
.update-notice { margin-bottom: 12px; border: 1px solid #2c3440; border-radius: 12px; background: #151a22; overflow: hidden; }
.update-notice.available { border-color: rgb(237 139 181 / 38%); background: rgb(237 139 181 / 6%); }
.notice-header { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 14px 18px; }
.version-summary { display: flex; align-items: baseline; min-width: 0; gap: 10px; }
.label { color: #7f8998; font-size: 13px; font-weight: 800; }
strong { color: #eef1f5; font-size: 15px; }
.status { color: #a4adba; font-size: 14px; }
.status b { color: #f0a1c3; }
.action { flex: 0 0 auto; color: #aab3c0; font-size: 14px; font-weight: 800; text-decoration: none; }
.available .action { color: #f2a6c6; }
.action span { margin-left: 4px; }
.action:hover { color: #ffd0e2; }
.action:focus-visible { outline: 3px solid rgb(239 139 182 / 28%); outline-offset: 3px; }
.latest-note { padding: 24px 26px 28px; border-top: 1px solid rgb(237 139 181 / 20%); background: rgb(13 16 22 / 38%); }
.latest-note > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; padding-bottom: 18px; border-bottom: 1px solid rgb(255 255 255 / 8%); }
.release-tag { color: #f0a1c3; font-size: 13px; font-weight: 900; }
.latest-note h2 { margin: 6px 0 0; color: #edf0f5; font-size: 22px; letter-spacing: -.025em; }
.latest-note time { flex: 0 0 auto; color: #858f9e; font-size: 13px; }
.markdown-body { padding-top: 18px; color: #b7bfca; font-size: 14px; line-height: 1.7; overflow-wrap: anywhere; }
.markdown-body :global(:first-child) { margin-top: 0; }
.markdown-body :global(:last-child) { margin-bottom: 0; }
.markdown-body :global(h1), .markdown-body :global(h2), .markdown-body :global(h3), .markdown-body :global(h4) { margin: 1.35em 0 .5em; color: #eef1f5; line-height: 1.35; }
.markdown-body :global(h1) { font-size: 22px; }
.markdown-body :global(h2) { font-size: 19px; }
.markdown-body :global(h3) { font-size: 17px; }
.markdown-body :global(p), .markdown-body :global(ul), .markdown-body :global(ol), .markdown-body :global(blockquote), .markdown-body :global(pre) { margin: .75em 0; }
.markdown-body :global(ul), .markdown-body :global(ol) { padding-left: 1.5em; }
.markdown-body :global(a) { color: #f0a1c3; text-underline-offset: 3px; }
.markdown-body :global(code) { padding: 2px 5px; border-radius: 5px; color: #e8b3ca; background: #202631; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: .9em; }
.markdown-body :global(pre) { padding: 14px; border: 1px solid #303846; border-radius: 9px; background: #10141a; overflow-x: auto; }
.markdown-body :global(pre code) { padding: 0; color: #c9d0da; background: transparent; }
.markdown-body :global(blockquote) { padding-left: 14px; border-left: 3px solid #596475; color: #929cab; }
.markdown-body :global(img) { max-width: 100%; border-radius: 9px; }
.no-notes { margin: 18px 0 0; color: #818b99; font-size: 14px; }
@media (max-width: 680px) {
.notice-header { align-items: flex-start; flex-direction: column; gap: 8px; }
.version-summary { align-items: flex-start; flex-wrap: wrap; }
.latest-note { padding: 22px 18px 24px; }
.latest-note > header { flex-direction: column; gap: 8px; }
}
</style>