feat: Implement advanced rating system with half-star precision
- Add 5-star display with half-star support - Implement dialog-based rating input with 0.5 increments - Add rating conversion functions (10-point ↔ 5-point) - Update visual styling for half-star display - Maintain default 0 rating and "Not rated" state - Add proper error handling and user feedback
This commit is contained in:
@@ -41,6 +41,33 @@ templ Dashboard(user User) {
|
||||
.btn-primary:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
.rating-star {
|
||||
display: inline-block;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
.rating-star:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.half-star {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.half-star::before {
|
||||
content: '★';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 50%;
|
||||
overflow: hidden;
|
||||
color: #facc15; /* yellow-400 */
|
||||
}
|
||||
.rating-text {
|
||||
font-size: 0.875rem;
|
||||
margin-left: 0.5rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="theme-tokyo-night">
|
||||
@@ -170,16 +197,62 @@ templ Dashboard(user User) {
|
||||
}
|
||||
}
|
||||
|
||||
// Convert 10-point rating to 5-point with half-star precision
|
||||
function convertToFivePoint(rating) {
|
||||
return rating / 2;
|
||||
}
|
||||
|
||||
// Convert 5-point with half-stars back to 10-point
|
||||
function convertToTenPoint(fivePointRating) {
|
||||
return Math.round(fivePointRating * 2);
|
||||
}
|
||||
|
||||
function renderRatingStars(ebookId, currentRating) {
|
||||
const fivePointRating = convertToFivePoint(currentRating);
|
||||
let stars = '';
|
||||
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const filled = i <= currentRating;
|
||||
const starClass = filled ? 'text-yellow-400' : 'text-gray-400';
|
||||
stars += `<span onclick="setRating('${ebookId}', ${i})" class="cursor-pointer text-xl ${starClass} hover:text-yellow-300 transition-colors">${filled ? '★' : '☆'}</span>`;
|
||||
const filled = i <= Math.floor(fivePointRating);
|
||||
const halfFilled = !filled && (i - 0.5) <= fivePointRating;
|
||||
let starHtml = '';
|
||||
|
||||
if (filled) {
|
||||
starHtml = '★';
|
||||
} else if (halfFilled) {
|
||||
starHtml = '⭐'; // Using different symbol for half-star
|
||||
} else {
|
||||
starHtml = '☆';
|
||||
}
|
||||
|
||||
const starClass = filled ? 'text-yellow-400' :
|
||||
halfFilled ? 'text-yellow-200' : 'text-gray-400';
|
||||
|
||||
stars += `<span onclick="openRatingDialog('${ebookId}', ${currentRating})" class="cursor-pointer text-xl ${starClass} hover:text-yellow-300 transition-colors" title="Click to rate (supports half-stars)">${starHtml}</span>`;
|
||||
}
|
||||
|
||||
// Add rating text
|
||||
stars += `<span class="text-sm ml-2" style="color: var(--text-secondary)">${currentRating > 0 ? fivePointRating.toFixed(1) + '/5' : 'Not rated'}</span>`;
|
||||
|
||||
return stars;
|
||||
}
|
||||
|
||||
function openRatingDialog(ebookId, currentRating) {
|
||||
const fivePointRating = convertToFivePoint(currentRating);
|
||||
const newRating = prompt(`Rate this ebook (1-5, supports 0.5 increments):`, currentRating > 0 ? fivePointRating.toFixed(1) : '');
|
||||
|
||||
if (newRating === null) return; // User cancelled
|
||||
|
||||
const rating = parseFloat(newRating);
|
||||
if (isNaN(rating) || rating < 0.5 || rating > 5) {
|
||||
alert('Please enter a valid rating between 0.5 and 5');
|
||||
return;
|
||||
}
|
||||
|
||||
// Round to nearest 0.5
|
||||
const roundedRating = Math.round(rating * 2) / 2;
|
||||
setRating(ebookId, convertToTenPoint(roundedRating));
|
||||
}
|
||||
|
||||
async function setRating(ebookId, rating) {
|
||||
try {
|
||||
const response = await fetch(`/api/ebooks/${ebookId}/rating`, {
|
||||
@@ -198,10 +271,13 @@ templ Dashboard(user User) {
|
||||
ratingContainer.innerHTML = renderRatingStars(ebookId, data.rating);
|
||||
}
|
||||
} else {
|
||||
console.error('Error setting rating:', await response.text());
|
||||
const errorText = await response.text();
|
||||
console.error('Error setting rating:', errorText);
|
||||
alert('Failed to set rating: ' + errorText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error setting rating:', error);
|
||||
alert('Failed to set rating due to network error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user