feat: chapter function
This commit is contained in:
199
app/page.tsx
199
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<Chapters>([])
|
||||
const [activeChapter,setActiveChapter] = useState<number|null>(null)
|
||||
const textareaRef = useRef<HTMLTextAreaElement|null>(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<string>((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 (
|
||||
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
|
||||
<div className="inline-block max-w-xl text-center justify-center">
|
||||
<span className={title()}>Place your changes here</span>
|
||||
</div>
|
||||
<div className="mt-8 gap-16">
|
||||
<Snippet hideCopyButton hideSymbol className="gap-4" variant="bordered">
|
||||
<span>
|
||||
Get started by editing <Code color="primary">app/page.tsx</Code>
|
||||
</span>
|
||||
<span>Please feel free to use the example components below.</span>
|
||||
</Snippet>
|
||||
</div>
|
||||
<div className="pt-6 w-48">
|
||||
{/* <div className="flex flex-row pt-6 w-48"> */}
|
||||
<div className="flex flex-row pt-6 w-auto">
|
||||
<Select
|
||||
className="flex-auto"
|
||||
items={[
|
||||
{ key: "story-1", label: "story-1" },
|
||||
{ key: "story-2", label: "story-2" },
|
||||
@@ -44,6 +97,8 @@ export default function Home() {
|
||||
>
|
||||
{(story) => <SelectItem key={story.key}>{story.label}</SelectItem>}
|
||||
</Select>
|
||||
|
||||
<Button className="flex-auto w-auto" onPress={selectFile}>Select</Button>
|
||||
</div>
|
||||
|
||||
<div className="pt-6">
|
||||
@@ -67,84 +122,28 @@ export default function Home() {
|
||||
id="menu-scroll"
|
||||
>
|
||||
<div className="flex flex-col gap-4 py-3 pr-4">
|
||||
<Card
|
||||
key="card-1"
|
||||
isPressable
|
||||
className={`max-w-[384px] border-1 border-divider/15 bg-themeBlue/20`}
|
||||
shadow="none"
|
||||
>
|
||||
<CardHeader className="flex items-center justify-between">
|
||||
<div className="flex gap-1.5">
|
||||
<Chip
|
||||
className="mr-1 text-themeBlue bg-themeBlue/20"
|
||||
radius="sm"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
Editing
|
||||
</Chip>
|
||||
<p className="text-left mr-1">
|
||||
Chapter 1 - Chapter 1 title
|
||||
</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<Divider />
|
||||
<CardBody>
|
||||
<p className="line-clamp-2">
|
||||
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
|
||||
</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
<Card
|
||||
key="card-2"
|
||||
isPressable
|
||||
className={`max-w-[384px] border-1 border-divider/15`}
|
||||
shadow="none"
|
||||
>
|
||||
<CardHeader className="flex items-center justify-between">
|
||||
<div className="flex gap-1.5">
|
||||
<p className="text-left mr-1">
|
||||
Chapter 2 - Chapter 2 title
|
||||
</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<Divider />
|
||||
<CardBody>
|
||||
<p className="line-clamp-2">
|
||||
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
|
||||
</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
<Card
|
||||
key="card-3"
|
||||
isPressable
|
||||
className={`max-w-[384px] border-1 border-divider/15`}
|
||||
shadow="none"
|
||||
>
|
||||
<CardHeader className="flex items-center justify-between">
|
||||
<div className="flex gap-1.5">
|
||||
<p className="text-left mr-1">
|
||||
Chapter 3 - Chapter 3 title
|
||||
</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<Divider />
|
||||
<CardBody>
|
||||
<p className="line-clamp-2">
|
||||
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
|
||||
</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
{
|
||||
chapters.map((chapter,index)=>{
|
||||
return <ChapterCard
|
||||
chapter={chapter}
|
||||
active={activeChapter===chapter.number}
|
||||
key={`chapter-${chapter.key}`}
|
||||
onPress={async ()=>{
|
||||
setActiveChapter(chapter.number)
|
||||
if(!textareaRef.current) return
|
||||
textareaRef.current.focus()
|
||||
await new Promise<void>(resolve=>{
|
||||
requestAnimationFrame(()=>{
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
textareaRef.current.selectionStart = chapter.indexOf
|
||||
textareaRef.current.selectionEnd = chapter.indexOf
|
||||
}}
|
||||
onMergeNext={index===chapters.length-1?undefined:onMergeNext}
|
||||
/>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</ScrollShadow>
|
||||
</div>
|
||||
@@ -197,8 +196,9 @@ export default function Home() {
|
||||
/>
|
||||
}
|
||||
variant="flat"
|
||||
onPress={onSplit}
|
||||
>
|
||||
button-2
|
||||
Split
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
@@ -207,6 +207,11 @@ export default function Home() {
|
||||
{/* Adjusted to use flex display for layout */}
|
||||
<textarea
|
||||
className="flex-1 p-3 resize-none rounded-md border border-transparent bg-slate-50 dark:bg-gray-200 text-gray-900" // Use flex-1 to allow the textarea to fill available space
|
||||
value={text}
|
||||
onChange={(event)=>{
|
||||
setText(event.target.value)
|
||||
}}
|
||||
ref={textareaRef}
|
||||
/>
|
||||
<div className="bg-gray-100 p-1 rounded-md self-end ml-2">
|
||||
{/* Added margin-left to separate from textarea, align-self to position at the bottom */}
|
||||
|
||||
Reference in New Issue
Block a user