simple cards for folder view with tag sorting

This commit is contained in:
vintro 2024-12-13 23:38:30 -08:00
parent 2fb48b1e88
commit f9b1856da8
No known key found for this signature in database
3 changed files with 158 additions and 14 deletions

View File

@ -42,7 +42,7 @@ export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit }: Pr
const tags = page.frontmatter?.tags ?? []
return (
<li class="section-li">
<li class="section-li" data-tags={JSON.stringify(tags)}>
<div class="section">
{page.dates && (
<p class="meta">
@ -56,18 +56,6 @@ export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit }: Pr
</a>
</h3>
</div>
<ul class="tags">
{tags.map((tag) => (
<li>
<a
class="internal tag-link"
href={resolveRelative(fileData.slug!, `tags/${tag}` as FullSlug)}
>
{tag}
</a>
</li>
))}
</ul>
</div>
</li>
)

View File

@ -33,6 +33,15 @@ export default ((opts?: Partial<FolderContentOptions>) => {
const isDirectChild = fileParts.length === folderParts.length + 1
return prefixed && isDirectChild
})
// Get all unique tags
const allTags = new Set<string>()
allPagesInFolder.forEach(file => {
const tags = file.frontmatter?.tags ?? []
tags.forEach(tag => allTags.add(tag))
})
const sortedTags = Array.from(allTags).sort()
const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? []
const classes = ["popover-hint", ...cssClasses].join(" ")
const listProps = {
@ -45,14 +54,63 @@ export default ((opts?: Partial<FolderContentOptions>) => {
? fileData.description
: htmlToJsx(fileData.filePath!, tree)
// Add client-side filtering script
const filterScript = `
document.addEventListener('DOMContentLoaded', () => {
const selectedTags = new Set()
const cards = document.querySelectorAll('.section-li')
const countEl = document.querySelector('.folder-count')
const originalCount = ${allPagesInFolder.length}
function updateVisibility() {
let visibleCount = 0
cards.forEach(card => {
const tags = JSON.parse(card.dataset.tags || '[]')
const shouldShow = selectedTags.size === 0 ||
tags.some(tag => selectedTags.has(tag))
card.style.display = shouldShow ? '' : 'none'
if (shouldShow) visibleCount++
})
if (countEl) {
countEl.textContent = ${JSON.stringify(i18n(cfg.locale).pages.folderContent.itemsUnderFolder({count: 0}))}
.replace('0', visibleCount.toString())
}
}
document.querySelectorAll('.tag-filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
const tag = btn.dataset.tag
if (selectedTags.has(tag)) {
selectedTags.delete(tag)
btn.classList.remove('selected')
} else {
selectedTags.add(tag)
btn.classList.add('selected')
}
updateVisibility()
})
})
})
`
return (
<div class={classes}>
<article>
<p>{content}</p>
</article>
<div class="page-listing">
<div class="tag-filter">
{sortedTags.map(tag => (
<button
data-tag={tag}
class="tag-filter-btn"
>
{tag}
</button>
))}
</div>
{options.showFolderCount && (
<p>
<p class="folder-count">
{i18n(cfg.locale).pages.folderContent.itemsUnderFolder({
count: allPagesInFolder.length,
})}
@ -62,6 +120,7 @@ export default ((opts?: Partial<FolderContentOptions>) => {
<PageList {...listProps} />
</div>
</div>
<script dangerouslySetInnerHTML={{ __html: filterScript }} />
</div>
)
}

View File

@ -545,3 +545,100 @@ input {
padding: 0.5rem;
}
// Blog listing styles
.page-listing {
margin-top: 2rem;
.tag-filter {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 2rem;
.tag-filter-btn {
padding: 0.4rem 0.8rem;
border-radius: 20px;
border: 1px solid var(--lightgray);
background: var(--light);
color: var(--gray);
font-size: 0.9rem;
font-family: var(--bodyFont);
cursor: pointer;
transition: all 0.2s ease;
&:hover {
border-color: var(--secondary);
color: var(--secondary);
}
&.selected {
background: var(--secondary);
color: var(--light);
border-color: var(--secondary);
}
&::before {
content: "#";
opacity: 0.7;
margin-right: 0.2rem;
}
}
}
.section-ul {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
margin-top: 1.5rem;
}
.section-li {
margin: 0;
transition: transform 0.2s ease, box-shadow 0.2s ease;
border-radius: 12px;
background: var(--light);
border: 1px solid var(--lightgray);
overflow: hidden;
&:hover {
transform: translateY(-3px);
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
}
.section {
display: flex;
flex-direction: column;
height: 100%;
padding: 1.25rem;
gap: 0.7rem;
.meta {
order: -1;
font-size: 0.9rem;
color: var(--gray);
}
.desc {
flex: 1;
h3 {
margin: 0;
font-size: 1.3rem;
line-height: 1.4;
a {
text-decoration: none;
background: none;
padding: 0;
&:hover {
color: var(--secondary);
background: none;
}
}
}
}
}
}
}