Merge pull request 'v1.3.2 - Hotfix -> Scroll to tup button + tag selection move to top' (#8) from comments into main

Reviewed-on: #8
This commit is contained in:
2025-08-18 13:05:28 +02:00
7 changed files with 54 additions and 24 deletions

View File

@ -52,7 +52,7 @@ start_server() {
if [ $# -eq 0 ]; then if [ $# -eq 0 ]; then
echo -e "${CYAN}╭───────────────────────────────────────────╮${NC}" echo -e "${CYAN}╭───────────────────────────────────────────╮${NC}"
echo -e "${CYAN}${NC} Lum${CYAN}eex${NC} - Version 1.3.1${NC} ${CYAN}${NC}" echo -e "${CYAN}${NC} Lum${CYAN}eex${NC} - Version 1.3.2${NC} ${CYAN}${NC}"
echo -e "${CYAN}├───────────────────────────────────────────┤${NC}" echo -e "${CYAN}├───────────────────────────────────────────┤${NC}"
echo -e "${CYAN}${NC} Source: https://git.djeex.fr/Djeex/lumeex ${CYAN}${NC}" echo -e "${CYAN}${NC} Source: https://git.djeex.fr/Djeex/lumeex ${CYAN}${NC}"
echo -e "${CYAN}${NC} Mirror: https://github.com/Djeex/lumeex ${CYAN}${NC}" echo -e "${CYAN}${NC} Mirror: https://github.com/Djeex/lumeex ${CYAN}${NC}"

View File

@ -69,7 +69,8 @@ const setupTagFilter = () => {
const allSections = document.querySelectorAll('.section[data-tags]'); const allSections = document.querySelectorAll('.section[data-tags]');
const allTags = document.querySelectorAll('.tag'); const allTags = document.querySelectorAll('.tag');
let activeTags = []; let activeTags = [];
let lastClickedTag = null; // mémorise le dernier tag cliqué let lastClickedTag = null; // remembers the last clicked tag
let lastClickedSection = null; // remembers the last clicked section (photo)
const applyFilter = () => { const applyFilter = () => {
let filteredSections = []; let filteredSections = [];
@ -81,30 +82,41 @@ const setupTagFilter = () => {
section.style.display = hasAllTags ? '' : 'none'; section.style.display = hasAllTags ? '' : 'none';
if (hasAllTags) { if (hasAllTags) {
filteredSections.push(section); if (lastClickedSection === section) {
if (lastClickedTag && sectionTags.includes(lastClickedTag) && !matchingSection) {
matchingSection = section; matchingSection = section;
} else {
filteredSections.push(section);
} }
} }
}); });
// Réorganise : la photo correspondante au dernier tag cliqué en premier // Remove all filtered sections from DOM before reordering
if (matchingSection && galleryContainer.contains(matchingSection)) { if (galleryContainer) {
[matchingSection, ...filteredSections].forEach(section => {
if (section && galleryContainer.contains(section)) {
galleryContainer.removeChild(section);
}
});
if (matchingSection) {
galleryContainer.prepend(matchingSection); galleryContainer.prepend(matchingSection);
} }
filteredSections.forEach(section => {
galleryContainer.appendChild(section);
});
}
// Met à jour le style des tags // Update tag styles
allTags.forEach((tagEl) => { allTags.forEach((tagEl) => {
const tagText = tagEl.textContent.replace('#', '').toLowerCase(); const tagText = tagEl.textContent.replace('#', '').toLowerCase();
tagEl.classList.toggle('active', activeTags.includes(tagText)); tagEl.classList.toggle('active', activeTags.includes(tagText));
}); });
// Met à jour l'URL // Update the URL
const base = window.location.pathname; const base = window.location.pathname;
const query = activeTags.length > 0 ? `?tag=${activeTags.join(',')}` : ''; const query = activeTags.length > 0 ? `?tag=${activeTags.join(',')}` : '';
window.history.pushState({}, '', base + query); window.history.pushState({}, '', base + query);
// Scroll jusqu'à la galerie // Scroll to the gallery
if (galleryContainer) { if (galleryContainer) {
galleryContainer.scrollIntoView({ behavior: 'smooth', block: 'start' }); galleryContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
} }
@ -113,7 +125,8 @@ const setupTagFilter = () => {
allTags.forEach((tagEl) => { allTags.forEach((tagEl) => {
tagEl.addEventListener('click', () => { tagEl.addEventListener('click', () => {
const tagText = tagEl.textContent.replace('#', '').toLowerCase(); const tagText = tagEl.textContent.replace('#', '').toLowerCase();
lastClickedTag = tagText; // mémorise le dernier tag cliqué lastClickedTag = tagText; // remembers the last clicked tag
lastClickedSection = tagEl.closest('.section'); // remembers the last clicked section
if (activeTags.includes(tagText)) { if (activeTags.includes(tagText)) {
activeTags = activeTags.filter((t) => t !== tagText); activeTags = activeTags.filter((t) => t !== tagText);
@ -130,6 +143,7 @@ const setupTagFilter = () => {
if (urlTags) { if (urlTags) {
activeTags = urlTags.split(',').map((t) => t.toLowerCase()); activeTags = urlTags.split(',').map((t) => t.toLowerCase());
lastClickedTag = activeTags[activeTags.length - 1] || null; lastClickedTag = activeTags[activeTags.length - 1] || null;
lastClickedSection = null; // No section selected from URL
applyFilter(); applyFilter();
} }
}); });
@ -141,12 +155,14 @@ const disableRightClickAndDrag = () => {
document.addEventListener('dragstart', (e) => e.preventDefault()); document.addEventListener('dragstart', (e) => e.preventDefault());
}; };
// Scroll to top button // Scroll-to-top button functionality
const setupScrollToTopButton = () => { const setupScrollToTopButton = () => {
const scrollToTopButton = document.querySelector('.scroll-to-top'); const scrollBtn = document.getElementById("scrollToTop");
if (!scrollToTopButton) return; window.addEventListener("scroll", () => {
scrollToTopButton.addEventListener('click', () => { scrollBtn.style.display = window.scrollY > 300 ? "block" : "none";
window.scrollTo({ top: 0, behavior: 'smooth' }); });
scrollBtn.addEventListener("click", () => {
window.scrollTo({ top: 0, behavior: "smooth" });
}); });
}; };

View File

@ -3,6 +3,7 @@ from pathlib import Path
from shutil import copyfile from shutil import copyfile
def generate_css_variables(colors_dict, output_path): def generate_css_variables(colors_dict, output_path):
"""Generate css variables for theme colors"""
css_lines = [":root {"] css_lines = [":root {"]
for key, value in colors_dict.items(): for key, value in colors_dict.items():
css_lines.append(f" --color-{key.replace('_', '-')}: {value};") css_lines.append(f" --color-{key.replace('_', '-')}: {value};")
@ -13,6 +14,7 @@ def generate_css_variables(colors_dict, output_path):
logging.info(f"[✓] CSS variables written to {output_path}") logging.info(f"[✓] CSS variables written to {output_path}")
def generate_fonts_css(fonts_dir, output_path, fonts_cfg=None): def generate_fonts_css(fonts_dir, output_path, fonts_cfg=None):
"""Generate css variables fonts"""
font_files = list(fonts_dir.glob("*")) font_files = list(fonts_dir.glob("*"))
font_faces = {} font_faces = {}
preload_links = [] preload_links = []
@ -57,6 +59,7 @@ def generate_fonts_css(fonts_dir, output_path, fonts_cfg=None):
return preload_links return preload_links
def generate_google_fonts_link(fonts): def generate_google_fonts_link(fonts):
"""Generate src link for Google fonts"""
if not fonts: if not fonts:
return "" return ""
families = [] families = []

View File

@ -10,6 +10,7 @@ GALLERY_DIR = Path("config/photos/gallery")
HERO_DIR = Path("config/photos/hero") HERO_DIR = Path("config/photos/hero")
def load_yaml(path): def load_yaml(path):
"""Load gallery config .yaml file"""
print(f"[→] Loading {path}...") print(f"[→] Loading {path}...")
if not os.path.exists(path): if not os.path.exists(path):
print(f"[✗] File not found: {path}") print(f"[✗] File not found: {path}")
@ -21,11 +22,13 @@ def load_yaml(path):
return data return data
def save_yaml(data, path): def save_yaml(data, path):
"""Save modified gallery config .yaml file"""
with open(path, "w", encoding="utf-8") as f: with open(path, "w", encoding="utf-8") as f:
yaml.dump(data, f, sort_keys=False, allow_unicode=True) yaml.dump(data, f, sort_keys=False, allow_unicode=True)
print(f"[✓] Saved updated YAML to {path}") print(f"[✓] Saved updated YAML to {path}")
def get_all_image_paths(directory): def get_all_image_paths(directory):
"""Get the path to record for builded site"""
return sorted([ return sorted([
str(p.relative_to(directory.parent)).replace("\\", "/") str(p.relative_to(directory.parent)).replace("\\", "/")
for p in directory.rglob("*") for p in directory.rglob("*")
@ -33,6 +36,7 @@ def get_all_image_paths(directory):
]) ])
def update_gallery(): def update_gallery():
"""Update the gallery photo list"""
print("\n=== Updating gallery.yaml (gallery section) ===") print("\n=== Updating gallery.yaml (gallery section) ===")
gallery = load_yaml(GALLERY_YAML) gallery = load_yaml(GALLERY_YAML)
@ -71,6 +75,7 @@ def update_gallery():
print("[✓] No changes to gallery.yaml (gallery)") print("[✓] No changes to gallery.yaml (gallery)")
def update_hero(): def update_hero():
"""Update the hero photo list"""
print("\n=== Updating gallery.yaml (hero section) ===") print("\n=== Updating gallery.yaml (hero section) ===")
gallery = load_yaml(GALLERY_YAML) gallery = load_yaml(GALLERY_YAML)

View File

@ -3,6 +3,7 @@ import logging
from pathlib import Path from pathlib import Path
def render_template(template_path, context): def render_template(template_path, context):
"""Render html templates"""
with open(template_path, encoding="utf-8") as f: with open(template_path, encoding="utf-8") as f:
content = f.read() content = f.read()
for key, value in context.items(): for key, value in context.items():
@ -11,6 +12,7 @@ def render_template(template_path, context):
return content return content
def render_gallery_images(images): def render_gallery_images(images):
"""Render the photo gallery"""
html = "" html = ""
for img in images: for img in images:
tags = " ".join(img.get("tags", [])) tags = " ".join(img.get("tags", []))
@ -24,6 +26,7 @@ def render_gallery_images(images):
return html return html
def generate_gallery_json_from_images(images, output_dir): def generate_gallery_json_from_images(images, output_dir):
"""Generte the hero carrousel photo list"""
try: try:
img_list = [img["src"] for img in images] img_list = [img["src"] for img in images]
output_path = output_dir / "data" / "gallery.json" output_path = output_dir / "data" / "gallery.json"
@ -35,6 +38,7 @@ def generate_gallery_json_from_images(images, output_dir):
logging.error(f"[✗] Error generating gallery JSON: {e}") logging.error(f"[✗] Error generating gallery JSON: {e}")
def generate_robots_txt(canonical_url, allowed_paths, output_dir): def generate_robots_txt(canonical_url, allowed_paths, output_dir):
"""Generate the robot.txt"""
robots_lines = ["User-agent: *"] robots_lines = ["User-agent: *"]
# Block everything by default # Block everything by default
@ -62,6 +66,7 @@ def generate_robots_txt(canonical_url, allowed_paths, output_dir):
logging.error(f"[✗] Failed to write robots.txt: {e}") logging.error(f"[✗] Failed to write robots.txt: {e}")
def generate_sitemap_xml(canonical_url, allowed_paths, output_dir): def generate_sitemap_xml(canonical_url, allowed_paths, output_dir):
"""Generate the sitemap"""
urlset_start = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' urlset_start = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
urlset_end = '</urlset>\n' urlset_end = '</urlset>\n'
urls = "" urls = ""

View File

@ -23,7 +23,7 @@ SITE_FILE = SRC_DIR / "config/site.yaml"
THEMES_DIR = SRC_DIR / "config/themes" THEMES_DIR = SRC_DIR / "config/themes"
def build(): def build():
build_version = "v1.3.1" build_version = "v1.3.2"
logging.info("\n") logging.info("\n")
logging.info("=" * 24) logging.info("=" * 24)
logging.info(f"🚀 Lumeex builder {build_version}") logging.info(f"🚀 Lumeex builder {build_version}")

View File

@ -4,6 +4,7 @@ from pathlib import Path
from shutil import copytree, rmtree, copyfile from shutil import copytree, rmtree, copyfile
def load_yaml(path): def load_yaml(path):
"""Load gallery and site .yaml conf"""
if not path.exists(): if not path.exists():
logging.warning(f"[!] YAML file not found: {path}") logging.warning(f"[!] YAML file not found: {path}")
return {} return {}
@ -11,6 +12,7 @@ def load_yaml(path):
return yaml.safe_load(f) return yaml.safe_load(f)
def load_theme_config(theme_name, themes_dir): def load_theme_config(theme_name, themes_dir):
"""Load theme.yaml"""
theme_dir = themes_dir / theme_name theme_dir = themes_dir / theme_name
theme_config_path = theme_dir / "theme.yaml" theme_config_path = theme_dir / "theme.yaml"
if not theme_config_path.exists(): if not theme_config_path.exists():
@ -20,26 +22,25 @@ def load_theme_config(theme_name, themes_dir):
return theme_vars, theme_dir return theme_vars, theme_dir
def clear_dir(path: Path): def clear_dir(path: Path):
"""Clear the output dir"""
if not path.exists(): if not path.exists():
path.mkdir(parents=True) path.mkdir(parents=True)
return return
# Remove all files and subdirectories inside path, but not path itself
for child in path.iterdir(): for child in path.iterdir():
if child.is_file() or child.is_symlink(): if child.is_file() or child.is_symlink():
child.unlink() # delete file or symlink child.unlink()
elif child.is_dir(): elif child.is_dir():
rmtree(child) # delete directory and contents rmtree(child)
# Then replace your ensure_dir with this:
def ensure_dir(path: Path): def ensure_dir(path: Path):
"""Create the output dir if it does not exist"""
if not path.exists(): if not path.exists():
path.mkdir(parents=True) path.mkdir(parents=True)
else: else:
clear_dir(path) clear_dir(path)
def copy_assets(js_dir, style_dir, build_dir): def copy_assets(js_dir, style_dir, build_dir):
"""Copy public assets to output dir"""
for folder in [js_dir, style_dir]: for folder in [js_dir, style_dir]:
if folder.exists(): if folder.exists():
dest = build_dir / folder.name dest = build_dir / folder.name