feat: add block movement
This commit is contained in:
parent
3b51161203
commit
8b1de14f79
@ -1,6 +1,7 @@
|
|||||||
|
import { Metadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getNote } from "@/lib/notes";
|
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> {
|
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
@ -20,8 +21,8 @@ export default async function Note({ params }: { params: Promise<{ id: string }>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex justify-center">
|
||||||
{note.name}
|
<Editor defaultTitle={note.name} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ChangeEvent } from "react";
|
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 { Action, IBlock, ILine } from "@/lib/editorReducer";
|
||||||
import IconOnlyButton from "../ui/IconOnlyButton";
|
import IconOnlyButton from "../ui/IconOnlyButton";
|
||||||
import LineInput from "./LineInput";
|
import LineInput from "./LineInput";
|
||||||
@ -33,6 +33,14 @@ export default function Block({
|
|||||||
dispatch({ type: "delete_block", blockId: block.id });
|
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 = () => {
|
const handleToggleLock = () => {
|
||||||
dispatch({ type: "toggle_lock", blockId: block.id });
|
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 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={handleAddLine} icon={<Plus size={18} />} />
|
||||||
<IconOnlyButton onClick={handleDeleteLine} icon={<Minus size={18} />} />
|
<IconOnlyButton onClick={handleDeleteLine} icon={<Minus size={18} />} />
|
||||||
</div>
|
</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">
|
<div className="flex gap-2 ml-auto">
|
||||||
<IconOnlyButton onClick={handleDeleteBlock} icon={<X size={18} />} />
|
<IconOnlyButton onClick={handleDeleteBlock} icon={<X size={18} />} />
|
||||||
<IconOnlyButton onClick={handleToggleLock} icon={block.locked ? <Lock size={18} /> : <LockOpen size={18} />} alwaysOn={block.locked} />
|
<IconOnlyButton onClick={handleToggleLock} icon={block.locked ? <Lock size={18} /> : <LockOpen size={18} />} alwaysOn={block.locked} />
|
||||||
<IconOnlyButton icon={<Menu size={18} />} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,22 +3,29 @@
|
|||||||
import { useReducer } from "react";
|
import { 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 } from "@/lib/editorReducer";
|
import { editorReducer, IBlock } from "@/lib/editorReducer";
|
||||||
import IconOnlyButton from "../ui/IconOnlyButton";
|
import IconOnlyButton from "../ui/IconOnlyButton";
|
||||||
import Block from "./Block";
|
import Block from "./Block";
|
||||||
|
|
||||||
export default function Editor() {
|
interface EditorProps {
|
||||||
const [state, dispatch] = useReducer(editorReducer, [
|
defaultTitle?: string;
|
||||||
{
|
defaultBlocks?: IBlock[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultBlocks = [
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
tag: "",
|
||||||
|
locked: false,
|
||||||
|
lines: Array.from({ length: 4 }, () => ({
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
tag: "",
|
text: "",
|
||||||
locked: false,
|
})),
|
||||||
lines: Array.from({ length: 4 }, () => ({
|
}
|
||||||
id: uuidv4(),
|
];
|
||||||
text: "",
|
|
||||||
})),
|
export default function Editor(props: EditorProps) {
|
||||||
}
|
const [state, dispatch] = useReducer(editorReducer, props.defaultBlocks || defaultBlocks);
|
||||||
]);
|
|
||||||
|
|
||||||
const handleAddBlock = () => {
|
const handleAddBlock = () => {
|
||||||
dispatch({ type: "add_block" });
|
dispatch({ type: "add_block" });
|
||||||
@ -30,7 +37,7 @@ export default function Editor() {
|
|||||||
|
|
||||||
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={props.defaultTitle || "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">
|
<div className="flex gap-2">
|
||||||
<IconOnlyButton onClick={handleAddBlock} icon={<Plus size={24} />} />
|
<IconOnlyButton onClick={handleAddBlock} icon={<Plus size={24} />} />
|
||||||
|
@ -22,6 +22,8 @@ export type Action =
|
|||||||
| { type: "delete_line", blockId: string }
|
| { type: "delete_line", blockId: 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_down", blockId: string }
|
||||||
| { type: "toggle_lock", blockId: string };
|
| { type: "toggle_lock", blockId: string };
|
||||||
|
|
||||||
export function editorReducer(state: EditorState, action: Action): EditorState {
|
export function editorReducer(state: EditorState, action: Action): EditorState {
|
||||||
@ -48,7 +50,11 @@ export function editorReducer(state: EditorState, action: Action): EditorState {
|
|||||||
copyText += `[${block.tag}]\n`;
|
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";
|
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":
|
case "toggle_lock":
|
||||||
return state.map((block) => {
|
return state.map((block) => {
|
||||||
if (block.id === action.blockId) {
|
if (block.id === action.blockId) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user