feat: implement responsive card layout with category filtering and consumption status
This commit is contained in:
parent
40ed683c5f
commit
e80e9a54fe
@ -8,15 +8,162 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
<style>
|
||||||
|
/* Sidebar Menu Styles */
|
||||||
|
.sidebar {
|
||||||
|
height: 100%;
|
||||||
|
width: 0;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 100;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
overflow-x: hidden;
|
||||||
|
transition: 0.3s;
|
||||||
|
padding-top: 60px;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.sidebar.open {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
.sidebar .close-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 20px;
|
||||||
|
font-size: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.sidebar-heading {
|
||||||
|
padding: 8px 16px 20px 16px;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.sidebar-menu {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.sidebar-menu li {
|
||||||
|
padding: 12px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
.sidebar-menu li:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.sidebar-menu li.active {
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
color: #1976d2;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.sidebar-section {
|
||||||
|
padding: 12px 16px 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #757575;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
.overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0,0,0,0.4);
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
.overlay.visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="sidebar" class="sidebar">
|
||||||
|
<span class="close-btn">×</span>
|
||||||
|
<div class="sidebar-heading">My Favorite Stuff</div>
|
||||||
|
<div class="sidebar-section">View Options</div>
|
||||||
|
<ul class="sidebar-menu" id="view-options">
|
||||||
|
<li data-filter="consumed" class="active">Consumed Items</li>
|
||||||
|
<li data-filter="not-consumed">Not Consumed Yet</li>
|
||||||
|
<li data-filter="all">Show All Items</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div id="overlay" class="overlay"></div>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<span style="font-size:1.7rem;cursor:pointer;margin-right:18px;">☰</span>
|
<span id="menu-toggle" style="font-size:1.7rem;cursor:pointer;margin-right:18px;">☰</span>
|
||||||
<h1 style="margin:0;font-size:2rem;font-weight:500;">My Favorite Stuff</h1>
|
<h1 style="margin:0;font-size:2rem;font-weight:500;">My Favorite Stuff</h1>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
{{ block "main" . }}{{ end }}
|
{{ block "main" . }}{{ end }}
|
||||||
</main>
|
</main>
|
||||||
<div class="fab">+</div>
|
<div class="fab">+</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const menuToggle = document.getElementById('menu-toggle');
|
||||||
|
const sidebar = document.getElementById('sidebar');
|
||||||
|
const closeBtn = document.querySelector('.close-btn');
|
||||||
|
const overlay = document.getElementById('overlay');
|
||||||
|
const viewOptions = document.querySelectorAll('#view-options li');
|
||||||
|
|
||||||
|
// Set the initial filter based on the selected sidebar option
|
||||||
|
let currentConsumedFilter = 'consumed';
|
||||||
|
|
||||||
|
// Functions to open and close the sidebar
|
||||||
|
function openSidebar() {
|
||||||
|
sidebar.classList.add('open');
|
||||||
|
overlay.classList.add('visible');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeSidebar() {
|
||||||
|
sidebar.classList.remove('open');
|
||||||
|
overlay.classList.remove('visible');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle sidebar when hamburger is clicked
|
||||||
|
menuToggle.addEventListener('click', openSidebar);
|
||||||
|
|
||||||
|
// Close sidebar when X is clicked
|
||||||
|
closeBtn.addEventListener('click', closeSidebar);
|
||||||
|
|
||||||
|
// Close sidebar when clicking outside
|
||||||
|
overlay.addEventListener('click', closeSidebar);
|
||||||
|
|
||||||
|
// Handle view option clicks
|
||||||
|
viewOptions.forEach(option => {
|
||||||
|
option.addEventListener('click', function() {
|
||||||
|
// Update active class
|
||||||
|
viewOptions.forEach(opt => opt.classList.remove('active'));
|
||||||
|
this.classList.add('active');
|
||||||
|
|
||||||
|
// Set filter and apply
|
||||||
|
currentConsumedFilter = this.getAttribute('data-filter');
|
||||||
|
applyConsumedFilter(currentConsumedFilter);
|
||||||
|
closeSidebar();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to apply the filter - will be called by list.html and index.html scripts
|
||||||
|
window.applyConsumedFilter = function(filterType) {
|
||||||
|
// Make this function available to other scripts
|
||||||
|
window.currentConsumedFilter = filterType;
|
||||||
|
|
||||||
|
// Dispatch a custom event that list.html and index.html can listen for
|
||||||
|
document.dispatchEvent(new CustomEvent('consumedFilterChanged', {
|
||||||
|
detail: { filterType: filterType }
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize with the default filter
|
||||||
|
window.currentConsumedFilter = 'consumed';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -59,6 +59,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
console.log('FUNC activateTabAndFilter (index.html): Active states updated for tabs.');
|
console.log('FUNC activateTabAndFilter (index.html): Active states updated for tabs.');
|
||||||
|
|
||||||
|
// Get the consumed filter status from window global
|
||||||
|
const consumedFilter = window.currentConsumedFilter || 'consumed';
|
||||||
|
console.log('FUNC activateTabAndFilter (index.html): Current consumed filter is:', consumedFilter);
|
||||||
|
|
||||||
let visibleCount = 0;
|
let visibleCount = 0;
|
||||||
allCards.forEach(cardLinkElement => {
|
allCards.forEach(cardLinkElement => {
|
||||||
const cardDiv = cardLinkElement.querySelector('.card');
|
const cardDiv = cardLinkElement.querySelector('.card');
|
||||||
@ -68,23 +72,45 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check category visibility first
|
||||||
|
let categoryVisible = false;
|
||||||
if (selectedCategory === 'all') {
|
if (selectedCategory === 'all') {
|
||||||
cardLinkElement.style.display = '';
|
categoryVisible = true;
|
||||||
visibleCount++;
|
|
||||||
} else {
|
} else {
|
||||||
const cardCategoryAttr = cardDiv.getAttribute('data-category');
|
const cardCategoryAttr = cardDiv.getAttribute('data-category');
|
||||||
const sCat = selectedCategory ? selectedCategory : 'null_or_empty';
|
categoryVisible = (cardCategoryAttr === selectedCategory);
|
||||||
const cCat = cardCategoryAttr ? cardCategoryAttr : 'null_or_empty';
|
}
|
||||||
console.log(
|
|
||||||
`FUNC activateTabAndFilter (index.html): Comparing: cardCategory='${cCat}' (length ${cCat.length})` +
|
// Now check consumed status
|
||||||
` vs selectedCategory='${sCat}' (length ${sCat.length})` +
|
const cardData = cardLinkElement.querySelector('meta[name="card-data"]');
|
||||||
` for card titled: '${cardDiv.querySelector('.card-title') ? cardDiv.querySelector('.card-title').textContent.trim() : 'N/A'}'`
|
let consumedStatus;
|
||||||
);
|
if (cardData) {
|
||||||
const isVisible = (cardCategoryAttr === selectedCategory);
|
try {
|
||||||
|
const cardDataObj = JSON.parse(cardData.getAttribute('content'));
|
||||||
|
consumedStatus = cardDataObj.consumed;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('FUNC activateTabAndFilter (index.html): Error parsing card data:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine visibility based on consumed filter
|
||||||
|
let consumedVisible = true; // Default to true if no consumed status is specified
|
||||||
|
|
||||||
|
if (consumedFilter === 'consumed') {
|
||||||
|
// Show only consumed=true or consumed not specified
|
||||||
|
consumedVisible = (consumedStatus !== false);
|
||||||
|
} else if (consumedFilter === 'not-consumed') {
|
||||||
|
// Show only consumed=false
|
||||||
|
consumedVisible = (consumedStatus === false);
|
||||||
|
}
|
||||||
|
// If consumedFilter is 'all', then consumedVisible remains true
|
||||||
|
|
||||||
|
// Final visibility is the combination of both filters
|
||||||
|
const isVisible = categoryVisible && consumedVisible;
|
||||||
cardLinkElement.style.display = isVisible ? '' : 'none';
|
cardLinkElement.style.display = isVisible ? '' : 'none';
|
||||||
if (isVisible) visibleCount++;
|
if (isVisible) visibleCount++;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('FUNC activateTabAndFilter (index.html): Visible cards after filtering:', visibleCount);
|
console.log('FUNC activateTabAndFilter (index.html): Visible cards after filtering:', visibleCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,6 +140,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
activateTabAndFilter(initialCategoryToDisplay);
|
activateTabAndFilter(initialCategoryToDisplay);
|
||||||
console.log('SCRIPT (index.html): Initial page load filtering complete.');
|
console.log('SCRIPT (index.html): Initial page load filtering complete.');
|
||||||
|
|
||||||
|
// Listen for consumed filter changes from the sidebar
|
||||||
|
document.addEventListener('consumedFilterChanged', function(event) {
|
||||||
|
console.log('EVENT (index.html): consumedFilterChanged received with filterType:', event.detail.filterType);
|
||||||
|
// Re-apply the current category filter when the consumed filter changes
|
||||||
|
const currentCategory = document.querySelector('#tabs .tab.active').getAttribute('data-tab');
|
||||||
|
activateTabAndFilter(currentCategory);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
@ -89,6 +89,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
console.log('FUNC activateTabAndFilter: Active states updated for tabs.');
|
console.log('FUNC activateTabAndFilter: Active states updated for tabs.');
|
||||||
|
|
||||||
|
// Get the consumed filter status from window global
|
||||||
|
const consumedFilter = window.currentConsumedFilter || 'consumed';
|
||||||
|
console.log('FUNC activateTabAndFilter: Current consumed filter is:', consumedFilter);
|
||||||
|
|
||||||
let visibleCount = 0;
|
let visibleCount = 0;
|
||||||
allCards.forEach(cardLinkElement => {
|
allCards.forEach(cardLinkElement => {
|
||||||
const cardDiv = cardLinkElement.querySelector('.card');
|
const cardDiv = cardLinkElement.querySelector('.card');
|
||||||
@ -98,23 +102,45 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check category visibility first
|
||||||
|
let categoryVisible = false;
|
||||||
if (selectedCategory === 'all') {
|
if (selectedCategory === 'all') {
|
||||||
cardLinkElement.style.display = '';
|
categoryVisible = true;
|
||||||
visibleCount++;
|
|
||||||
} else {
|
} else {
|
||||||
const cardCategoryAttr = cardDiv.getAttribute('data-category');
|
const cardCategoryAttr = cardDiv.getAttribute('data-category');
|
||||||
const sCat = selectedCategory ? selectedCategory : 'null_or_empty';
|
categoryVisible = (cardCategoryAttr === selectedCategory);
|
||||||
const cCat = cardCategoryAttr ? cardCategoryAttr : 'null_or_empty';
|
}
|
||||||
console.log(
|
|
||||||
`FUNC activateTabAndFilter: Comparing: cardCategory='${cCat}' (length ${cCat.length})` +
|
// Now check consumed status
|
||||||
` vs selectedCategory='${sCat}' (length ${sCat.length})` +
|
const cardData = cardLinkElement.querySelector('meta[name="card-data"]');
|
||||||
` for card titled: '${cardDiv.querySelector('.card-title') ? cardDiv.querySelector('.card-title').textContent.trim() : 'N/A'}'`
|
let consumedStatus;
|
||||||
);
|
if (cardData) {
|
||||||
const isVisible = (cardCategoryAttr === selectedCategory);
|
try {
|
||||||
|
const cardDataObj = JSON.parse(cardData.getAttribute('content'));
|
||||||
|
consumedStatus = cardDataObj.consumed;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('FUNC activateTabAndFilter: Error parsing card data:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine visibility based on consumed filter
|
||||||
|
let consumedVisible = true; // Default to true if no consumed status is specified
|
||||||
|
|
||||||
|
if (consumedFilter === 'consumed') {
|
||||||
|
// Show only consumed=true or consumed not specified
|
||||||
|
consumedVisible = (consumedStatus !== false);
|
||||||
|
} else if (consumedFilter === 'not-consumed') {
|
||||||
|
// Show only consumed=false
|
||||||
|
consumedVisible = (consumedStatus === false);
|
||||||
|
}
|
||||||
|
// If consumedFilter is 'all', then consumedVisible remains true
|
||||||
|
|
||||||
|
// Final visibility is the combination of both filters
|
||||||
|
const isVisible = categoryVisible && consumedVisible;
|
||||||
cardLinkElement.style.display = isVisible ? '' : 'none';
|
cardLinkElement.style.display = isVisible ? '' : 'none';
|
||||||
if (isVisible) visibleCount++;
|
if (isVisible) visibleCount++;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('FUNC activateTabAndFilter: Visible cards after filtering:', visibleCount);
|
console.log('FUNC activateTabAndFilter: Visible cards after filtering:', visibleCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,6 +172,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
activateTabAndFilter(initialCategoryToDisplay);
|
activateTabAndFilter(initialCategoryToDisplay);
|
||||||
console.log('SCRIPT: Initial page load filtering complete.');
|
console.log('SCRIPT: Initial page load filtering complete.');
|
||||||
|
|
||||||
|
// Listen for consumed filter changes from the sidebar
|
||||||
|
document.addEventListener('consumedFilterChanged', function(event) {
|
||||||
|
console.log('EVENT: consumedFilterChanged received in list.html with filterType:', event.detail.filterType);
|
||||||
|
// Re-apply the current category filter when the consumed filter changes
|
||||||
|
const currentCategory = document.querySelector('#tabs .tab.active').getAttribute('data-tab');
|
||||||
|
activateTabAndFilter(currentCategory);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
<a class="card-link-outer" href="{{ .RelPermalink }}">
|
<a class="card-link-outer" href="{{ .RelPermalink }}">
|
||||||
|
<meta name="card-data" content='{{ jsonify (dict "consumed" .Params.consumed) }}'>
|
||||||
<div class="card" data-category="{{ .Params.category }}">
|
<div class="card" data-category="{{ .Params.category }}">
|
||||||
{{ with .Params.image }}
|
{{ with .Params.image }}
|
||||||
<img src="{{ . }}" alt="{{ $.Title }} cover">
|
<img src="{{ . }}" alt="{{ $.Title }} cover">
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<img src="/img/no-cover-available-yet.png" alt="{{ $.Title }} - No cover available">
|
<img src="/img/no-cover-available-yet.png" alt="{{ $.Title }} - No cover available">
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ if eq .Params.consumed false }}
|
|
||||||
<div class="not-consumed-badge">Not {{ if eq .Params.category "Books" }}Read{{ else if eq .Params.category "Movies" }}Watched{{ else if eq .Params.category "TV Series" }}Watched{{ else if eq .Params.category "Restaurants" }}Visited{{ else if eq .Params.category "Recipes" }}Made{{ else if eq .Params.category "Concerts" }}Attended{{ else }}Experienced{{ end }} Yet</div>
|
|
||||||
{{ end }}
|
|
||||||
<div class="card-title">{{ .Title }}</div>
|
<div class="card-title">{{ .Title }}</div>
|
||||||
{{ with .Params.rating }}<div class="card-rating">{{ partial "stars.html" (dict "rating" .) }} {{ . }}</div>{{ end }}
|
{{ with .Params.rating }}<div class="card-rating">{{ partial "stars.html" (dict "rating" .) }} {{ . }}</div>{{ end }}
|
||||||
{{ with .Params.likes }}<div class="card-likes">👍 {{ . }}</div>{{ end }}
|
{{ with .Params.likes }}<div class="card-likes">👍 {{ . }}</div>{{ end }}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user