Skip to content

Commit

Permalink
✨ feat: add tag filtering for projects
Browse files Browse the repository at this point in the history
  • Loading branch information
welpo committed Nov 15, 2024
1 parent 8e1acdb commit 8b28514
Show file tree
Hide file tree
Showing 17 changed files with 267 additions and 9 deletions.
6 changes: 6 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ compact_tags = false
# Default: "name".
tag_sorting = "name"

# Show clickable tags above cards.html template (e.g. projects/) to filter the displayed items.
# Items need tags (or another taxonomy) in their front matter for this to work.
# Can be set at the section or config.toml level, following the hierarchy: section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy
# Default: true
enable_cards_tag_filtering = true

# Invert the order of the site title and page title in the browser tab.
# Example: true => "Blog • ~/tabi", false => "~/tabi • Blog"
invert_title_order = false
Expand Down
3 changes: 3 additions & 0 deletions content/projects/doteki/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ title = "dōteki"
description = "Add dynamic content to your GitHub profile through an intuitive plugin system."
weight = 30

[taxonomies]
tags = ["GitHub Actions", "automation", "Python"]

[extra]
local_image = "projects/doteki/doteki_logo.webp"
social_media_card = "social_cards/projects_doteki.jpg"
Expand Down
3 changes: 3 additions & 0 deletions content/projects/git-sumi/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ title = "git-sumi"
description = "The non-opinionated Rust-based commit message linter."
weight = 10

[taxonomies]
tags = ["Git", "Rust", "Continuous Integration", "GitHub Actions", "CLI", "automation"]

[extra]
local_image = "projects/git-sumi/git-sumi_logo.webp"
social_media_card = "social_cards/projects_git-sumi.jpg"
Expand Down
3 changes: 3 additions & 0 deletions content/projects/nani/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ title = "nani"
description = "Bash script to create public URLs from files or text on remote servers."
weight = 50

[taxonomies]
tags = ["bash", "CLI"]

[extra]
local_image = "projects/nani/nani_logo.webp"
canonical_url = "https://osc.garden/projects/tabi/"
Expand Down
3 changes: 3 additions & 0 deletions content/projects/ramu/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ title = "ラム (ramu)"
description = "A web app to practice reading and listening to Japanese numbers."
weight = 30

[taxonomies]
tags = ["Japanese", "education", "web app", "PWA", "web", "JavaScript"]

[extra]
local_image = "projects/ramu/ramu_logo.png"
canonical_url = "https://osc.garden/projects/ramu/"
Expand Down
3 changes: 3 additions & 0 deletions content/projects/streaming-royalties-calculator/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ title = "Streaming Royalties Calculator"
description = "A tool to calculate streaming royalties for musicians."
weight = 45

[taxonomies]
tags = ["music", "web app", "JavaScript", "data analysis", "web"]

[extra]
local_image = "projects/streaming-royalties-calculator/streaming-royalties-calculator_logo.webp"
canonical_url = "https://osc.garden/projects/streaming-royalties-calculator/"
Expand Down
3 changes: 3 additions & 0 deletions content/projects/tabi/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ title = "tabi"
description = "A feature-rich modern Zola theme with first-class multi-language support."
weight = 40

[taxonomies]
tags = ["Zola", "web", "JavaScript"]

[extra]
local_image = "projects/tabi/tabi.webp"
social_media_card = "social_cards/projects_tabi.jpg"
Expand Down
50 changes: 50 additions & 0 deletions sass/parts/_cards.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
.filter-controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 12px;
margin-top: 1.2rem;
margin-bottom: -1rem;
padding: 0;
list-style: none;

#all-projects-filter {
display: none;
}

.taxonomy-item {
margin: 0;

a {
display: inline-block;
box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;
border-radius: 1rem;
background: var(--bg-2);
padding: 0 16px;
color: var(--text-color);
font-size: 0.8rem;
text-decoration: none;

&:hover {
background: var(--primary-color);
color: var(--hover-color);
}

&.active {
background: var(--primary-color);
color: var(--hover-color);
}
}
}
}

.cards {
display: grid;
grid-template-rows: auto;
Expand Down Expand Up @@ -53,4 +94,13 @@
.cards {
gap: 18px;
}

.filter-controls {
gap: 8px;
margin: 18px 0;

.taxonomy-item a {
padding: 4px 12px;
}
}
}
15 changes: 11 additions & 4 deletions sass/parts/_posts_list.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ $padding: 2.5rem;
align-items: flex-start;
background-color: var(--navbar-color);
padding-block: $padding;
min-width: 13.5rem;

.thumbnail-image {
margin: 0;
margin-top: 0.7rem;
max-width: 90%;
}

