update the theme using webcomponents

This commit is contained in:
Alexis Métaireau 2024-12-25 19:49:50 +01:00
parent 1273ac3c46
commit 9979ee9003
No known key found for this signature in database
GPG key ID: 1C21B876828E5FF2
4 changed files with 254 additions and 30 deletions

View file

@ -0,0 +1,171 @@
class FilteredArticles extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
const style = `
<style>
.filters {
margin-bottom: 1rem;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
}
.filters-label {
color: #666;
line-height: 30px;
}
.filter-btn {
background: none;
border: none;
border-bottom: 3px solid;
padding: 5px 10px;
margin: 0.2rem;
cursor: pointer;
line-height: 30px;
opacity: 0.6;
}
.filter-btn:hover {
opacity: 1;
}
.filter-btn.active {
opacity: 1;
}
.search-container {
display: flex;
align-items: center;
margin-left: auto;
}
.search-input {
padding: 5px 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin-left: 10px;
line-height: 20px;
}
::slotted(.items) {
padding-left: 0px !important;
}
::slotted(.item) {
display: none !important;
flex-direction: row !important;
padding-bottom: 0.5em !important;
padding-left: 1em !important;
border-left: 3px solid !important;
border-left-color: var(--item-color) !important;
line-height: 30px !important;
}
::slotted(.item.visible) {
display: flex !important;
}
::slotted(.item time) {
flex: 1 !important;
text-align: right !important;
color: #797878 !important;
padding-left: 1em !important;
}
@media screen and (max-width: 600px) {
.search-container {
width: 100%;
margin-left: 0;
}
.search-input {
width: 100%;
}
::slotted(.item time) {
display: none !important;
}
}
</style>
`;
this.shadowRoot.innerHTML = `
${style}
<div class="filters">
<span class="filters-label">Filter by:</span>
<div class="categories-container"></div>
<div class="search-container">
<span class="filters-label">Search:</span>
<input type="text" class="search-input" placeholder="Type to search...">
</div>
</div>
<slot></slot>
`;
this.currentCategory = 'all';
this.searchText = '';
}
connectedCallback() {
const slot = this.shadowRoot.querySelector('slot');
const searchInput = this.shadowRoot.querySelector('.search-input');
slot.addEventListener('slotchange', () => {
this.init(slot.assignedElements());
});
searchInput.addEventListener('input', (e) => {
this.searchText = e.target.value.toLowerCase();
const items = slot.assignedElements();
this.filterItems(items, this.currentCategory);
});
}
init(items) {
const categories = new Map();
items.forEach(item => {
const categoryClass = [...item.classList].find(cls => cls.startsWith('link-'));
if (categoryClass) {
const category = categoryClass.replace('link-', '');
const color = getComputedStyle(item).getPropertyValue('--item-color').trim();
categories.set(category, color);
}
});
const categoriesContainer = this.shadowRoot.querySelector('.categories-container');
categoriesContainer.innerHTML = ''; // Clear existing buttons
// "All" button with a neutral color
const allBtn = document.createElement('button');
allBtn.className = 'filter-btn active';
allBtn.textContent = 'All';
allBtn.dataset.category = 'all';
allBtn.style.borderBottomColor = '#666';
categoriesContainer.appendChild(allBtn);
// Category buttons with matching colors
categories.forEach((color, category) => {
const btn = document.createElement('button');
btn.className = 'filter-btn';
btn.textContent = category.charAt(0).toUpperCase() + category.slice(1);
btn.dataset.category = category;
btn.style.borderBottomColor = color;
categoriesContainer.appendChild(btn);
});
this.shadowRoot.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
this.currentCategory = btn.dataset.category;
this.filterItems(items, this.currentCategory);
});
});
this.filterItems(items, 'all');
}
filterItems(items, category) {
this.shadowRoot.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.category === category);
});
items.forEach(item => {
const matchesCategory = category === 'all' || item.classList.contains(`link-${category}`);
const matchesSearch = this.searchText === '' ||
item.textContent.toLowerCase().includes(this.searchText);
item.classList.toggle('visible', matchesCategory && matchesSearch);
});
}
}
customElements.define('filtered-articles', FilteredArticles);

View file

@ -0,0 +1,52 @@
class TranslatedText extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// Détection de la langue du navigateur
const userLang = navigator.language || navigator.userLanguage;
const isFrench = userLang.startsWith('fr');
const style = document.createElement('style');
style.textContent = `
.controls {
text-align: right;
margin-bottom: 10px;
}
.lang-toggle { display: none; }
.lang-label {
padding: 5px 10px;
cursor: pointer;
}
.content-section { display: none; }
#fr:checked ~ .content .fr-content,
#en:checked ~ .content .en-content { display: block; }
#fr:checked ~ .controls label[for="fr"],
#en:checked ~ .controls label[for="en"] {
display: none;
}
`;
const html = `
<input type="radio" id="fr" name="lang" class="lang-toggle" ${isFrench ? 'checked' : ''}>
<input type="radio" id="en" name="lang" class="lang-toggle" ${!isFrench ? 'checked' : ''}>
<div class="controls">
<label for="fr" class="lang-label">(voir le contenu en Français)</label>
<label for="en" class="lang-label">(see English content)</label>
</div>
<div class="content">
<div class="content-section fr-content">
<slot name="fr"></slot>
</div>
<div class="content-section en-content">
<slot name="en"></slot>
</div>
</div>
`;
this.shadowRoot.innerHTML = `${style.outerHTML}${html}`;
}
}
customElements.define('translated-text', TranslatedText);

