Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b6b2b8ece | |||
| 9f412d81a8 | |||
| 9c401f13de | |||
| 5857404ac1 | |||
| 34a96f8aef |
@@ -1,4 +1,7 @@
|
|||||||
import { RichText } from "@payloadcms/richtext-lexical/react";
|
import {
|
||||||
|
RichText,
|
||||||
|
defaultJSXConverters,
|
||||||
|
} from "@payloadcms/richtext-lexical/react";
|
||||||
import type { JSXConverters } from "@payloadcms/richtext-lexical/react";
|
import type { JSXConverters } from "@payloadcms/richtext-lexical/react";
|
||||||
import { MemeCard } from "@/src/components/MemeCard";
|
import { MemeCard } from "@/src/components/MemeCard";
|
||||||
import { Mermaid } from "@/src/components/Mermaid";
|
import { Mermaid } from "@/src/components/Mermaid";
|
||||||
@@ -40,6 +43,20 @@ function renderInlineMarkdown(text: string): React.ReactNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const jsxConverters: JSXConverters = {
|
const jsxConverters: JSXConverters = {
|
||||||
|
...defaultJSXConverters,
|
||||||
|
// Override paragraph to filter out leftover <TableOfContents /> raw text
|
||||||
|
paragraph: ({ node, nodesToJSX }: any) => {
|
||||||
|
const children = node?.children;
|
||||||
|
if (
|
||||||
|
children?.length === 1 &&
|
||||||
|
children[0]?.type === "text" &&
|
||||||
|
children[0]?.text?.trim()?.startsWith("<") &&
|
||||||
|
children[0]?.text?.trim()?.endsWith("/>")
|
||||||
|
) {
|
||||||
|
return null; // suppress raw JSX component text like <TableOfContents />
|
||||||
|
}
|
||||||
|
return <p>{nodesToJSX({ nodes: children })}</p>;
|
||||||
|
},
|
||||||
blocks: {
|
blocks: {
|
||||||
memeCard: ({ node }: any) => (
|
memeCard: ({ node }: any) => (
|
||||||
<div className="my-8">
|
<div className="my-8">
|
||||||
@@ -179,12 +196,22 @@ const jsxConverters: JSXConverters = {
|
|||||||
),
|
),
|
||||||
iconList: ({ node }: any) => (
|
iconList: ({ node }: any) => (
|
||||||
<mdxComponents.IconList>
|
<mdxComponents.IconList>
|
||||||
{node.fields.items?.map((item: any, i: number) => (
|
{node.fields.items?.map((item: any, i: number) => {
|
||||||
// @ts-ignore
|
const isCheck = item.icon === "check" || !item.icon;
|
||||||
<mdxComponents.IconListItem key={i} icon={item.icon || "check"}>
|
const isCross = item.icon === "x" || item.icon === "cross";
|
||||||
{item.title || item.description}
|
const isBullet = item.icon === "circle" || item.icon === "bullet";
|
||||||
</mdxComponents.IconListItem>
|
return (
|
||||||
))}
|
// @ts-ignore
|
||||||
|
<mdxComponents.IconListItem
|
||||||
|
key={i}
|
||||||
|
check={isCheck}
|
||||||
|
cross={isCross}
|
||||||
|
bullet={isBullet}
|
||||||
|
>
|
||||||
|
{item.title || item.description}
|
||||||
|
</mdxComponents.IconListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</mdxComponents.IconList>
|
</mdxComponents.IconList>
|
||||||
),
|
),
|
||||||
statsGrid: ({ node }: any) => {
|
statsGrid: ({ node }: any) => {
|
||||||
@@ -210,8 +237,8 @@ const jsxConverters: JSXConverters = {
|
|||||||
<mdxComponents.Carousel
|
<mdxComponents.Carousel
|
||||||
items={
|
items={
|
||||||
node.fields.slides?.map((s: any) => ({
|
node.fields.slides?.map((s: any) => ({
|
||||||
title: s.caption || "Image",
|
title: s.title || s.caption || "Slide",
|
||||||
content: "",
|
content: s.content || s.caption || "",
|
||||||
icon: undefined,
|
icon: undefined,
|
||||||
})) || []
|
})) || []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { ComponentShareButton } from '../ComponentShareButton';
|
import { ComponentShareButton } from '../ComponentShareButton';
|
||||||
import { Reveal } from '../Reveal';
|
import { Reveal } from '../Reveal';
|
||||||
import { Play, RotateCcw } from 'lucide-react';
|
import { RotateCcw } from 'lucide-react';
|
||||||
|
|
||||||
export function LoadTimeSimulator({ className = '' }: { className?: string }) {
|
export function LoadTimeSimulator({ className = '' }: { className?: string }) {
|
||||||
const [isRunning, setIsRunning] = useState(false);
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
const [timeElapsed, setTimeElapsed] = useState(0);
|
const [timeElapsed, setTimeElapsed] = useState(0);
|
||||||
const [legacyState, setLegacyState] = useState(0);
|
const [legacyState, setLegacyState] = useState(0);
|
||||||
|
const [hasAutoStarted, setHasAutoStarted] = useState(false);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [mintelState, setMintelState] = useState(0);
|
const [mintelState, setMintelState] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -36,6 +38,25 @@ export function LoadTimeSimulator({ className = '' }: { className?: string }) {
|
|||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [isRunning, timeElapsed]);
|
}, [isRunning, timeElapsed]);
|
||||||
|
|
||||||
|
// Auto-start the race when scrolled into viewport
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasAutoStarted) return;
|
||||||
|
const el = containerRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setHasAutoStarted(true);
|
||||||
|
setIsRunning(true);
|
||||||
|
observer.disconnect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.4 }
|
||||||
|
);
|
||||||
|
observer.observe(el);
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, [hasAutoStarted]);
|
||||||
|
|
||||||
const startRace = () => {
|
const startRace = () => {
|
||||||
setTimeElapsed(0);
|
setTimeElapsed(0);
|
||||||
setLegacyState(0);
|
setLegacyState(0);
|
||||||
@@ -45,7 +66,7 @@ export function LoadTimeSimulator({ className = '' }: { className?: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Reveal direction="up" delay={0.1}>
|
<Reveal direction="up" delay={0.1}>
|
||||||
<div className={`not-prose max-w-4xl mx-auto my-12 relative group ${className}`}>
|
<div ref={containerRef} className={`not-prose max-w-4xl mx-auto my-12 relative group ${className}`}>
|
||||||
<div className="absolute -inset-1 bg-gradient-to-r from-red-100 to-emerald-100 rounded-3xl blur opacity-30" />
|
<div className="absolute -inset-1 bg-gradient-to-r from-red-100 to-emerald-100 rounded-3xl blur opacity-30" />
|
||||||
|
|
||||||
<div id="sim-load-time" className="relative bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden flex flex-col">
|
<div id="sim-load-time" className="relative bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden flex flex-col">
|
||||||
@@ -63,13 +84,15 @@ export function LoadTimeSimulator({ className = '' }: { className?: string }) {
|
|||||||
Simulieren Sie den Unterschied zwischen dynamischem Server-Rendering (PHP/MySQL) und statischer Edge-Auslieferung (<span className="font-mono bg-slate-200 px-1 rounded text-[10px]">TTV < 500ms</span>).
|
Simulieren Sie den Unterschied zwischen dynamischem Server-Rendering (PHP/MySQL) und statischer Edge-Auslieferung (<span className="font-mono bg-slate-200 px-1 rounded text-[10px]">TTV < 500ms</span>).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
{timeElapsed > 0 && !isRunning && (
|
||||||
onClick={startRace}
|
<button
|
||||||
className="shrink-0 flex items-center gap-2 px-6 py-2.5 bg-slate-900 !text-white rounded-full font-bold text-sm hover:hover:bg-black hover:scale-105 active:scale-95 transition-all shadow-md"
|
onClick={startRace}
|
||||||
>
|
className="shrink-0 flex items-center gap-2 px-6 py-2.5 bg-slate-900 !text-white rounded-full font-bold text-sm hover:hover:bg-black hover:scale-105 active:scale-95 transition-all shadow-md"
|
||||||
{timeElapsed > 0 ? <RotateCcw size={16} /> : <Play size={16} />}
|
>
|
||||||
{timeElapsed > 0 ? "Neustart" : "Rennen Starten"}
|
<RotateCcw size={16} />
|
||||||
</button>
|
Neustart
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 divide-y md:divide-y-0 md:divide-x divide-slate-100 bg-slate-50/50">
|
<div className="grid md:grid-cols-2 divide-y md:divide-y-0 md:divide-x divide-slate-100 bg-slate-50/50">
|
||||||
|
|||||||
@@ -16,6 +16,16 @@ export const CarouselBlock: MintelBlock = {
|
|||||||
name: "slides",
|
name: "slides",
|
||||||
type: "array",
|
type: "array",
|
||||||
fields: [
|
fields: [
|
||||||
|
{
|
||||||
|
name: "title",
|
||||||
|
type: "text",
|
||||||
|
admin: { description: "Titel der Slide-Karte." },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "content",
|
||||||
|
type: "textarea",
|
||||||
|
admin: { description: "Beschreibungstext der Slide-Karte." },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "image",
|
name: "image",
|
||||||
type: "upload",
|
type: "upload",
|
||||||
|
|||||||
Reference in New Issue
Block a user