feat: add copy button

This commit is contained in:
Kirill Siukhin 2025-07-10 19:12:02 +05:00
parent b3ead435cf
commit 8aaf8bb42d
13 changed files with 62 additions and 56 deletions

View File

@ -1,14 +0,0 @@
import Header from "@/components/Header";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<>
<Header allowExport />
{children}
</>
);
}

View File

@ -1,16 +0,0 @@
import Header from "@/components/Header";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<>
<Header />
<div className="m-4">
{children}
</div>
</>
);
}

View File

@ -1,5 +1,6 @@
import { Metadata } from "next";
import { Noto_Sans_Mono } from "next/font/google";
import Header from "@/components/Header";
import "./globals.css";
const notoSansMono = Noto_Sans_Mono({
@ -20,7 +21,10 @@ export default function RootLayout({
return (
<html lang="en">
<body className={`${notoSansMono.variable} font-mono antialiased`}>
{children}
<Header />
<div className="m-4">
{children}
</div>
</body>
</html>
);

View File

@ -1,5 +1,7 @@
import { Metadata } from "next";
import { redirect } from "next/navigation";
import { getNotes } from "@/lib/notes";
import { getAuth } from "@/lib/auth";
import NoteCard from "@/components/ui/NoteCard";
export const metadata: Metadata = {
@ -8,11 +10,16 @@ export const metadata: Metadata = {
};
export default async function Notes() {
const notes = await getNotes();
const auth = await getAuth();
if (!auth) {
redirect("/auth");
}
const notes = await getNotes(auth.id);
return (
<>
<h1 className="font-bold text-xl mb-4">Notes</h1>
<h1 className="font-bold text-xl mb-4">Notes of {auth.username}:</h1>
{notes ? (
notes.length > 0 ? (
notes.map((note) => <NoteCard key={note.id} note={note} />)

View File

@ -1,10 +1,10 @@
import Link from "next/link";
import { ArrowRightFromLine, CircleQuestionMark, List, Plus, UserRound, UserRoundMinus } from "lucide-react";
import { CircleQuestionMark, List, Plus, UserRound, UserRoundMinus } from "lucide-react";
import { logOut } from "@/app/actions";
import { getAuth } from "@/lib/auth";
import HeaderButton from "./ui/HeaderButton";
export default async function Header({ allowExport = false }: { allowExport?: boolean }) {
export default async function Header() {
const auth = await getAuth();
return (
@ -13,19 +13,16 @@ export default async function Header({ allowExport = false }: { allowExport?: bo
<HeaderButton title="rhyme" className="bg-sky-600 hover:bg-sky-500" />
</Link>
<div className="flex gap-2">
{auth && (
<>
{auth && (
<div className="flex gap-2">
<Link href="/notes/new">
<HeaderButton title="new" icon={<Plus size={20} />} />
</Link>
<Link href="/notes">
<HeaderButton title="list" icon={<List size={20} />} />
</Link>
</>
)}
{allowExport && <HeaderButton title="export" icon={<ArrowRightFromLine size={20} />} />}
</div>
</div>
)}
<div className="flex gap-2 ml-auto">
<Link href="/about">

View File

@ -1,7 +1,7 @@
"use client";
import { useReducer } from "react";
import { Plus } from "lucide-react";
import { Copy, Plus } from "lucide-react";
import { editorReducer } from "@/lib/editorReducer";
import IconOnlyButton from "../ui/IconOnlyButton";
import Block from "./Block";
@ -23,11 +23,18 @@ export default function Editor() {
dispatch({ type: "add_block" });
}
const handleCopy = () => {
dispatch({ type: "copy" });
}
return (
<div className="flex flex-col items-center px-4 max-w-2xl w-full gap-4">
<input className="font-bold text-xl w-full text-center focus:outline-none" defaultValue="Untitled" />
{state.map((block) => <Block key={block.id} block={block} dispatch={dispatch} /> )}
<IconOnlyButton onClick={handleAddBlock} icon={<Plus size={24} />} />
<div className="flex gap-2">
<IconOnlyButton onClick={handleAddBlock} icon={<Plus size={24} />} />
<IconOnlyButton onClick={handleCopy} icon={<Copy size={24} />} title="Copy note to clipboard" />
</div>
</div>
);
}

View File

@ -5,6 +5,15 @@ import { notesTable } from "@/lib/db/schema";
import { deleteNote } from "@/lib/notes";
import IconOnlyButton from "./IconOnlyButton";
function makeTimestamp(date: Date) {
const day = date.getDate();
const month = date.getMonth() + 1;
const year = date.getFullYear();
const hours = date.getHours();
const minutes = date.getMinutes();
return `${day}.${month}.${year} ${hours}:${minutes}`;
}
export default function NoteCard({ note }: { note: typeof notesTable.$inferSelect }) {
const handleDeleteNote = async () => {
"use server";
@ -13,11 +22,11 @@ export default function NoteCard({ note }: { note: typeof notesTable.$inferSelec
}
return (
<div className="flex items-center mb-2 gap-4">
<Link href={`/notes/${note.id}`} className="flex flex-col border-2 border-neutral-700 py-3 px-4 rounded-lg hover:bg-neutral-800 w-full">
<div className="flex items-center mb-3 gap-4">
<Link href={`/notes/${note.id}`} className="flex flex-col border border-neutral-500 py-3 px-4 rounded-lg hover:bg-neutral-800 w-full">
<h1 className="font-bold">{note.name}</h1>
<i className="text-neutral-400 text-sm">Creation date: {note.creationTime.toString()}</i>
<i className="text-neutral-400 text-sm">Last time edited: {note.lastEdited.toString()}</i>
<i className="text-neutral-400 text-sm">Last time edited: {makeTimestamp(note.lastEdited)}</i>
<i className="text-neutral-400 text-sm">Creation date: {makeTimestamp(note.creationTime)}</i>
</Link>
<IconOnlyButton icon={<X size={24} />} onClick={handleDeleteNote} />
</div>

View File

@ -14,6 +14,7 @@ type EditorState = IBlock[];
export type Action =
| { type: "add_block" }
| { type: "copy" }
| { type: "delete_block", blockId: string }
| { type: "add_line", blockId: string }
| { type: "delete_line", blockId: string }
@ -36,6 +37,21 @@ export function editorReducer(state: EditorState, action: Action): EditorState {
})),
}
];
case "copy":
let copyText = "";
state.forEach((block) => {
if (block.tag !== "") {
copyText += `[${block.tag}]\n`;
}
block.lines.forEach((line) => copyText += line.text + "\n")
copyText += "\n";
});
navigator.clipboard.writeText(copyText);
return state;
case "delete_block":
return state.filter((block) => block.id !== action.blockId);

View File

@ -1,17 +1,13 @@
import { eq } from "drizzle-orm";
import { desc, eq } from "drizzle-orm";
import { notesTable } from "./db/schema";
import { getAuth } from "./auth";
import { db } from "./db";
export async function getNotes() {
const auth = await getAuth();
if (!auth) {
return null;
}
export async function getNotes(authorId: string) {
return db.select()
.from(notesTable)
.where(eq(notesTable.authorId, auth.id));
.where(eq(notesTable.authorId, authorId))
.orderBy(desc(notesTable.lastEdited));
}
export async function createNote() {