Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions docs/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
import "prismjs";
import "prismjs/components/prism-toml";
import "prismjs/components/prism-bash";
import "prismjs/components/prism-json";
import "prismjs/components/prism-typescript";
import { MetaProvider } from "@solidjs/meta";

import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import Prism from "prismjs";
import { onMount, Suspense } from "solid-js";
import { Suspense } from "solid-js";
import { MDXProvider } from "solid-mdx";
import * as components from "~/components";
import "virtual:uno.css";
import "./app.css";
import Layout from "./routes/layout";

export default function App() {
onMount(() => {
Prism.highlightAll();
});

return (
<Router
base={import.meta.env.BASE_URL || undefined}
Expand Down
17 changes: 10 additions & 7 deletions docs/src/components/AnchorHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useLocation } from "@solidjs/router";
import { createEffect, onMount } from "solid-js";
import { handleInitialHash } from "../utils/anchor-scroll";
import { createEffect, onCleanup, onMount } from "solid-js";
import { handleAnchorClick, handleInitialHash } from "../utils/anchor-scroll";

/**
* Component that handles anchor link scrolling and route changes
Expand All @@ -10,10 +10,12 @@ export function AnchorHandler() {
const location = useLocation();

onMount(() => {
// Handle initial route load
setTimeout(() => {
handleInitialHash();
}, 50);
window.addEventListener("hashchange", handleInitialHash);
document.addEventListener("click", handleAnchorClick);
onCleanup(() => {
window.removeEventListener("hashchange", handleInitialHash);
document.removeEventListener("click", handleAnchorClick);
});
});

// Watch for route changes using createEffect
Expand All @@ -22,9 +24,10 @@ export function AnchorHandler() {
location.pathname;

// Small delay to ensure DOM is updated after route change
setTimeout(() => {
const timeoutId = setTimeout(() => {
handleInitialHash();
}, 50);
onCleanup(() => clearTimeout(timeoutId));
});

return null; // This component doesn't render anything
Expand Down
38 changes: 34 additions & 4 deletions docs/src/components/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import Prism from "prismjs";
import "prismjs/components/prism-toml";
import "prismjs/components/prism-bash";
import "prismjs/components/prism-json";
import "prismjs/components/prism-typescript";
import "prismjs/components/prism-powershell";
import type { ParentComponent } from "solid-js";
import { CopyButton } from "./CopyButton";

Expand All @@ -6,16 +12,40 @@ interface CodeBlockProps {
language?: string;
}

function escapeHtml(value: string) {
return value.replace(
/[&<>"']/g,
(character) =>
({
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;",
})[character] || character,
);
}

export const CodeBlock: ParentComponent<CodeBlockProps> = (props) => {
const language = () => props.language || "text";
const highlightedCode = () => {
const grammar = Prism.languages[language()];
return grammar
? Prism.highlight(props.code, grammar, language())
: escapeHtml(props.code);
};

return (
<div class="code-block-wrapper relative max-w-full overflow-hidden my-4">
<pre
class={`language-${props.language || "text"} overflow-x-auto max-w-full pr-20`}
class={`language-${language()} overflow-x-auto max-w-full pr-20`}
tabindex="-1"
>
<code class={`language-${props.language || "text"}`} tabindex="-1">
{`${props.code}`}
</code>
<code
class={`language-${language()}`}
tabindex="-1"
innerHTML={highlightedCode()}
/>
</pre>
<div class="copy-button language-text">
<CopyButton text={props.code} />
Expand Down
9 changes: 3 additions & 6 deletions docs/src/components/InstallationMethodGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Component } from "solid-js";
import { createEffect, createSignal, For } from "solid-js";
import { createSignal, For, onCleanup, onMount } from "solid-js";

interface InstallationMethod {
id: string;
Expand Down Expand Up @@ -114,7 +114,7 @@ export const InstallationMethodGrid: Component<InstallationMethodGridProps> = (
const [isDarkMode, setIsDarkMode] = createSignal(false);

// Check for dark mode
createEffect(() => {
onMount(() => {
const checkDarkMode = () => {
const storedTheme = localStorage.getItem("theme");
const hasDarkClass = document.documentElement.classList.contains("dark");
Expand Down Expand Up @@ -144,10 +144,7 @@ export const InstallationMethodGrid: Component<InstallationMethodGridProps> = (
attributeFilter: ["class"],
});

// Cleanup
return () => {
observer.disconnect();
};
onCleanup(() => observer.disconnect());
});

const categories = [
Expand Down
6 changes: 3 additions & 3 deletions docs/src/components/header/HeaderDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Accessor } from "solid-js";
import { createEffect, For } from "solid-js";
import { createEffect, For, onCleanup } from "solid-js";
import { isServer } from "solid-js/web";
import type { DocIndex } from "~/utils/doc-index";
import docIndex from "../../../doc-index.json";
Expand Down Expand Up @@ -30,9 +30,9 @@ export function HeaderDropdown(props: HeaderDropdownProps) {
createEffect(() => {
if (!isServer && props.isExpanded()) {
document.addEventListener("click", handleClickOutside);
return () => {
onCleanup(() => {
document.removeEventListener("click", handleClickOutside);
};
});
}
});

Expand Down
34 changes: 19 additions & 15 deletions docs/src/components/header/HeaderSearch.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TbLoaderQuarter, TbSearch } from "solid-icons/tb";
import { createSignal, onMount } from "solid-js";
import { createSignal, onCleanup, onMount } from "solid-js";
import { detectOperatingSystem } from "~/utils/platform";
import { type SearchResult, searchDocumentation } from "~/utils/search";
import breakpoints from "../../../breakpoints.json";
Expand All @@ -26,21 +26,25 @@ export function HeaderSearch(props: HeaderSearchProps) {
setIsMac(detectOperatingSystem() === "mac");
setIsDesktop(window.innerWidth >= breakpoints.md);

if (typeof window !== "undefined") {
window.addEventListener("resize", () => {
setIsDesktop(window.innerWidth >= breakpoints.md);
});
const handleResize = () => {
setIsDesktop(window.innerWidth >= breakpoints.md);
};
const handleShortcut = (event: KeyboardEvent) => {
if ((event.metaKey || event.ctrlKey) && event.key === "k") {
event.preventDefault();
// Save the currently focused element before opening search
props.savePreviousActiveElement?.();
searchInputRef?.focus();
props.setIsSearchOpen(true);
}
};

document.addEventListener("keydown", (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
e.preventDefault();
// Save the currently focused element before opening search
props.savePreviousActiveElement?.();
searchInputRef?.focus();
props.setIsSearchOpen(true);
}
});
}
window.addEventListener("resize", handleResize);
document.addEventListener("keydown", handleShortcut);
onCleanup(() => {
window.removeEventListener("resize", handleResize);
document.removeEventListener("keydown", handleShortcut);
});
});

const handleSearch = async (query: string) => {
Expand Down
4 changes: 0 additions & 4 deletions docs/src/entry-client.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
// @refresh reload
import { mount, StartClient } from "@solidjs/start/client";
import { setupAnchorHandling } from "./utils/anchor-scroll";

const app = document.getElementById("app");
if (!app) throw new Error("Failed to find app element");

// Set up anchor link handling
setupAnchorHandling();

mount(() => <StartClient />, app);
14 changes: 9 additions & 5 deletions docs/src/routes/docs.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import type { RouteSectionProps } from "@solidjs/router";
import { useLocation } from "@solidjs/router";
import Prism from "prismjs";
import { createEffect } from "solid-js";
import { createEffect, onCleanup, onMount } from "solid-js";
import { DocNavigation } from "~/components/DocNavigation";
import { Sidebar } from "~/components/Sidebar";
import { setupAnchors } from "~/utils/anchor";
import { setupAnchorCopyHandling, setupAnchors } from "~/utils/anchor";

export default function DocumentationLayout(props: RouteSectionProps) {
const location = useLocation();
let mainRef: HTMLElement | undefined;

onMount(() => {
const cleanupAnchorCopyHandling = setupAnchorCopyHandling();
onCleanup(cleanupAnchorCopyHandling);
});

createEffect(() => {
location.pathname;
requestAnimationFrame(() => {
Prism.highlightAll();
const frameId = requestAnimationFrame(() => {
setupAnchors();
// Focus on main content after page transition
if (mainRef) {
mainRef.focus();
}
});
onCleanup(() => cancelAnimationFrame(frameId));
});

return (
Expand Down
21 changes: 1 addition & 20 deletions docs/src/utils/anchor-scroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,25 +64,6 @@ export function handleInitialHash() {
const targetElement = document.getElementById(targetId);

if (targetElement) {
// Use setTimeout to ensure the page is fully rendered
setTimeout(() => {
scrollToElement(targetElement, false);
}, 100);
scrollToElement(targetElement, false);
}
}

/**
* Sets up anchor link handling for the entire document
*/
export function setupAnchorHandling() {
// Handle initial hash on page load
handleInitialHash();

// Handle hash changes (browser back/forward)
window.addEventListener("hashchange", () => {
handleInitialHash();
});

// Handle anchor link clicks
document.addEventListener("click", handleAnchorClick);
}
39 changes: 31 additions & 8 deletions docs/src/utils/anchor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,53 @@ const checkIcon = `<svg fill="none" stroke-width="2" xmlns="http://www.w3.org/20

export function setupAnchors() {
for (const heading of document.querySelectorAll(".heading-with-anchor")) {
if (heading.querySelector(".anchor-link")) {
continue;
}

const anchorLink = document.createElement("a");
anchorLink.className = "anchor-link";
anchorLink.setAttribute("data-anchor", "true");
anchorLink.setAttribute("aria-hidden", "true");
anchorLink.innerHTML = clipIcon;
heading.appendChild(anchorLink);
}
}

document.addEventListener("click", (event) => {
const target = event.target as HTMLElement;
const anchorLink = target.closest(".anchor-link");
export function setupAnchorCopyHandling() {
const resetTimeoutIds = new Set<ReturnType<typeof setTimeout>>();
const handleClick = async (event: MouseEvent) => {
if (!(event.target instanceof Element)) {
return;
}

const anchorLink = event.target.closest<HTMLElement>(".anchor-link");
if (anchorLink) {
const heading = target.closest(".heading-with-anchor") as HTMLElement;
const heading = anchorLink.closest<HTMLElement>(".heading-with-anchor");
if (heading?.id) {
event.preventDefault();
const url = `${window.location.href.split("#")[0]}#${heading.id}`;
navigator.clipboard.writeText(url).then(() => {
try {
await navigator.clipboard.writeText(url);
anchorLink.innerHTML = checkIcon;

setTimeout(() => {
const timeoutId = setTimeout(() => {
anchorLink.innerHTML = clipIcon;
resetTimeoutIds.delete(timeoutId);
}, 500);
});
resetTimeoutIds.add(timeoutId);
} catch {
// Keep the link icon unchanged when clipboard access is unavailable.
}
}
}
});
};

document.addEventListener("click", handleClick);
return () => {
document.removeEventListener("click", handleClick);
for (const timeoutId of resetTimeoutIds) {
clearTimeout(timeoutId);
}
};
}