♿ display prompt separately from story text
This commit is contained in:
@@ -9,6 +9,7 @@ export interface StoryMeta {
|
|||||||
wordCountTarget?: number
|
wordCountTarget?: number
|
||||||
tags?: string[]
|
tags?: string[]
|
||||||
notes?: string
|
notes?: string
|
||||||
|
prompt?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Market {
|
export interface Market {
|
||||||
|
|||||||
@@ -72,13 +72,12 @@ export function PromptHero(): JSX.Element {
|
|||||||
}
|
}
|
||||||
const name = `Story ${stories.length + 1}`
|
const name = `Story ${stories.length + 1}`
|
||||||
const created = await window.api.createStory(name)
|
const created = await window.api.createStory(name)
|
||||||
|
await window.api.setStoryMeta(created.id, { prompt })
|
||||||
const refreshed = await window.api.listStories()
|
const refreshed = await window.api.listStories()
|
||||||
setStories(refreshed)
|
setStories(refreshed)
|
||||||
const story = refreshed.find((s) => s.path === created.path)
|
const story = refreshed.find((s) => s.path === created.path)
|
||||||
if (story) {
|
if (story) {
|
||||||
const initial = `> ${prompt}\n\n`
|
setActiveStory(story.path, story.id, '')
|
||||||
await window.api.writeStory(story.path, initial)
|
|
||||||
setActiveStory(story.path, story.id, initial)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { EditorView, keymap, Decoration, type DecorationSet, ViewPlugin, type ViewUpdate } from '@codemirror/view'
|
import { EditorView, keymap, Decoration, type DecorationSet, ViewPlugin, type ViewUpdate } from '@codemirror/view'
|
||||||
import { EditorState, StateField, StateEffect, RangeSetBuilder, Compartment } from '@codemirror/state'
|
import { EditorState, StateField, StateEffect, RangeSetBuilder, Compartment } from '@codemirror/state'
|
||||||
import { markdown } from '@codemirror/lang-markdown'
|
import { markdown } from '@codemirror/lang-markdown'
|
||||||
@@ -53,11 +53,11 @@ const annPlugin = ViewPlugin.fromClass(class {
|
|||||||
const themeCompartment = new Compartment()
|
const themeCompartment = new Compartment()
|
||||||
const fontSizeCompartment = new Compartment()
|
const fontSizeCompartment = new Compartment()
|
||||||
|
|
||||||
function buildTheme(fontSize: number, isDark: boolean): ReturnType<typeof EditorView.theme> {
|
function buildTheme(fontSize: number, isDark: boolean, hasPrompt = false): ReturnType<typeof EditorView.theme> {
|
||||||
return EditorView.theme({
|
return EditorView.theme({
|
||||||
'&': { height: '100%', background: 'transparent' },
|
'&': { height: '100%', background: 'transparent' },
|
||||||
'.cm-scroller': { fontFamily: 'Georgia, "Times New Roman", serif', fontSize: `${fontSize}px`, lineHeight: '1.85', overflow: 'auto' },
|
'.cm-scroller': { fontFamily: 'Georgia, "Times New Roman", serif', fontSize: `${fontSize}px`, lineHeight: '1.85', overflow: 'auto' },
|
||||||
'.cm-content': { maxWidth: '680px', margin: '0 auto', padding: '48px 24px 24px', caretColor: 'var(--accent)' },
|
'.cm-content': { maxWidth: '680px', margin: '0 auto', padding: `${hasPrompt ? '24px' : '48px'} 24px 24px`, caretColor: 'var(--accent)' },
|
||||||
'.cm-line': { padding: '0' },
|
'.cm-line': { padding: '0' },
|
||||||
'.cm-cursor': { borderLeftColor: 'var(--accent)', borderLeftWidth: '2px' },
|
'.cm-cursor': { borderLeftColor: 'var(--accent)', borderLeftWidth: '2px' },
|
||||||
'.cm-selectionBackground': { background: 'rgba(200,169,110,0.25)' },
|
'.cm-selectionBackground': { background: 'rgba(200,169,110,0.25)' },
|
||||||
@@ -85,6 +85,7 @@ export function MarkdownEditor(): JSX.Element {
|
|||||||
const viewRef = useRef<EditorView | null>(null)
|
const viewRef = useRef<EditorView | null>(null)
|
||||||
const lastPathRef = useRef<string | null>(null)
|
const lastPathRef = useRef<string | null>(null)
|
||||||
const storeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
const storeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
|
const [storyPrompt, setStoryPrompt] = useState<string | null>(null)
|
||||||
|
|
||||||
|
|
||||||
// Initialize editor
|
// Initialize editor
|
||||||
@@ -159,6 +160,14 @@ export function MarkdownEditor(): JSX.Element {
|
|||||||
}
|
}
|
||||||
}, [activeStoryPath, activeStoryContent, setRevisions])
|
}, [activeStoryPath, activeStoryContent, setRevisions])
|
||||||
|
|
||||||
|
// Load prompt from meta when story changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!activeStoryId) { setStoryPrompt(null); return }
|
||||||
|
window.api.getStoryMeta(activeStoryId).then((meta) => {
|
||||||
|
setStoryPrompt(meta.prompt ?? null)
|
||||||
|
}).catch(() => setStoryPrompt(null))
|
||||||
|
}, [activeStoryId])
|
||||||
|
|
||||||
// Sync annotations into editor
|
// Sync annotations into editor
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const view = viewRef.current
|
const view = viewRef.current
|
||||||
@@ -168,8 +177,8 @@ export function MarkdownEditor(): JSX.Element {
|
|||||||
|
|
||||||
// Update theme
|
// Update theme
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
viewRef.current?.dispatch({ effects: themeCompartment.reconfigure(buildTheme(fontSize, theme === 'dark')) })
|
viewRef.current?.dispatch({ effects: themeCompartment.reconfigure(buildTheme(fontSize, theme === 'dark', !!storyPrompt)) })
|
||||||
}, [theme, fontSize])
|
}, [theme, fontSize, storyPrompt])
|
||||||
|
|
||||||
if (!activeStoryId) {
|
if (!activeStoryId) {
|
||||||
return <div className="no-story-placeholder">Select a story or create a new one</div>
|
return <div className="no-story-placeholder">Select a story or create a new one</div>
|
||||||
@@ -178,6 +187,14 @@ export function MarkdownEditor(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', position: 'relative' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', position: 'relative' }}>
|
||||||
<WordCountBar />
|
<WordCountBar />
|
||||||
|
{storyPrompt && (
|
||||||
|
<div className="story-prompt-banner">
|
||||||
|
<div className="story-prompt-banner-inner">
|
||||||
|
<span className="story-prompt-banner-label">Prompt</span>
|
||||||
|
{storyPrompt}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
style={{ flex: 1, overflow: 'hidden', position: 'relative' }}
|
style={{ flex: 1, overflow: 'hidden', position: 'relative' }}
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
|
|||||||
@@ -799,3 +799,31 @@ textarea { resize: vertical; }
|
|||||||
/* Misc */
|
/* Misc */
|
||||||
.inline-edit { background: none; border: none; padding: 0; font: inherit; color: inherit; width: 100%; outline: none; border-bottom: 1px solid var(--accent); }
|
.inline-edit { background: none; border: none; padding: 0; font: inherit; color: inherit; width: 100%; outline: none; border-bottom: 1px solid var(--accent); }
|
||||||
.no-story-placeholder { display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text3); font-size: 14px; }
|
.no-story-placeholder { display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text3); font-size: 14px; }
|
||||||
|
|
||||||
|
/* ── Story prompt banner ─────────────────────────────────────────────────── */
|
||||||
|
.story-prompt-banner {
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: Georgia, "Times New Roman", serif;
|
||||||
|
}
|
||||||
|
.story-prompt-banner-inner {
|
||||||
|
max-width: 680px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 28px 24px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--text3);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.story-prompt-banner-label {
|
||||||
|
display: block;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.07em;
|
||||||
|
color: var(--text3);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export interface StoryMeta {
|
|||||||
wordCountTarget?: number
|
wordCountTarget?: number
|
||||||
tags?: string[]
|
tags?: string[]
|
||||||
notes?: string
|
notes?: string
|
||||||
|
prompt?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoryFile {
|
export interface StoryFile {
|
||||||
|
|||||||
Reference in New Issue
Block a user