diff --git a/apps/blog/features/post-editor/lib/utils.ts b/apps/blog/features/post-editor/lib/utils.ts index 932a074b18bd2be762dd490cbcd5ba41fd48b998..ba26f8c18ae7dcf2dd1584db7aef554af3c1760b 100644 --- a/apps/blog/features/post-editor/lib/utils.ts +++ b/apps/blog/features/post-editor/lib/utils.ts @@ -158,9 +158,27 @@ export const onImageUpload = async ( signer: Signer ) => { const url = await uploadImg(file, username, signer); - const insertedMarkdown = insertToTextArea(` ![${file.name}](${!url ? 'UPLOAD FAILED' : url}) `); - if (!insertedMarkdown) return; - setMarkdown(insertedMarkdown); + const imageMarkdown = ` ![${file.name}](${!url ? 'UPLOAD FAILED' : url}) `; + + // Get cursor position from textarea before updating state + const textarea = document.querySelector('.w-md-editor-text-input') as HTMLTextAreaElement; + const cursorPos = textarea?.selectionStart ?? 0; + + // Use functional update to avoid race conditions with React state + setMarkdown((prevMarkdown) => { + const front = prevMarkdown.slice(0, cursorPos); + const back = prevMarkdown.slice(cursorPos); + return front + imageMarkdown + back; + }); + + // Restore cursor position after React re-renders + setTimeout(() => { + if (textarea) { + const newPos = cursorPos + imageMarkdown.length; + textarea.setSelectionRange(newPos, newPos); + textarea.focus(); + } + }, 0); }; export const onImageDrop = async ( @@ -198,27 +216,7 @@ export const onImagePaste = async ( return true; }; -export const insertToTextArea = (insertString: string) => { - const textarea = document.querySelector('textarea'); - if (!textarea) { - return null; - } - - let sentence = textarea.value; - const len = sentence.length; - const pos = textarea.selectionStart; - const end = textarea.selectionEnd; - - const front = sentence.slice(0, pos); - const back = sentence.slice(pos, len); - - sentence = front + insertString + back; - - textarea.value = sentence; - textarea.selectionEnd = end + insertString.length; - - return sentence; -}; +// insertToTextArea removed - caused #672 by DOM manipulation instead of React state export const postClassName = 'font-source text-[16.5px] prose-h1:text-[26.4px] prose-h2:text-[23.1px] prose-h3:text-[19.8px] prose-h4:text-[18.1px] sm:text-[17.6px] sm:prose-h1:text-[28px] sm:prose-h2:text-[24.7px] sm:prose-h3:text-[22.1px] sm:prose-h4:text-[19.4px] lg:text-[19.2px] lg:prose-h1:text-[30.7px] lg:prose-h2:text-[28.9px] lg:prose-h3:text-[23px] lg:prose-h4:text-[21.1px] prose-p:mb-6 prose-p:mt-0 prose-img:cursor-pointer prose-img:max-w-full prose-img:h-auto'; diff --git a/apps/blog/features/post-editor/md-editor.tsx b/apps/blog/features/post-editor/md-editor.tsx index 580be3e2f680118b176af3cbedb8910a3af743c5..f7afe253ba95f595c8abef88220bccf12040e067 100644 --- a/apps/blog/features/post-editor/md-editor.tsx +++ b/apps/blog/features/post-editor/md-editor.tsx @@ -2,10 +2,9 @@ import '@uiw/react-md-editor/markdown-editor.css'; import { - Dispatch, FC, + KeyboardEvent, MutableRefObject, - SetStateAction, useCallback, useEffect, useRef, @@ -105,6 +104,30 @@ const MdEditor: FC = ({ onChange, persistedValue = '', placeholde [setFormValue, signer] ); + // Handle Ctrl+Home/End to ensure cursor scrolls into view (Firefox fix) + const keyDownHandler = useCallback((event: KeyboardEvent) => { + const isCtrlOrCmd = event.ctrlKey || event.metaKey; + if (isCtrlOrCmd && (event.key === 'Home' || event.key === 'End')) { + // Let the default behavior happen first, then scroll + setTimeout(() => { + const textarea = document.querySelector('.w-md-editor-text-input') as HTMLTextAreaElement; + if (textarea) { + // Create a temporary span at cursor position to scroll to + const cursorPos = event.key === 'Home' ? 0 : textarea.value.length; + textarea.setSelectionRange(cursorPos, cursorPos); + textarea.blur(); + textarea.focus(); + // Ensure the textarea scrolls to show cursor + if (event.key === 'Home') { + textarea.scrollTop = 0; + } else { + textarea.scrollTop = textarea.scrollHeight; + } + } + }, 0); + } + }, []); + const imgBtn = (inputRef: MutableRefObject): commands.ICommand => ({ name: 'Text To Image', keyCommand: 'text2image', @@ -189,7 +212,7 @@ const MdEditor: FC = ({ onChange, persistedValue = '', placeholde //@ts-ignore onChange={inputImageHandler} /> -
+
{ link.classList.add('videoWrapper'); }); - const paragraphs = ref.current?.querySelectorAll('p'); - if (!mainPost) paragraphs?.forEach((p) => (p.className = 'my-0')); + // Note: Previously removed margins from paragraphs when !mainPost (preview mode) + // This caused issue #759 where line breaks/spacing weren't visible in preview + // Now paragraphs keep their default prose styling in both preview and published view if (communityDescription) { const elementsWithVideoWrapper = document.querySelectorAll('.videoWrapper'); elementsWithVideoWrapper.forEach((element) => { diff --git a/packages/renderer/src/renderers/default/embedder/HtmlDOMParser.ts b/packages/renderer/src/renderers/default/embedder/HtmlDOMParser.ts index 19bfcbf60a335ad8638fa5cfbfe2b0c8d96ca7cc..7ac95dd1b6fab8ced49fe73cfc27376b99e5e0c6 100644 --- a/packages/renderer/src/renderers/default/embedder/HtmlDOMParser.ts +++ b/packages/renderer/src/renderers/default/embedder/HtmlDOMParser.ts @@ -327,12 +327,18 @@ export class HtmlDOMParser { const data = this.xmlSerializer.serializeToString(child); const content = this.linkify(data); if (this.mutate && content !== data) { - const newChild = this.domParser.parseFromString(`${content}`).childNodes[0]; const parent = child.parentNode; if (parent) { - parent.insertBefore(newChild, child); + // Parse linkified content and insert children directly (without span wrapper) + // This fixes issue #632 where span wrappers could break table cell rendering + const tempDoc = this.domParser.parseFromString(`${content}`); + const wrapper = tempDoc.childNodes[0] as Element; + if (wrapper && wrapper.childNodes) { + Array.from(wrapper.childNodes).forEach((newChild) => { + parent.insertBefore(newChild.cloneNode(true), child); + }); + } parent.removeChild(child); - parent.appendChild; } return; } diff --git a/packages/tailwindcss/globals.css b/packages/tailwindcss/globals.css index 2569b001fa324d938e95330425a3ba05545ae7a6..c5e5911aeff12e603fa0f158200d42cff461bbda 100644 --- a/packages/tailwindcss/globals.css +++ b/packages/tailwindcss/globals.css @@ -214,6 +214,19 @@ hyphens: none; } +/* Fix table cell content rendering - issue #632 */ +.prose table td a, +.prose table th a { + word-break: normal; + white-space: nowrap; +} + +.prose table td, +.prose table th { + vertical-align: middle; + padding: 8px 12px; +} + .dark .w-md-editor-text .token.title { color: #fff !important; }