Skip to content

Commit ead4a80

Browse files
authored
feat(docs): improve misc (#1722)
1 parent da78689 commit ead4a80

File tree

9 files changed

+392
-108
lines changed

9 files changed

+392
-108
lines changed

apps/next/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
"react-dom": "^19.0.0",
5252
"react-icons": "^5.3.0",
5353
"react-monaco-editor": "^0.56.2",
54-
"react-text-transition": "^3.1.0",
5554
"react-use-measure": "^2.1.1",
5655
"rehype-katex": "^7.0.1",
5756
"remark-math": "^6.0.0",

apps/next/source.config.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins";
22
import { fileGenerator, remarkDocGen, remarkInstall } from "fumadocs-docgen";
33
import { defineConfig, defineDocs } from "fumadocs-mdx/config";
44
import { transformerTwoslash } from "fumadocs-twoslash";
5+
import { createFileSystemTypesCache } from "fumadocs-twoslash/cache-fs";
56
import rehypeKatex from "rehype-katex";
67
import remarkMath from "remark-math";
78

@@ -20,7 +21,11 @@ export default defineConfig({
2021
},
2122
transformers: [
2223
...(rehypeCodeDefaultOptions.transformers ?? []),
23-
transformerTwoslash(),
24+
transformerTwoslash({
25+
typesCache: createFileSystemTypesCache({
26+
dir: ".next/cache/twoslash",
27+
}),
28+
}),
2429
{
2530
name: "transformers:remove-notation-escape",
2631
code(hast) {

apps/next/src/app/(home)/page.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from "@/components/infinite-providers";
99
import { MagicMove } from "@/components/magic-move";
1010
import { NpmInstall } from "@/components/npm-install";
11-
import { TextEffect } from "@/components/text-effect";
11+
import { Supports } from "@/components/supports";
1212
import { Button } from "@/components/ui/button";
1313
import { Skeleton } from "@/components/ui/skeleton";
1414
import { LEGACY_DOCUMENT_URL } from "@/lib/const";
@@ -35,7 +35,7 @@ export default function HomePage() {
3535
</p>
3636
<div className="text-center text-lg text-fd-muted-foreground mb-12">
3737
<span>Designed for building web applications in </span>
38-
<TextEffect />
38+
<Supports />
3939
</div>
4040

4141
<div className="flex flex-wrap justify-center gap-4">

apps/next/src/app/docs/[[...slug]]/page.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { notFound } from "next/navigation";
1313

1414
const { AutoTypeTable } = createTypeTable();
1515

16+
export const revalidate = false;
17+
1618
export default async function Page(props: {
1719
params: Promise<{ slug?: string[] }>;
1820
}) {
@@ -26,6 +28,7 @@ export default async function Page(props: {
2628
<DocsPage
2729
toc={page.data.toc}
2830
full={page.data.full}
31+
lastUpdate={page.data.lastModified}
2932
editOnGithub={{
3033
owner: "run-llama",
3134
repo: "LlamaIndexTS",

apps/next/src/app/llms.txt/route.ts

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import fg from "fast-glob";
2+
import { fileGenerator, remarkDocGen, remarkInstall } from "fumadocs-docgen";
3+
import { remarkInclude } from "fumadocs-mdx/config";
4+
import { remarkAutoTypeTable } from "fumadocs-typescript";
5+
import matter from "gray-matter";
6+
import * as fs from "node:fs/promises";
7+
import path from "node:path";
8+
import { remark } from "remark";
9+
import remarkGfm from "remark-gfm";
10+
import remarkMdx from "remark-mdx";
11+
import remarkStringify from "remark-stringify";
12+
13+
export const revalidate = false;
14+
15+
export async function GET() {
16+
const files = await fg([
17+
"./src/content/docs/**/*.mdx",
18+
// remove generated openapi files
19+
"!./src/content/docs/cloud/api/**/*",
20+
]);
21+
22+
const scan = files.map(async (file) => {
23+
const fileContent = await fs.readFile(file);
24+
const { content, data } = matter(fileContent.toString());
25+
26+
const dir = path.dirname(file).split(path.sep).at(4);
27+
const category = {
28+
llamaindex: "LlamaIndexTS Framework",
29+
api: "LlamaIndexTS API",
30+
cloud: "LlamaCloud Service",
31+
}[dir ?? ""];
32+
33+
const processed = await processContent(file, content);
34+
return `file: ${file}
35+
# ${category}: ${data.title}
36+
37+
${data.description}
38+
39+
${processed}`;
40+
});
41+
42+
const scanned = await Promise.all(scan);
43+
44+
return new Response(scanned.join("\n\n"));
45+
}
46+
47+
async function processContent(path: string, content: string): Promise<string> {
48+
const file = await remark()
49+
.use(remarkMdx)
50+
.use(remarkInclude)
51+
.use(remarkGfm)
52+
.use(remarkAutoTypeTable)
53+
.use(remarkDocGen, { generators: [fileGenerator()] })
54+
.use(remarkInstall, { persist: { id: "package-manager" } })
55+
.use(remarkStringify)
56+
.process({
57+
path,
58+
value: content,
59+
});
60+
61+
return String(file);
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
import { cn } from "@/lib/utils";
2+
import {
3+
AnimatePresence,
4+
motion,
5+
Transition,
6+
type AnimationControls,
7+
type Target,
8+
type TargetAndTransition,
9+
type VariantLabels,
10+
} from "framer-motion";
11+
import React, {
12+
forwardRef,
13+
useCallback,
14+
useEffect,
15+
useImperativeHandle,
16+
useMemo,
17+
useState,
18+
} from "react";
19+
20+
export interface RotatingTextRef {
21+
next: () => void;
22+
previous: () => void;
23+
jumpTo: (index: number) => void;
24+
reset: () => void;
25+
}
26+
27+
export interface RotatingTextProps
28+
extends Omit<
29+
React.ComponentPropsWithoutRef<typeof motion.span>,
30+
"children" | "transition" | "initial" | "animate" | "exit"
31+
> {
32+
texts: string[];
33+
transition?: Transition;
34+
initial?: boolean | Target | VariantLabels;
35+
animate?: boolean | VariantLabels | AnimationControls | TargetAndTransition;
36+
exit?: Target | VariantLabels;
37+
animatePresenceMode?: "sync" | "wait";
38+
animatePresenceInitial?: boolean;
39+
rotationInterval?: number;
40+
staggerDuration?: number;
41+
staggerFrom?: "first" | "last" | "center" | "random" | number;
42+
loop?: boolean;
43+
auto?: boolean;
44+
splitBy?: string;
45+
onNext?: (index: number) => void;
46+
mainClassName?: string;
47+
splitLevelClassName?: string;
48+
elementLevelClassName?: string;
49+
}
50+
51+
export const RotatingText = forwardRef<RotatingTextRef, RotatingTextProps>(
52+
(
53+
{
54+
texts,
55+
transition = { type: "spring", damping: 25, stiffness: 300 },
56+
initial = { y: "100%", opacity: 0 },
57+
animate = { y: 0, opacity: 1 },
58+
exit = { y: "-120%", opacity: 0 },
59+
animatePresenceMode = "wait",
60+
animatePresenceInitial = false,
61+
rotationInterval = 2000,
62+
staggerDuration = 0,
63+
staggerFrom = "first",
64+
loop = true,
65+
auto = true,
66+
splitBy = "characters",
67+
onNext,
68+
mainClassName,
69+
splitLevelClassName,
70+
elementLevelClassName,
71+
...rest
72+
},
73+
ref,
74+
) => {
75+
const [currentTextIndex, setCurrentTextIndex] = useState<number>(0);
76+
77+
const splitIntoCharacters = (text: string): string[] => {
78+
if (typeof Intl !== "undefined" && Intl.Segmenter) {
79+
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
80+
return Array.from(
81+
segmenter.segment(text),
82+
(segment) => segment.segment,
83+
);
84+
}
85+
return Array.from(text);
86+
};
87+
88+
const elements = useMemo(() => {
89+
const currentText: string = texts[currentTextIndex];
90+
if (splitBy === "characters") {
91+
const words = currentText.split(" ");
92+
return words.map((word, i) => ({
93+
characters: splitIntoCharacters(word),
94+
needsSpace: i !== words.length - 1,
95+
}));
96+
}
97+
if (splitBy === "words") {
98+
return currentText.split(" ").map((word, i, arr) => ({
99+
characters: [word],
100+
needsSpace: i !== arr.length - 1,
101+
}));
102+
}
103+
if (splitBy === "lines") {
104+
return currentText.split("\n").map((line, i, arr) => ({
105+
characters: [line],
106+
needsSpace: i !== arr.length - 1,
107+
}));
108+
}
109+
110+
return currentText.split(splitBy).map((part, i, arr) => ({
111+
characters: [part],
112+
needsSpace: i !== arr.length - 1,
113+
}));
114+
}, [texts, currentTextIndex, splitBy]);
115+
116+
const getStaggerDelay = useCallback(
117+
(index: number, totalChars: number): number => {
118+
const total = totalChars;
119+
if (staggerFrom === "first") return index * staggerDuration;
120+
if (staggerFrom === "last")
121+
return (total - 1 - index) * staggerDuration;
122+
if (staggerFrom === "center") {
123+
const center = Math.floor(total / 2);
124+
return Math.abs(center - index) * staggerDuration;
125+
}
126+
if (staggerFrom === "random") {
127+
const randomIndex = Math.floor(Math.random() * total);
128+
return Math.abs(randomIndex - index) * staggerDuration;
129+
}
130+
return Math.abs((staggerFrom as number) - index) * staggerDuration;
131+
},
132+
[staggerFrom, staggerDuration],
133+
);
134+
135+
const handleIndexChange = useCallback(
136+
(newIndex: number) => {
137+
setCurrentTextIndex(newIndex);
138+
if (onNext) onNext(newIndex);
139+
},
140+
[onNext],
141+
);
142+
143+
const next = useCallback(() => {
144+
const nextIndex =
145+
currentTextIndex === texts.length - 1
146+
? loop
147+
? 0
148+
: currentTextIndex
149+
: currentTextIndex + 1;
150+
if (nextIndex !== currentTextIndex) {
151+
handleIndexChange(nextIndex);
152+
}
153+
}, [currentTextIndex, texts.length, loop, handleIndexChange]);
154+
155+
const previous = useCallback(() => {
156+
const prevIndex =
157+
currentTextIndex === 0
158+
? loop
159+
? texts.length - 1
160+
: currentTextIndex
161+
: currentTextIndex - 1;
162+
if (prevIndex !== currentTextIndex) {
163+
handleIndexChange(prevIndex);
164+
}
165+
}, [currentTextIndex, texts.length, loop, handleIndexChange]);
166+
167+
const jumpTo = useCallback(
168+
(index: number) => {
169+
const validIndex = Math.max(0, Math.min(index, texts.length - 1));
170+
if (validIndex !== currentTextIndex) {
171+
handleIndexChange(validIndex);
172+
}
173+
},
174+
[texts.length, currentTextIndex, handleIndexChange],
175+
);
176+
177+
const reset = useCallback(() => {
178+
if (currentTextIndex !== 0) {
179+
handleIndexChange(0);
180+
}
181+
}, [currentTextIndex, handleIndexChange]);
182+
183+
useImperativeHandle(
184+
ref,
185+
() => ({
186+
next,
187+
previous,
188+
jumpTo,
189+
reset,
190+
}),
191+
[next, previous, jumpTo, reset],
192+
);
193+
194+
useEffect(() => {
195+
if (!auto) return;
196+
const intervalId = setInterval(next, rotationInterval);
197+
return () => clearInterval(intervalId);
198+
}, [next, rotationInterval, auto]);
199+
200+
return (
201+
<motion.span
202+
className={cn(
203+
"flex flex-wrap whitespace-pre-wrap relative",
204+
mainClassName,
205+
)}
206+
{...rest}
207+
layout
208+
transition={transition}
209+
>
210+
<span className="sr-only">{texts[currentTextIndex]}</span>
211+
<AnimatePresence
212+
mode={animatePresenceMode}
213+
initial={animatePresenceInitial}
214+
>
215+
<motion.div
216+
key={currentTextIndex}
217+
className={cn(
218+
splitBy === "lines"
219+
? "flex flex-col w-full"
220+
: "flex flex-wrap whitespace-pre-wrap relative",
221+
)}
222+
layout
223+
aria-hidden="true"
224+
>
225+
{elements.map((wordObj, wordIndex, array) => {
226+
const previousCharsCount = array
227+
.slice(0, wordIndex)
228+
.reduce((sum, word) => sum + word.characters.length, 0);
229+
return (
230+
<span
231+
key={wordIndex}
232+
className={cn("inline-flex", splitLevelClassName)}
233+
>
234+
{wordObj.characters.map((char, charIndex) => (
235+
<motion.span
236+
key={charIndex}
237+
initial={initial}
238+
animate={animate}
239+
exit={exit}
240+
transition={{
241+
...transition,
242+
delay: getStaggerDelay(
243+
previousCharsCount + charIndex,
244+
array.reduce(
245+
(sum, word) => sum + word.characters.length,
246+
0,
247+
),
248+
),
249+
}}
250+
className={cn("inline-block", elementLevelClassName)}
251+
>
252+
{char}
253+
</motion.span>
254+
))}
255+
{wordObj.needsSpace && (
256+
<span className="whitespace-pre"> </span>
257+
)}
258+
</span>
259+
);
260+
})}
261+
</motion.div>
262+
</AnimatePresence>
263+
</motion.span>
264+
);
265+
},
266+
);
267+
268+
RotatingText.displayName = "RotatingText";

0 commit comments

Comments
 (0)