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
27 changes: 27 additions & 0 deletions app/[[...mdxPath]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { generateStaticParamsFor, importPage } from "nextra/pages";
import { useMDXComponents as getMDXComponents } from "../../mdx-components";

export const generateStaticParams = generateStaticParamsFor("mdxPath");

export async function generateMetadata(props: {
params: Promise<{ mdxPath?: string[] }>;
}) {
const params = await props.params;
const { metadata } = await importPage(params.mdxPath);
return metadata;
}

const Wrapper = getMDXComponents().wrapper;

export default async function Page(props: {
params: Promise<{ mdxPath?: string[] }>;
}) {
const params = await props.params;
const result = await importPage(params.mdxPath);
const { default: MDXContent, toc, metadata, sourceCode } = result;
return (
<Wrapper toc={toc} metadata={metadata} sourceCode={sourceCode}>
<MDXContent {...props} params={params} />
</Wrapper>
);
}
42 changes: 42 additions & 0 deletions app/api/raw-mdx/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { promises as fs } from "fs";
import path from "path";
import { NextResponse, type NextRequest } from "next/server";

export async function GET(req: NextRequest) {
try {
const doc = req.nextUrl.searchParams.get("doc");
if (doc !== "js_api") {
return NextResponse.json(
{ message: "Invalid document requested" },
{ status: 400 }
);
}

const projectRoot = process.cwd();
const mdxAbsolutePath = path.join(
projectRoot,
"content",
"docs",
"api",
"js.mdx"
);
const content = await fs.readFile(mdxAbsolutePath, "utf8");

return new NextResponse(content, {
status: 200,
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control":
"public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400",
},
});
} catch (error) {
return NextResponse.json(
{
message: "Failed to read MDX file",
error: (error as Error).message,
},
{ status: 500 }
);
}
}
164 changes: 164 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import type { Metadata, Viewport } from "next";
import type { ReactNode } from "react";
import Image from "next/image";
import Script from "next/script";
import { Footer, Layout, Navbar } from "nextra-theme-docs";
import { Head } from "nextra/components";
import { getPageMap } from "nextra/page-map";
import LandingFooter from "../components/landing/Footer";
import LanguageDropdown from "../components/LanguageDropdown";
import RouteBodyClass from "../components/RouteBodyClass";

import "@fontsource/poppins/100.css";
import "@fontsource/poppins/200.css";
import "@fontsource/poppins/300.css";
import "@fontsource/poppins/400.css";
import "@fontsource/poppins/500.css";
import "@fontsource/poppins/600.css";
import "@fontsource/poppins/700.css";
import "@fontsource/poppins/800.css";
import "../styles/quill.core.css";
import "../styles/quill.loro.css";
import "../style.css";
import "nextra-theme-docs/style-prefixed.css";

const DEFAULT_IMAGE = "https://i.ibb.co/T1x1bSf/IMG-8191.jpg";

export const metadata: Metadata = {
metadataBase: new URL("https://loro.dev"),
title: {
default: "Loro",
template: "%s – Loro",
},
description: "Loro",
applicationName: "Loro",
appleWebApp: {
title: "Loro",
},
icons: {
other: [
{ rel: "msapplication-TileImage", url: "/favicon.ico" },
],
},
twitter: {
card: "summary_large_image",
site: "@loro_dev",
images: DEFAULT_IMAGE,
},
openGraph: {
images: DEFAULT_IMAGE,
},
alternates: {
types: {
"application/rss+xml": [
{ url: "/blog.xml", title: "Loro Blog" },
{ url: "/changelog.xml", title: "Loro Changelog" },
],
},
},
other: {
"msapplication-TileColor": "#fff",
"Content-Language": "en",
},
};

export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
themeColor: "#fff",
};

const logo = (
<span
className="flex"
style={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
verticalAlign: "middle",
}}
>
<Image
src="/LORO_PURE.svg"
alt="Logo"
width={24}
height={24}
style={{ margin: "0 6px", display: "inline-block" }}
/>
Loro
</span>
);

const navbar = (
<Navbar
logo={logo}
projectLink="https://github.com/loro-dev/loro"
chatLink="https://discord.gg/tUsBSVfqzf"
>
<LanguageDropdown />
</Navbar>
);

const footer = (
<Footer className="!p-0 !m-0 !block">
<LandingFooter />
</Footer>
);

