UI fixes & updates

This commit is contained in:
Tim Zöller 2026-01-09 09:05:51 +01:00
parent 22f7f7c271
commit 6a8598ef30
7 changed files with 118 additions and 32 deletions

View file

@ -444,6 +444,23 @@
let hoverMarker = null;
let currentTrackPoints = null;
/**
* Throttle function to limit how often a function can be called
* @param {Function} func - Function to throttle
* @param {number} limit - Minimum time between calls in milliseconds
* @returns {Function} Throttled function
*/
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Load activity details
loadActivity();
@ -798,6 +815,18 @@
// Smooth elevation data to remove zero/invalid values
const smoothedData = smoothElevationData(elevationData);
// Create throttled hover handler for elevation chart
const elevationHoverHandler = throttle((event, activeElements) => {
if (activeElements && activeElements.length > 0) {
const dataIndex = activeElements[0].index;
if (smoothedData[dataIndex]) {
updateMapMarker(smoothedData[dataIndex].trackPointIndex);
}
} else {
hideMapMarker();
}
}, 50); // 50ms throttle
// Create elevation chart with hover interaction
const ctx = document.getElementById('elevationChart').getContext('2d');
new Chart(ctx, {
@ -813,22 +842,13 @@
fill: true,
tension: 0.3,
pointRadius: 0,
pointHoverRadius: 5
pointHoverRadius: 0 // Disable hover radius for better performance
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
onHover: (event, activeElements) => {
if (activeElements && activeElements.length > 0) {
const dataIndex = activeElements[0].index;
if (smoothedData[dataIndex]) {
updateMapMarker(smoothedData[dataIndex].trackPointIndex);
}
} else {
hideMapMarker();
}
},
onHover: elevationHoverHandler,
plugins: {
legend: {
display: false
@ -1010,6 +1030,18 @@
// Calculate total duration to determine time format
const totalMinutes = heartRateData[heartRateData.length - 1].time;
// Create throttled hover handler for heart rate chart
const heartRateHoverHandler = throttle((event, activeElements) => {
if (activeElements && activeElements.length > 0) {
const dataIndex = activeElements[0].index;
if (heartRateData[dataIndex]) {
updateMapMarker(heartRateData[dataIndex].trackPointIndex);
}
} else {
hideMapMarker();
}
}, 50); // 50ms throttle
// Create heart rate chart using Chart.js
const ctx = document.getElementById('heartRateChart').getContext('2d');
new Chart(ctx, {
@ -1025,22 +1057,13 @@
fill: true,
tension: 0.3,
pointRadius: 0,
pointHoverRadius: 5
pointHoverRadius: 0 // Disable hover radius for better performance
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
onHover: (event, activeElements) => {
if (activeElements && activeElements.length > 0) {
const dataIndex = activeElements[0].index;
if (heartRateData[dataIndex]) {
updateMapMarker(heartRateData[dataIndex].trackPointIndex);
}
} else {
hideMapMarker();
}
},
onHover: heartRateHoverHandler,
plugins: {
legend: {
display: false
@ -1114,7 +1137,8 @@
speedData.push({
time: elapsedMinutes,
speed: speedKmh
speed: speedKmh,
trackPointIndex: i // Store the original track point index
});
}
}
@ -1126,6 +1150,18 @@
// Calculate total duration to determine time format
const totalMinutes = smoothedSpeedData[smoothedSpeedData.length - 1].time;
// Create throttled hover handler for speed chart
const speedHoverHandler = throttle((event, activeElements) => {
if (activeElements && activeElements.length > 0) {
const dataIndex = activeElements[0].index;
if (smoothedSpeedData[dataIndex]) {
updateMapMarker(smoothedSpeedData[dataIndex].trackPointIndex);
}
} else {
hideMapMarker();
}
}, 50); // 50ms throttle
// Create speed chart using Chart.js
const ctx = document.getElementById('speedChart').getContext('2d');
new Chart(ctx, {
@ -1141,12 +1177,13 @@
fill: true,
tension: 0.3,
pointRadius: 0,
pointHoverRadius: 5
pointHoverRadius: 0 // Disable hover radius for better performance
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
onHover: speedHoverHandler,
plugins: {
legend: {
display: false
@ -1199,7 +1236,7 @@
/**
* Smooth speed data by applying moving average
* @param {Array} data - Array of {time, speed} objects
* @param {Array} data - Array of {time, speed, trackPointIndex} objects
* @returns {Array} Smoothed speed data
*/
function smoothSpeedData(data) {
@ -1224,7 +1261,8 @@
smoothed.push({
time: data[i].time,
speed: count > 0 ? sum / count : data[i].speed
speed: count > 0 ? sum / count : data[i].speed,
trackPointIndex: data[i].trackPointIndex // Preserve track point index
});
}

View file

@ -268,6 +268,9 @@
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<!-- DOMPurify for HTML sanitization -->
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js"></script>
<!-- Custom JS -->
<script th:src="@{/js/auth.js}"></script>
<script th:src="@{/js/fitpub.js}"></script>

View file

@ -118,7 +118,7 @@
<p class="text-muted small mb-0">
@${escapeHtml(follower.handle)}
</p>
${follower.bio ? `<p class="small mt-1 mb-0 text-muted">${escapeHtml(follower.bio)}</p>` : ''}
${follower.bio ? `<p class="small mt-1 mb-0 text-muted">${sanitizeHtml(follower.bio)}</p>` : ''}
</div>
</div>
</div>
@ -132,6 +132,15 @@
div.textContent = text;
return div.innerHTML;
}
function sanitizeHtml(html) {
if (!html) return '';
// Use DOMPurify to sanitize HTML, allowing safe tags like p, br, a
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'br', 'a', 'strong', 'em', 'b', 'i', 'span'],
ALLOWED_ATTR: ['href', 'class', 'rel', 'target']
});
}
});
</script>
</th:block>

View file

@ -118,7 +118,7 @@
<p class="text-muted small mb-0">
@${escapeHtml(user.handle)}
</p>
${user.bio ? `<p class="small mt-1 mb-0 text-muted">${escapeHtml(user.bio)}</p>` : ''}
${user.bio ? `<p class="small mt-1 mb-0 text-muted">${sanitizeHtml(user.bio)}</p>` : ''}
</div>
</div>
</div>
@ -132,6 +132,15 @@
div.textContent = text;
return div.innerHTML;
}
function sanitizeHtml(html) {
if (!html) return '';
// Use DOMPurify to sanitize HTML, allowing safe tags like p, br, a
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'br', 'a', 'strong', 'em', 'b', 'i', 'span'],
ALLOWED_ATTR: ['href', 'class', 'rel', 'target']
});
}
});
</script>
</th:block>

