<!DOCTYPE html> <html> <head> <title>Card List Parser</title> <style> #toastMessage { visibility: hidden; min-width: 250px; margin-left: -125px; background-color: #333; color: #fff; text-align: center; border-radius: 2px; padding: 16px; position: fixed; z-index: 1; left: 50%; bottom: 30px; font-size: 30px; } </style> </head> <select id="listSelector"> <input type="text" id="customListName" placeholder="Enter new list name"> <button id="addListButton">Add List</button> <label><input type="checkbox" id="toggleEditor"> Hide Text Editor</label><br> <textarea id="textInput" rows="10" cols="50"></textarea><br> <button id="applyButton">Apply</button> <button id="resetButton">Reset</button> <input type="file" id="fileInput" /> <label><input type="checkbox" id="toggleInfo"> Show card names only</label> <button id="copyToClipboard">Copy to Clipboard</button> <button id="copyToClipboardSum">Sum to Clipboard</button> <div id="toastMessage">클립보드에 현재 리스트가 저장되었습니다.</div> <div id="cardListOutput"></div> <script> document.getElementById('listSelector').addEventListener('change', function() { const selectedList = this.value; let displayList; switch (selectedList) { case 'parsedList': let parsedList = getParsedList(); sortCards(parsedList) currentDisplayList = parsedList; listType = 'parsedList'; break; case 'addedList': let addedList = getAddedList(); sortCards(addedList); currentDisplayList = addedList; listType = 'addedList'; break; case 'removedList': let removedList = getRemoveList(); sortCards(removedList); currentDisplayList = removedList; listType = 'removedList'; break; default: // 커스텀 리스트 if (selectedList in cardListPool.customLists) { sortCards(cardListPool.customLists[selectedList]); currentDisplayList = cardListPool.customLists[selectedList]; listType = selectedList; // 사용자 정의 리스트 이름을 listType으로 사용 } break; } displayCards(currentDisplayList, false, listType); }); document.getElementById('toggleEditor').addEventListener('change', function() { const isHidden = this.checked; const editorElements = [document.getElementById('textInput'), document.getElementById('applyButton'), document.getElementById('resetButton')]; editorElements.forEach(element => { element.style.display = isHidden ? 'none' : 'block'; }); }); document.getElementById('fileInput').addEventListener('change', handleFileSelect, false); document.getElementById('toggleInfo').addEventListener('change', handleToggleChange, false); document.getElementById('applyButton').addEventListener('click', function() { const textInput = document.getElementById('textInput').value; applyText(textInput); }); document.getElementById('resetButton').addEventListener('click', function() { document.getElementById('textInput').value = ''; document.getElementById('cardListOutput').innerHTML = ''; }); document.getElementById('addListButton').addEventListener('click', function() { const listName = document.getElementById('customListName').value.trim(); if (listName) { addCustomList(listName); document.getElementById('customListName').value = ''; } else { alert('Please enter a valid list name.'); } }); document.getElementById('copyToClipboard').addEventListener('click', function() { const cardNames = currentDisplayList.map(card => card.name).join('\\n'); navigator.clipboard.writeText(cardNames).then(() => { const toast = document.getElementById('toastMessage'); toast.style.visibility = 'visible'; setTimeout(() => toast.style.visibility = 'hidden', 2500); }).catch(err => { console.error('클립보드 복사 실패:', err); }); }); document.getElementById('copyToClipboardSum').addEventListener('click', function() { let totalQuantity = 0; let totalPrice = 0.0; let finalText = ""; if (listType != 'parsedList' && listType != 'addedList' && listType != 'removedList') { finalText = `List: ${listType}\\n`; } const cardInfo = currentDisplayList.map(card => { const cardTotalPrice = card.quantity * card.price; totalQuantity += card.quantity; totalPrice += cardTotalPrice; const priceFormatted = `$${cardTotalPrice.toFixed(2)}`.padEnd(10, ' '); const quantityFormatted = `${card.quantity}장`.padEnd(6, ' '); const nameFormatted = card.name.padEnd(30, ' '); return `${priceFormatted}${quantityFormatted}${nameFormatted}`; }).join('\\n'); const summary = `총 계 ${totalQuantity}장, $${totalPrice.toFixed(2)}`; finalText += cardInfo; finalText += '\\n--------------------------------------------------------------------------------------------\\n' finalText += summary; navigator.clipboard.writeText(finalText).then(() => { const toast = document.getElementById('toastMessage'); toast.style.visibility = 'visible'; setTimeout(() => toast.style.visibility = 'hidden', 2500); }).catch(err => { console.error('클립보드 복사 실패:', err); }); }); let cardListPool = { parsedList: [], addedList: [], removedList: [], customLists: {} }; let currentDisplayList = getParsedList(); let listType = "parsedList"; function applyText(text) { clearCardList(); var defaultParsedList = getParsedList(); parseCardList(defaultParsedList, text); sortCards(defaultParsedList); displayCards(defaultParsedList, false, listType); } function getParsedList() { return cardListPool.parsedList; } function getAddedList() { return cardListPool.addedList; } function getRemoveList() { return cardListPool.removedList; } function getCustomList(listName) { if (listName in cardListPool.customLists) { return cardListPool.customLists[listName]; } else { console.warn(`List '${listName}' does not exist.`); return null; } } function clearCardList() { getParsedList().length = 0; getRemoveList().length = 0; getAddedList().length = 0; Object.keys(cardListPool.customLists).forEach(listName => { cardListPool.customLists[listName].length = 0; }); } function addCustomList(listName) { if (cardListPool.customLists[listName]) { console.warn(`List '${listName}' already exists.`); return; } cardListPool.customLists[listName] = []; const listSelector = document.getElementById('listSelector'); const newOption = document.createElement('option'); newOption.value = listName; newOption.textContent = listName; listSelector.appendChild(newOption); } function initializeComboBox() { const listSelector = document.getElementById('listSelector'); ['parsedList', 'addedList', 'removedList'].forEach(listName => { const option = document.createElement('option'); option.value = listName; option.textContent = listName; listSelector.appendChild(option); }); } // 페이지 로드 시 초기화 window.onload = initializeComboBox; function handleFileSelect(event) { const file = event.target.files[0]; if (!file) { return; } const reader = new FileReader(); reader.onload = function(fileEvent) { const text = fileEvent.target.result; applyText(text); }; reader.readAsText(file); } function handleToggleChange() { const isChecked = document.getElementById('toggleInfo').checked; displayCards(currentDisplayList, isChecked, listType); } function displayCards(displayList, showNamesOnly, listType) { const outputDiv = document.getElementById('cardListOutput'); outputDiv.innerHTML = ''; // 장수별 컬러 const quantityColorMap = { 2: 'blue', 3: 'green', 4: 'orange', 5: 'red' }; displayList.forEach((card, index) => { let color = quantityColorMap[card.quantity] || 'black'; // 카드 정보 표시 const cardElement = document.createElement('div'); // 카드 정보 구성 let cardInfo = `<strong style="color: ${color};">${card.name}</strong>`; if (!showNamesOnly) { cardInfo = `<br>${card.edition} (${card.condition}, Quantity: ${card.quantity}, Price: $${card.price.toFixed(2)}, Total: $${(card.quantity * card.price).toFixed(2)})</br>` + cardInfo; } cardElement.innerHTML = cardInfo; if (listType == "parsedList") { // add 버튼 const addButton = document.createElement('button'); addButton.textContent = 'Add'; addButton.onclick = function() { if (card.quantity > 1) { card.quantity -= 1; getAddedList().push({...card, quantity: 1}); } else { getAddedList().push(card); displayList.splice(index, 1); } displayCards(displayList, showNamesOnly, listType); }; cardElement.appendChild(addButton); // remove 버튼 const removeButton = document.createElement('button'); removeButton.textContent = 'Remove'; removeButton.onclick = function() { if (card.quantity > 1) { card.quantity -= 1; getRemoveList().push({...card, quantity: 1}); } else { getRemoveList().push(card); displayList.splice(index, 1); } displayCards(displayList, showNamesOnly, listType); }; cardElement.appendChild(removeButton); // 커스텀 리스트 UI if (Object.keys(cardListPool.customLists).length > 0) { const listSelector = document.createElement('select'); Object.keys(cardListPool.customLists).forEach(listName => { const option = document.createElement('option'); option.value = listName; option.textContent = listName; listSelector.appendChild(option); }); const addToCustomListButton = document.createElement('button'); addToCustomListButton.textContent = 'Add to Custom List'; addToCustomListButton.onclick = function() { const selectedList = listSelector.value; let list = getCustomList(listSelector.value); if (list) { if (card.quantity > 1) { card.quantity -= 1; list.push({...card, quantity: 1}); } else { list.push(card); displayList.splice(index, 1); } } else { console.warn('no exist custom list error'); } displayCards(displayList, showNamesOnly, listType); }; cardElement.appendChild(listSelector); cardElement.appendChild(addToCustomListButton); } } else { // 복구 버튼 const restoreButton = document.createElement('button'); restoreButton.textContent = '복구'; restoreButton.onclick = function() { displayList.splice(index, 1); const existingCardIndex = getParsedList().findIndex(c => c.name === card.name && c.edition === card.edition ); if (existingCardIndex !== -1) { getParsedList()[existingCardIndex].quantity += card.quantity; } else { getParsedList().push({...card}); } displayCards(displayList, showNamesOnly, listType); }; cardElement.appendChild(restoreButton); } // 클릭 시, 바뀌는 배경 컬러 const bgColors = ['white', '#add8e6', '#90ee90']; let currentColorIndex = 0; // 클릭시 배경 변경 cardElement.style.cursor = 'pointer'; cardElement.onclick = function() { currentColorIndex = (currentColorIndex + 1) % bgColors.length; this.style.backgroundColor = bgColors[currentColorIndex]; }; outputDiv.appendChild(cardElement); }); } function parseCardList(_cards, cardListText) { const lines = cardListText.split('\\n'); let currentCondition = ''; lines.forEach(line => { if ((/^\\s*$/.test(line)) || (/SINGLES\\s*$/.test(line)) || (/Description\\s*/.test(line))) { return; } const firstColonIndex = line.indexOf(':'); if (firstColonIndex === -1) return; // 카드명, 판본 분리, ':'가 없으면 유효 X const name = line.substring(0, firstColonIndex).trim(); const restLine = line.substring(firstColonIndex + 1).trim(); const parts = restLine.split('\\t'); // 나머지 분리 if (parts.length >= 4) { const edition = parts[0].trim(); const condition = parts[1].trim(); const quantity = parseInt(parts[2]); const price = parseFloat(parts[3].substring(1)); const total = parseFloat(parts[4].substring(1)); _cards.push({ name, edition, condition, quantity, price, total }); } }); return _cards; } function sortCards(_cards) { const conditionOrder = { 'NM': 1, 'EX': 2, 'G': 3, 'VG': 4 }; // 상태 / 판본 / 이름 순 정렬 return _cards.sort((a, b) => { const conditionCompare = (conditionOrder[a.condition] || 5) - (conditionOrder[b.condition] || 5); if (conditionCompare !== 0) return conditionCompare; const editionCompare = a.edition.localeCompare(b.edition); if (editionCompare !== 0) return editionCompare; return a.name.localeCompare(b.name); }); } </script> </body> </html>
사용법
1번에 이름 써넣고 2번 클릭 하면 추가됨
카킹 리스트 긁어다 텍스트박스에 복사
Version 1 : 카킹 주문 긁는 방법은 여기서 확인
텍스트박스에 카드 리스트를 복사해 넣고, apply 버튼 클릭
카드 리스트에서 구매 요청 받은 대로, 내가 추가한 사람 클릭 → Add to CustomList 버튼 클릭 하면 해당 사람의 리스트로 이동함
List: 김길수씨 $0.49 1장 Animist's Might (Foil Etched) $0.49 1장 Animist's Might (Retro Frame) $2.49 1장 Arni Metalbrow (Halo Foil) $1.99 1장 Ayara's Oathsworn (Extended Art) $4.99 1장 Ayara's Oathsworn (Halo Foil) $0.49 1장 Blot Out (Foil Etched) $0.49 1장 Blot Out (Showcase) $4.99 1장 Aggravated Assault (0039) $0.99 1장 As Foretold (0014) -------------------------------------------------------------------------------------------- 총 계 9장, $17.41