terms: dark editor BG, vertical row layout, add/delete custom kinds

- 약관 편집기 배경/슬래시 메뉴를 사이트 다크 팔레트로 통일 (흰 배경 + 흰 글씨 가시성 문제 해결)
- 약관 목록을 가로 풀폭 1줄씩 세로로 쌓이는 레이아웃으로 변경
- 사용자 정의 약관 추가/삭제 지원
  - manifest/terms/_meta.json 에 라벨 저장
  - builtin 5종(map/resourcepack/mod/installer/installer-rp)은 삭제 불가, "기본" 배지 표시
  - kind 식별자 규칙: 소문자/숫자/하이픈 32자 이내
  - 공개 라우트 /manifest/terms/<file>.md 는 isPublicTermsFile() 로 _meta.json 차단
- 0.3.0 → 0.3.1

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 01:12:10 +09:00
parent ffb2048627
commit c14b0507c7
7 changed files with 338 additions and 53 deletions

View File

@@ -5,6 +5,48 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><%= t('terms.browserTitle') %></title>
<link rel="stylesheet" href="/static/styles.css" />
<style>
/* 약관 목록 — 카드 한 줄(가로 풀폭) 씩 세로로 쌓이도록. */
.termsList { display: flex; flex-direction: column; gap: 10px; margin-top: 16px; }
.termsRow {
display: flex; align-items: center; justify-content: space-between;
gap: 12px;
background: var(--bg-card);
border: 1px solid var(--border, #30363d);
border-radius: 10px;
padding: 14px 18px;
}
.termsRow .termsRowMain { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.termsRow .termsRowLabel { display: flex; align-items: center; gap: 8px; }
.termsRow .termsRowLabel h2 { margin: 0; font-size: 16px; }
.termsRow .termsRowSub { color: var(--text-muted); font-size: 12px; margin-top: 2px; }
.termsRow .termsRowActions { display: flex; gap: 8px; align-items: center; }
.builtinBadge {
display: inline-block; padding: 2px 8px; border-radius: 999px;
background: rgba(255,255,255,0.08); color: var(--text-muted);
font-size: 11px;
}
.termsAddSection {
margin-top: 24px;
background: var(--bg-card);
border: 1px solid var(--border, #30363d);
border-radius: 10px;
padding: 16px 18px;
}
.termsAddSection h2 { margin: 0 0 12px; font-size: 15px; }
.termsAddForm { display: grid; grid-template-columns: 1fr 2fr auto; gap: 10px; align-items: end; }
.termsAddForm .field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.termsAddForm label { font-size: 12px; color: var(--text-muted); }
.termsAddForm input {
background: var(--bg-alt); color: var(--text);
border: 1px solid var(--border, #30363d); border-radius: 6px;
padding: 8px 10px; font-size: 13px;
}
.termsAddForm .hint { color: var(--text-muted); font-size: 11px; }
@media (max-width: 700px) {
.termsAddForm { grid-template-columns: 1fr; }
}
</style>
</head>
<body class="siteBody">
<%- include('../partials/navbar', { userId }) %>
@@ -19,16 +61,53 @@
<p class="muted"><%= t('terms.hint') %></p>
<section class="cardRow horizontalScroll">
<section class="termsList">
<% items.forEach(function (item) { %>
<article class="packCard">
<a class="cardLink" href="/op/agreement/<%= item.kind %>">
<h2><%= item.label %></h2>
<p class="muted"><%= item.kind %>.md</p>
<article class="termsRow">
<a class="termsRowMain" href="/op/agreement/<%= item.kind %>" style="text-decoration:none; color:inherit;">
<div class="termsRowLabel">
<h2><%= item.label %></h2>
<% if (item.builtin) { %>
<span class="builtinBadge"><%= t('terms.builtinBadge') %></span>
<% } %>
</div>
<div class="termsRowSub"><%= item.kind %>.md</div>
</a>
<div class="termsRowActions">
<a class="secondaryButton" href="/op/agreement/<%= item.kind %>"><%= t('terms.edit') %></a>
<% if (!item.builtin) { %>
<form method="post" action="/op/agreement/<%= item.kind %>/delete"
onsubmit="return confirm('<%= t('terms.deleteConfirm', { label: item.label }).replace(/'/g, "\\'") %>');"
style="margin:0;">
<button type="submit" class="dangerButton"><%= t('terms.deleteButton') %></button>
</form>
<% } %>
</div>
</article>
<% }) %>
</section>
<section class="termsAddSection">
<h2><%= t('terms.addHeading') %></h2>
<form method="post" action="/op/agreement/create" class="termsAddForm">
<div class="field">
<label for="newKind"><%= t('terms.kindLabel') %></label>
<input id="newKind" name="kind" type="text" required
pattern="[a-z0-9][a-z0-9-]{0,31}"
placeholder="<%= t('terms.kindPlaceholder') %>" />
<span class="hint"><%= t('terms.kindHint') %></span>
</div>
<div class="field">
<label for="newLabel"><%= t('terms.labelLabel') %></label>
<input id="newLabel" name="label" type="text" required maxlength="50"
placeholder="<%= t('terms.labelPlaceholder') %>" />
</div>
<div class="field">
<label>&nbsp;</label>
<button type="submit" class="primaryButton"><%= t('terms.addButton') %></button>
</div>
</form>
</section>
</main>
</body>
</html>