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; 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;

View File

@ -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..."

View File

@ -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>

View File

@ -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>

View File

@ -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)' },
)),
}), }),
}) })
}; };