feat: add copy button
This commit is contained in:
parent
b3ead435cf
commit
8aaf8bb42d
@ -1,14 +0,0 @@
|
|||||||
import Header from "@/components/Header";
|
|
||||||
|
|
||||||
export default function RootLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
}>) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Header allowExport />
|
|
||||||
{children}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import { Noto_Sans_Mono } from "next/font/google";
|
import { Noto_Sans_Mono } from "next/font/google";
|
||||||
|
import Header from "@/components/Header";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const notoSansMono = Noto_Sans_Mono({
|
const notoSansMono = Noto_Sans_Mono({
|
||||||
@ -20,7 +21,10 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body className={`${notoSansMono.variable} font-mono antialiased`}>
|
<body className={`${notoSansMono.variable} font-mono antialiased`}>
|
||||||
|
<Header />
|
||||||
|
<div className="m-4">
|
||||||
{children}
|
{children}
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
import { getNotes } from "@/lib/notes";
|
import { getNotes } from "@/lib/notes";
|
||||||
|
import { getAuth } from "@/lib/auth";
|
||||||
import NoteCard from "@/components/ui/NoteCard";
|
import NoteCard from "@/components/ui/NoteCard";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
@ -8,11 +10,16 @@ export const metadata: Metadata = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default async function Notes() {
|
export default async function Notes() {
|
||||||
const notes = await getNotes();
|
const auth = await getAuth();
|
||||||
|
if (!auth) {
|
||||||
|
redirect("/auth");
|
||||||
|
}
|
||||||
|
|
||||||
|
const notes = await getNotes(auth.id);
|
||||||
|
|
||||||
return (
|
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 ? (
|
||||||
notes.length > 0 ? (
|
notes.length > 0 ? (
|
||||||
notes.map((note) => <NoteCard key={note.id} note={note} />)
|
notes.map((note) => <NoteCard key={note.id} note={note} />)
|
@ -1,10 +1,10 @@
|
|||||||
import Link from "next/link";
|
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 { logOut } from "@/app/actions";
|
||||||
import { getAuth } from "@/lib/auth";
|
import { getAuth } from "@/lib/auth";
|
||||||
import HeaderButton from "./ui/HeaderButton";
|
import HeaderButton from "./ui/HeaderButton";
|
||||||
|
|
||||||
export default async function Header({ allowExport = false }: { allowExport?: boolean }) {
|
export default async function Header() {
|
||||||
const auth = await getAuth();
|
const auth = await getAuth();
|
||||||
|
|
||||||
return (
|
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" />
|
<HeaderButton title="rhyme" className="bg-sky-600 hover:bg-sky-500" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{auth && (
|
{auth && (
|
||||||
<>
|
<div className="flex gap-2">
|
||||||
<Link href="/notes/new">
|
<Link href="/notes/new">
|
||||||
<HeaderButton title="new" icon={<Plus size={20} />} />
|
<HeaderButton title="new" icon={<Plus size={20} />} />
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/notes">
|
<Link href="/notes">
|
||||||
<HeaderButton title="list" icon={<List size={20} />} />
|
<HeaderButton title="list" icon={<List size={20} />} />
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{allowExport && <HeaderButton title="export" icon={<ArrowRightFromLine size={20} />} />}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex gap-2 ml-auto">
|
<div className="flex gap-2 ml-auto">
|
||||||
<Link href="/about">
|
<Link href="/about">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useReducer } from "react";
|
import { useReducer } from "react";
|
||||||
import { Plus } from "lucide-react";
|
import { Copy, Plus } from "lucide-react";
|
||||||
import { editorReducer } from "@/lib/editorReducer";
|
import { editorReducer } from "@/lib/editorReducer";
|
||||||
import IconOnlyButton from "../ui/IconOnlyButton";
|
import IconOnlyButton from "../ui/IconOnlyButton";
|
||||||
import Block from "./Block";
|
import Block from "./Block";
|
||||||
@ -23,11 +23,18 @@ export default function Editor() {
|
|||||||
dispatch({ type: "add_block" });
|
dispatch({ type: "add_block" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCopy = () => {
|
||||||
|
dispatch({ type: "copy" });
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center px-4 max-w-2xl w-full gap-4">
|
<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" />
|
<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} /> )}
|
{state.map((block) => <Block key={block.id} block={block} dispatch={dispatch} /> )}
|
||||||
|
<div className="flex gap-2">
|
||||||
<IconOnlyButton onClick={handleAddBlock} icon={<Plus size={24} />} />
|
<IconOnlyButton onClick={handleAddBlock} icon={<Plus size={24} />} />
|
||||||
|
<IconOnlyButton onClick={handleCopy} icon={<Copy size={24} />} title="Copy note to clipboard" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,15 @@ import { notesTable } from "@/lib/db/schema";
|
|||||||
import { deleteNote } from "@/lib/notes";
|
import { deleteNote } from "@/lib/notes";
|
||||||
import IconOnlyButton from "./IconOnlyButton";
|
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 }) {
|
export default function NoteCard({ note }: { note: typeof notesTable.$inferSelect }) {
|
||||||
const handleDeleteNote = async () => {
|
const handleDeleteNote = async () => {
|
||||||
"use server";
|
"use server";
|
||||||
@ -13,11 +22,11 @@ export default function NoteCard({ note }: { note: typeof notesTable.$inferSelec
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center mb-2 gap-4">
|
<div className="flex items-center mb-3 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">
|
<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>
|
<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: {makeTimestamp(note.lastEdited)}</i>
|
||||||
<i className="text-neutral-400 text-sm">Last time edited: {note.lastEdited.toString()}</i>
|
<i className="text-neutral-400 text-sm">Creation date: {makeTimestamp(note.creationTime)}</i>
|
||||||
</Link>
|
</Link>
|
||||||
<IconOnlyButton icon={<X size={24} />} onClick={handleDeleteNote} />
|
<IconOnlyButton icon={<X size={24} />} onClick={handleDeleteNote} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,6 +14,7 @@ type EditorState = IBlock[];
|
|||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
| { type: "add_block" }
|
| { type: "add_block" }
|
||||||
|
| { type: "copy" }
|
||||||
| { type: "delete_block", blockId: string }
|
| { type: "delete_block", blockId: string }
|
||||||
| { type: "add_line", blockId: string }
|
| { type: "add_line", blockId: string }
|
||||||
| { type: "delete_line", blockId: string }
|
| { type: "delete_line", blockId: string }
|
||||||
@ -37,6 +38,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":
|
case "delete_block":
|
||||||
return state.filter((block) => block.id !== action.blockId);
|
return state.filter((block) => block.id !== action.blockId);
|
||||||
|
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
import { eq } from "drizzle-orm";
|
import { desc, eq } from "drizzle-orm";
|
||||||
import { notesTable } from "./db/schema";
|
import { notesTable } from "./db/schema";
|
||||||
import { getAuth } from "./auth";
|
import { getAuth } from "./auth";
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
|
|
||||||
export async function getNotes() {
|
export async function getNotes(authorId: string) {
|
||||||
const auth = await getAuth();
|
|
||||||
if (!auth) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.select()
|
return db.select()
|
||||||
.from(notesTable)
|
.from(notesTable)
|
||||||
.where(eq(notesTable.authorId, auth.id));
|
.where(eq(notesTable.authorId, authorId))
|
||||||
|
.orderBy(desc(notesTable.lastEdited));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createNote() {
|
export async function createNote() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user