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 { 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 HeaderButton from "./ui/HeaderButton";

View File

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

View File

@ -1,7 +1,7 @@
"use client";
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 }) {
const [state, formAction] = useActionState(isRegister ? register : login, null);

View File

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