feat: add block movement

This commit is contained in:
Kirill Siukhin 2025-07-11 00:01:25 +05:00
parent 3b51161203
commit 8b1de14f79
4 changed files with 61 additions and 20 deletions

View File

@ -1,6 +1,7 @@
import { Metadata } from "next";
import { notFound } from "next/navigation";
import { getNote } from "@/lib/notes";
import { Metadata } from "next";
import Editor from "@/components/editor/Editor";
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
const { id } = await params;
@ -20,8 +21,8 @@ export default async function Note({ params }: { params: Promise<{ id: string }>
}
return (
<div>
{note.name}
<div className="flex justify-center">
<Editor defaultTitle={note.name} />
</div>
);
}

View File

@ -1,7 +1,7 @@
"use client";
import { ChangeEvent } from "react";
import { Lock, LockOpen, Menu, Minus, Plus, X } from "lucide-react";
import { ArrowDown, ArrowUp, Lock, LockOpen, Minus, Plus, X } from "lucide-react";
import { Action, IBlock, ILine } from "@/lib/editorReducer";
import IconOnlyButton from "../ui/IconOnlyButton";
import LineInput from "./LineInput";
@ -33,6 +33,14 @@ export default function Block({
dispatch({ type: "delete_block", blockId: block.id });
}
const handleBlockUp = () => {
dispatch({ type: "move_block_up", blockId: block.id });
}
const handleBlockDown = () => {
dispatch({ type: "move_block_down", blockId: block.id });
}
const handleToggleLock = () => {
dispatch({ type: "toggle_lock", blockId: block.id });
}
@ -57,14 +65,17 @@ export default function Block({
/>
))}
<div className="flex items-center mx-2 mt-2">
<div className="flex gap-2">
<div className="flex gap-1 mr-4">
<IconOnlyButton onClick={handleAddLine} icon={<Plus size={18} />} />
<IconOnlyButton onClick={handleDeleteLine} icon={<Minus size={18} />} />
</div>
<div className="flex gap-1">
<IconOnlyButton onClick={handleBlockUp} icon={<ArrowUp size={18} />} />
<IconOnlyButton onClick={handleBlockDown} icon={<ArrowDown size={18} />} />
</div>
<div className="flex gap-2 ml-auto">
<IconOnlyButton onClick={handleDeleteBlock} icon={<X size={18} />} />
<IconOnlyButton onClick={handleToggleLock} icon={block.locked ? <Lock size={18} /> : <LockOpen size={18} />} alwaysOn={block.locked} />
<IconOnlyButton icon={<Menu size={18} />} />
</div>
</div>
</div>

View File

@ -3,22 +3,29 @@
import { useReducer } from "react";
import { Copy, Plus } from "lucide-react";
import { v4 as uuidv4 } from "uuid";
import { editorReducer } from "@/lib/editorReducer";
import { editorReducer, IBlock } from "@/lib/editorReducer";
import IconOnlyButton from "../ui/IconOnlyButton";
import Block from "./Block";
export default function Editor() {
const [state, dispatch] = useReducer(editorReducer, [
{
interface EditorProps {
defaultTitle?: string;
defaultBlocks?: IBlock[];
}
const defaultBlocks = [
{
id: uuidv4(),
tag: "",
locked: false,
lines: Array.from({ length: 4 }, () => ({
id: uuidv4(),
tag: "",
locked: false,
lines: Array.from({ length: 4 }, () => ({
id: uuidv4(),
text: "",
})),
}
]);
text: "",
})),
}
];
export default function Editor(props: EditorProps) {
const [state, dispatch] = useReducer(editorReducer, props.defaultBlocks || defaultBlocks);
const handleAddBlock = () => {
dispatch({ type: "add_block" });
@ -30,7 +37,7 @@ export default function Editor() {
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" />
<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} /> )}
<div className="flex gap-2">
<IconOnlyButton onClick={handleAddBlock} icon={<Plus size={24} />} />

View File

@ -22,6 +22,8 @@ export type Action =
| { type: "delete_line", blockId: string }
| { type: "update_line_text"; blockId: string; lineId: string; text: string }
| { type: "update_tag"; blockId: string; tag: string }
| { type: "move_block_up", blockId: string }
| { type: "move_block_down", blockId: string }
| { type: "toggle_lock", blockId: string };
export function editorReducer(state: EditorState, action: Action): EditorState {
@ -48,7 +50,11 @@ export function editorReducer(state: EditorState, action: Action): EditorState {
copyText += `[${block.tag}]\n`;
}
block.lines.forEach((line) => copyText += line.text + "\n")
block.lines.forEach((line) => {
if (line.text !== "") {
copyText += line.text + "\n";
}
})
copyText += "\n";
});
@ -115,6 +121,22 @@ export function editorReducer(state: EditorState, action: Action): EditorState {
}
});
case "move_block_up": {
const index = state.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;
}
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;
}
case "toggle_lock":
return state.map((block) => {
if (block.id === action.blockId) {