✨ Publisher pack
This commit is contained in:
@@ -9,6 +9,7 @@ const sessionFile = (): string => join(hohoffDir(), 'session.json')
|
|||||||
const telemetryFile = (): string => join(hohoffDir(), 'telemetry.json')
|
const telemetryFile = (): string => join(hohoffDir(), 'telemetry.json')
|
||||||
const revisionsDir = (): string => join(hohoffDir(), 'revisions')
|
const revisionsDir = (): string => join(hohoffDir(), 'revisions')
|
||||||
export const getStoryBiblePath = (): string => join(hohoffDir(), 'Story Bible.md')
|
export const getStoryBiblePath = (): string => join(hohoffDir(), 'Story Bible.md')
|
||||||
|
export const getPublisherPackPath = (): string => join(hohoffDir(), 'Publisher Pack.md')
|
||||||
const submissionsFile = (): string => join(hohoffDir(), 'submissions.json')
|
const submissionsFile = (): string => join(hohoffDir(), 'submissions.json')
|
||||||
|
|
||||||
const STORY_BIBLE_TEMPLATE = `# Story Bible
|
const STORY_BIBLE_TEMPLATE = `# Story Bible
|
||||||
@@ -257,6 +258,27 @@ export async function openStoryBibleFile(): Promise<{ path: string; content: str
|
|||||||
return { path: getStoryBiblePath(), content }
|
return { path: getStoryBiblePath(), content }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PUBLISHER_PACK_TEMPLATE = `# Synopsis
|
||||||
|
|
||||||
|
<!-- A concise summary of the novel's plot, themes, and arc -->
|
||||||
|
|
||||||
|
# Author Bio
|
||||||
|
|
||||||
|
<!-- A short professional biography in third person -->
|
||||||
|
`
|
||||||
|
|
||||||
|
export async function openPublisherPackFile(): Promise<{ path: string; content: string }> {
|
||||||
|
await mkdir(hohoffDir(), { recursive: true })
|
||||||
|
let content: string
|
||||||
|
try {
|
||||||
|
content = await readFile(getPublisherPackPath(), 'utf-8')
|
||||||
|
} catch {
|
||||||
|
content = PUBLISHER_PACK_TEMPLATE
|
||||||
|
await writeFile(getPublisherPackPath(), content, 'utf-8')
|
||||||
|
}
|
||||||
|
return { path: getPublisherPackPath(), content }
|
||||||
|
}
|
||||||
|
|
||||||
// Parse a markdown document into a preamble (text before first ## heading) and
|
// Parse a markdown document into a preamble (text before first ## heading) and
|
||||||
// an ordered list of { header, body } sections delimited by ## headings.
|
// an ordered list of { header, body } sections delimited by ## headings.
|
||||||
function parseSections(content: string): { preamble: string; sections: { header: string; body: string }[] } {
|
function parseSections(content: string): { preamble: string; sections: { header: string; body: string }[] } {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ipcMain, dialog, BrowserWindow } from 'electron'
|
|||||||
import { readFileSync, writeFileSync, unlinkSync } from 'fs'
|
import { readFileSync, writeFileSync, unlinkSync } from 'fs'
|
||||||
import { extname, basename, join } from 'path'
|
import { extname, basename, join } from 'path'
|
||||||
import { tmpdir } from 'os'
|
import { tmpdir } from 'os'
|
||||||
import { listDraftFiles, readMarkdownFile, writeMarkdownFile, getProjectWordCount, saveOrderFile, readSession, writeSession, saveRevision, listRevisions, loadRevision, deleteRevision, renameFileOrDir, deleteFileOrDir, createMarkdownFile, createSubdirectory, moveFileOrDir, readStoryBibleFile, openStoryBibleFile, writeStoryBibleFile, searchAcrossFiles, replaceInFiles, readAllDraftFiles, readProjectConfig, writeProjectConfig, PROJECT_CONFIG_FIELDS, readTelemetry, readSubmissions, writeSubmissions } from './fileSystem'
|
import { listDraftFiles, readMarkdownFile, writeMarkdownFile, getProjectWordCount, saveOrderFile, readSession, writeSession, saveRevision, listRevisions, loadRevision, deleteRevision, renameFileOrDir, deleteFileOrDir, createMarkdownFile, createSubdirectory, moveFileOrDir, readStoryBibleFile, openStoryBibleFile, writeStoryBibleFile, openPublisherPackFile, searchAcrossFiles, replaceInFiles, readAllDraftFiles, readProjectConfig, writeProjectConfig, PROJECT_CONFIG_FIELDS, readTelemetry, readSubmissions, writeSubmissions } from './fileSystem'
|
||||||
import type { SearchOptions, ProjectConfig } from './fileSystem'
|
import type { SearchOptions, ProjectConfig } from './fileSystem'
|
||||||
import { streamMessage, resetClient } from './aiService'
|
import { streamMessage, resetClient } from './aiService'
|
||||||
import { onWordSnapshot, flushTelemetry } from './telemetry'
|
import { onWordSnapshot, flushTelemetry } from './telemetry'
|
||||||
@@ -91,6 +91,10 @@ export function registerIpcHandlers(): void {
|
|||||||
return await writeStoryBibleFile(content)
|
return await writeStoryBibleFile(content)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('fs:openPublisherPack', async () => {
|
||||||
|
return await openPublisherPackFile()
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.handle('fs:pickAttachments', async (event): Promise<Attachment[]> => {
|
ipcMain.handle('fs:pickAttachments', async (event): Promise<Attachment[]> => {
|
||||||
const win = BrowserWindow.fromWebContents(event.sender)
|
const win = BrowserWindow.fromWebContents(event.sender)
|
||||||
const result = await dialog.showOpenDialog(win!, {
|
const result = await dialog.showOpenDialog(win!, {
|
||||||
|
|||||||
@@ -95,6 +95,9 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
openStoryBible: (): Promise<{ path: string; content: string }> =>
|
openStoryBible: (): Promise<{ path: string; content: string }> =>
|
||||||
ipcRenderer.invoke('fs:openStoryBible'),
|
ipcRenderer.invoke('fs:openStoryBible'),
|
||||||
|
|
||||||
|
openPublisherPack: (): Promise<{ path: string; content: string }> =>
|
||||||
|
ipcRenderer.invoke('fs:openPublisherPack'),
|
||||||
|
|
||||||
writeStoryBible: (content: string): Promise<string> =>
|
writeStoryBible: (content: string): Promise<string> =>
|
||||||
ipcRenderer.invoke('fs:writeStoryBible', content),
|
ipcRenderer.invoke('fs:writeStoryBible', content),
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,15 @@ export default function App(): JSX.Element {
|
|||||||
console.error('[App] openStoryBible failed:', err)
|
console.error('[App] openStoryBible failed:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOpenPublisherPack = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const { path, content } = await window.api.openPublisherPack()
|
||||||
|
setActiveFile(path, content)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[App] openPublisherPack failed:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(
|
const [sidebarOpen, setSidebarOpen] = useState(
|
||||||
() => localStorage.getItem('sidebarOpen') !== 'false'
|
() => localStorage.getItem('sidebarOpen') !== 'false'
|
||||||
)
|
)
|
||||||
@@ -212,16 +221,23 @@ export default function App(): JSX.Element {
|
|||||||
<button
|
<button
|
||||||
className="sidebar-nav-btn"
|
className="sidebar-nav-btn"
|
||||||
onClick={handleOpenStoryBible}
|
onClick={handleOpenStoryBible}
|
||||||
title="Open Story Bible"
|
title="Story Bible"
|
||||||
>
|
>
|
||||||
<span className="sidebar-nav-icon">📖</span>
|
<span className="sidebar-nav-icon">📖</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className="sidebar-nav-btn"
|
||||||
|
onClick={handleOpenPublisherPack}
|
||||||
|
title="Publisher Pack"
|
||||||
|
>
|
||||||
|
<span className="sidebar-nav-label">Pack</span>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
className={`sidebar-nav-btn${showSubmissions ? ' active' : ''}`}
|
className={`sidebar-nav-btn${showSubmissions ? ' active' : ''}`}
|
||||||
onClick={goSubmissions}
|
onClick={goSubmissions}
|
||||||
title="Submissions"
|
title="Submissions"
|
||||||
>
|
>
|
||||||
<span className="sidebar-nav-icon">✉</span>
|
<span className="sidebar-nav-label">Sub</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<FileTree />
|
<FileTree />
|
||||||
|
|||||||
@@ -207,6 +207,15 @@
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-nav-label {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── Editor area ──────────────────────────────────────────────── */
|
/* ─── Editor area ──────────────────────────────────────────────── */
|
||||||
.editor-area {
|
.editor-area {
|
||||||
grid-row: 2;
|
grid-row: 2;
|
||||||
|
|||||||
1
src/renderer/types/global.d.ts
vendored
1
src/renderer/types/global.d.ts
vendored
@@ -32,6 +32,7 @@ declare global {
|
|||||||
moveFile: (sourcePath: string, targetDirPath: string) => Promise<string>
|
moveFile: (sourcePath: string, targetDirPath: string) => Promise<string>
|
||||||
openStoryBible: () => Promise<{ path: string; content: string }>
|
openStoryBible: () => Promise<{ path: string; content: string }>
|
||||||
writeStoryBible: (content: string) => Promise<string>
|
writeStoryBible: (content: string) => Promise<string>
|
||||||
|
openPublisherPack: () => Promise<{ path: string; content: string }>
|
||||||
onMenuAction: (handler: (action: string) => void) => () => void
|
onMenuAction: (handler: (action: string) => void) => () => void
|
||||||
searchFiles: (query: string, options: SearchOptions) => Promise<SearchFileResult[]>
|
searchFiles: (query: string, options: SearchOptions) => Promise<SearchFileResult[]>
|
||||||
replaceInFiles: (query: string, replacement: string, options: SearchOptions, filePaths: string[]) => Promise<string[]>
|
replaceInFiles: (query: string, replacement: string, options: SearchOptions, filePaths: string[]) => Promise<string[]>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user