feat: add title to the editor state

This commit is contained in:
Kirill Siukhin 2025-07-11 00:30:47 +05:00
parent 8b1de14f79
commit 2ad220e525
6 changed files with 155 additions and 88 deletions

23
src/app/actions/blocks.ts Normal file
View File

@ -0,0 +1,23 @@
"use server";
import { eq } from "drizzle-orm";
import { blocksTable } from "@/lib/db/schema";
import { db } from "@/lib/db";
export async function updateBlock({
id,
tag,
lines,
}: {
id: string;
tag?: string;
lines?: string[];
}) {
await db
.update(blocksTable)
.set({
...(tag !== undefined ? { tag } : {}),
...(lines !== undefined ? { lines } : {}),
})
.where(eq(blocksTable.id, id));
}

View File

@ -1,6 +1,6 @@
import Link from "next/link"; import Link from "next/link";
import { 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/auth";
import { getAuth } from "@/lib/auth"; import { getAuth } from "@/lib/auth";
import HeaderButton from "./ui/HeaderButton"; import HeaderButton from "./ui/HeaderButton";

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import { useReducer } from "react"; import { ChangeEvent, useReducer } from "react";
import { Copy, Plus } from "lucide-react"; import { Copy, Plus } from "lucide-react";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { editorReducer, IBlock } from "@/lib/editorReducer"; import { editorReducer, IBlock } from "@/lib/editorReducer";
@ -25,7 +25,14 @@ const defaultBlocks = [
]; ];
export default function Editor(props: EditorProps) { export default function Editor(props: EditorProps) {
const [state, dispatch] = useReducer(editorReducer, props.defaultBlocks || defaultBlocks); const [state, dispatch] = useReducer(editorReducer, {
title: props.defaultTitle || "Untitled",
blocks: props.defaultBlocks || defaultBlocks,
});
const handleUpdateTitle = (e: ChangeEvent<HTMLInputElement>) => {
dispatch({ type: "update_title", title: e.target.value });
}
const handleAddBlock = () => { const handleAddBlock = () => {
dispatch({ type: "add_block" }); dispatch({ type: "add_block" });
@ -37,8 +44,8 @@ export default function Editor(props: EditorProps) {
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={props.defaultTitle || "Untitled"} /> <input className="font-bold text-xl w-full text-center focus:outline-none" defaultValue={state.title} onChange={handleUpdateTitle} />
{state.map((block) => <Block key={block.id} block={block} dispatch={dispatch} /> )} {state.blocks.map((block) => <Block key={block.id} block={block} dispatch={dispatch} /> )}
<div className="flex gap-2"> <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" /> <IconOnlyButton onClick={handleCopy} icon={<Copy size={24} />} title="Copy note to clipboard" />

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import { useActionState } from "react"; import { useActionState } from "react";
import { login, register } from "@/app/actions"; import { login, register } from "@/app/actions/auth";
export default function AuthForm({ isRegister = false }: { isRegister?: boolean }) { export default function AuthForm({ isRegister = false }: { isRegister?: boolean }) {
const [state, formAction] = useActionState(isRegister ? register : login, null); const [state, formAction] = useActionState(isRegister ? register : login, null);

View File

@ -12,7 +12,10 @@ export type IBlock = {
lines: ILine[]; lines: ILine[];
}; };
type EditorState = IBlock[]; type EditorState = {
title: string;
blocks: IBlock[];
};
export type Action = export type Action =
| { type: "add_block" } | { type: "add_block" }
@ -20,6 +23,7 @@ export type Action =
| { 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 }
| { type: "update_title", title: string }
| { type: "update_line_text"; blockId: string; lineId: string; text: string } | { type: "update_line_text"; blockId: string; lineId: string; text: string }
| { type: "update_tag"; blockId: string; tag: string } | { type: "update_tag"; blockId: string; tag: string }
| { type: "move_block_up", blockId: string } | { type: "move_block_up", blockId: string }
@ -28,24 +32,33 @@ export type Action =
export function editorReducer(state: EditorState, action: Action): EditorState { export function editorReducer(state: EditorState, action: Action): EditorState {
switch (action.type) { switch (action.type) {
case "add_block": case "update_title":
return [ return {
...state, ...state,
{ title: action.title,
id: uuidv4(), };
tag: "",
locked: false, case "add_block":
lines: Array.from({ length: 4 }, () => ({ return {
...state,
blocks: [
...state.blocks,
{
id: uuidv4(), id: uuidv4(),
text: "", tag: "",
})), locked: false,
} lines: Array.from({ length: 4 }, () => ({
]; id: uuidv4(),
text: "",
})),
}
]
};
case "copy": case "copy":
let copyText = ""; let copyText = "";
state.forEach((block) => { state.blocks.forEach((block) => {
if (block.tag !== "") { if (block.tag !== "") {
copyText += `[${block.tag}]\n`; copyText += `[${block.tag}]\n`;
} }
@ -62,92 +75,116 @@ export function editorReducer(state: EditorState, action: Action): EditorState {
return state; return state;
case "delete_block": case "delete_block":
return state.filter((block) => block.id !== action.blockId); return {
...state,
blocks: state.blocks.filter((block) => block.id !== action.blockId),
};
case "add_line": case "add_line":
return state.map((block) => { return {
if (block.id === action.blockId) { ...state,
return { blocks: state.blocks.map((block) => {
...block, if (block.id === action.blockId) {
lines: [...block.lines, { id: uuidv4(), text: "" }], return {
}; ...block,
} else { lines: [...block.lines, { id: uuidv4(), text: "" }],
return block; };
} } else {
}); return block;
}
}),
};
case "delete_line": case "delete_line":
return state.map((block) => { return {
if (block.id === action.blockId && block.lines.length > 0) { ...state,
return { blocks: state.blocks.map((block) => {
...block, if (block.id === action.blockId && block.lines.length > 0) {
lines: block.lines.slice(0, -1), return {
}; ...block,
} else { lines: block.lines.slice(0, -1),
return block; };
} } else {
}); return block;
}
}),
};
case "update_line_text": case "update_line_text":
return state.map((block) => { return {
if (block.id === action.blockId) { ...state,
return { blocks: state.blocks.map((block) => {
...block, if (block.id === action.blockId) {
lines: block.lines.map((line) => { return {
if (line.id === action.lineId) { ...block,
return { lines: block.lines.map((line) => {
...line, if (line.id === action.lineId) {
text: action.text, return {
}; ...line,
} else { text: action.text,
return line; };
} } else {
}), return line;
}; }
} else { }),
return block; };
} } else {
}); return block;
}
}),
};
case "update_tag": case "update_tag":
return state.map((block) => { return {
if (block.id === action.blockId) { ...state,
return { blocks: state.blocks.map((block) => {
...block, if (block.id === action.blockId) {
tag: action.tag, return {
}; ...block,
} else { tag: action.tag,
return block; };
} } else {
}); return block;
}
}),
};
case "move_block_up": { case "move_block_up": {
const index = state.findIndex((b) => b.id === action.blockId); const index = state.blocks.findIndex((b) => b.id === action.blockId);
if (index <= 0) return state; if (index <= 0) return state;
const newState = [...state]; const newBlocks = [...state.blocks];
[newState[index - 1], newState[index]] = [newState[index], newState[index - 1]]; [newBlocks[index - 1], newBlocks[index]] = [newBlocks[index], newBlocks[index - 1]];
return newState; return {
...state,
blocks: newBlocks,
};
} }
case "move_block_down": { case "move_block_down": {
const index = state.findIndex((b) => b.id === action.blockId); const index = state.blocks.findIndex((b) => b.id === action.blockId);
if (index === state.length - 1) return state; if (index === state.blocks.length - 1) return state;
const newState = [...state]; const newBlocks = [...state.blocks];
[newState[index], newState[index + 1]] = [newState[index + 1], newState[index]]; [newBlocks[index], newBlocks[index + 1]] = [newBlocks[index + 1], newBlocks[index]];
return newState; return {
...state,
blocks: newBlocks,
};
} }
case "toggle_lock": case "toggle_lock":
return state.map((block) => { return {
if (block.id === action.blockId) { ...state,
return { blocks: state.blocks.map((block) => {
...block, if (block.id === action.blockId) {
locked: !block.locked, return {
}; ...block,
} else { locked: !block.locked,
return block; };
} } else {
}); return block;
}
}),
};
default: default:
return state; return state;