adding new post page and button on home page
This commit is contained in:
parent
d454b337b9
commit
256d76dbca
@ -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 (
|
||||
<>
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
9
app/(app)/article/new/layout.tsx
Normal file
9
app/(app)/article/new/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
23
app/(app)/article/new/page.tsx
Normal file
23
app/(app)/article/new/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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();
|
||||
},
|
||||
});
|
||||
|
||||
|
149
components/LongForm/ToolBar.tsx
Normal file
149
components/LongForm/ToolBar.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
69
containers/Article/Editor.tsx
Normal file
69
containers/Article/Editor.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user