From 7f2e119188b3d46ae46cae51a71b078284cd0fc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=ACnei?= Date: Sat, 7 Jun 2025 21:37:21 +0000 Subject: [PATCH] After prettier --- quartz/components/scripts/carousel.inline.ts | 397 +++++++++--------- quartz/components/styles/carousel.inline.scss | 61 ++- quartz/plugins/transformers/carousel.ts | 93 ++-- quartz/plugins/transformers/index.ts | 2 +- 4 files changed, 290 insertions(+), 263 deletions(-) diff --git a/quartz/components/scripts/carousel.inline.ts b/quartz/components/scripts/carousel.inline.ts index 935005240..4f881ccdd 100644 --- a/quartz/components/scripts/carousel.inline.ts +++ b/quartz/components/scripts/carousel.inline.ts @@ -1,69 +1,74 @@ interface CarouselInstance { - currentIndex: number; - goToSlide: (index: number) => void; - destroy: () => void; + currentIndex: number + goToSlide: (index: number) => void + destroy: () => void } // Cache for initialized carousels to avoid re-processing -const initializedCarousels = new WeakSet(); +const initializedCarousels = new WeakSet() + +document.addEventListener("DOMContentLoaded", () => { + const contentArea = + document.querySelector("article") || + document.querySelector("#quartz-body .center") || + document.body + + initAllCarousels(contentArea) + setupCarouselObserver(contentArea) -document.addEventListener('DOMContentLoaded', () => { - const contentArea = document.querySelector('article') - || document.querySelector('#quartz-body .center') - || document.body; - - initAllCarousels(contentArea); - setupCarouselObserver(contentArea); - // Make initCarousel available globally - (window as any).initCarousel = initCarousel; -}); + ;(window as any).initCarousel = initCarousel +}) // Initializes all carousels within the specified container function initAllCarousels(container: Element): void { - const carousels = container.querySelectorAll('.quartz-carousel[data-needs-init="true"]'); - carousels.forEach(initCarousel); + const carousels = container.querySelectorAll( + '.quartz-carousel[data-needs-init="true"]', + ) + carousels.forEach(initCarousel) } // Setup a MutationObserver to watch for new carousels being added to the DOM function setupCarouselObserver(contentArea: Element): void { - let debounceTimer: number | null = null; + let debounceTimer: number | null = null const observer = new MutationObserver((mutations) => { - const hasNewCarousels = mutations.some((mutation) => + const hasNewCarousels = mutations.some((mutation) => Array.from(mutation.addedNodes).some((node) => { - if (node.nodeType !== Node.ELEMENT_NODE) return false; - - const element = node as HTMLElement; - + if (node.nodeType !== Node.ELEMENT_NODE) return false + + const element = node as HTMLElement + // Check if the node itself is a carousel - if (element.classList?.contains('quartz-carousel') && - element.getAttribute('data-needs-init') === 'true') { - return true; + if ( + element.classList?.contains("quartz-carousel") && + element.getAttribute("data-needs-init") === "true" + ) { + return true } - + // Check for nested carousels - return element.querySelectorAll?.('.quartz-carousel[data-needs-init="true"]').length > 0; - }) - ); - + return element.querySelectorAll?.('.quartz-carousel[data-needs-init="true"]').length > 0 + }), + ) + if (hasNewCarousels) { // Debounce to avoid multiple rapid initializations - if (debounceTimer) clearTimeout(debounceTimer); - debounceTimer = window.setTimeout(() => initAllCarousels(contentArea), 50); + if (debounceTimer) clearTimeout(debounceTimer) + debounceTimer = window.setTimeout(() => initAllCarousels(contentArea), 50) } - }); + }) - observer.observe(contentArea, { - childList: true, - subtree: true - }); + observer.observe(contentArea, { + childList: true, + subtree: true, + }) } // Create a modal for displaying images in full screen function createImageModal(): HTMLElement { - const modal = document.createElement('div'); - modal.className = 'carousel-image-modal'; + const modal = document.createElement("div") + modal.className = "carousel-image-modal" modal.innerHTML = ` - `; - - document.body.appendChild(modal); - return modal; + ` + + document.body.appendChild(modal) + return modal } // Show the image in a modal when clicked function showImageModal(img: HTMLImageElement): void { - let modal = document.querySelector('.carousel-image-modal') as HTMLElement; - + let modal = document.querySelector(".carousel-image-modal") as HTMLElement + if (!modal) { - modal = createImageModal(); + modal = createImageModal() } - - const modalImg = modal.querySelector('.carousel-modal-image') as HTMLImageElement; - const closeBtn = modal.querySelector('.carousel-modal-close') as HTMLButtonElement; - const overlay = modal.querySelector('.carousel-modal-overlay') as HTMLElement; - + + const modalImg = modal.querySelector(".carousel-modal-image") as HTMLImageElement + const closeBtn = modal.querySelector(".carousel-modal-close") as HTMLButtonElement + const overlay = modal.querySelector(".carousel-modal-overlay") as HTMLElement + // Set image source and alt - modalImg.src = img.src; - modalImg.alt = img.alt; - + modalImg.src = img.src + modalImg.alt = img.alt + // Show modal - modal.style.display = 'flex'; - document.body.style.overflow = 'hidden'; - + modal.style.display = "flex" + document.body.style.overflow = "hidden" + // Close handlers const closeModal = () => { - modal.style.display = 'none'; - document.body.style.overflow = ''; - }; - + modal.style.display = "none" + document.body.style.overflow = "" + } + // Remove existing listeners to avoid duplicates - closeBtn.onclick = null; - overlay.onclick = null; - - closeBtn.onclick = closeModal; + closeBtn.onclick = null + overlay.onclick = null + + closeBtn.onclick = closeModal overlay.onclick = (e) => { if (e.target === overlay) { - closeModal(); + closeModal() } - }; - + } + // Keyboard handler (Escape key) const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - closeModal(); - document.removeEventListener('keydown', handleKeyDown); + if (e.key === "Escape") { + closeModal() + document.removeEventListener("keydown", handleKeyDown) } - }; - - document.addEventListener('keydown', handleKeyDown); + } + + document.addEventListener("keydown", handleKeyDown) } // Initialize a carousel and return an instance function initCarousel(carousel: HTMLElement): CarouselInstance | null { // Prevent re-initialization using WeakSet - if (initializedCarousels.has(carousel) || carousel.getAttribute('data-needs-init') !== 'true') { - return null; + if (initializedCarousels.has(carousel) || carousel.getAttribute("data-needs-init") !== "true") { + return null } - + // Mark as initialized - initializedCarousels.add(carousel); - carousel.removeAttribute('data-needs-init'); - - const slidesContainer = carousel.querySelector('.quartz-carousel-slides'); + initializedCarousels.add(carousel) + carousel.removeAttribute("data-needs-init") + + const slidesContainer = carousel.querySelector(".quartz-carousel-slides") if (!slidesContainer) { - return null; + return null } - - const slides = slidesContainer.querySelectorAll('.quartz-carousel-slide'); - const dotsContainer = carousel.querySelector('.quartz-carousel-dots'); - const prevButton = carousel.querySelector('.quartz-carousel-prev'); - const nextButton = carousel.querySelector('.quartz-carousel-next'); - - let currentIndex = 0; - + + const slides = slidesContainer.querySelectorAll(".quartz-carousel-slide") + const dotsContainer = carousel.querySelector(".quartz-carousel-dots") + const prevButton = carousel.querySelector(".quartz-carousel-prev") + const nextButton = carousel.querySelector(".quartz-carousel-next") + + let currentIndex = 0 + // Early return for single slide if (slides.length <= 1) { - hideNavigationElements(); - setupImageClickHandlers(); - return createCarouselInstance(); + hideNavigationElements() + setupImageClickHandlers() + return createCarouselInstance() } - - setupDots(); - setupNavigation(); - setupKeyboardNavigation(); - setupTouchNavigation(); - setupImageClickHandlers(); - + + setupDots() + setupNavigation() + setupKeyboardNavigation() + setupTouchNavigation() + setupImageClickHandlers() + // Initialize first slide - goToSlide(0); - + goToSlide(0) + function hideNavigationElements(): void { - prevButton?.style.setProperty('display', 'none'); - nextButton?.style.setProperty('display', 'none'); - dotsContainer?.style.setProperty('display', 'none'); + prevButton?.style.setProperty("display", "none") + nextButton?.style.setProperty("display", "none") + dotsContainer?.style.setProperty("display", "none") } - + function setupImageClickHandlers(): void { - slides.forEach(slide => { - const img = slide.querySelector('img'); + slides.forEach((slide) => { + const img = slide.querySelector("img") if (img) { - img.style.cursor = 'pointer'; - img.addEventListener('click', (e) => { - e.stopPropagation(); - showImageModal(img); - }, { passive: true }); + img.style.cursor = "pointer" + img.addEventListener( + "click", + (e) => { + e.stopPropagation() + showImageModal(img) + }, + { passive: true }, + ) } - }); + }) } - + function setupDots(): void { - if (!dotsContainer) return; - + if (!dotsContainer) return + // Use DocumentFragment for better performance - const fragment = document.createDocumentFragment(); - + const fragment = document.createDocumentFragment() + slides.forEach((_, index) => { - const dot = document.createElement('span'); - dot.className = index === 0 ? 'dot active' : 'dot'; - dot.addEventListener('click', () => goToSlide(index), { passive: true }); - fragment.appendChild(dot); - }); - - dotsContainer.innerHTML = ''; - dotsContainer.appendChild(fragment); + const dot = document.createElement("span") + dot.className = index === 0 ? "dot active" : "dot" + dot.addEventListener("click", () => goToSlide(index), { passive: true }) + fragment.appendChild(dot) + }) + + dotsContainer.innerHTML = "" + dotsContainer.appendChild(fragment) } - + function setupNavigation(): void { const handlePrevClick = (e: Event): void => { - e.preventDefault(); - goToSlide(currentIndex - 1); - }; - + e.preventDefault() + goToSlide(currentIndex - 1) + } + const handleNextClick = (e: Event): void => { - e.preventDefault(); - goToSlide(currentIndex + 1); - }; - - prevButton?.addEventListener('click', handlePrevClick, { passive: false }); - nextButton?.addEventListener('click', handleNextClick, { passive: false }); + e.preventDefault() + goToSlide(currentIndex + 1) + } + + prevButton?.addEventListener("click", handlePrevClick, { passive: false }) + nextButton?.addEventListener("click", handleNextClick, { passive: false }) } - + // Setup keyboard navigation (Arrow keys) function setupKeyboardNavigation(): void { - carousel.setAttribute('tabindex', '0'); - carousel.addEventListener('keydown', (e: KeyboardEvent) => { - switch (e.key) { - case 'ArrowLeft': - e.preventDefault(); - goToSlide(currentIndex - 1); - break; - case 'ArrowRight': - e.preventDefault(); - goToSlide(currentIndex + 1); - break; - } - }, { passive: false }); + carousel.setAttribute("tabindex", "0") + carousel.addEventListener( + "keydown", + (e: KeyboardEvent) => { + switch (e.key) { + case "ArrowLeft": + e.preventDefault() + goToSlide(currentIndex - 1) + break + case "ArrowRight": + e.preventDefault() + goToSlide(currentIndex + 1) + break + } + }, + { passive: false }, + ) } - + // Setup touch navigation (swipe gestures) function setupTouchNavigation(): void { - let touchStartX = 0; - let touchEndX = 0; - - carousel.addEventListener('touchstart', (e: TouchEvent) => { - touchStartX = e.changedTouches[0].screenX; - }, { passive: true }); - - carousel.addEventListener('touchend', (e: TouchEvent) => { - touchEndX = e.changedTouches[0].screenX; - handleSwipe(); - }, { passive: true }); - + let touchStartX = 0 + let touchEndX = 0 + + carousel.addEventListener( + "touchstart", + (e: TouchEvent) => { + touchStartX = e.changedTouches[0].screenX + }, + { passive: true }, + ) + + carousel.addEventListener( + "touchend", + (e: TouchEvent) => { + touchEndX = e.changedTouches[0].screenX + handleSwipe() + }, + { passive: true }, + ) + function handleSwipe(): void { - const minSwipeDistance = 50; - const swipeDistance = touchEndX - touchStartX; - - if (Math.abs(swipeDistance) < minSwipeDistance) return; - + const minSwipeDistance = 50 + const swipeDistance = touchEndX - touchStartX + + if (Math.abs(swipeDistance) < minSwipeDistance) return + if (swipeDistance < 0) { - goToSlide(currentIndex + 1); // Swipe left -> next + goToSlide(currentIndex + 1) // Swipe left -> next } else { - goToSlide(currentIndex - 1); // Swipe right -> prev + goToSlide(currentIndex - 1) // Swipe right -> prev } } } - + // Function to go to a specific slide function goToSlide(index: number): void { // Handle wrapping - currentIndex = ((index % slides.length) + slides.length) % slides.length; - + currentIndex = ((index % slides.length) + slides.length) % slides.length + // Use transform for better performance if (slidesContainer) { - slidesContainer.style.transform = `translateX(-${currentIndex * 100}%)`; + slidesContainer.style.transform = `translateX(-${currentIndex * 100}%)` } - + // Update dots efficiently if (dotsContainer) { - const dots = dotsContainer.querySelectorAll('.dot'); + const dots = dotsContainer.querySelectorAll(".dot") dots.forEach((dot, i) => { - dot.classList.toggle('active', i === currentIndex); - }); + dot.classList.toggle("active", i === currentIndex) + }) } } - + // Create and return the carousel instance that references the carousel element function createCarouselInstance(): CarouselInstance { return { currentIndex, goToSlide, destroy: () => { - initializedCarousels.delete(carousel); - carousel.setAttribute('data-needs-init', 'true'); - } - }; + initializedCarousels.delete(carousel) + carousel.setAttribute("data-needs-init", "true") + }, + } } - - return createCarouselInstance(); -} \ No newline at end of file + + return createCarouselInstance() +} diff --git a/quartz/components/styles/carousel.inline.scss b/quartz/components/styles/carousel.inline.scss index 64061f9e9..04f6274d7 100644 --- a/quartz/components/styles/carousel.inline.scss +++ b/quartz/components/styles/carousel.inline.scss @@ -11,36 +11,36 @@ article .quartz-carousel { overflow: hidden; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - + .quartz-carousel-slides { display: flex; transition: transform 0.5s ease-in-out; - + .quartz-carousel-slide { flex: 0 0 100%; display: flex; justify-content: center; align-items: center; - + img { max-width: 100%; max-height: 400px; object-fit: contain; display: block; transition: opacity 0.2s ease; - + &:hover { opacity: 0.9; } } } } - + .quartz-carousel-dots { text-align: center; margin-top: 1rem; padding: 0.5rem 0; - + .dot { width: 10px; height: 10px; @@ -50,13 +50,13 @@ article .quartz-carousel { display: inline-block; transition: background-color 0.3s ease; cursor: pointer; - + &.active { background-color: #555; } } } - + .quartz-carousel-prev, .quartz-carousel-next { position: absolute; @@ -72,22 +72,22 @@ article .quartz-carousel { align-items: center; justify-content: center; transition: background-color 0.3s ease; - + svg { width: 24px; height: 24px; fill: $carousel-control-color; } - + &:hover { background-color: $carousel-control-hover-bg; } } - + .quartz-carousel-prev { left: 10px; } - + .quartz-carousel-next { right: 10px; } @@ -102,7 +102,7 @@ article .quartz-carousel { width: 100%; height: 100%; z-index: 9999; - + .carousel-modal-overlay { position: absolute; top: 0; @@ -116,7 +116,7 @@ article .quartz-carousel { padding: 2rem; box-sizing: border-box; } - + .carousel-modal-content { position: relative; max-width: 90vw; @@ -125,7 +125,7 @@ article .quartz-carousel { justify-content: center; align-items: center; } - + .carousel-modal-image { max-width: 100%; max-height: 100%; @@ -133,7 +133,7 @@ article .quartz-carousel { border-radius: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); } - + .carousel-modal-close { position: absolute; top: -50px; @@ -149,13 +149,13 @@ article .quartz-carousel { justify-content: center; transition: background-color 0.3s ease; z-index: 10001; - + svg { width: 24px; height: 24px; fill: #333; } - + &:hover { background-color: rgba(255, 255, 255, 1); } @@ -168,38 +168,37 @@ html[saved-theme="dark"] .carousel-image-modal { .quartz-carousel-prev, .quartz-carousel-next { background-color: rgba(50, 50, 50, 0.8); - + svg { fill: #eee; } - + &:hover { background-color: rgba(70, 70, 70, 0.95); } } - + .dot { background-color: #555; - + &.active { background-color: #ddd; } } - + .carousel-modal-close { background-color: rgba(50, 50, 50, 0.9); - + svg { fill: #eee; } - + &:hover { background-color: rgba(70, 70, 70, 1); } } } - // Mobile responsiveness @media (max-width: 768px) { article .quartz-carousel { @@ -207,29 +206,29 @@ html[saved-theme="dark"] .carousel-image-modal { .quartz-carousel-next { width: 35px; height: 35px; - + svg { width: 18px; height: 18px; } } } - + .carousel-image-modal { .carousel-modal-overlay { padding: 1rem; } - + .carousel-modal-close { top: -35px; right: -35px; width: 35px; height: 35px; - + svg { width: 20px; height: 20px; } } } -} \ No newline at end of file +} diff --git a/quartz/plugins/transformers/carousel.ts b/quartz/plugins/transformers/carousel.ts index 40c702974..2c52807a1 100644 --- a/quartz/plugins/transformers/carousel.ts +++ b/quartz/plugins/transformers/carousel.ts @@ -1,5 +1,5 @@ import { QuartzTransformerPlugin } from "../types" -import { visit } from "unist-util-visit"; +import { visit } from "unist-util-visit" // @ts-ignore import carouselScript from "../../components/scripts/carousel.inline" @@ -10,30 +10,33 @@ interface CarouselOptions { } export const Carousel: QuartzTransformerPlugin> = (opts) => { - const showDots = opts?.showDots ?? true + const showDots = opts?.showDots ?? true - function carouselTransformer() { - return (tree: any) => { - visit(tree, 'html', (node: any) => { - // Check if the node contains a carousel tag - const content = node.value as string - if (content.startsWith('') && content.endsWith('')) { - // Extract the content inside the carousel tag - const innerContent = content.slice(''.length, -''.length).trim() - - // Process images correctly by using a more reliable approach - const processedContent = innerContent.split(' { - if (index === 0) return ''; // Skip the first part before any img tag - return `