Music list tab:
- Title + artist are now contenteditable in-place. Typing updates state on
every input event; Enter blurs (no embedded newlines), and on Save the
current DOM text is re-synced into state.music[i].title/.artist before
the JSON POST so a quick-save right after typing doesn't drop the last
keystroke. While focused, the field is highlighted with the accent
outline and unclamped so long titles wrap. Empty cells show a muted
placeholder via :empty::before { content: attr(data-placeholder) }.
- Edit modal "save" now POSTs the new URL to /op/list/<pack>/video-meta
(new endpoint backed by yt-dlp --dump-json --no-playlist) and patches
state.music[i] with the returned title/channel/durationSec. If yt-dlp
is unavailable or the lookup fails, the modal asks the user whether to
apply just the URL change without metadata.
- Drag-and-drop UX: replaced the simple "highlight target" feedback with a
"gap opens above the target" animation. The hovered row gets a
.dropAbove class that animates margin-top to 64px via CSS transition,
visually carving out the slot the dragged item will land in. Insertion
math is now strictly "drop before the hover target" (with the
srcIdx<dstIdx → dstIdx-1 adjustment after the splice removal), matching
what the gap implies. Contenteditable spans no longer hijack drag start
— on focus the parent <li>.draggable is flipped off so the user can
freely select text, and back on at blur.
Image list tab:
- New "음악목록에서 가져오기" button. Copies state.music[*].url into
state.images, which (via the existing thumbUrl() helper) renders each
song's YouTube thumbnail as the image. Behind a confirm prompt because
it replaces the entire image list.
- Same drag gap animation (.dropAbove → margin-left 80px) applied to the
grid cards for consistency.
Server:
- youtube.ts: add fetchVideoMeta(url) using the same ensureYtDlp() path
(auto-installed binary under %appdata%/.mc_custom). Returns one
YtPlaylistEntry or null.
- routes/op.ts: POST /op/list/:packName/video-meta. 400 on missing URL,
503 NO_YTDLP if the auto-install failed, 500 on other yt-dlp errors,
200 { ok: true, entry } otherwise.
Smoke test (Rick Astley URL) returns
title=Rick Astley - Never Gonna..., channel=Rick Astley, durationSec=213.
- /op/dashboard: remove underline from <a class="secondaryButton"> by adding
text-decoration:none + inline-flex centering on .primaryButton/.secondaryButton/
.dangerButton, plus margin-left:auto on .dashboardActions so the buttons stick
to the right side of the row even when the header is laid out as flex+wrap.
- /op/list/<pack>: fix the duplicate "save/clear + playlist URL" UI showing in
the active tab. .tabPanel had a baseline `display: block;` that overrode the
browser default `[hidden] { display: none }`. Add an explicit
`.tabPanel[hidden] { display: none !important }` rule so the inactive panel
actually hides.
- /op/list, /op/list/<pack>, /op/datapack: bump the gap between the "돌아가기"
ghost link and the page title from 8px to 20px.
- docs/yt-dlp-setup.md: install guide (single-binary curl + pipx + apt) since
the server doesn't have pip/pipx/yt-dlp installed. Server keeps the graceful
"수동 입력으로 진행" fallback when yt-dlp is missing.
docs/add.md
- 사진 PNG 규격을 1024×1024 (4×4 블록 슬롯 × ×16 배율) 로 못박음
- 짧은 변 기준 가운데 정사각 크롭 + 1024 초과 시만 축소, 미만은 native 유지
신규 라우트 (모두 requireAuth):
- GET /op/list → manifest 카드 목록
- GET /op/list/:pack → 음악목록·사진목록 탭 편집기
- POST /op/list/:pack → file/list/<pack>.json 저장 (JSON)
- POST /op/list/:pack/playlist → yt-dlp 로 플레이리스트 펼치기
- GET /op/datapack → 음악퀴즈 선택 + 출력
- GET /op/datapack/:pack/generate → 임시 포맷 mcfunction 텍스트
shared/types.ts: PackList / MusicListEntry / ImageListEntry
shared/store.ts: loadPackList, savePackList, normalizePackList
shared/paths.ts: fileListDirPath = file/list/
server/youtube.ts: yt-dlp 플레이리스트 펼치기 (--flat-playlist --dump-json),
설치 안 됐을 때 NO_YTDLP 코드로 503.
UI:
- views/op/list.ejs: 가로 카드 목록 + 돌아가기 버튼
- views/op/listEditor.ejs + public/listEditor.js: 탭 전환, 드래그 정렬,
우클릭 컨텍스트 메뉴(수정/삭제), 사진 수정 모달의 [유튜브 / 이미지] 토글,
목록 저장·초기화·플레이리스트 불러오기 확인 팝업
- views/op/datapack.ejs: 음악퀴즈 카드 선택 팝업 → 데이터팩 출력 + 복사
- views/op/dashboard.ejs: 상단에 [음악목록 수정] [데이터팩 수정] 버튼
- public/styles.css: 탭, 트랙 로우, 이미지 그리드, 컨텍스트 메뉴, 모달, 코드블록
.gitignore: conversations/ 추가.
스모크: login → /op/list 렌더, POST 저장 라운드트립 OK,
/op/datapack/.../generate 텍스트 출력 OK, 플레이리스트 fetch는 yt-dlp 미설치
환경에서 503 NO_YTDLP 메시지 정상.
Section 1 (리소스팩 설치기 EXE: yt-dlp 음악 다운로드, painting variant
텍스처 패키징, 리소스팩 zip 배치) 은 후속 커밋에서 작업.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- PackDefinition: replace mods[]/resourcepacks[] with modsFolder (string) + resourcepackPath (string); drop PackAsset
- Editor: replace dynamic add/remove lists with two single inputs; remove the now-dead JS for adding/removing rows
- Server: expose GET /file/mods/<folder>/index.json that returns the list of .jar names; folder name restricted to [a-zA-Z0-9_-]+
- Installer: fetch the listing JSON and download each jar from /file/mods/<folder>/<file>.jar; download the single resourcepack from /file/resourcepacks/<file>.zip directly into resourcepacks/
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Path is set by the loader upload form below, so the manual
text input was redundant. Drop the field from the editor UI
and stop overwriting the saved value on form submit so an
uploaded loader keeps its path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>