terms: per-term installer visibility toggles + universal delete (v0.3.4)
- _meta.json: customLabels -> terms.{label,showInInstaller,showInInstallerRp}
- Drop builtin protection; any term kind can be deleted/added/toggled
- New public route /manifest/terms/<pack>/index.json for installer term lists
- Installers fetch terms:list dynamically; skip agreement step if list empty
- Term editor: 2 visibility checkboxes (설치기 / 리소스팩 설치기), multi-select
- Migration from old schema preserves custom labels (default: visible in both)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,9 @@ import {
|
||||
manifestRootPath, manifestDirPath, manifestTermsDirPath,
|
||||
fileDirPath, viewsDirPath, publicDirPath
|
||||
} from '../shared/paths.js'
|
||||
import { ensurePackTermsDir, isPublicTermsFile, loadPackDefinition } from '../shared/store.js'
|
||||
import {
|
||||
ensurePackTermsDir, isPublicTermsFile, listTermsWithLabels, loadPackDefinition
|
||||
} from '../shared/store.js'
|
||||
import { loadEnv } from '../shared/env.js'
|
||||
import { t, localeDict } from './i18n.js'
|
||||
import { indexRouter } from './routes/index.js'
|
||||
@@ -71,6 +73,29 @@ app.get('/manifest.json', (_req, res) => {
|
||||
// 요청하는 경우에도 작동하도록, 실제 pack 이면 ensurePackTermsDir 로 v0.3.1
|
||||
// 전역 .md 들을 시드 복사한 뒤 sendFile 한다. 임의 packKey 로 빈 폴더가
|
||||
// 생성되는 것은 loadPackDefinition 으로 차단.
|
||||
// 설치기가 자기에게 표시할 약관 목록을 받아갈 수 있도록 packKey 별 index.json.
|
||||
// 응답: [{ kind, label, showInInstaller, showInInstallerRp }]. v0.3.4~ builtin 개념이
|
||||
// 없어졌으므로 인스톨러는 이 목록을 받아 자기 인스톨러용(`showInInstaller` / `showInInstallerRp`)
|
||||
// 으로 필터링해서 탭을 만든다.
|
||||
app.get('/manifest/terms/:packKey/index.json', async (req, res, next) => {
|
||||
try {
|
||||
const { packKey } = req.params
|
||||
if (!/^[a-zA-Z0-9_\-]+$/.test(packKey)) {
|
||||
res.status(404).json({ terms: [] })
|
||||
return
|
||||
}
|
||||
const pack = await loadPackDefinition(packKey)
|
||||
if (!pack) {
|
||||
res.status(404).json({ terms: [] })
|
||||
return
|
||||
}
|
||||
const terms = await listTermsWithLabels(packKey)
|
||||
res.json({ terms })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/manifest/terms/:packKey/:fileName', async (req, res, next) => {
|
||||
try {
|
||||
const { packKey, fileName } = req.params
|
||||
|
||||
@@ -5,9 +5,8 @@ import {
|
||||
createTerm,
|
||||
deletePackKeys,
|
||||
deleteTerm,
|
||||
getTermLabel,
|
||||
getTermEntry,
|
||||
importTerms,
|
||||
isBuiltinTermKind,
|
||||
isTermKind,
|
||||
listPackKeys,
|
||||
listTermsWithLabels,
|
||||
@@ -20,7 +19,8 @@ import {
|
||||
renamePack,
|
||||
sanitizePackKey,
|
||||
saveTerm,
|
||||
savePackList
|
||||
savePackList,
|
||||
setTermVisibility
|
||||
} from '../../shared/store.js'
|
||||
import { fetchReleaseVersions } from '../../shared/mojang.js'
|
||||
import { fetchPlaylistEntries, fetchVideoMeta, YtDlpUnavailableError } from '../youtube.js'
|
||||
@@ -306,8 +306,9 @@ opRouter.get('/op/datapack/:packName/images-zip', requireAuth, async (req, res,
|
||||
|
||||
// ─── /op/agreement ─────────────────────────────────────────────────────
|
||||
// 약관(Markdown) 편집기. 음악퀴즈(pack) 단위로 따로 저장한다.
|
||||
// builtin 5종은 어느 pack 에서나 항상 존재하고 삭제 불가, 그 외 임의 kind 는
|
||||
// 사이트에서 추가/삭제 가능. 인스톨러는 /manifest/terms/<packKey>/<kind>.md 로 받아 표시한다.
|
||||
// 5종 기본 약관(map/mod/installer/resourcepack/installer-rp) 은 첫 접근 시 시드되지만
|
||||
// 사용자가 자유롭게 삭제/추가/표시 대상 변경할 수 있다 (v0.3.4~). 인스톨러는
|
||||
// /manifest/terms/<packKey>/index.json 으로 자신에게 표시할 약관 목록을 받는다.
|
||||
|
||||
// /op/agreement → 음악퀴즈 선택(/op/list 와 동일한 카드 형식).
|
||||
opRouter.get('/op/agreement', requireAuth, async (req, res, next) => {
|
||||
@@ -411,10 +412,6 @@ opRouter.post('/op/agreement/:packName/:kind/delete', requireAuth, async (req, r
|
||||
res.status(400).send(t('terms.invalidKind'))
|
||||
return
|
||||
}
|
||||
if (isBuiltinTermKind(kind)) {
|
||||
res.status(400).send(t('terms.cannotDeleteBuiltin'))
|
||||
return
|
||||
}
|
||||
await deleteTerm(packKey, kind)
|
||||
res.redirect(`/op/agreement/${packKey}`)
|
||||
} catch (error) {
|
||||
@@ -435,14 +432,20 @@ opRouter.get('/op/agreement/:packName/:kind', requireAuth, async (req, res, next
|
||||
res.status(404).send(t('errors.unknown'))
|
||||
return
|
||||
}
|
||||
const entry = await getTermEntry(packKey, kind)
|
||||
if (!entry) {
|
||||
res.status(404).send(t('errors.unknown'))
|
||||
return
|
||||
}
|
||||
const content = await loadTerm(packKey, kind)
|
||||
const label = await getTermLabel(packKey, kind)
|
||||
res.render('op/termsEditor', {
|
||||
userId: req.session.userId,
|
||||
packKey,
|
||||
pack: definition,
|
||||
kind,
|
||||
label,
|
||||
label: entry.label,
|
||||
showInInstaller: entry.showInInstaller,
|
||||
showInInstallerRp: entry.showInInstallerRp,
|
||||
content
|
||||
})
|
||||
} catch (error) {
|
||||
@@ -465,6 +468,17 @@ opRouter.post('/op/agreement/:packName/:kind', requireAuth, async (req, res, nex
|
||||
}
|
||||
const content = typeof req.body?.content === 'string' ? req.body.content : ''
|
||||
await saveTerm(packKey, kind, content)
|
||||
// visibility 토글이 함께 전송되면 동시에 갱신. 두 값이 모두 false 면 어디에도
|
||||
// 표시되지 않지만 사용자가 의도적으로 선택한 결과이므로 그대로 저장한다.
|
||||
if (
|
||||
typeof req.body?.showInInstaller === 'boolean'
|
||||
|| typeof req.body?.showInInstallerRp === 'boolean'
|
||||
) {
|
||||
await setTermVisibility(packKey, kind, {
|
||||
showInInstaller: req.body.showInInstaller === true,
|
||||
showInInstallerRp: req.body.showInInstallerRp === true
|
||||
})
|
||||
}
|
||||
res.json({ ok: true })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
|
||||
Reference in New Issue
Block a user