From 360bb1d714486e0bd324b1c00fe395e41a11a90f Mon Sep 17 00:00:00 2001 From: Pabloader Date: Wed, 25 Mar 2026 14:51:02 +0000 Subject: [PATCH] Fix newlines in the contenteditable --- src/common/assets/content-editable.module.css | 10 +++++++++- src/common/components/ContentEditable.tsx | 11 +++++++++-- src/games/storywriter/assets/editor.module.css | 5 ----- src/games/storywriter/components/editor.tsx | 11 +++++------ 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/common/assets/content-editable.module.css b/src/common/assets/content-editable.module.css index 8eca9b9..8331659 100644 --- a/src/common/assets/content-editable.module.css +++ b/src/common/assets/content-editable.module.css @@ -2,9 +2,17 @@ overflow: hidden; } +.root { + white-space: pre-wrap; + outline: none; + word-wrap: break-word; + overflow-wrap: break-word; + border: none; +} + .root:empty::before { content: attr(data-placeholder); color: var(--text-muted); font-style: italic; pointer-events: none; -} +} \ No newline at end of file diff --git a/src/common/components/ContentEditable.tsx b/src/common/components/ContentEditable.tsx index a52a6c8..81328f6 100644 --- a/src/common/components/ContentEditable.tsx +++ b/src/common/components/ContentEditable.tsx @@ -70,7 +70,7 @@ export const ContentEditable = ({ value, placeholder, autoLines, onInput, class: }, [value]); const handleKeyDown: JSX.KeyboardEventHandler = (e) => { - if (e.key !== 'Enter') return; + if (e.key !== 'Enter' || !ref.current) return; e.preventDefault(); const sel = window.getSelection(); @@ -79,7 +79,10 @@ export const ContentEditable = ({ value, placeholder, autoLines, onInput, class: const range = sel.getRangeAt(0); 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.setStartAfter(newline); range.collapse(true); @@ -87,11 +90,15 @@ export const ContentEditable = ({ value, placeholder, autoLines, onInput, class: sel.removeAllRanges(); sel.addRange(range); + (ref.current as any).value = ref.current.textContent; ref.current?.dispatchEvent(new InputEvent('input', { bubbles: true })); }; const handleInput: JSX.EventHandler> = (e) => { if (autoLines && ref.current) resizeToContent(ref.current); + if (ref.current) { + (ref.current as any).value = ref.current.textContent; + } onInput?.(e); }; diff --git a/src/games/storywriter/assets/editor.module.css b/src/games/storywriter/assets/editor.module.css index c88b025..03994d4 100644 --- a/src/games/storywriter/assets/editor.module.css +++ b/src/games/storywriter/assets/editor.module.css @@ -33,12 +33,7 @@ line-height: 1.9; color: var(--textColor); background: transparent; - border: none; - outline: none; box-sizing: border-box; - white-space: pre-wrap; - word-wrap: break-word; - overflow-wrap: break-word; &::placeholder { color: var(--text-muted); diff --git a/src/games/storywriter/components/editor.tsx b/src/games/storywriter/components/editor.tsx index 213e485..9f122ba 100644 --- a/src/games/storywriter/components/editor.tsx +++ b/src/games/storywriter/components/editor.tsx @@ -7,6 +7,7 @@ import clsx from "clsx"; import { CharacterEditor } from "./character-editor"; import { LocationEditor } from "./location-editor"; import { ChaptersEditor } from "./chapters-editor"; +import { useInputCallback } from "@common/hooks/useInputCallback"; const TABS: { id: Tab; label: string }[] = [ { id: "story", label: "Story" }, @@ -23,23 +24,21 @@ export const Editor = () => { return
; } - const handleInput = (e: Event) => { - const text = (e.target as HTMLElement).textContent || ''; + const handleInput = useInputCallback((text: string) => { dispatch({ type: 'EDIT_STORY', id: currentStory.id, text, }); - }; + }, []); - const handleLoreInput = (e: Event) => { - const lore = (e.target as HTMLElement).textContent || ''; + const handleLoreInput = useInputCallback((lore: string) => { dispatch({ type: 'EDIT_LORE', id: currentStory.id, lore, }); - }; + }, []); const handleTabChange = (tab: Tab) => { dispatch({