1
0
Fork 0

Fix newlines in the contenteditable

This commit is contained in:
Pabloader 2026-03-25 14:51:02 +00:00
parent a605c95890
commit 360bb1d714
4 changed files with 23 additions and 14 deletions

View File

@ -2,6 +2,14 @@
overflow: hidden; overflow: hidden;
} }
.root {
white-space: pre-wrap;
outline: none;
word-wrap: break-word;
overflow-wrap: break-word;
border: none;
}
.root:empty::before { .root:empty::before {
content: attr(data-placeholder); content: attr(data-placeholder);
color: var(--text-muted); color: var(--text-muted);

View File

@ -70,7 +70,7 @@ export const ContentEditable = ({ value, placeholder, autoLines, onInput, class:
}, [value]); }, [value]);
const handleKeyDown: JSX.KeyboardEventHandler<HTMLDivElement> = (e) => { const handleKeyDown: JSX.KeyboardEventHandler<HTMLDivElement> = (e) => {
if (e.key !== 'Enter') return; if (e.key !== 'Enter' || !ref.current) return;
e.preventDefault(); e.preventDefault();
const sel = window.getSelection(); const sel = window.getSelection();
@ -79,7 +79,10 @@ export const ContentEditable = ({ value, placeholder, autoLines, onInput, class:
const range = sel.getRangeAt(0); const range = sel.getRangeAt(0);
range.deleteContents(); range.deleteContents();
const newline = document.createTextNode('\n'); const endsWithNewline = ref.current.textContent?.endsWith('\n');
const caretAtEnd = getCaretOffset(ref.current) === ref.current.textContent.length;
const newline = document.createTextNode('\n'.repeat((endsWithNewline || !caretAtEnd) ? 1 : 2));
range.insertNode(newline); range.insertNode(newline);
range.setStartAfter(newline); range.setStartAfter(newline);
range.collapse(true); range.collapse(true);
@ -87,11 +90,15 @@ export const ContentEditable = ({ value, placeholder, autoLines, onInput, class:
sel.removeAllRanges(); sel.removeAllRanges();
sel.addRange(range); sel.addRange(range);
(ref.current as any).value = ref.current.textContent;
ref.current?.dispatchEvent(new InputEvent('input', { bubbles: true })); ref.current?.dispatchEvent(new InputEvent('input', { bubbles: true }));
}; };
const handleInput: JSX.EventHandler<JSX.TargetedInputEvent<HTMLDivElement>> = (e) => { const handleInput: JSX.EventHandler<JSX.TargetedInputEvent<HTMLDivElement>> = (e) => {
if (autoLines && ref.current) resizeToContent(ref.current); if (autoLines && ref.current) resizeToContent(ref.current);
if (ref.current) {
(ref.current as any).value = ref.current.textContent;
}
onInput?.(e); onInput?.(e);
}; };

View File

@ -33,12 +33,7 @@
line-height: 1.9; line-height: 1.9;
color: var(--textColor); color: var(--textColor);
background: transparent; background: transparent;
border: none;
outline: none;
box-sizing: border-box; box-sizing: border-box;
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
&::placeholder { &::placeholder {
color: var(--text-muted); color: var(--text-muted);

View File

@ -7,6 +7,7 @@ import clsx from "clsx";
import { CharacterEditor } from "./character-editor"; import { CharacterEditor } from "./character-editor";
import { LocationEditor } from "./location-editor"; import { LocationEditor } from "./location-editor";
import { ChaptersEditor } from "./chapters-editor"; import { ChaptersEditor } from "./chapters-editor";
import { useInputCallback } from "@common/hooks/useInputCallback";
const TABS: { id: Tab; label: string }[] = [ const TABS: { id: Tab; label: string }[] = [
{ id: "story", label: "Story" }, { id: "story", label: "Story" },
@ -23,23 +24,21 @@ export const Editor = () => {
return <div class={styles.editor} />; return <div class={styles.editor} />;
} }
const handleInput = (e: Event) => { const handleInput = useInputCallback((text: string) => {
const text = (e.target as HTMLElement).textContent || '';
dispatch({ dispatch({
type: 'EDIT_STORY', type: 'EDIT_STORY',
id: currentStory.id, id: currentStory.id,
text, text,
}); });
}; }, []);
const handleLoreInput = (e: Event) => { const handleLoreInput = useInputCallback((lore: string) => {
const lore = (e.target as HTMLElement).textContent || '';
dispatch({ dispatch({
type: 'EDIT_LORE', type: 'EDIT_LORE',
id: currentStory.id, id: currentStory.id,
lore, lore,
}); });
}; }, []);
const handleTabChange = (tab: Tab) => { const handleTabChange = (tab: Tab) => {
dispatch({ dispatch({