Add switch between markdown or raw files on rich preview and readme

This commit is contained in:
mbaharip
2024-04-11 22:52:24 +07:00
parent ac7449f967
commit bccd550275
7 changed files with 290 additions and 135 deletions
+51 -32
View File
@@ -10,6 +10,7 @@ import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import remarkSlug from "remark-slug";
import remarkToc from "remark-toc";
import { cn } from "~/utils";
import Icon from "~/components/Icon";
@@ -17,41 +18,59 @@ import "./highlight.css";
type Props = {
content: string;
view: "markdown" | "raw";
};
export default function Markdown({ content }: Props) {
export default function Markdown({ content, view }: Props) {
return (
<div className='markdown w-full rounded-[var(--radius)] p-3'>
<ReactMarkdown
className='w-full'
disallowedElements={["script"]}
remarkPlugins={[remarkGfm, remarkMath, remarkSlug, remarkToc]}
rehypePlugins={[
rehypeKatex,
rehypeRaw,
[rehypePrism, { ignoreMissing: true }],
]}
components={{
p: ({ node, children, ...props }) => (
<p
{...props}
className='paragraph text-balance'
>
{children}
</p>
),
pre: PreComponent,
code: ({ node, inline, className, children, ...props }) => (
<code
className={`${className} font-mono !text-sm`}
{...props}
>
{children}
</code>
),
}}
<div className='flex flex-col p-3'>
<div
className={cn(
"markdown w-full rounded-[var(--radius)]",
view !== "raw" && "hidden",
)}
>
{content}
</ReactMarkdown>
<pre className='w-full whitespace-pre-wrap break-words bg-transparent !p-0 shadow shadow-background'>
{content}
</pre>
</div>
<div
className={cn(
"markdown w-full rounded-[var(--radius)]",
view !== "markdown" && "hidden",
)}
>
<ReactMarkdown
className='w-full'
disallowedElements={["script"]}
remarkPlugins={[remarkGfm, remarkMath, remarkSlug, remarkToc]}
rehypePlugins={[
rehypeKatex,
rehypeRaw,
[rehypePrism, { ignoreMissing: true }],
]}
components={{
p: ({ node, children, ...props }) => (
<p
{...props}
className='paragraph text-balance'
>
{children}
</p>
),
pre: PreComponent,
code: ({ node, inline, className, children, ...props }) => (
<code
className={`${className} font-mono !text-sm`}
{...props}
>
{children}
</code>
),
}}
>
{content}
</ReactMarkdown>
</div>
</div>
);
}
+75
View File
@@ -0,0 +1,75 @@
"use client";
import { useState } from "react";
import { z } from "zod";
import { Schema_File } from "~/schema";
import { Card, CardContent } from "~/components/ui/card";
import { getFileType } from "~/utils/previewHelper";
import PreviewAction from "./@preview.action";
import PreviewAudio from "./@preview.audio";
import PreviewDoc from "./@preview.doc";
import PreviewImage from "./@preview.image";
import PreviewManga from "./@preview.manga";
import PreviewRich from "./@preview.rich";
import PreviewUnknown from "./@preview.unknown";
import PreviewVideo from "./@preview.video";
import RichHeader from "./@rich-header";
type Props = {
data: z.infer<typeof Schema_File>;
fileType: "unknown" | ReturnType<typeof getFileType>;
};
export default function FilePreviewLayout({ data, fileType }: Props) {
const [view, setView] = useState<"markdown" | "raw">("markdown");
return (
<>
<Card>
<RichHeader
title={data.name}
view={view}
onViewChange={setView}
/>
<CardContent className='p-1.5 pt-0 tablet:p-3 tablet:pt-0'>
<div className='px-3'>
{fileType === "image" ? (
<PreviewImage file={data} />
) : fileType === "audio" ? (
<PreviewAudio file={data} />
) : fileType === "video" ? (
<PreviewVideo file={data} />
) : fileType === "code" ? (
<PreviewRich
file={data}
code
view={view}
/>
) : fileType === "text" ? (
<PreviewRich
file={data}
view={view}
/>
) : fileType === "markdown" ? (
<PreviewRich
file={data}
view={view}
/>
) : fileType === "document" ? (
<PreviewDoc file={data} />
) : fileType === "pdf" ? (
<PreviewDoc file={data} />
) : fileType === "manga" ? (
<PreviewManga file={data} />
) : (
<PreviewUnknown />
)}
</div>
</CardContent>
</Card>
<PreviewAction file={data} />
</>
);
}
+25 -12
View File
@@ -13,9 +13,11 @@ import { GetContent } from "./actions";
type Props = {
file: z.infer<typeof Schema_File>;
view: "markdown" | "raw";
code?: boolean;
};
export default function PreviewRich({ file, code }: Props) {
export default function PreviewRich({ file, code, view }: Props) {
const [fetchedContent, setFetchedContent] = useState<string>("");
const [content, setContent] = useState<string>("");
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string>("");
@@ -30,11 +32,12 @@ export default function PreviewRich({ file, code }: Props) {
setError("Looks like there is no content to preview");
return;
}
if (code) {
setContent(`\`\`\`${file.fileExtension}\n${text}\`\`\``);
} else {
setContent(text);
}
// setFetchedContent(text);
// if (code) {
// setContent(`\`\`\`${file.fileExtension}\n${text}\`\`\``);
// } else {
setContent(text);
// }
} catch (error) {
const e = error as Error;
console.error(e);
@@ -46,7 +49,7 @@ export default function PreviewRich({ file, code }: Props) {
}, [file, code]);
return (
<div className='flex min-h-[33dvh] w-full items-center justify-center py-3'>
<div className='flex h-fit min-h-[33dvh] w-full items-center justify-center py-3'>
{loading ? (
<div
className={cn(
@@ -72,17 +75,27 @@ export default function PreviewRich({ file, code }: Props) {
</div>
) : (
<div
className={cn("relative w-full", expand ? "h-full" : "max-h-[50dvh]")}
className={cn(
"relative w-full overflow-hidden",
expand ? "h-full" : "max-h-[50dvh]",
)}
>
<div
className={cn(
"h-full w-full",
"w-full overflow-hidden",
expand
? " [mask-image:linear-gradient(180deg,white_65%,white]"
: "max-h-[50dvh] [mask-image:linear-gradient(180deg,white_65%,rgba(255,255,255,0))]",
? "[mask-image:linear-gradient(180deg,white_65%,white] h-full"
: "h-[50dvh] max-h-[50dvh] [mask-image:linear-gradient(180deg,white_65%,rgba(255,255,255,0))]",
)}
>
<Markdown content={content} />
<Markdown
content={
code && view === "markdown"
? `\`\`\`${file.fileExtension}\n${content}\`\`\``
: content
}
view={view}
/>
</div>
<div
className={cn(
+37
View File
@@ -0,0 +1,37 @@
"use client";
import { useState } from "react";
import { Card, CardContent } from "~/components/ui/card";
import Markdown from "./@markdown";
import RichHeader from "./@rich-header";
type Props = {
content: string;
title: string;
};
export default function Readme({ content, title }: Props) {
const [view, setView] = useState<"markdown" | "raw">("markdown");
return (
<div
slot='readme'
className='w-full'
>
<Card>
<RichHeader
title={title}
view={view}
onViewChange={setView}
/>
<CardContent className='p-1.5 pt-0 tablet:p-3 tablet:pt-0'>
<Markdown
content={content}
view={view}
/>
</CardContent>
</Card>
</div>
);
}
+39
View File
@@ -0,0 +1,39 @@
"use client";
import { Button } from "~/components/ui/button";
import { CardHeader, CardTitle } from "~/components/ui/card";
import { Separator } from "~/components/ui/separator";
type Props = {
title: string;
view: "markdown" | "raw";
onViewChange: (value: "markdown" | "raw") => void;
};
export default function RichHeader({ title, view, onViewChange }: Props) {
return (
<CardHeader className='pb-0'>
<div className='flex flex-col gap-3 mobile:flex-row mobile:items-center mobile:justify-between'>
<CardTitle>{title}</CardTitle>
<div className='flex w-full items-center mobile:w-fit'>
<Button
size={"sm"}
variant={view === "markdown" ? "default" : "secondary"}
onClick={() => onViewChange("markdown")}
className='w-full rounded-r-none mobile:w-fit'
>
Markdown
</Button>
<Button
size={"sm"}
variant={view === "raw" ? "default" : "secondary"}
onClick={() => onViewChange("raw")}
className='w-full rounded-l-none mobile:w-fit'
>
Raw
</Button>
</div>
</div>
<Separator />
</CardHeader>
);
}
+44 -76
View File
@@ -14,16 +14,9 @@ import { getFileType } from "~/utils/previewHelper";
import FileBrowser from "../@explorer";
import Header from "../@header";
import HeaderButton from "../@header.button";
import Markdown from "../@markdown";
import Password from "../@password";
import PreviewAction from "../@preview.action";
import PreviewAudio from "../@preview.audio";
import PreviewDoc from "../@preview.doc";
import PreviewImage from "../@preview.image";
import PreviewManga from "../@preview.manga";
import PreviewRich from "../@preview.rich";
import PreviewUnknown from "../@preview.unknown";
import PreviewVideo from "../@preview.video";
import FilePreviewLayout from "../@preview.layout";
import Readme from "../@readme";
import {
CheckPassword,
CheckPaths,
@@ -141,76 +134,51 @@ export default async function RestPage({ params: { rest } }: Props) {
slot='content'
className='w-full'
>
<Card>
<CardHeader className='pb-0'>
{isFile ? (
<div className='flex w-full gap-3'>
<CardTitle className='line-clamp-2 flex-grow whitespace-pre-wrap break-all'>
{data.name}
</CardTitle>
</div>
) : (
<div className='flex w-full items-center justify-between gap-3'>
<CardTitle className='flex-grow'>Browse files</CardTitle>
<HeaderButton />
</div>
)}
<Separator />
</CardHeader>
<CardContent className='p-1.5 pt-0 tablet:p-3 tablet:pt-0'>
{isFile ? (
<div className='px-3'>
{fileType === "image" ? (
<PreviewImage file={data} />
) : fileType === "audio" ? (
<PreviewAudio file={data} />
) : fileType === "video" ? (
<PreviewVideo file={data} />
) : fileType === "code" ? (
<PreviewRich
file={data}
code
/>
) : fileType === "text" ? (
<PreviewRich file={data} />
) : fileType === "markdown" ? (
<PreviewRich file={data} />
) : fileType === "document" ? (
<PreviewDoc file={data} />
) : fileType === "pdf" ? (
<PreviewDoc file={data} />
) : fileType === "manga" ? (
<PreviewManga file={data} />
) : (
<PreviewUnknown />
)}
</div>
) : (
<FileBrowser
files={data.files}
nextPageToken={data.nextPageToken}
{isFile ? (
<FilePreviewLayout
data={data}
fileType={fileType || "unknown"}
/>
) : (
<>
<Card>
<CardHeader className='pb-0'>
<div className='flex w-full items-center justify-between gap-3'>
<CardTitle className='flex-grow'>Browse files</CardTitle>
<HeaderButton />
</div>
<Separator />
</CardHeader>
<CardContent className='p-1.5 pt-0 tablet:p-3 tablet:pt-0'>
<FileBrowser
files={data.files}
nextPageToken={data.nextPageToken}
/>
</CardContent>
</Card>
{readme && (
<Readme
content={readme}
title={"README.md"}
/>
// <div
// slot='readme'
// className='w-full'
// >
// <Card>
// <CardHeader className='pb-0'>
// <CardTitle>README.md</CardTitle>
// <Separator />
// </CardHeader>
// <CardContent className='p-1.5 pt-0 tablet:p-3 tablet:pt-0'>
// <Markdown content={readme} />
// </CardContent>
// </Card>
// </div>
)}
</CardContent>
</Card>
</>
)}
</div>
{readme && (
<div
slot='readme'
className='w-full'
>
<Card>
<CardHeader className='pb-0'>
<CardTitle>README.md</CardTitle>
<Separator />
</CardHeader>
<CardContent className='p-1.5 pt-0 tablet:p-3 tablet:pt-0'>
<Markdown content={readme} />
</CardContent>
</Card>
</div>
)}
{isFile && <PreviewAction file={data} />}
</div>
);
}
+19 -15
View File
@@ -6,7 +6,7 @@ import { Separator } from "~/components/ui/separator";
import FileBrowser from "./@explorer";
import Header from "./@header";
import HeaderButton from "./@header.button";
import Markdown from "./@markdown";
import Readme from "./@readme";
import { GetFiles, GetReadme } from "./actions";
export const revalidate = 300;
@@ -43,20 +43,24 @@ export default async function RootPage() {
</Card>
</div>
{readme && (
<div
slot='readme'
className='w-full'
>
<Card>
<CardHeader className='pb-0'>
<CardTitle>README.md</CardTitle>
<Separator />
</CardHeader>
<CardContent className='p-1.5 pt-0 tablet:p-3 tablet:pt-0'>
<Markdown content={readme} />
</CardContent>
</Card>
</div>
<Readme
content={readme}
title={"README.md"}
/>
// <div
// slot='readme'
// className='w-full'
// >
// <Card>
// <CardHeader className='pb-0'>
// <CardTitle>README.md</CardTitle>
// <Separator />
// </CardHeader>
// <CardContent className='p-1.5 pt-0 tablet:p-3 tablet:pt-0'>
// <Markdown content={readme} />
// </CardContent>
// </Card>
// </div>
)}
</div>
);