mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-09 15:07:10 +02:00
Fix newline rendering in chat messages (#80)
* Fix newline rendering in chat messages The Text component was not properly rendering newlines in chat messages. The previous implementation had buggy logic that only rendered <br /> for empty lines and used an inconsistent mix of spans and divs for non-empty lines, which didn't create proper line breaks between consecutive text lines. Changes: - Render each line in a span with <br /> between consecutive lines - Remove unused useMemo import and fix React hooks violation - Simplify logic for better maintainability This ensures that multi-line messages in chat (and other text content) display correctly with proper line breaks. Fixes rendering of newlines in NIP-29 groups and NIP-53 live chat. * Preserve newlines when sending chat messages The MentionEditor's serializeContent function was not handling hardBreak nodes created by Shift+Enter. This caused newlines within messages to be lost during serialization, even though the editor displayed them correctly. Changes: - Add hardBreak node handling in serializeContent - Preserve newlines (\n) from Shift+Enter keypresses - Ensure multi-line messages are sent with proper line breaks With this fix and the previous Text.tsx fix, newlines are now properly: 1. Captured when typing (Shift+Enter creates hardBreak) 2. Preserved when sending (hardBreak serialized as \n) 3. Rendered when displaying (Text component renders \n as <br />) * Make Enter insert newline on mobile devices On mobile devices, pressing Enter now inserts a newline (hardBreak) instead of submitting the message. This provides better UX since mobile keyboards don't have easy access to Shift+Enter for multiline input. Behavior: - Desktop: Enter submits, Shift+Enter inserts newline (unchanged) - Mobile: Enter inserts newline, Cmd/Ctrl+Enter submits - Mobile detection: Uses touch support API (ontouchstart or maxTouchPoints) Users can still submit messages on mobile using: 1. The Send button (primary method) 2. Ctrl+Enter keyboard shortcut (if available) --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -584,6 +584,9 @@ export const MentionEditor = forwardRef<
|
||||
node.content?.forEach((child: any) => {
|
||||
if (child.type === "text") {
|
||||
text += child.text;
|
||||
} else if (child.type === "hardBreak") {
|
||||
// Preserve newlines from Shift+Enter
|
||||
text += "\n";
|
||||
} else if (child.type === "mention") {
|
||||
const pubkey = child.attrs?.id;
|
||||
if (pubkey) {
|
||||
@@ -664,6 +667,9 @@ export const MentionEditor = forwardRef<
|
||||
|
||||
// Build extensions array
|
||||
const extensions = useMemo(() => {
|
||||
// Detect mobile devices (touch support)
|
||||
const isMobile = "ontouchstart" in window || navigator.maxTouchPoints > 0;
|
||||
|
||||
// Custom extension for keyboard shortcuts (runs before suggestion plugins)
|
||||
const SubmitShortcut = Extension.create({
|
||||
name: "submitShortcut",
|
||||
@@ -674,10 +680,16 @@ export const MentionEditor = forwardRef<
|
||||
handleSubmitRef.current(editor);
|
||||
return true;
|
||||
},
|
||||
// Plain Enter submits (Shift+Enter handled by hardBreak for newlines)
|
||||
// Plain Enter behavior depends on device
|
||||
Enter: ({ editor }) => {
|
||||
handleSubmitRef.current(editor);
|
||||
return true;
|
||||
if (isMobile) {
|
||||
// On mobile, Enter inserts a newline (hardBreak)
|
||||
return editor.commands.setHardBreak();
|
||||
} else {
|
||||
// On desktop, Enter submits the message
|
||||
handleSubmitRef.current(editor);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { CommonData } from "applesauce-content/nast";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface TextNodeProps {
|
||||
node: {
|
||||
@@ -11,23 +10,22 @@ interface TextNodeProps {
|
||||
|
||||
export function Text({ node }: TextNodeProps) {
|
||||
const text = node.value;
|
||||
const lines = useMemo(() => text.split("\n"), [text]);
|
||||
if (text.includes("\n")) {
|
||||
return (
|
||||
<>
|
||||
{lines.map((line, idx) =>
|
||||
line.trim().length === 0 ? (
|
||||
<br />
|
||||
) : idx === 0 || idx === lines.length - 1 ? (
|
||||
<span dir="auto">{line}</span> // FIXME: this should be span or div depnding on context
|
||||
) : (
|
||||
<div dir="auto" key={idx}>
|
||||
{line}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
// If no newlines, render as simple span
|
||||
if (!text.includes("\n")) {
|
||||
return <span dir="auto">{text}</span>;
|
||||
}
|
||||
return <span dir="auto">{text}</span>;
|
||||
|
||||
// Multi-line text: split and render with <br /> between lines
|
||||
const lines = text.split("\n");
|
||||
return (
|
||||
<>
|
||||
{lines.map((line, idx) => (
|
||||
<span key={idx} dir="auto">
|
||||
{line}
|
||||
{idx < lines.length - 1 && <br />}
|
||||
</span>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user