export default async function RootLayout({
children,
}: {
children: ReactNode;
}) {
const pageMap = await getPageMap();
return (
<html lang="en" dir="ltr" suppressHydrationWarning>
<Head />
<body>
<RouteBodyClass />
<Script
strategy="afterInteractive"
src="https://www.googletagmanager.com/gtag/js?id=G-M8FTP4QZ81"
/>
<Script
id="google-analytics"
strategy="afterInteractive"
>
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-M8FTP4QZ81');
`}
</Script>
<Script id="ms-clarity" strategy="afterInteractive">
{`
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "mmgroay4p9");
`}
</Script>
<Script
strategy="afterInteractive"
src="https://us.umami.is/script.js"
data-website-id="5a4c9e46-22c9-46ee-82d8-901253485cf1"
/>
<Layout
navbar={navbar}
pageMap={pageMap}
docsRepositoryBase="https://github.com/loro-dev/loro-docs/tree/main"
footer={footer}
sidebar={{ defaultMenuCollapseLevel: 1, autoCollapse: true }}
copyPageButton={false}
darkMode
nextThemes={{ defaultTheme: "dark" }}
>
{children}
</Layout>
</body>
</html>
);
}
2 changes: 2 additions & 0 deletions components/ExpandableContent.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { useEffect, useRef, useState } from "react";

const MaxHeight = 300;
Expand Down
15 changes: 15 additions & 0 deletions components/RouteBodyClass.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import { usePathname } from "next/navigation";
import { useEffect } from "react";

const PLAIN_ROUTE_PATTERNS = [/^\/blog\/?$/, /^\/changelog\/?$/, /^\/about\/?$/];

export default function RouteBodyClass() {
const pathname = usePathname();
useEffect(() => {
const isPlain = PLAIN_ROUTE_PATTERNS.some((re) => re.test(pathname));
document.body.dataset.layout = isPlain ? "plain" : "docs";
}, [pathname]);
return null;
}
2 changes: 2 additions & 0 deletions components/Testimonial.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { useState, useRef, useEffect } from "react";

// Define CSS animations with styled component approach
Expand Down
2 changes: 2 additions & 0 deletions components/TimelineView/Cursor.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { cn } from "@/lib/utils/cn";
import {
useCurrentTimeState,
Expand Down
2 changes: 2 additions & 0 deletions components/TimelineView/GeneralToolBar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import {
Dialog,
DialogContent,
Expand Down
2 changes: 2 additions & 0 deletions components/TimelineView/PlayerToolBar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import {
Tooltip,
TooltipContent,
Expand Down
2 changes: 2 additions & 0 deletions components/TimelineView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { useIsHistoryEmpty } from "@components/landing/store/timeline-history";
import { useCallback, useEffect, useRef, useState } from "react";
import { useIsomorphicLayoutEffect } from "usehooks-ts";
Expand Down
2 changes: 2 additions & 0 deletions components/TwinEditors/ConnectionToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import {
Tooltip,
TooltipContent,
Expand Down
2 changes: 2 additions & 0 deletions components/TwinEditors/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { Separator } from "@ariakit/react";
import { ForwardedRef, forwardRef, useEffect, useRef } from "react";
import ConnectionToggle from "./ConnectionToggle";
Expand Down
12 changes: 7 additions & 5 deletions components/api/LanguageSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use client';

import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import { useSearchParams } from 'next/navigation';

const languages = [
{ id: 'js', name: 'JavaScript / TypeScript', available: true },
Expand All @@ -9,21 +11,21 @@ const languages = [
];

export default function LanguageSelector() {
const router = useRouter();
const searchParams = useSearchParams();
const [selectedLang, setSelectedLang] = useState('js');

useEffect(() => {
// Get language from URL query or localStorage
const urlLang = router.query.lang as string;
const urlLang = searchParams?.get('lang') ?? null;
const storedLang = localStorage.getItem('loro-api-lang');

if (urlLang && languages.find(l => l.id === urlLang)) {
setSelectedLang(urlLang);
localStorage.setItem('loro-api-lang', urlLang);
} else if (storedLang && languages.find(l => l.id === storedLang)) {
setSelectedLang(storedLang);
}
}, [router.query.lang]);
}, [searchParams]);

const handleLanguageChange = (langId: string) => {
const lang = languages.find(l => l.id === langId);
Expand Down
2 changes: 2 additions & 0 deletions components/counters.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

// Example from https://beta.reactjs.org/learn

import { useState } from 'react'
Expand Down
4 changes: 3 additions & 1 deletion components/landing/Background.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { useState } from "react";
import { useIsomorphicLayoutEffect } from "usehooks-ts";

Expand All @@ -15,7 +17,7 @@ export default function Background(): JSX.Element {
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
const elements: JSX.Element[] = [];
const elements: React.JSX.Element[] = [];
if (pageHeight > 0 && viewportHeight > 0) {
const limit = Math.floor((pageHeight / viewportWidth) * 100); // number of vw
// Place circles
Expand Down
2 changes: 2 additions & 0 deletions components/landing/Demonstration/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import Timeline from "@/components/TimelineView";
import TwinEditors, { TwinEditorRefs } from "@/components/TwinEditors";
import {
Expand Down
2 changes: 2 additions & 0 deletions components/landing/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import React, { SVGProps } from "react";

export default function Footer(): JSX.Element {
Expand Down
2 changes: 2 additions & 0 deletions components/richtextDemo.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import dynamic from "next/dynamic";

export default dynamic(
Expand Down
2 changes: 2 additions & 0 deletions components/video.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import dynamic from 'next/dynamic'

export const ReactPlayer = dynamic(() => import('react-player'), { ssr: false })
Loading
Loading