ul {
margin-inline-end: 0.7rem;
Expand All @@ -24,10 +31,6 @@ $padding: 2.5rem;
white-space: nowrap;
}

li.date {
width: 13.5rem;
}

li.draft-label {
width: fit-content;
line-height: 1.2rem;
Expand Down Expand Up @@ -137,6 +140,10 @@ $padding: 2.5rem;
margin-inline-end: 0.3rem;
}
}

.post-thumbnail {
display: none;
}
}

.bloglist-content {
Expand Down
99 changes: 99 additions & 0 deletions static/js/filterCards.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
document.addEventListener('DOMContentLoaded', () => {
const cards = document.querySelectorAll('.card');
const filterLinks = document.querySelectorAll('.filter-controls a');
const allProjectsFilter = document.querySelector('#all-projects-filter');
if (!cards.length || !filterLinks.length) return;
allProjectsFilter.style.display = 'block';

// Create a Map for O(1) lookups of links by filter value.
const linkMap = new Map(
Array.from(filterLinks).map(link => [link.dataset.filter, link])
);

// Pre-process cards data for faster filtering.
const cardData = Array.from(cards).map(card => ({
element: card,
tags: card.dataset.tags?.toLowerCase().split(',').filter(Boolean) ?? []
}));

function getTagSlugFromUrl(url) {
return url.split('/').filter(Boolean).pop();
}

function getFilterFromHash() {
if (!window.location.hash) return 'all';
const hash = decodeURIComponent(window.location.hash.slice(1));
const matchingLink = Array.from(filterLinks).find(link =>
getTagSlugFromUrl(link.getAttribute('href')) === hash
);
return matchingLink?.dataset.filter ?? 'all';
}

function setActiveFilter(filterValue, updateHash = true) {
if (updateHash) {
if (filterValue === 'all') {
history.pushState(null, '', window.location.pathname);
} else {
const activeLink = linkMap.get(filterValue);
if (activeLink) {
const tagSlug = getTagSlugFromUrl(activeLink.getAttribute('href'));
history.pushState(null, '', `#${tagSlug}`);
}
}
}
const isAll = filterValue === 'all';
const display = isAll ? '' : 'none';
const ariaHidden = isAll ? 'false' : 'true';
requestAnimationFrame(() => {
filterLinks.forEach(link => {
const isActive = link.dataset.filter === filterValue;
link.classList.toggle('active', isActive);
link.setAttribute('aria-pressed', isActive);
});
if (isAll) {
cardData.forEach(({ element }) => {
element.style.display = display;
element.setAttribute('aria-hidden', ariaHidden);
});
} else {
cardData.forEach(({ element, tags }) => {
const shouldShow = tags.includes(filterValue);
element.style.display = shouldShow ? '' : 'none';
element.setAttribute('aria-hidden', !shouldShow);
});
}
});
}

const filterContainer = filterLinks[0].parentElement.parentElement;
filterContainer.addEventListener('click', e => {
const link = e.target.closest('a');
if (!link) return;
e.preventDefault();
const filterValue = link.dataset.filter;
if (filterValue) setActiveFilter(filterValue);
});

filterContainer.addEventListener('keydown', e => {
const link = e.target.closest('a');
if (!link) return;
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
link.click();
}
});

filterLinks.forEach(link => {
link.setAttribute('role', 'button');
link.setAttribute('aria-pressed', link.classList.contains('active'));
});

window.addEventListener('popstate', () => {
setActiveFilter(getFilterFromHash(), false);
});

const initialFilter = getFilterFromHash();
if (initialFilter !== 'all') {
setActiveFilter(initialFilter, false);
}
});
1 change: 1 addition & 0 deletions static/js/filterCards.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion templates/cards.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
{%- set show_pages = section.pages -%}
{% endif -%}

{%- include "partials/cards_pages.html" -%}
{%- if macros_settings::evaluate_setting_priority(setting="enable_cards_tag_filtering", page=section, default_global_value=true) == "true" -%}
{%- include "partials/filter_card_tags.html" -%}
{%- endif -%}


{%- include "partials/cards_pages.html" -%}
</main>

{% if paginator %}
Expand Down
19 changes: 19 additions & 0 deletions templates/macros/list_posts.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,25 @@
{%- endif -%}
{%- endif -%}