View file

@ -27,6 +27,8 @@
title="{{ SITENAME }} RSS Feed"
/>
{% endif %} {% block extra_head %}{% endblock %}
<script src="{{ SITEURL }}/theme/js/translated-text.js"></script>
<script src="{{ SITEURL }}/theme/js/filtered-list.js"></script>
</head>
<body>
<div id="content">

View file

@ -1,42 +1,41 @@
{% extends "base.html" %}
{% block content %}
<header>
<figure>
<h1 class="post-title"><del>not</del> my ideas</h1>
</figure>
</header>
<article>
<p>👋 <strong>Welcome here</strong>, I'm Alexis,</p>
<p>I am a software engineer interested by digital freedom and privacy.</p>
<p>I'm also a fellow human, exploring how to participate to healthy collectives via listening and conflict-resolution techniques.</p>
<p>I mostly publish here in French, but some stuff is in English. You can find here <a class="link-journal" href="/journal">journal entries (fr)</a>,
<a class="link-lectures" href="/lectures">reading notes (fr)</a> and some stuff related to <a class="link-code" href="/code">software engineering (en)</a>. Also, some <a class="link-ecriture" href="/ecriture">writing (fr)</a></p>
<hr>
<p>👋 <strong>Bienvenue par ici</strong>, je suis Alexis, un développeur intéressé par les
dynamiques collectives, les libertés numériques et la facilitation.</p>
<p>Vous retrouverez sur ce site quelques
<a href="/journal" class="link-journal">billets de blog</a>, des <a href="/lectures" class="link-lectures">notes de lectures</a>, <a class="link-code" href="/code">des bouts
de code</a> et <a href="/ecriture" class="link-textes">des textes</a> que je veux garder quelque part. Bonne lecture !</p>
<p>Pour me contacter, envoyez-moi un email sur <code>alexis@</code> ce domaine (en enlevant <code>blog.</code>).</p>
<translated-text>
<div slot="fr">
<p>👋 <strong>Bienvenue par ici</strong>, je suis Alexis, un développeur intéressé par les
dynamiques collectives et les libertés numériques.</p>
<p>J'aime aussi explorer comment participer à des pratiques collectives, via la résolution de conflit et l'écoute.</p>
<p>Vous retrouverez sur ce site quelques
<a href="/journal" class="link-journal">billets de blog</a>, des <a href="/lectures" class="link-lectures">notes de lectures</a>, <a class="link-code" href="/code">des bouts
de code</a> et <a href="/ecriture" class="link-textes">des textes</a> que je veux garder quelque part. Bonne lecture !</p>
<p>Pour me contacter, envoyez-moi un email sur <code>alexis@</code> ce domaine (en enlevant <code>blog.</code>).</p>
</div>
<div slot="en">
<p>👋 <strong>Welcome here</strong>, I'm Alexis Métaireau, a software engineer interested by digital freedom and privacy.</p>
<p>I'm also a fellow human, exploring how to participate to healthy collectives via listening and conflict-resolution techniques.</p>
<p>I mostly publish here in French, but some stuff is in English. You can find here <a class="link-journal" href="/journal">journal entries (fr)</a>,
<a class="link-lectures" href="/lectures">reading notes (fr)</a> and some stuff related to <a class="link-code" href="/code">software engineering (en)</a>. Also, some <a class="link-ecriture" href="/ecriture">writing (fr)</a></p>
<p>To contact me, send me an email on <code>alexis@</code> this domain (without <code>blog.</code>).</p>
</div>
</translated-text>
</article>
{% if articles %}
<hr />
<div id="articles">
<h2>Les derniers articles / Last articles</h2>
<ul class="items">
{% set articles_in_categories = articles | rejectattr('category', 'in', HOMEPAGE_EXCLUDED_CATEGORIES) | list %}
{% set limited_articles = articles_in_categories[:20] %}
{% for article in limited_articles %}
<filtered-articles>
{% set articles_in_categories = articles | rejectattr('category', 'in', HOMEPAGE_EXCLUDED_CATEGORIES) | list %}
{% set limited_articles = articles_in_categories[:20] %}
{% for article in articles_in_categories %}
<li class="item link-{{ article.category }}">
{% set category_description = CATEGORIES_DESCRIPTION.get(article.category)[0] %}
<a href="{{ SITEURL }}/{{ article.url }}" class="page-title">{{ category_icon }} {{ category_description }}: {{ article.title.replace(category_description, "") }}</a>
<time datetime="{{ article.date.isoformat() }}">{{ article.date.strftime("%Y-%m-%d") }}</time>
{% set category_description = CATEGORIES_DESCRIPTION.get(article.category)[0] or "" %}
<a href="{{ SITEURL }}/{{ article.url }}" class="page-title">
{{ category_icon }} {{ category_description }}: {{ article.title.replace(category_description, "") }}
</a>
<time datetime="{{ article.date.isoformat() }}">{{ article.date.strftime("%Y-%m-%d") }}</time>
</li>
{% endfor %}
</ul>
<a href="archives.html">Archives</a>
{% endfor %}
</filtered-articles>
</div>
{% endif %}
{% endblock content %}