Replace all textarea to contenteditable
This commit is contained in:
parent
42d88394c7
commit
ad8895430b
|
|
@ -71,6 +71,7 @@
|
||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
resize: vertical;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ export const ChaptersEditor = () => {
|
||||||
)}
|
)}
|
||||||
<div class={styles.chunkPreview}>{body}</div>
|
<div class={styles.chunkPreview}>{body}</div>
|
||||||
<ContentEditable
|
<ContentEditable
|
||||||
|
autoLines
|
||||||
class={styles.summaryEditable}
|
class={styles.summaryEditable}
|
||||||
value={highlight(summary ?? '')}
|
value={highlight(summary ?? '')}
|
||||||
placeholder="Not summarized yet..."
|
placeholder="Not summarized yet..."
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useAppState, type Character } from "../contexts/state";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import styles from '../assets/character-editor.module.css';
|
import styles from '../assets/character-editor.module.css';
|
||||||
import LLM from "../utils/llm";
|
import LLM from "../utils/llm";
|
||||||
|
import { ContentEditable } from "@common/components/ContentEditable";
|
||||||
|
|
||||||
export const CharacterEditor = () => {
|
export const CharacterEditor = () => {
|
||||||
const { currentStory, dispatch, connection, model } = useAppState();
|
const { currentStory, dispatch, connection, model } = useAppState();
|
||||||
|
|
@ -174,12 +175,12 @@ export const CharacterEditor = () => {
|
||||||
|
|
||||||
<div class={styles.field}>
|
<div class={styles.field}>
|
||||||
<div class={styles.label}>Description</div>
|
<div class={styles.label}>Description</div>
|
||||||
<textarea
|
<ContentEditable
|
||||||
|
autoLines
|
||||||
class={styles.textarea}
|
class={styles.textarea}
|
||||||
value={character.description}
|
value={character.description}
|
||||||
onInput={(e) => handleEditCharacter(character.id, 'description', e.currentTarget.value)}
|
onInput={(e) => handleEditCharacter(character.id, 'description', e.currentTarget.textContent)}
|
||||||
placeholder="Full character description..."
|
placeholder="Full character description..."
|
||||||
rows={4}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useAppState, type Location, LocationScale } from "../contexts/state";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import styles from '../assets/location-editor.module.css';
|
import styles from '../assets/location-editor.module.css';
|
||||||
import LLM from "../utils/llm";
|
import LLM from "../utils/llm";
|
||||||
|
import { ContentEditable } from "@common/components/ContentEditable";
|
||||||
|
|
||||||
const SCALE_OPTIONS = Object.entries(LocationScale)
|
const SCALE_OPTIONS = Object.entries(LocationScale)
|
||||||
.filter(([, value]) => typeof value === 'number')
|
.filter(([, value]) => typeof value === 'number')
|
||||||
|
|
@ -138,12 +139,12 @@ export const LocationEditor = () => {
|
||||||
|
|
||||||
<div class={styles.field}>
|
<div class={styles.field}>
|
||||||
<div class={styles.label}>Description</div>
|
<div class={styles.label}>Description</div>
|
||||||
<textarea
|
<ContentEditable
|
||||||
|
autoLines
|
||||||
class={styles.textarea}
|
class={styles.textarea}
|
||||||
value={location.description}
|
value={location.description}
|
||||||
onInput={(e) => handleEditLocation(location.id, 'description', e.currentTarget.value)}
|
onInput={(e) => handleEditLocation(location.id, 'description', e.currentTarget.textContent)}
|
||||||
placeholder="Full location description..."
|
placeholder="Full location description..."
|
||||||
rows={4}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export namespace Tools {
|
||||||
appState.dispatch({
|
appState.dispatch({
|
||||||
type: 'EDIT_STORY',
|
type: 'EDIT_STORY',
|
||||||
id: appState.currentStory.id,
|
id: appState.currentStory.id,
|
||||||
text: appState.currentStory.text + args.text,
|
text: appState.currentStory.text + '\n' + args.text,
|
||||||
});
|
});
|
||||||
appState.dispatch({
|
appState.dispatch({
|
||||||
type: 'SET_CURRENT_TAB',
|
type: 'SET_CURRENT_TAB',
|
||||||
|
|
@ -50,7 +50,7 @@ export namespace Tools {
|
||||||
appState.dispatch({
|
appState.dispatch({
|
||||||
type: 'EDIT_LORE',
|
type: 'EDIT_LORE',
|
||||||
id: appState.currentStory.id,
|
id: appState.currentStory.id,
|
||||||
lore: appState.currentStory.lore + args.text
|
lore: appState.currentStory.lore + '\n' + args.text,
|
||||||
});
|
});
|
||||||
appState.dispatch({
|
appState.dispatch({
|
||||||
type: 'SET_CURRENT_TAB',
|
type: 'SET_CURRENT_TAB',
|
||||||
|
|
@ -296,7 +296,11 @@ export namespace Tools {
|
||||||
if (!appState.currentStory) {
|
if (!appState.currentStory) {
|
||||||
return 'Error: No story selected';
|
return 'Error: No story selected';
|
||||||
}
|
}
|
||||||
const lines = appState.currentStory.text.split('\n');
|
const source = args.source === 'lore'
|
||||||
|
? appState.currentStory.lore
|
||||||
|
: appState.currentStory.text;
|
||||||
|
|
||||||
|
const lines = source.split('\n');
|
||||||
const matches: { line: number; content: string }[] = [];
|
const matches: { line: number; content: string }[] = [];
|
||||||
const pattern = new RegExp(args.pattern, args.case_sensitive ? 'g' : 'gi');
|
const pattern = new RegExp(args.pattern, args.case_sensitive ? 'g' : 'gi');
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
|
@ -317,11 +321,14 @@ export namespace Tools {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
description: 'Search for a pattern in the story text',
|
description: 'Search for a pattern in the story text or lore',
|
||||||
parameters: Type.Object({
|
parameters: Type.Object({
|
||||||
pattern: Type.String({ description: 'The regex pattern to search for' }),
|
pattern: Type.String({ description: 'The regex pattern to search for' }),
|
||||||
case_sensitive: Type.Optional(Type.Boolean({ description: 'If true, search is case-sensitive (default: false)' })),
|
case_sensitive: Type.Optional(Type.Boolean({ description: 'If true, search is case-sensitive (default: false)' })),
|
||||||
limit: Type.Optional(Type.Integer({ description: 'Maximum number of matches to return (default: 20)' })),
|
limit: Type.Optional(Type.Integer({ description: 'Maximum number of matches to return (default: 20)' })),
|
||||||
|
source: Type.Optional(Type.Enum(['lore', 'story'],
|
||||||
|
{ description: 'Source to search (story or lore, default: story)' },
|
||||||
|
)),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue