adding new post page and button on home page

This commit is contained in:
zmeyer44 2023-10-16 16:46:47 -04:00
parent d454b337b9
commit 256d76dbca
10 changed files with 288 additions and 14 deletions

View File

@ -36,11 +36,12 @@ const LoginModal = dynamic(() => import("@/components/Modals/Login"), {
export default function AuthActions() {
const modal = useModal();
const { currentUser, logout, attemptLogin } = useCurrentUser();
const { ndk } = useNDK();
useEffect(() => {
if (!currentUser) {
if (ndk && !currentUser) {
void attemptLogin();
}
}, []);
}, [ndk]);
if (currentUser) {
return (
<>

View File

@ -27,7 +27,6 @@ import { getTagValues, getTagsValues } from "@/lib/nostr/utils";
import { NOTABLE_ACCOUNTS } from "@/constants";
import { type NDKKind } from "@nostr-dev-kit/ndk";
import { uniqBy } from "ramda";
import useProfile from "@/lib/hooks/useProfile";
import ListCard from "@/components/ListCard";
export default function FeaturedLists() {
@ -66,7 +65,6 @@ export default function FeaturedLists() {
<SectionHeader>
<div className="center gap-x-2">
<SectionTitle>Featured Lists</SectionTitle>
{processedEvents.length}
</div>
<Button variant={"ghost"}>
View all <RiArrowRightLine className="ml-1 h-4 w-4" />

View File

@ -1,8 +1,10 @@
import dynamic from "next/dynamic";
import Link from "next/link";
import ExploreCreators from "./_sections/ExploreCreators";
import LongFormContentSection from "./_sections/LongFormContent";
import BecomeACreator from "./_sections/BecomeACreator";
import { RiAddFill } from "react-icons/ri";
import { Button } from "@/components/ui/button";
const LiveStreamingSection = dynamic(
() => import("./_sections/LiveStreaming"),
{
@ -23,6 +25,13 @@ export default function Page() {
<BecomeACreator />
<LiveStreamingSection />
<FeaturedListsSection />
<div className="z-overlay- fixed bottom-[calc(var(--bottom-nav-height)_+_20px)] right-[15px] sm:hidden">
<Link href="/article/new">
<Button size={"icon"} className="h-[50px] w-[50px]">
<RiAddFill className="h-[32px] w-[32px]" />
</Button>
</Link>
</div>
</div>
);
}

View File

@ -0,0 +1,9 @@
import { ReactElement, ReactNode } from "react";
export default function ModalLayout(props: { children: ReactElement }) {
return (
<div className="z-overlay fixed inset-y-[10px] left-[10px] right-[10px] overflow-hidden overflow-y-auto rounded-lg border bg-background px-4 sm:left-[calc(10px_+_var(--sidebar-closed-width))] xl:left-[calc(10px_+_var(--sidebar-open-width))]">
{props.children}
</div>
);
}

View File

@ -0,0 +1,23 @@
"use client";
import { useEffect } from "react";
import Editor_ from "@/containers/Article/Editor";
import Editor from "@/components/LongForm/Editor";
import { useNDK } from "@nostr-dev-kit/ndk-react";
import { nip19 } from "nostr-tools";
import Spinner from "@/components/spinner";
import useEvents from "@/lib/hooks/useEvents";
export default function EditorPage({
params: { key },
}: {
params: {
key: string;
};
}) {
return (
<div className="">
{/* <Editor /> */}
<Editor_ />
</div>
);
}

View File

@ -7,16 +7,24 @@ import "@blocknote/core/style.css";
interface EditorProps {
editable?: boolean;
onContentChange: (text: string) => void;
}
const Editor = ({ editable }: EditorProps) => {
const Editor = ({ editable, onContentChange }: EditorProps) => {
const { resolvedTheme } = useTheme();
const [content, setContent] = useState("");
const editor: BlockNoteEditor = useBlockNote({
editable,
onEditorContentChange: (editor) => {
setContent(JSON.stringify(editor.topLevelBlocks, null, 2));
// Converts the editor's contents from Block objects to Markdown and
// saves them.
const saveBlocksAsMarkdown = async () => {
const markdown: string = await editor.blocksToMarkdown(
editor.topLevelBlocks,
);
onContentChange(markdown);
};
saveBlocksAsMarkdown();
},
});

View File

@ -0,0 +1,149 @@
"use client";
import { useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import { Textarea } from "../ui/textarea";
import useAutosizeTextArea from "@/lib/hooks/useAutoSizeTextArea";
import { HiOutlinePhoto } from "react-icons/hi2";
import { cn } from "@/lib/utils";
interface ToolbarProps {
initialData?: {
title?: string;
summary?: string;
image?: string;
};
preview?: boolean;
onSubmit: ({
title,
summary,
image,
}: {
title: string;
summary: string;
image?: string;
}) => void;
}
export const Toolbar = ({ initialData, preview, onSubmit }: ToolbarProps) => {
const [loading, setLoading] = useState(false);
const [title, setTitle] = useState(initialData?.title ?? "");
const [summary, setSummary] = useState(initialData?.summary ?? "");
const [image, setImage] = useState(initialData?.image);
const titleRef = useRef<HTMLTextAreaElement>(null);
const summaryRef = useRef<HTMLTextAreaElement>(null);
useAutosizeTextArea(titleRef.current, title);
useAutosizeTextArea(summaryRef.current, summary);
const [isEditingTitle, setIsEditingTitle] = useState(false);
const [isEditingSummary, setIsEditingSummary] = useState(false);
const [value, setValue] = useState(initialData?.title);
const update = () => {
console.log("Update");
};
const enableInput = (type: "title" | "summary") => {
if (preview) return;
if (type === "title") {
setIsEditingTitle(true);
setTimeout(() => {
titleRef.current?.focus();
}, 0);
} else {
summaryRef.current?.focus();
setIsEditingSummary(true);
setTimeout(() => {
summaryRef.current?.focus();
}, 0);
}
};
const disableInput = () => {
setIsEditingTitle(false);
setIsEditingSummary(false);
};
const handleSubmit = async () => {
try {
setLoading(true);
await onSubmit({ title, summary, image });
setLoading(false);
} catch (err) {
console.log("Error", err);
}
};
const onKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (event.key === "Enter") {
event.preventDefault();
disableInput();
}
};
return (
<div className="group relative">
<div className="flex items-center gap-x-1 py-4">
<div className="opacity-0 group-hover:opacity-100">
{!initialData?.image && !preview && (
<Button
className="text-xs text-muted-foreground"
variant="outline"
size="sm"
>
<HiOutlinePhoto className="mr-2 h-4 w-4" />
Add cover
</Button>
)}
</div>
<Button loading={loading} onClick={handleSubmit} className="ml-auto">
Publish
</Button>
</div>
{isEditingTitle && !preview ? (
<Textarea
ref={titleRef}
onBlur={disableInput}
onKeyDown={onKeyDown}
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Untitled"
className={cn(
"resize-none break-words border-0 bg-transparent p-0 text-5xl font-bold text-foreground shadow-none outline-none focus-visible:ring-0",
title === "" && "max-h-[60px]",
)}
/>
) : (
<div
onClick={() => enableInput("title")}
className="break-words pb-[11.5px] text-5xl font-bold text-foreground outline-none"
>
{title || "Untitled"}
</div>
)}
{isEditingSummary && !preview ? (
<Textarea
ref={summaryRef}
onBlur={disableInput}
onKeyDown={onKeyDown}
value={summary}
onChange={(e) => setSummary(e.target.value)}
placeholder="Write a short summary of your content..."
className={cn(
"min-h-0 resize-none break-words border-0 bg-transparent p-0 text-base text-foreground shadow-none outline-none focus-visible:ring-0",
summary === "" && "!max-h-[35.5px]",
)}
/>
) : (
<div
onClick={() => enableInput("summary")}
className="break-words pb-[11.5px] text-base text-muted-foreground/80 outline-none"
>
{summary || "Write a short summary of your content..."}
</div>
)}
</div>
);
};

View File

@ -4,18 +4,22 @@ import dynamic from "next/dynamic";
import { useTheme } from "next-themes";
import { BlockNoteEditor, Block } from "@blocknote/core";
import { BlockNoteView, useBlockNote } from "@blocknote/react";
import Spinner from "../spinner";
import { Skeleton } from "@/components/ui/skeleton";
import { Toolbar } from "./ToolBar";
type MarkdoneProps = {
content?: string;
editable?: boolean;
};
export default function Markdown({ content }: MarkdoneProps) {
export default function Markdown({ content, editable = false }: MarkdoneProps) {
const { resolvedTheme } = useTheme();
const [loading, setLoading] = useState(true);
const editor: BlockNoteEditor = useBlockNote({
editable: false,
editable,
onEditorContentChange: (e) => {
console.log("EDITOR CHANGE", e);
},
});
useEffect(() => {
@ -38,8 +42,11 @@ export default function Markdown({ content }: MarkdoneProps) {
if (loading) {
return (
<div className="center py-20 text-primary">
<Spinner />
<div className="space-y-4 pl-8 pt-5">
<Skeleton className="h-14 w-[50%]" />
<Skeleton className="h-4 w-[80%]" />
<Skeleton className="h-4 w-[40%]" />
<Skeleton className="h-4 w-[60%]" />
</div>
);
}

View File

@ -0,0 +1,69 @@
"use client";
import { useState } from "react";
import dynamic from "next/dynamic";
import Image from "next/image";
import { Button } from "@/components/ui/button";
import { RiCloseFill } from "react-icons/ri";
import { Avatar, AvatarImage, AvatarFallback } from "@radix-ui/react-avatar";
import { useRouter } from "next/navigation";
import { formatDate } from "@/lib/utils/dates";
import Actions from "./Actions";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { getTagAllValues, getTagValues } from "@/lib/nostr/utils";
import Editor from "@/components/LongForm/Editor";
import { Toolbar } from "@/components/LongForm/ToolBar";
type ArticleProps = {
event?: NDKEvent;
};
export default function EditorPage({ event }: ArticleProps) {
const router = useRouter();
const [content, setContent] = useState("");
async function handleSubmit({
title,
summary,
image,
}: {
title: string;
summary: string;
image?: string;
}) {
console.log("Writing", title, summary, image, content);
}
return (
<div className="relative @container">
<div className="sticky inset-x-0 top-0 z-10 flex items-center justify-between border-b bg-background pb-4 pt-4">
<div className="center gap-x-3">
<span className="text-xs uppercase text-muted-foreground">
New Long Form
</span>
</div>
<Button
onClick={() => {
if (sessionStorage.getItem("RichHistory")) {
void router.back();
} else {
void router.push("/app");
}
}}
size="icon"
variant={"outline"}
className=""
>
<RiCloseFill className="h-5 w-5" />
</Button>
</div>
<div className="h-[20px] w-full"></div>
<article className="relative mx-auto -mt-5 max-w-3xl">
<div className="pb-4 pl-[54px]">
<Toolbar onSubmit={handleSubmit} />
</div>
<Editor
editable={true}
onContentChange={(content) => setContent(content)}
/>
</article>
</div>
);
}

View File

@ -3,7 +3,7 @@ import { useEffect } from "react";
// Updates the height of a <textarea> when the value changes.
const useAutosizeTextArea = (
textAreaRef: HTMLTextAreaElement | null,
value: string
value: string,
) => {
useEffect(() => {
if (textAreaRef) {
@ -13,6 +13,7 @@ const useAutosizeTextArea = (
// We then set the height directly, outside of the render loop
// Trying to set this with state or a ref will product an incorrect value.
console.log("setting h", scrollHeight);
textAreaRef.style.height = `${scrollHeight}px`;
}
}, [textAreaRef, value]);