124 lines
4.8 KiB
TypeScript
124 lines
4.8 KiB
TypeScript
import { StrictMode, useState } from 'react'
|
||
import { createRoot } from 'react-dom/client'
|
||
import { useEditorStore } from '@renderer/store/editorStore'
|
||
import { MarkdownEditor } from '@renderer/components/Editor/MarkdownEditor'
|
||
import { ChatPanel } from '@renderer/components/AIChat/ChatPanel'
|
||
import { RevisionPanel } from '@renderer/components/Revisions/RevisionPanel'
|
||
import '@renderer/styles/global.css'
|
||
import '@renderer/styles/app.css'
|
||
|
||
// Mock window.api so the renderer doesn't crash in a plain browser context
|
||
;(window as unknown as Record<string, unknown>).api = {
|
||
listFiles: () => Promise.resolve([]),
|
||
readFile: () => Promise.resolve(''),
|
||
writeFile: () => Promise.resolve(),
|
||
streamAIMessage: () => Promise.resolve(),
|
||
removeAIListener: () => {},
|
||
getProjectWordCount: () => Promise.resolve(0),
|
||
saveOrder: () => Promise.resolve(),
|
||
pickAttachments: () => Promise.resolve([]),
|
||
saveRevision: () => Promise.resolve(),
|
||
listRevisions: () => Promise.resolve([]),
|
||
loadRevision: () => Promise.resolve(''),
|
||
deleteRevision: () => Promise.resolve(),
|
||
renameNode: () => Promise.resolve(''),
|
||
deleteNode: () => Promise.resolve(),
|
||
createFile: () => Promise.resolve(''),
|
||
createDir: () => Promise.resolve(''),
|
||
moveFile: () => Promise.resolve(''),
|
||
openStoryBible: () => Promise.resolve({ path: '', content: '' }),
|
||
writeStoryBible: () => Promise.resolve(''),
|
||
readSession: () => Promise.resolve({}),
|
||
writeSession: () => Promise.resolve(),
|
||
onMenuAction: () => () => {}
|
||
}
|
||
|
||
const DEMO_TEXT =
|
||
`She was seen by him walking down the cobblestone path.\n\n` +
|
||
`The old manuscript was written by an unknown author many centuries ago.\n\n` +
|
||
`The message had been delivered by the courier at dawn.`
|
||
|
||
// Compute annotation character ranges directly from the text
|
||
const a1s = DEMO_TEXT.indexOf('She was seen')
|
||
const a1e = DEMO_TEXT.indexOf('.', a1s) + 1
|
||
const a2s = DEMO_TEXT.indexOf('The old manuscript')
|
||
const a2e = DEMO_TEXT.indexOf('.', a2s) + 1
|
||
const a3s = DEMO_TEXT.indexOf('The message')
|
||
const a3e = DEMO_TEXT.indexOf('.', a3s) + 1
|
||
|
||
// Pre-seed store before React renders so all three effects fire correctly on mount
|
||
const store = useEditorStore.getState()
|
||
store.setActiveFile('demo.md', DEMO_TEXT)
|
||
store.setAnnotations([
|
||
{
|
||
id: 'ann-1',
|
||
type: 'passive_voice',
|
||
from: a1s,
|
||
to: a1e,
|
||
matchedText: 'was seen by',
|
||
message: 'Passive voice weakens the narrative tension here — placing the subject in an active role would make the sentence more immediate and visceral, which is essential for gothic prose where atmosphere must feel alive and urgent\u2026',
|
||
suggestion: 'He saw her walking down the cobblestone path.'
|
||
},
|
||
{
|
||
id: 'ann-2',
|
||
type: 'passive_voice',
|
||
from: a2s,
|
||
to: a2e,
|
||
matchedText: 'was written by',
|
||
message: 'Passive construction distances the reader from the action; consider foregrounding the actor to strengthen the historical atmosphere.',
|
||
suggestion: 'An unknown author wrote the old manuscript many centuries ago.'
|
||
},
|
||
{
|
||
id: 'ann-3',
|
||
type: 'style',
|
||
from: a3s,
|
||
to: a3e,
|
||
matchedText: 'had been delivered by',
|
||
message: 'Wordy passive construction: "had been delivered by"',
|
||
suggestion: 'The courier delivered the message at dawn.'
|
||
}
|
||
])
|
||
|
||
function DemoApp(): JSX.Element {
|
||
const { revisionPanelOpen, toggleRevisionPanel } = useEditorStore()
|
||
return (
|
||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', background: 'var(--bg-base)' }}>
|
||
{/* Titlebar – same markup as App.tsx */}
|
||
<div className="app-titlebar">
|
||
<span className="app-titlebar-title">Hohoff Editor</span>
|
||
<div className="app-titlebar-right">
|
||
<div className="app-layout-toggle">
|
||
<button className="app-layout-toggle-seg active" />
|
||
<div className="app-layout-toggle-seg app-layout-toggle-seg--mid" />
|
||
<button className="app-layout-toggle-seg active" />
|
||
</div>
|
||
<div className="app-titlebar-icon-group">
|
||
<button
|
||
className={`app-titlebar-theme-btn app-titlebar-revision-btn${revisionPanelOpen ? ' active' : ''}`}
|
||
onClick={toggleRevisionPanel}
|
||
title="Revision history"
|
||
>⟳</button>
|
||
<button className="app-titlebar-theme-btn" title="Toggle theme">☀</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{/* Editor content below */}
|
||
<div style={{ flex: 1, display: 'flex', minHeight: 0 }}>
|
||
<div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', position: 'relative' }}>
|
||
<MarkdownEditor />
|
||
{revisionPanelOpen && <RevisionPanel />}
|
||
</div>
|
||
<div style={{ width: '300px', flexShrink: 0 }}>
|
||
<ChatPanel />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
createRoot(document.getElementById('root')!).render(
|
||
<StrictMode>
|
||
<DemoApp />
|
||
</StrictMode>
|
||
)
|