Merge pull request #41 from mearashadowfax/update-toc

Update README and refactor Table of Contents (ToC) code
This commit is contained in:
Emil Gulamov 2024-04-02 22:02:08 +04:00 committed by GitHub
commit f28987a86e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 62 additions and 60 deletions

View file

@ -94,15 +94,18 @@ ScrewFast is an open-source template designed for quick and efficient web projec
- Convenient and reusable Icon component that allows adding icons simply by providing a name prop.
- Render any pre-defined icon SVG using `<Icon name="iconName" />` in your Astro components.
- The Icon Component offers a centralized location for all SVG Icons across the project in one TypeScript file - allowing unified updates and easy maintenance.
- **Note:** Users have the option to use other community integrations like [astro-icons](https://github.com/natemoo-re/astro-icon). However, the author decided to create a custom icon set component for managing custom icons.
- **Note:** Developers have the option to use other community integrations like [astro-icons](https://github.com/natemoo-re/astro-icon). However, the author decided to create a custom icon set component for managing custom icons.
- [x] **Internationalization (i18n) Features**:
- Integrates [Astros internationalization (i18n) features](https://docs.astro.build/en/guides/internationalization/).
- Additionally, a custom LanguagePicker component has been developed to facilitate language selection.
### Planned Improvements
- [ ] Implement a table of contents (ToC) sidebar for blog articles.
- [x] **Dynamic Table of Contents (ToC) with Scroll Progress Indicator**:
- Enhances ease of navigation in insight posts by highlighting the relevant section in the ToC, and includes a progress indicator to visually represent scroll progress.
- Developers seeking alternatives might consider the [remark-toc](https://github.com/remarkjs/remark-toc) plugin.
### Planned Improvements
- Currently, there are no planned improvements. We'll update this section as plans develop.
### Bug Fixes
- Currently, there are no known bugs. If you encounter any issues, please report them on our [issues page](https://github.com/mearashadowfax/ScrewFast/issues).

View file

@ -82,6 +82,7 @@ const pageTitle: string = `${post.data.title} | ${SITE.title}`;
html {
scroll-behavior: smooth;
}
article h2,
article h3,
article h4,
@ -91,21 +92,26 @@ const pageTitle: string = `${post.data.title} | ${SITE.title}`;
margin-top: 2.5rem;
scroll-margin-top: 3rem;
}
h2 {
font-size: 1.5rem;
line-height: 2rem;
}
h3 {
font-size: 1.25rem;
line-height: 1.75rem;
}
h4 {
font-size: 1.125rem;
line-height: 1.75rem;
}
p {
margin-top: 1.5rem;
}
@keyframes grow-progress {
from {
transform: scaleX(0);
@ -114,20 +120,24 @@ const pageTitle: string = `${post.data.title} | ${SITE.title}`;
transform: scaleX(1);
}
}
#progress {
transform-origin: 0 50%;
animation: grow-progress auto linear;
animation-timeline: scroll(block root);
}
#toc li {
display: flex;
align-items: center;
opacity: 0.8;
transition: all 300ms var(--transition-cubic);
}
#toc li.selected {
opacity: 1;
}
#toc li svg {
width: 0;
height: 0;
@ -135,6 +145,7 @@ const pageTitle: string = `${post.data.title} | ${SITE.title}`;
height 400ms var(--transition-cubic),
width 400ms var(--transition-cubic);
}
#toc li.selected svg {
width: 1.25rem;
height: 1.25rem;
@ -159,67 +170,55 @@ const pageTitle: string = `${post.data.title} | ${SITE.title}`;
},
});
const SVG_HTML_STRING = '<svg class="w-0 h-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="#fa5a15"><path stroke-linecap="round" stroke-linejoin="round" d="m12.75 15 3-3m0 0-3-3m3 3h-7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"></svg>';
function setActiveLinkById(id: string | null) {
const listItems = document.querySelectorAll("#toc li");
listItems.forEach(item => item.classList.remove("selected"));
if (!id) return;
const activeLink = document.querySelector(`#toc a[href="#${id}"]`);
if (!activeLink) return;
const listItem = activeLink.parentElement;
listItem?.classList.add("selected");
}
document.addEventListener("DOMContentLoaded", function () {
// The article element that contains the Markdown content
const article: HTMLElement | null = document.querySelector("article");
// The ToC container <ul> element
const tocList: HTMLElement | null = document.querySelector("#toc ul");
// Function to create and add an item to the ToC list
function addToC(heading: HTMLElement) {
const li = document.createElement("li");
li.className = "toc-level-" + heading.tagName.toLowerCase();
const headings: NodeListOf<HTMLElement> | [] = article ? article.querySelectorAll("h1, h2, h3, h4, h5, h6") : [];
headings.forEach((heading, i) => {
if (heading instanceof HTMLElement) {
const listItem = document.createElement("li");
listItem.className = "toc-level-" + heading.tagName.toLowerCase();
const tempDiv = document.createElement("div");
tempDiv.innerHTML =
'<svg class="w-0 h-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="#fa5a15"><path stroke-linecap="round" stroke-linejoin="round" d="m12.75 15 3-3m0 0-3-3m3 3h-7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"></svg>';
const svg = tempDiv.firstChild;
tempDiv.innerHTML = SVG_HTML_STRING;
li.appendChild(svg as Node);
const svg = tempDiv.firstChild;
listItem.appendChild(svg as Node);
const link = document.createElement("a");
link.href = "#" + heading.id;
link.textContent = heading.textContent;
listItem.appendChild(link);
li.appendChild(link);
tocList?.appendChild(li);
return li;
}
// Helper function to toggle the 'selected' class
function setActiveLink(id: string) {
document.querySelectorAll("#toc li").forEach((li) => {
li.classList.remove("selected");
});
const activeLink = document.querySelector(`#toc a[href="#${id}"]`);
if (activeLink) {
const li: HTMLElement | null = activeLink.parentElement;
li?.classList.add("selected");
}
}
// Observe headings and add them to the ToC
let headings: NodeListOf<HTMLElement> | [] = article
? article.querySelectorAll("h1, h2, h3, h4, h5, h6")
: [];
headings.forEach((heading: Element, i: number) => {
if (heading instanceof HTMLElement) {
addToC(heading);
tocList?.appendChild(listItem);
gsap.timeline({
scrollTrigger: {
trigger: heading,
start: "top 20%",
end: () =>
`bottom top+=${i === headings.length - 1 ? 0 : headings[i + 1].getBoundingClientRect().height}`,
onEnter: () => setActiveLink(heading.id),
onLeaveBack: () =>
setActiveLink((headings[i - 1] || { id: null }).id),
end: () => `bottom top+=${i === headings.length - 1 ? 0 : (headings[i + 1] as HTMLElement).getBoundingClientRect().height}`,
onEnter: () => setActiveLinkById(heading.id),
onLeaveBack: () => setActiveLinkById((headings[i - 1] as HTMLElement)?.id),
},
});
}