mirror of
https://github.com/almet/notmyidea.git
synced 2025-04-28 11:32:39 +02:00
update the theme using webcomponents
This commit is contained in:
parent
1273ac3c46
commit
9979ee9003
4 changed files with 254 additions and 30 deletions
171
mnmlist/static/js/filtered-list.js
Normal file
171
mnmlist/static/js/filtered-list.js
Normal 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);
|
52
mnmlist/static/js/translated-text.js
Normal file
52
mnmlist/static/js/translated-text.js
Normal 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);
|
|
@ -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">
|
||||
|
|
|
@ -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 %}
|
||||
|
|
Loading…
Reference in a new issue