terms: per-pack storage + import from another pack (v0.3.2)

- store.ts: 약관을 manifest/terms/<packKey>/ 폴더별로 저장. 첫 접근 시
  legacy 전역 .md 파일을 시드로 자동 복사한다.
- importTerms() 추가: 다른 음악퀴즈의 .md + _meta.json 을 현재 pack 으로
  복사한다. 동일 kind 는 source 값으로 덮어쓴다.
- /op/agreement 라우트를 세 단계로 분리:
  · /op/agreement → 음악퀴즈 카드 선택 페이지
  · /op/agreement/:packName → 해당 pack 의 약관 목록 + 추가 + 불러오기
  · /op/agreement/:packName/:kind → 에디터
- 공개 라우트도 /manifest/terms/:packKey/:fileName 으로 변경.
- 설치기 main.ts: state.selectedKey 를 약관 URL 에 포함하도록 수정 (메인 +
  rp 양쪽). pack 미선택 상태에서는 에러 반환.
- termsEditor.js: PACK_KEY 를 받아 저장 URL 에 포함.
- 다른 음악퀴즈 후보 select + 확인 모달 + locale 추가.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 01:29:04 +09:00
parent c14b0507c7
commit 25977d894b
11 changed files with 429 additions and 141 deletions

View File

@@ -252,14 +252,15 @@ ipcMain.handle('rp:packs:select', async (_event, packKey: string) => {
ipcMain.handle('rp:i18n:dict', () => localeDict)
// ── IPC: 약관 다운로드 ──────────────────────────────
// 사이트가 /manifest/terms/<kind>.md 로 노출하는 md 파일을 그대로 받아 본문(string) 으로 반환.
// 사이트가 /manifest/terms/<packKey>/<kind>.md 로 노출하는 md 파일을 그대로 받아 본문(string) 으로 반환.
// rp 인스톨러에서는 'resourcepack' 과 'installer-rp' 두 종류만 실제로 사용하지만, 메인
// 인스톨러와 동일한 화이트리스트를 둬서 사이트 컴포넌트 분류와 1:1 로 매칭되게 한다.
const TERM_KIND_WHITELIST = new Set(['map', 'resourcepack', 'mod', 'installer', 'installer-rp'])
ipcMain.handle('rp:terms:get', async (_event, kind: string) => {
if (!TERM_KIND_WHITELIST.has(kind)) return { ok: false, message: 'unknown term kind' }
if (!state.selectedKey) return { ok: false, message: 'pack not selected' }
try {
const url = `${state.baseUrl}/manifest/terms/${encodeURIComponent(kind)}.md`
const url = `${state.baseUrl}/manifest/terms/${encodeURIComponent(state.selectedKey)}/${encodeURIComponent(kind)}.md`
const buf = await fetchBuffer(url)
return { ok: true, content: buf.toString('utf8') }
} catch (error) {