View file

@ -172,7 +172,7 @@
// Bio
const bioElement = document.getElementById('bio');
if (user.bio) {
bioElement.textContent = user.bio;
bioElement.innerHTML = sanitizeHtml(user.bio);
} else {
bioElement.innerHTML = '<span class="text-muted">No bio</span>';
}
@ -429,6 +429,15 @@
div.textContent = text;
return div.innerHTML;
}
function sanitizeHtml(html) {
if (!html) return '';
// Use DOMPurify to sanitize HTML, allowing safe tags like p, br, a
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'br', 'a', 'strong', 'em', 'b', 'i', 'span'],
ALLOWED_ATTR: ['href', 'class', 'rel', 'target']
});
}
});
</script>
</th:block>

View file

@ -175,7 +175,7 @@
// Bio
const bioElement = document.getElementById('bio');
if (user.bio) {
bioElement.textContent = user.bio;
bioElement.innerHTML = sanitizeHtml(user.bio);
} else {
bioElement.innerHTML = '<span class="text-muted">No bio yet. <a href="/profile/edit">Add one?</a></span>';
}
@ -290,6 +290,15 @@
div.textContent = text;
return div.innerHTML;
}
function sanitizeHtml(html) {
if (!html) return '';
// Use DOMPurify to sanitize HTML, allowing safe tags like p, br, a
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'br', 'a', 'strong', 'em', 'b', 'i', 'span'],
ALLOWED_ATTR: ['href', 'class', 'rel', 'target']
});
}
});
</script>
</th:block>

View file

@ -311,7 +311,7 @@
</div>
${actor.bio
? `<p class="card-text small mb-3">${escapeHtml(actor.bio)}</p>`
? `<p class="card-text small mb-3">${sanitizeHtml(actor.bio)}</p>`
: '<p class="card-text small text-muted mb-3 fst-italic">No bio</p>'
}
@ -444,7 +444,7 @@
<!-- Bio -->
${user.bio
? `<p class="card-text small text-muted mb-3 bio-preview">${escapeHtml(user.bio)}</p>`
? `<p class="card-text small text-muted mb-3 bio-preview">${sanitizeHtml(user.bio)}</p>`
: '<p class="card-text small text-muted mb-3 fst-italic">No bio</p>'
}
@ -540,6 +540,15 @@
div.textContent = text;
return div.innerHTML;
}
function sanitizeHtml(html) {
if (!html) return '';
// Use DOMPurify to sanitize HTML, allowing safe tags like p, br, a
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'br', 'a', 'strong', 'em', 'b', 'i', 'span'],
ALLOWED_ATTR: ['href', 'class', 'rel', 'target']
});
}
</script>
<style>