1
0
Fork 0

Replace all textarea to contenteditable

This commit is contained in:
Pabloader 2026-03-25 15:50:51 +00:00
parent 42d88394c7
commit ad8895430b
5 changed files with 21 additions and 10 deletions

View File

@ -71,6 +71,7 @@
min-height: 80px;
white-space: pre-wrap;
word-wrap: break-word;
resize: vertical;
&:focus {
outline: none;

View File

@ -44,6 +44,7 @@ export const ChaptersEditor = () => {
)}
<div class={styles.chunkPreview}>{body}</div>
<ContentEditable
autoLines
class={styles.summaryEditable}
value={highlight(summary ?? '')}
placeholder="Not summarized yet..."

View File

@ -2,6 +2,7 @@ import { useAppState, type Character } from "../contexts/state";
import { useState } from "preact/hooks";
import styles from '../assets/character-editor.module.css';
import LLM from "../utils/llm";
import { ContentEditable } from "@common/components/ContentEditable";
export const CharacterEditor = () => {
const { currentStory, dispatch, connection, model } = useAppState();
@ -174,12 +175,12 @@ export const CharacterEditor = () => {
<div class={styles.field}>
<div class={styles.label}>Description</div>
<textarea
<ContentEditable
autoLines
class={styles.textarea}
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..."
rows={4}
/>
</div>

View File

@ -2,6 +2,7 @@ import { useAppState, type Location, LocationScale } from "../contexts/state";
import { useState } from "preact/hooks";
import styles from '../assets/location-editor.module.css';
import LLM from "../utils/llm";
import { ContentEditable } from "@common/components/ContentEditable";
const SCALE_OPTIONS = Object.entries(LocationScale)
.filter(([, value]) => typeof value === 'number')
@ -138,12 +139,12 @@ export const LocationEditor = () => {
<div class={styles.field}>
<div class={styles.label}>Description</div>
<textarea
<ContentEditable
autoLines
class={styles.textarea}
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..."
rows={4}
/>
</div>

View File

@ -28,7 +28,7 @@ export namespace Tools {
appState.dispatch({
type: 'EDIT_STORY',
id: appState.currentStory.id,
text: appState.currentStory.text + args.text,
text: appState.currentStory.text + '\n' + args.text,
});
appState.dispatch({
type: 'SET_CURRENT_TAB',
@ -50,7 +50,7 @@ export namespace Tools {
appState.dispatch({
type: 'EDIT_LORE',
id: appState.currentStory.id,
lore: appState.currentStory.lore + args.text
lore: appState.currentStory.lore + '\n' + args.text,
});
appState.dispatch({
type: 'SET_CURRENT_TAB',
@ -296,7 +296,11 @@ export namespace Tools {
if (!appState.currentStory) {
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 pattern = new RegExp(args.pattern, args.case_sensitive ? 'g' : 'gi');
for (let i = 0; i < lines.length; i++) {
@ -317,11 +321,14 @@ export namespace Tools {
}
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({
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)' })),
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)' },
)),
}),
})
};