feat: ship v1.1.0 writer toolkit with templates, import, export, and inspiration capture

Implement chapter templates, txt/md/docx import, submission export presets, and global inspiration shortcut. Split import handlers into a separate main bundle and fix EditorLayout setTarget loop that broke E2E.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 13:53:22 +08:00
parent ea4819847f
commit adf877861d
49 changed files with 2450 additions and 87 deletions
@@ -25,6 +25,9 @@ import { LandmarkModal } from '@renderer/components/landmark/LandmarkModal'
import { CockpitModal } from '@renderer/components/cockpit/CockpitModal'
import { CharacterGraphModal } from '@renderer/components/graph/CharacterGraphModal'
import { ChapterBridgeModal } from '@renderer/components/bridge/ChapterBridgeModal'
import { TemplateQuickCreateModal } from '@renderer/components/chapter/TemplateQuickCreateModal'
import { ExportModal } from '@renderer/components/export/ExportModal'
import { useExportStore } from '@renderer/stores/useExportStore'
import {
editorDirtyAtom,
editorSavingAtom,
@@ -117,6 +120,7 @@ export function EditorLayout(): React.JSX.Element {
const documentTitle = useDocumentTitle()
const [bridgeOpen, setBridgeOpen] = useState(false)
const [templateModalOpen, setTemplateModalOpen] = useState(false)
const [cockpitSummary, setCockpitSummary] = useState<CockpitSummary | null>(null)
const bookMeta = useBookStore.getState().books.find((b) => b.id === currentBookId)
@@ -148,10 +152,10 @@ export function EditorLayout(): React.JSX.Element {
}, [currentBookId, pomodoroRunning, pomodoroBookId, pomodoroCancel])
useEffect(() => {
if (selectedChapterId && (!target || target.kind === 'chapter')) {
setTarget({ kind: 'chapter', id: selectedChapterId })
}
}, [selectedChapterId, setTarget, target])
if (!selectedChapterId) return
if (target?.kind === 'chapter' && target.id === selectedChapterId) return
setTarget({ kind: 'chapter', id: selectedChapterId })
}, [selectedChapterId, setTarget, target?.kind, target?.id])
useEffect(() => {
if (!currentBookId || target?.kind !== 'chapter') return
@@ -405,6 +409,14 @@ export function EditorLayout(): React.JSX.Element {
<button type="button" onClick={() => void handleNewChapter()}>
+ {t('editor.newChapter')}
</button>
<button
type="button"
data-testid="chapter-quick-new"
style={{ marginTop: 6 }}
onClick={() => setTemplateModalOpen(true)}
>
+ {t('template.quickNew')}
</button>
<button type="button" style={{ marginTop: 6 }} onClick={() => void handleNewVolume()}>
+ {t('editor.newVolume')}
</button>
@@ -427,6 +439,16 @@ export function EditorLayout(): React.JSX.Element {
) : (
<>
<div id="editor-toolbar">
<button
type="button"
className="tool-btn"
title={t('export.title')}
data-testid="open-export"
disabled={!isChapterTarget}
onClick={() => useExportStore.getState().openModal()}
>
📦
</button>
<button
type="button"
className="tool-btn"
@@ -572,6 +594,11 @@ export function EditorLayout(): React.JSX.Element {
clearBridgeChapter()
}}
/>
<TemplateQuickCreateModal
open={templateModalOpen}
onClose={() => setTemplateModalOpen(false)}
/>
<ExportModal />
</div>
)
}