File: /var/www/quadcode.com/src/components/blocks/blog/Anchors.svelte
<script lang="ts">
import { onMount } from 'svelte';
import { get } from 'svelte/store';
import { blogAnchors, isMobile, postActiveAnchor } from '../../../store';
import { t } from '$lib/translations';
export let className: string | undefined = '';
export let notUseH3 = false;
export let divideTitles: boolean = false;
export let title: string = '';
export let toTopButton: boolean = false;
let mobile = false;
// let activeAnchor: HTMLElement | null = null;
isMobile.subscribe((value) => {
mobile = value;
});
const toTopClickHandler = () => {
window.scrollTo(0,0);
blogAnchors.set(false);
};
onMount(() => {
const titles = document.querySelectorAll(
notUseH3 ? '.anchors-content h2' : '.anchors-content h2, .anchors-content h3'
);
const tocContainer = document.querySelector('#tocContainer');
const block = document.querySelector('.anchors__block');
const list = document.querySelector('.anchors__list');
const mobileList = document.querySelector('#tocListMobile');
const inner = document.querySelector('.anchors__inner');
const progress: HTMLElement | null = document.querySelector('#progressBar');
const content = document.querySelector('.anchors-content');
const prev = document.querySelector('.anchors-prev');
const anchors = document.querySelector('.anchors-link');
const tocMobileToggle = document.querySelector('#tocMobileTrigger');
const tocToggle = document.querySelector('#tocToggle');
const scrollEl = document.getElementById('tocScroll');
if (tocToggle && scrollEl) {
const tr = get(t);
const showAllText = tr('blog.showAllToc');
const hideAllText = tr('blog.hideAllToc');
if (titles.length <= 5) tocToggle.classList.add('hidden');
tocToggle.addEventListener('click', function () {
const expanded = scrollEl.classList.toggle('toc-expanded');
tocToggle.textContent = expanded ? hideAllText : showAllText;
tocToggle.setAttribute('aria-label', expanded ? hideAllText : showAllText);
});
}
if (list && titles.length) {
titles.forEach((title, index) => {
title.querySelector('.copyButton')?.remove();
title.id = 'anchor' + (index + 1);
if (divideTitles) {
list.innerHTML += `
<li class="anchors__item ${title.tagName === 'H2' ? '' : 'toc-item--sub'}">
<a aria-label="${title.textContent}" data-target="anchor${index + 1}" href="${window.location.pathname}#anchor${index + 1}" class="anchors__link ${title.tagName === 'H2' ? 'title' : ''}" data-index="${index + 1}">
${title.innerHTML}
</a>
</li>
`;
}
else {
list.innerHTML += `
<li class="anchors__item ${title.tagName === 'H2' ? '' : 'toc-item--sub'}">
<a aria-label="${title.textContent}" href="${window.location.pathname}#anchor${index + 1}" class="anchors__link" data-index="${index + 1}">
${title.innerHTML}
</a>
</li>
`;
}
if (mobileList) {
mobileList.innerHTML = list.innerHTML;
}
const links = document.querySelectorAll<HTMLElement>('.anchors__link');
links.forEach((link) => {
const index = link.dataset.index;
const scrollIntoView = () => {
if (!window.location.hash) return;
const div = document.querySelector(window.location.hash);
if (!div) return;
window.scrollTo({
behavior: 'smooth',
top: div.getBoundingClientRect().top - document.body.getBoundingClientRect().top - 40,
});
};
scrollIntoView();
link.addEventListener('click', (e) => {
e.preventDefault();
window.location.hash = 'anchor' + (index);
// if (mobile && anchors) {
// blogAnchors.set(false);
// }
scrollIntoView();
});
});
});
block?.classList.remove('hide');
} else {
tocContainer?.remove();
tocMobileToggle?.remove();
}
if (progress && content) {
window.addEventListener('scroll', () => {
const winScroll = document.body.scrollTop || document.documentElement.scrollTop;
const heightContent = document.documentElement.scrollHeight - content.clientHeight;
const height = document.documentElement.scrollHeight - heightContent;
const scrolled = (winScroll / height) * 100;
if(scrolled > 50){
inner?.scrollTo({
top: inner.clientHeight * scrolled / 100,
});
}
else {
inner?.scrollTo({
top: 0,
});
}
progress.style.width = scrolled + '%';
});
}
postActiveAnchor.subscribe((value) => {
const titles = document.querySelectorAll('.anchors__link');
titles.forEach((title) => {
title.classList.remove('active');
});
if(value === null) return;
document.querySelectorAll(`[data-target="anchor${value + 1}"]`).forEach((item) => {
item?.classList.add('active');
})
});
});
let open = false;
blogAnchors.subscribe((value: boolean) => {
open = value;
});
</script>
<div class="sidebar-toc-wrap" id="tocContainer">
<div class="toc-title">{title || $t('Table of contents')}</div>
<div class="toc-scroll" id="tocScroll">
<ul class="toc-list anchors__list" id="tocList">
</ul>
</div>
<button type="button" class="toc-toggle" id="tocToggle" aria-label={$t('blog.showAllToc')}>{$t('blog.showAllToc')}</button>
</div>
<style lang="scss">
@import 'src/scss/media';
@import 'src/scss/mixins';
@import 'src/scss/variables';
.sidebar-toc-wrap { margin-bottom: 32px; }
.toc-title {
font-size: 0.8125rem; font-weight: 700; color: #141414;
text-transform: uppercase; letter-spacing: 0.02em;
margin-bottom: 12px;
}
.toc-scroll {
max-height: 165px; overflow-y: auto;
scrollbar-width: thin; scrollbar-color: #c5cfd6 #f1f5f9;
}
.toc-scroll::-webkit-scrollbar { width: 6px; }
.toc-scroll::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 3px; }
:global(.toc-scroll.toc-expanded) { max-height: calc(100vh - 350px); }
.toc-list { list-style: none; padding: 0; margin: 0; }
.toc-list :global(li) { margin-bottom: 2px; }
.toc-list :global(a) {
font-size: 0.8125rem; font-weight: 500; color: #5a6872;
display: block; padding: 6px 0 6px 12px;
border-left: 2px solid transparent; margin-left: 0;
transition: all 0.15s; line-height: 1.4;
}
.toc-list :global(a:hover) { color: #E62334; border-left-color: #E62334; }
.toc-list :global(a.active) { color: #E62334; font-weight: 600; border-left-color: #E62334; }
.toc-list :global(.toc-item--sub a) { padding-left: 24px; font-size: 0.75rem; color: #6b7280; }
.toc-list :global(.toc-item--sub a:hover), .toc-list :global(.toc-item--sub a.active) { color: #E62334; }
.toc-toggle {
display: block; margin-top: 10px; font-size: 0.8125rem; font-weight: 600;
color: #E62334; cursor: pointer; background: none; border: none;
font-family: inherit; padding: 0; text-align: left; text-decoration: underline;
}
.toc-toggle:hover { color: #c41e2a; }
:global(.toc-toggle.hidden) { display: none; }
</style>