{% if post.extra.local_image or post.extra.remote_image %}
<li class="post-thumbnail">
<a href="{{ post.permalink }}">
{% if post.extra.local_image %}
{% set meta = get_image_metadata(path=post.extra.local_image, allow_missing=true) %}
<img class="thumbnail-image"
alt="{{ post.extra.local_image }}"
src="{{ get_url(path=post.extra.local_image) }}"
{% if meta.width %}width="{{ meta.width }}"{% endif %}
{% if meta.height %}height="{{ meta.height }}"{% endif %}>
{% elif post.extra.remote_image %}
<img class="thumbnail-image"
alt="{{ post.extra.remote_image }}"
src="{{ post.extra.remote_image }}">
{% endif %}
</a>
</li>
{% endif %}

{% if post.draft %}
<li class="draft-label">{{ macros_translate::translate(key="draft", default="DRAFT", language_strings=language_strings) }}</li>
{% endif %}
Expand Down
1 change: 1 addition & 0 deletions templates/page.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
</pre></div>

{% set settings_to_test = [
"enable_cards_tag_filtering",
"footnote_backlinks",
"add_src_to_code_block",
"force_codeblock_ltr",
Expand Down
19 changes: 15 additions & 4 deletions templates/partials/cards_pages.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,24 @@

{% set target_url = page.extra.link_to | default(value=page.permalink) %}

<a rel="{{ rel_attributes }}" {{ blank_target }} href="{{ target_url }}" class="card">
<div>
<a rel="{{ rel_attributes }}"
{{ blank_target }}
href="{{ target_url }}"
class="card"
{% if page.taxonomies %}
data-tags="{% for tax_name, terms in page.taxonomies %}{% for term in terms | unique %}{{ term | lower }}{% if not loop.last %},{% endif %}{% endfor %}{% endfor %}"
{% endif %}>
{% if page.extra.local_image %}
{% set meta = get_image_metadata(path=page.extra.local_image, allow_missing=true) %}
<img class="card-image" alt="{{ page.extra.local_image }}" src="{{ get_url(path=page.extra.local_image) }}" {% if meta.width %}width="{{ meta.width }}" {% endif %} {% if meta.height %}height="{{ meta.height }}" {% endif %}>
<img class="card-image"
alt="{{ page.extra.local_image }}"
src="{{ get_url(path=page.extra.local_image) }}"
{% if meta.width %}width="{{ meta.width }}"{% endif %}
{% if meta.height %}height="{{ meta.height }}"{% endif %}>
{% elif page.extra.remote_image %}
<img class="card-image" alt="{{ page.extra.remote_image }}" src="{{ page.extra.remote_image }}">
<img class="card-image"
alt="{{ page.extra.remote_image }}"
src="{{ page.extra.remote_image }}">
{% else %}
<div class="card-image-placeholder"></div>
{% endif %}
Expand Down
35 changes: 35 additions & 0 deletions templates/partials/filter_card_tags.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{#- Collect all terms. -#}
{#- We don't use `get_taxonomy` so users aren't forced to use 'tags' -#}
{% set all_terms = [] %}
{% for page in show_pages %}
{% if page.taxonomies %}
{% for tax_name, terms in page.taxonomies %}
{% for term in terms %}
{% set_global all_terms = all_terms | concat(with=term) %}
{% endfor %}
{% endfor %}
{% endif %}
{% endfor %}

{#- Display unique terms -#}
{%- if all_terms -%}
<ul class="filter-controls" role="group" aria-label="{{ macros_translate::translate(key='project_filters', default='Project filters', language_strings=language_strings) }}">
<li class="taxonomy-item no-hover-padding">
<a id="all-projects-filter" class="no-hover-padding active"
href="{{ get_url(path="projects", lang=lang) }}"
data-filter="all">
{{- macros_translate::translate(key="all_projects", default="All projects", language_strings=language_strings) -}}
</a>
</li>
{% for term in all_terms | unique | sort %}
<li class="taxonomy-item no-hover-padding">
<a class="no-hover-padding"
href="{{ get_taxonomy_url(kind="tags", name=term) }}"
data-filter="{{ term | lower }}">{{ term }}</a>
</li>
{% endfor %}
</ul>
{% endif %}

{#- Load the script -#}
<script src="{{ get_url(path='js/filterCards.min.js', trailing_slash=false) | safe }}" async></script>
6 changes: 6 additions & 0 deletions theme.toml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ compact_tags = false
# Default: "name".
tag_sorting = "name"

# Show clickable tags above cards.html template (e.g. projects/) to filter the displayed items.
# Items need tags (or another taxonomy) in their front matter for this to work.
# Can be set at the section or config.toml level, following the hierarchy: section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy
# Default: true
enable_cards_tag_filtering = true

# Invert the order of the site title and page title in the browser tab.
# Example: true => "Blog • ~/tabi", false => "~/tabi • Blog"
invert_title_order = false
Expand Down

0 comments on commit 8b28514

Please sign in to comment.