Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -172,6 +172,21 @@ if __name__ == '__main__':
|
|
| 172 |
margin-bottom: 1rem;
|
| 173 |
}
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
.user-controls {
|
| 176 |
display: flex;
|
| 177 |
justify-content: space-between;
|
|
@@ -237,19 +252,43 @@ if __name__ == '__main__':
|
|
| 237 |
}
|
| 238 |
|
| 239 |
.card {
|
| 240 |
-
border: 1px solid #
|
| 241 |
-
border-radius:
|
| 242 |
padding: 1rem;
|
| 243 |
width: 300px;
|
| 244 |
-
box-shadow:
|
| 245 |
position: relative;
|
| 246 |
-
background-color: #
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
}
|
| 248 |
|
| 249 |
.card a {
|
| 250 |
text-decoration: none;
|
| 251 |
-
color: #
|
| 252 |
word-break: break-all;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
}
|
| 254 |
|
| 255 |
.like-button {
|
|
@@ -258,18 +297,40 @@ if __name__ == '__main__':
|
|
| 258 |
right: 1rem;
|
| 259 |
border: none;
|
| 260 |
background: transparent;
|
| 261 |
-
font-size: 1.
|
| 262 |
cursor: pointer;
|
| 263 |
-
transition:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
}
|
| 265 |
|
| 266 |
.like-button.liked {
|
| 267 |
-
color:
|
|
|
|
| 268 |
}
|
| 269 |
|
| 270 |
.like-button.not-liked {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
color: white;
|
| 272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
}
|
| 274 |
|
| 275 |
.status-message {
|
|
@@ -312,6 +373,23 @@ if __name__ == '__main__':
|
|
| 312 |
margin-top: 1rem;
|
| 313 |
}
|
| 314 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
@media (max-width: 768px) {
|
| 316 |
.user-controls {
|
| 317 |
flex-direction: column;
|
|
@@ -325,6 +403,12 @@ if __name__ == '__main__':
|
|
| 325 |
.card {
|
| 326 |
width: 100%;
|
| 327 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
}
|
| 329 |
</style>
|
| 330 |
</head>
|
|
@@ -351,12 +435,19 @@ if __name__ == '__main__':
|
|
| 351 |
</div>
|
| 352 |
</div>
|
| 353 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
<div class="filter-controls">
|
| 355 |
<label>
|
| 356 |
-
<input type="checkbox" id="showOnlyLiked" />
|
| 357 |
-
λ΄κ° μ’μμν URLλ§ λ³΄κΈ°
|
| 358 |
-
</label>
|
| 359 |
-
<label style="margin-left: 1rem;">
|
| 360 |
<input type="text" id="searchInput" placeholder="URL λλ μ λͺ©μΌλ‘ κ²μ" style="width: 250px;" />
|
| 361 |
</label>
|
| 362 |
</div>
|
|
@@ -378,17 +469,23 @@ if __name__ == '__main__':
|
|
| 378 |
cardsContainer: document.getElementById('cardsContainer'),
|
| 379 |
loadingIndicator: document.getElementById('loadingIndicator'),
|
| 380 |
statusMessage: document.getElementById('statusMessage'),
|
| 381 |
-
showOnlyLiked: document.getElementById('showOnlyLiked'),
|
| 382 |
searchInput: document.getElementById('searchInput'),
|
| 383 |
loginSection: document.getElementById('loginSection'),
|
| 384 |
-
loggedInSection: document.getElementById('loggedInSection')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
};
|
| 386 |
|
| 387 |
// μ ν리μΌμ΄μ
μν
|
| 388 |
const state = {
|
| 389 |
username: null,
|
| 390 |
likedURLs: {},
|
| 391 |
-
|
|
|
|
|
|
|
| 392 |
};
|
| 393 |
|
| 394 |
// λ‘컬 μ€ν λ¦¬μ§ ν€
|
|
@@ -413,6 +510,15 @@ if __name__ == '__main__':
|
|
| 413 |
localStorage.setItem(key, JSON.stringify(state.likedURLs));
|
| 414 |
}
|
| 415 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 416 |
// λ‘λ© μν νμ ν¨μ
|
| 417 |
function setLoading(isLoading) {
|
| 418 |
state.isLoading = isLoading;
|
|
@@ -451,6 +557,7 @@ if __name__ == '__main__':
|
|
| 451 |
elements.currentUser.textContent = data.username;
|
| 452 |
elements.loginSection.style.display = 'none';
|
| 453 |
elements.loggedInSection.style.display = 'block';
|
|
|
|
| 454 |
|
| 455 |
// λ‘컬 μ€ν 리μ§μμ μ’μμ μ 보 λ‘λ
|
| 456 |
state.likedURLs = loadLikesFromStorage();
|
|
@@ -492,6 +599,7 @@ if __name__ == '__main__':
|
|
| 492 |
elements.currentUser.textContent = state.username;
|
| 493 |
elements.loginSection.style.display = 'none';
|
| 494 |
elements.loggedInSection.style.display = 'block';
|
|
|
|
| 495 |
|
| 496 |
showMessage(`${state.username}λμΌλ‘ λ‘κ·ΈμΈλμμ΅λλ€.`);
|
| 497 |
|
|
@@ -522,11 +630,13 @@ if __name__ == '__main__':
|
|
| 522 |
if (data.success) {
|
| 523 |
state.username = null;
|
| 524 |
state.likedURLs = {};
|
|
|
|
| 525 |
|
| 526 |
elements.currentUser.textContent = 'λ‘κ·ΈμΈλμ§ μμ';
|
| 527 |
elements.tokenInput.value = '';
|
| 528 |
elements.loginSection.style.display = 'block';
|
| 529 |
elements.loggedInSection.style.display = 'none';
|
|
|
|
| 530 |
|
| 531 |
showMessage('λ‘κ·Έμμλμμ΅λλ€.');
|
| 532 |
|
|
@@ -549,27 +659,13 @@ if __name__ == '__main__':
|
|
| 549 |
const response = await fetch('/api/urls');
|
| 550 |
const urls = await handleApiResponse(response);
|
| 551 |
|
| 552 |
-
|
| 553 |
-
const showOnlyLiked = elements.showOnlyLiked.checked;
|
| 554 |
-
const searchText = elements.searchInput.value.toLowerCase();
|
| 555 |
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
// μ’μμ νν°λ§
|
| 560 |
-
if (showOnlyLiked && !state.likedURLs[url]) {
|
| 561 |
-
return false;
|
| 562 |
-
}
|
| 563 |
-
|
| 564 |
-
// κ²μ νν°λ§
|
| 565 |
-
if (searchText && !url.toLowerCase().includes(searchText) && !title.toLowerCase().includes(searchText)) {
|
| 566 |
-
return false;
|
| 567 |
-
}
|
| 568 |
-
|
| 569 |
-
return true;
|
| 570 |
-
});
|
| 571 |
|
| 572 |
-
|
|
|
|
| 573 |
} catch (error) {
|
| 574 |
console.error('URL λͺ©λ‘ λ‘λ μ€λ₯:', error);
|
| 575 |
showMessage(`URL λ‘λ μ€λ₯: ${error.message}`, true);
|
|
@@ -578,8 +674,32 @@ if __name__ == '__main__':
|
|
| 578 |
}
|
| 579 |
}
|
| 580 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 581 |
// μ’μμ ν κΈ
|
| 582 |
-
function toggleLike(url,
|
| 583 |
if (!state.username) {
|
| 584 |
showMessage('μ’μμλ₯Ό νλ €λ©΄ νκΉ
νμ΄μ€ API ν ν°μΌλ‘ μΈμ¦μ΄ νμν©λλ€.', true);
|
| 585 |
return;
|
|
@@ -594,22 +714,33 @@ if __name__ == '__main__':
|
|
| 594 |
// μν ν κΈ
|
| 595 |
if (isCurrentlyLiked) {
|
| 596 |
delete state.likedURLs[url];
|
| 597 |
-
|
| 598 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 599 |
showMessage('μ’μμλ₯Ό μ·¨μνμ΅λλ€.');
|
| 600 |
} else {
|
| 601 |
state.likedURLs[url] = true;
|
| 602 |
-
|
| 603 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 604 |
showMessage('μ’μμλ₯Ό μΆκ°νμ΅λλ€.');
|
| 605 |
}
|
| 606 |
|
| 607 |
// λ‘컬 μ€ν 리μ§μ μ μ₯
|
| 608 |
saveLikesToStorage();
|
| 609 |
|
| 610 |
-
//
|
| 611 |
-
|
| 612 |
-
|
|
|
|
|
|
|
|
|
|
| 613 |
}
|
| 614 |
} catch (error) {
|
| 615 |
console.error('μ’μμ ν κΈ μ€λ₯:', error);
|
|
@@ -638,10 +769,17 @@ if __name__ == '__main__':
|
|
| 638 |
|
| 639 |
// μΉ΄λ μμ±
|
| 640 |
const card = document.createElement('div');
|
| 641 |
-
card.className =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 642 |
|
| 643 |
// μ λͺ©
|
| 644 |
const titleEl = document.createElement('h3');
|
|
|
|
| 645 |
titleEl.textContent = title;
|
| 646 |
card.appendChild(titleEl);
|
| 647 |
|
|
@@ -656,10 +794,11 @@ if __name__ == '__main__':
|
|
| 656 |
const likeBtn = document.createElement('button');
|
| 657 |
likeBtn.className = `like-button ${isLiked ? 'liked' : 'not-liked'}`;
|
| 658 |
likeBtn.textContent = 'β₯';
|
|
|
|
| 659 |
|
| 660 |
likeBtn.addEventListener('click', function(e) {
|
| 661 |
e.preventDefault();
|
| 662 |
-
toggleLike(url,
|
| 663 |
});
|
| 664 |
card.appendChild(likeBtn);
|
| 665 |
|
|
@@ -668,6 +807,18 @@ if __name__ == '__main__':
|
|
| 668 |
});
|
| 669 |
}
|
| 670 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 671 |
// μ΄λ²€νΈ 리μ€λ μ€μ
|
| 672 |
elements.loginButton.addEventListener('click', () => {
|
| 673 |
login(elements.tokenInput.value);
|
|
@@ -682,14 +833,17 @@ if __name__ == '__main__':
|
|
| 682 |
}
|
| 683 |
});
|
| 684 |
|
| 685 |
-
//
|
| 686 |
-
elements.showOnlyLiked.addEventListener('change', loadUrls);
|
| 687 |
elements.searchInput.addEventListener('input', () => {
|
| 688 |
-
// μ
λ ₯ μ§μ° μ²λ¦¬ (νμ΄νν λλ§λ€
|
| 689 |
clearTimeout(state.searchTimeout);
|
| 690 |
-
state.searchTimeout = setTimeout(
|
| 691 |
});
|
| 692 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 693 |
// μ΄κΈ°ν
|
| 694 |
checkSessionStatus();
|
| 695 |
</script>
|
|
|
|
| 172 |
margin-bottom: 1rem;
|
| 173 |
}
|
| 174 |
|
| 175 |
+
.stats-bar {
|
| 176 |
+
background-color: #e9f7ef;
|
| 177 |
+
padding: 1rem;
|
| 178 |
+
border-radius: 5px;
|
| 179 |
+
margin-bottom: 1rem;
|
| 180 |
+
display: flex;
|
| 181 |
+
justify-content: space-between;
|
| 182 |
+
align-items: center;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
.stats-bar .liked-count {
|
| 186 |
+
font-weight: bold;
|
| 187 |
+
color: #e74c3c;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
.user-controls {
|
| 191 |
display: flex;
|
| 192 |
justify-content: space-between;
|
|
|
|
| 252 |
}
|
| 253 |
|
| 254 |
.card {
|
| 255 |
+
border: 1px solid #ddd;
|
| 256 |
+
border-radius: 8px;
|
| 257 |
padding: 1rem;
|
| 258 |
width: 300px;
|
| 259 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
| 260 |
position: relative;
|
| 261 |
+
background-color: #fff;
|
| 262 |
+
transition: all 0.3s ease;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
.card:hover {
|
| 266 |
+
transform: translateY(-5px);
|
| 267 |
+
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
.card.liked {
|
| 271 |
+
border-color: #e74c3c;
|
| 272 |
+
background-color: #fff9f9;
|
| 273 |
}
|
| 274 |
|
| 275 |
.card a {
|
| 276 |
text-decoration: none;
|
| 277 |
+
color: #2980b9;
|
| 278 |
word-break: break-all;
|
| 279 |
+
display: block;
|
| 280 |
+
margin-top: 0.5rem;
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
.card a:hover {
|
| 284 |
+
text-decoration: underline;
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
.card-title {
|
| 288 |
+
margin-top: 0;
|
| 289 |
+
color: #333;
|
| 290 |
+
font-size: 1.2rem;
|
| 291 |
+
padding-right: 30px; /* μ’μμ λ²νΌ κ³΅κ° ν보 */
|
| 292 |
}
|
| 293 |
|
| 294 |
.like-button {
|
|
|
|
| 297 |
right: 1rem;
|
| 298 |
border: none;
|
| 299 |
background: transparent;
|
| 300 |
+
font-size: 1.8rem;
|
| 301 |
cursor: pointer;
|
| 302 |
+
transition: all 0.2s;
|
| 303 |
+
z-index: 2;
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
.like-button:hover {
|
| 307 |
+
transform: scale(1.2);
|
| 308 |
}
|
| 309 |
|
| 310 |
.like-button.liked {
|
| 311 |
+
color: #e74c3c;
|
| 312 |
+
text-shadow: 0 0 5px rgba(231, 76, 60, 0.3);
|
| 313 |
}
|
| 314 |
|
| 315 |
.like-button.not-liked {
|
| 316 |
+
color: #ccc;
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
.like-label {
|
| 320 |
+
position: absolute;
|
| 321 |
+
top: 10px;
|
| 322 |
+
left: 10px;
|
| 323 |
+
background-color: #e74c3c;
|
| 324 |
color: white;
|
| 325 |
+
padding: 2px 8px;
|
| 326 |
+
border-radius: 10px;
|
| 327 |
+
font-size: 0.7rem;
|
| 328 |
+
font-weight: bold;
|
| 329 |
+
display: none;
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
.card.liked .like-label {
|
| 333 |
+
display: block;
|
| 334 |
}
|
| 335 |
|
| 336 |
.status-message {
|
|
|
|
| 373 |
margin-top: 1rem;
|
| 374 |
}
|
| 375 |
|
| 376 |
+
.view-toggle {
|
| 377 |
+
margin-bottom: 1rem;
|
| 378 |
+
display: flex;
|
| 379 |
+
gap: 0.5rem;
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
.view-toggle button {
|
| 383 |
+
background-color: #f8f9fa;
|
| 384 |
+
color: #333;
|
| 385 |
+
border: 1px solid #ddd;
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
.view-toggle button.active {
|
| 389 |
+
background-color: #4CAF50;
|
| 390 |
+
color: white;
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
@media (max-width: 768px) {
|
| 394 |
.user-controls {
|
| 395 |
flex-direction: column;
|
|
|
|
| 403 |
.card {
|
| 404 |
width: 100%;
|
| 405 |
}
|
| 406 |
+
|
| 407 |
+
.stats-bar {
|
| 408 |
+
flex-direction: column;
|
| 409 |
+
gap: 0.5rem;
|
| 410 |
+
align-items: flex-start;
|
| 411 |
+
}
|
| 412 |
}
|
| 413 |
</style>
|
| 414 |
</head>
|
|
|
|
| 435 |
</div>
|
| 436 |
</div>
|
| 437 |
|
| 438 |
+
<!-- μ’μμ ν΅κ³ λ° νμ ν κΈ -->
|
| 439 |
+
<div id="statsBar" class="stats-bar" style="display: none;">
|
| 440 |
+
<div>
|
| 441 |
+
μ΄ <span id="totalCount">0</span>κ° μ€ <span id="likedCount" class="liked-count">0</span>κ° μ’μμ ν¨
|
| 442 |
+
</div>
|
| 443 |
+
<div class="view-toggle">
|
| 444 |
+
<button id="allViewBtn" class="active">μ 체 보기</button>
|
| 445 |
+
<button id="likedViewBtn">μ’μμλ§ λ³΄κΈ°</button>
|
| 446 |
+
</div>
|
| 447 |
+
</div>
|
| 448 |
+
|
| 449 |
<div class="filter-controls">
|
| 450 |
<label>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
<input type="text" id="searchInput" placeholder="URL λλ μ λͺ©μΌλ‘ κ²μ" style="width: 250px;" />
|
| 452 |
</label>
|
| 453 |
</div>
|
|
|
|
| 469 |
cardsContainer: document.getElementById('cardsContainer'),
|
| 470 |
loadingIndicator: document.getElementById('loadingIndicator'),
|
| 471 |
statusMessage: document.getElementById('statusMessage'),
|
|
|
|
| 472 |
searchInput: document.getElementById('searchInput'),
|
| 473 |
loginSection: document.getElementById('loginSection'),
|
| 474 |
+
loggedInSection: document.getElementById('loggedInSection'),
|
| 475 |
+
statsBar: document.getElementById('statsBar'),
|
| 476 |
+
totalCount: document.getElementById('totalCount'),
|
| 477 |
+
likedCount: document.getElementById('likedCount'),
|
| 478 |
+
allViewBtn: document.getElementById('allViewBtn'),
|
| 479 |
+
likedViewBtn: document.getElementById('likedViewBtn')
|
| 480 |
};
|
| 481 |
|
| 482 |
// μ ν리μΌμ΄μ
μν
|
| 483 |
const state = {
|
| 484 |
username: null,
|
| 485 |
likedURLs: {},
|
| 486 |
+
allURLs: [],
|
| 487 |
+
isLoading: false,
|
| 488 |
+
viewMode: 'all' // 'all' λλ 'liked'
|
| 489 |
};
|
| 490 |
|
| 491 |
// λ‘컬 μ€ν λ¦¬μ§ ν€
|
|
|
|
| 510 |
localStorage.setItem(key, JSON.stringify(state.likedURLs));
|
| 511 |
}
|
| 512 |
|
| 513 |
+
// μ’μμ ν΅κ³ μ
λ°μ΄νΈ
|
| 514 |
+
function updateLikeStats() {
|
| 515 |
+
const totalCount = state.allURLs.length;
|
| 516 |
+
const likedCount = Object.keys(state.likedURLs).length;
|
| 517 |
+
|
| 518 |
+
elements.totalCount.textContent = totalCount;
|
| 519 |
+
elements.likedCount.textContent = likedCount;
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
// λ‘λ© μν νμ ν¨μ
|
| 523 |
function setLoading(isLoading) {
|
| 524 |
state.isLoading = isLoading;
|
|
|
|
| 557 |
elements.currentUser.textContent = data.username;
|
| 558 |
elements.loginSection.style.display = 'none';
|
| 559 |
elements.loggedInSection.style.display = 'block';
|
| 560 |
+
elements.statsBar.style.display = 'flex';
|
| 561 |
|
| 562 |
// λ‘컬 μ€ν 리μ§μμ μ’μμ μ 보 λ‘λ
|
| 563 |
state.likedURLs = loadLikesFromStorage();
|
|
|
|
| 599 |
elements.currentUser.textContent = state.username;
|
| 600 |
elements.loginSection.style.display = 'none';
|
| 601 |
elements.loggedInSection.style.display = 'block';
|
| 602 |
+
elements.statsBar.style.display = 'flex';
|
| 603 |
|
| 604 |
showMessage(`${state.username}λμΌλ‘ λ‘κ·ΈμΈλμμ΅λλ€.`);
|
| 605 |
|
|
|
|
| 630 |
if (data.success) {
|
| 631 |
state.username = null;
|
| 632 |
state.likedURLs = {};
|
| 633 |
+
state.allURLs = [];
|
| 634 |
|
| 635 |
elements.currentUser.textContent = 'λ‘κ·ΈμΈλμ§ μμ';
|
| 636 |
elements.tokenInput.value = '';
|
| 637 |
elements.loginSection.style.display = 'block';
|
| 638 |
elements.loggedInSection.style.display = 'none';
|
| 639 |
+
elements.statsBar.style.display = 'none';
|
| 640 |
|
| 641 |
showMessage('λ‘κ·Έμμλμμ΅λλ€.');
|
| 642 |
|
|
|
|
| 659 |
const response = await fetch('/api/urls');
|
| 660 |
const urls = await handleApiResponse(response);
|
| 661 |
|
| 662 |
+
state.allURLs = urls;
|
|
|
|
|
|
|
| 663 |
|
| 664 |
+
// νν°λ§ λ° λ λλ§
|
| 665 |
+
filterAndRenderCards();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 666 |
|
| 667 |
+
// μ’μμ ν΅κ³ μ
λ°μ΄νΈ
|
| 668 |
+
updateLikeStats();
|
| 669 |
} catch (error) {
|
| 670 |
console.error('URL λͺ©λ‘ λ‘λ μ€λ₯:', error);
|
| 671 |
showMessage(`URL λ‘λ μ€λ₯: ${error.message}`, true);
|
|
|
|
| 674 |
}
|
| 675 |
}
|
| 676 |
|
| 677 |
+
// νν°λ§ λ° μΉ΄λ λ λλ§
|
| 678 |
+
function filterAndRenderCards() {
|
| 679 |
+
const searchText = elements.searchInput.value.toLowerCase();
|
| 680 |
+
|
| 681 |
+
// νν°λ§ μ μ©
|
| 682 |
+
const filteredUrls = state.allURLs.filter(item => {
|
| 683 |
+
const { url, title } = item;
|
| 684 |
+
|
| 685 |
+
// μ’μμ νν°λ§ (μ’μμλ§ λ³΄κΈ° λͺ¨λ)
|
| 686 |
+
if (state.viewMode === 'liked' && !state.likedURLs[url]) {
|
| 687 |
+
return false;
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
// κ²μ νν°λ§
|
| 691 |
+
if (searchText && !url.toLowerCase().includes(searchText) && !title.toLowerCase().includes(searchText)) {
|
| 692 |
+
return false;
|
| 693 |
+
}
|
| 694 |
+
|
| 695 |
+
return true;
|
| 696 |
+
});
|
| 697 |
+
|
| 698 |
+
renderCards(filteredUrls);
|
| 699 |
+
}
|
| 700 |
+
|
| 701 |
// μ’μμ ν κΈ
|
| 702 |
+
function toggleLike(url, card) {
|
| 703 |
if (!state.username) {
|
| 704 |
showMessage('μ’μμλ₯Ό νλ €λ©΄ νκΉ
νμ΄μ€ API ν ν°μΌλ‘ μΈμ¦μ΄ νμν©λλ€.', true);
|
| 705 |
return;
|
|
|
|
| 714 |
// μν ν κΈ
|
| 715 |
if (isCurrentlyLiked) {
|
| 716 |
delete state.likedURLs[url];
|
| 717 |
+
card.classList.remove('liked');
|
| 718 |
+
const likeBtn = card.querySelector('.like-button');
|
| 719 |
+
if (likeBtn) {
|
| 720 |
+
likeBtn.classList.remove('liked');
|
| 721 |
+
likeBtn.classList.add('not-liked');
|
| 722 |
+
}
|
| 723 |
showMessage('μ’μμλ₯Ό μ·¨μνμ΅λλ€.');
|
| 724 |
} else {
|
| 725 |
state.likedURLs[url] = true;
|
| 726 |
+
card.classList.add('liked');
|
| 727 |
+
const likeBtn = card.querySelector('.like-button');
|
| 728 |
+
if (likeBtn) {
|
| 729 |
+
likeBtn.classList.add('liked');
|
| 730 |
+
likeBtn.classList.remove('not-liked');
|
| 731 |
+
}
|
| 732 |
showMessage('μ’μμλ₯Ό μΆκ°νμ΅λλ€.');
|
| 733 |
}
|
| 734 |
|
| 735 |
// λ‘컬 μ€ν 리μ§μ μ μ₯
|
| 736 |
saveLikesToStorage();
|
| 737 |
|
| 738 |
+
// μ’μμ ν΅κ³ μ
λ°μ΄νΈ
|
| 739 |
+
updateLikeStats();
|
| 740 |
+
|
| 741 |
+
// μ’μμλ§ λ³΄κΈ° λͺ¨λμΈ κ²½μ° λͺ©λ‘ λ€μ λ λλ§
|
| 742 |
+
if (state.viewMode === 'liked') {
|
| 743 |
+
filterAndRenderCards();
|
| 744 |
}
|
| 745 |
} catch (error) {
|
| 746 |
console.error('μ’μμ ν κΈ μ€λ₯:', error);
|
|
|
|
| 769 |
|
| 770 |
// μΉ΄λ μμ±
|
| 771 |
const card = document.createElement('div');
|
| 772 |
+
card.className = `card ${isLiked ? 'liked' : ''}`;
|
| 773 |
+
|
| 774 |
+
// μ’μμ λΌλ²¨
|
| 775 |
+
const likeLabel = document.createElement('span');
|
| 776 |
+
likeLabel.className = 'like-label';
|
| 777 |
+
likeLabel.textContent = 'μ’μμ';
|
| 778 |
+
card.appendChild(likeLabel);
|
| 779 |
|
| 780 |
// μ λͺ©
|
| 781 |
const titleEl = document.createElement('h3');
|
| 782 |
+
titleEl.className = 'card-title';
|
| 783 |
titleEl.textContent = title;
|
| 784 |
card.appendChild(titleEl);
|
| 785 |
|
|
|
|
| 794 |
const likeBtn = document.createElement('button');
|
| 795 |
likeBtn.className = `like-button ${isLiked ? 'liked' : 'not-liked'}`;
|
| 796 |
likeBtn.textContent = 'β₯';
|
| 797 |
+
likeBtn.title = isLiked ? 'μ’μμ μ·¨μ' : 'μ’μμ';
|
| 798 |
|
| 799 |
likeBtn.addEventListener('click', function(e) {
|
| 800 |
e.preventDefault();
|
| 801 |
+
toggleLike(url, card);
|
| 802 |
});
|
| 803 |
card.appendChild(likeBtn);
|
| 804 |
|
|
|
|
| 807 |
});
|
| 808 |
}
|
| 809 |
|
| 810 |
+
// λͺ¨λ λ³κ²½ ν¨μ
|
| 811 |
+
function changeViewMode(mode) {
|
| 812 |
+
state.viewMode = mode;
|
| 813 |
+
|
| 814 |
+
// λ²νΌ μν μ
λ°μ΄νΈ
|
| 815 |
+
elements.allViewBtn.classList.toggle('active', mode === 'all');
|
| 816 |
+
elements.likedViewBtn.classList.toggle('active', mode === 'liked');
|
| 817 |
+
|
| 818 |
+
// μΉ΄λ λ€μ λ λλ§
|
| 819 |
+
filterAndRenderCards();
|
| 820 |
+
}
|
| 821 |
+
|
| 822 |
// μ΄λ²€νΈ 리μ€λ μ€μ
|
| 823 |
elements.loginButton.addEventListener('click', () => {
|
| 824 |
login(elements.tokenInput.value);
|
|
|
|
| 833 |
}
|
| 834 |
});
|
| 835 |
|
| 836 |
+
// κ²μ μ΄λ²€νΈ 리μ€λ
|
|
|
|
| 837 |
elements.searchInput.addEventListener('input', () => {
|
| 838 |
+
// μ
λ ₯ μ§μ° μ²λ¦¬ (νμ΄νν λλ§λ€ νν°λ§ λ°©μ§)
|
| 839 |
clearTimeout(state.searchTimeout);
|
| 840 |
+
state.searchTimeout = setTimeout(filterAndRenderCards, 300);
|
| 841 |
});
|
| 842 |
|
| 843 |
+
// 보기 λͺ¨λ μ ν λ²νΌ
|
| 844 |
+
elements.allViewBtn.addEventListener('click', () => changeViewMode('all'));
|
| 845 |
+
elements.likedViewBtn.addEventListener('click', () => changeViewMode('liked'));
|
| 846 |
+
|
| 847 |
// μ΄κΈ°ν
|
| 848 |
checkSessionStatus();
|
| 849 |
</script>
|