From dbfe0572923abc2b536adf7330d96618e7c46cbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=82=BD=E9=94=AE?= <397201698@qq.com> Date: Tue, 15 Jul 2025 18:17:48 +0800 Subject: [PATCH] feat: chapter function --- app/page.tsx | 199 +- app/utils/chapter.ts | 145 + components/ChapterCard.tsx | 76 + package.json | 3 +- tsconfig.json | 3 +- yarn.lock | 6502 ++++++++++++++++++++++++++++++++++++ 6 files changed, 6829 insertions(+), 99 deletions(-) create mode 100644 app/utils/chapter.ts create mode 100644 components/ChapterCard.tsx create mode 100644 yarn.lock diff --git a/app/page.tsx b/app/page.tsx index 913a015..d686d1d 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -4,36 +4,89 @@ import { Code } from "@nextui-org/code"; import { Icon } from "@iconify/react"; import { Button, - Card, - CardBody, - CardHeader, - Chip, - Divider, ScrollShadow, Select, SelectItem, } from "@nextui-org/react"; import { cn } from "@nextui-org/theme"; -import React from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import { title } from "@/components/primitives"; +import { ChapterItem, Chapters, readFile, splitChapter } from "./utils/chapter"; +import { ChapterCard } from "@/components/ChapterCard"; export default function Home() { + + const [text,setText] = useState("") + const [chapters,setChapters] = useState([]) + const [activeChapter,setActiveChapter] = useState(null) + const textareaRef = useRef(null) + + const onSplit = useCallback(async ()=>{ + const dom = textareaRef.current + if(!dom) return + const index = dom.selectionStart + if(index<0) return + console.log("bookmark",index) + const content = '====SPLIT CHAPTER====' + setText(txt=>{ + return txt.substring(0,index)+'\n'+content+"\n"+txt.substring(15) + }) + },[]) + + const onMergeNext = useCallback((chapter:ChapterItem)=>{ + const current = chapters.findIndex(detail=>detail.key===chapter.key) + if(current==-1) return + const next = current+1 + const nextChapter = chapters[next] + if(!nextChapter) return + chapter.text += ["",nextChapter.title,nextChapter.text].join("\n") + const newChapters = chapters.slice() + newChapters.splice(next,1) + const newText = newChapters.reduce((txt,chapter)=>{ + txt += chapter.rawTitle+'\n' + txt += chapter.text+"\n" + return txt + },"") + setText(newText) + + },[chapters]) + + const selectFile = useCallback(()=>{ + const input = document.createElement("input") + input.type = 'file' + input.accept = '.txt' + input.addEventListener("change",async function(event){ + setActiveChapter(null) + const [file] = input.files||[] + if(!file) { + setText("") + return + } + console.time("read-txt") + setText(await readFile(file)) + console.timeEnd("read-txt") + + }) + input.click() + },[]) + + useEffect(()=>{ + console.time("split-chapter") + const chapters = splitChapter(text) + setChapters(chapters) + if(chapters.length>0) { + setActiveChapter(1) + } + console.timeEnd("split-chapter") + },[text]) + return (
-
- Place your changes here -
-
- - - Get started by editing app/page.tsx - - Please feel free to use the example components below. - -
-
+ {/*
*/} +
+ +
@@ -67,84 +122,28 @@ export default function Home() { id="menu-scroll" >
- - -
- - Editing - -

- Chapter 1 - Chapter 1 title -

-
-
- - - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Sed do eiusmod tempor incididunt ut labore et dolore magna - aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip -

-
-
- - -
-

- Chapter 2 - Chapter 2 title -

-
-
- - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Sed do eiusmod tempor incididunt ut labore et dolore magna - aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip -

-
-
- - -
-

- Chapter 3 - Chapter 3 title -

-
-
- - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Sed do eiusmod tempor incididunt ut labore et dolore magna - aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip -

-
-
+ { + chapters.map((chapter,index)=>{ + return { + setActiveChapter(chapter.number) + if(!textareaRef.current) return + textareaRef.current.focus() + await new Promise(resolve=>{ + requestAnimationFrame(()=>{ + resolve() + }) + }) + textareaRef.current.selectionStart = chapter.indexOf + textareaRef.current.selectionEnd = chapter.indexOf + }} + onMergeNext={index===chapters.length-1?undefined:onMergeNext} + /> + }) + }
@@ -197,8 +196,9 @@ export default function Home() { /> } variant="flat" + onPress={onSplit} > - button-2 + Split
@@ -207,6 +207,11 @@ export default function Home() { {/* Adjusted to use flex display for layout */}