From 86705f9c8e33ce1cb2d7310e456d73cf85d316c0 Mon Sep 17 00:00:00 2001 From: Louis Knight-Webb Date: Fri, 5 Dec 2025 16:25:23 +0000 Subject: [PATCH] The fix is complete. Here's a summary of the changes: (#1441) **Changes made:** 1. **`frontend/src/components/ui/wysiwyg.tsx`**: - Removed imports for `INLINE_CODE`, `InlineCodeNode`, and `INLINE_CODE_TRANSFORMER` - Removed `InlineCodeNode` from the registered nodes array - Simplified transformers to use Lexical's built-in `TRANSFORMERS` (which includes `INLINE_CODE`) 2. **Deleted files:** - `frontend/src/components/ui/wysiwyg/nodes/inline-code-node.tsx` - `frontend/src/components/ui/wysiwyg/transformers/inline-code-transformer.ts` **What this fixes:** 1. **Escaping bug** - Backticks will no longer be incorrectly escaped when saving inline code 2. **Cannot edit inline code** - You can now edit characters within inline code blocks (TextNode is editable, unlike DecoratorNode which was atomic) The TypeScript and lint checks pass. You can test by: 1. Creating a task with inline code like `` `console.log()` `` 2. Saving and reloading - no backslashes should appear 3. Clicking inside inline code and editing individual characters --- frontend/src/components/ui/wysiwyg.tsx | 13 +- .../ui/wysiwyg/nodes/inline-code-node.tsx | 154 ------------------ .../transformers/inline-code-transformer.ts | 27 --- 3 files changed, 2 insertions(+), 192 deletions(-) delete mode 100644 frontend/src/components/ui/wysiwyg/nodes/inline-code-node.tsx delete mode 100644 frontend/src/components/ui/wysiwyg/transformers/inline-code-transformer.ts diff --git a/frontend/src/components/ui/wysiwyg.tsx b/frontend/src/components/ui/wysiwyg.tsx index 0f40ee81..304080af 100644 --- a/frontend/src/components/ui/wysiwyg.tsx +++ b/frontend/src/components/ui/wysiwyg.tsx @@ -5,12 +5,10 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; import { ContentEditable } from '@lexical/react/LexicalContentEditable'; import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin'; -import { TRANSFORMERS, INLINE_CODE, type Transformer } from '@lexical/markdown'; +import { TRANSFORMERS, type Transformer } from '@lexical/markdown'; import { ImageNode } from './wysiwyg/nodes/image-node'; -import { InlineCodeNode } from './wysiwyg/nodes/inline-code-node'; import { IMAGE_TRANSFORMER } from './wysiwyg/transformers/image-transformer'; import { CODE_BLOCK_TRANSFORMER } from './wysiwyg/transformers/code-block-transformer'; -import { INLINE_CODE_TRANSFORMER } from './wysiwyg/transformers/inline-code-transformer'; import { TaskAttemptContext, TaskContext, @@ -142,21 +140,14 @@ function WYSIWYGEditor({ CodeHighlightNode, LinkNode, ImageNode, - InlineCodeNode, ], }), [] ); // Extended transformers with image and code block support (memoized to prevent unnecessary re-renders) - // Filter out default INLINE_CODE to use our custom INLINE_CODE_TRANSFORMER with syntax highlighting const extendedTransformers: Transformer[] = useMemo( - () => [ - IMAGE_TRANSFORMER, - CODE_BLOCK_TRANSFORMER, - INLINE_CODE_TRANSFORMER, - ...TRANSFORMERS.filter((t) => t !== INLINE_CODE), - ], + () => [IMAGE_TRANSFORMER, CODE_BLOCK_TRANSFORMER, ...TRANSFORMERS], [] ); diff --git a/frontend/src/components/ui/wysiwyg/nodes/inline-code-node.tsx b/frontend/src/components/ui/wysiwyg/nodes/inline-code-node.tsx deleted file mode 100644 index 5cd1f6ba..00000000 --- a/frontend/src/components/ui/wysiwyg/nodes/inline-code-node.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { - DecoratorNode, - DOMConversionMap, - DOMExportOutput, - LexicalNode, - NodeKey, - SerializedLexicalNode, - Spread, -} from 'lexical'; -import { PrismTokenizer } from '@lexical/code'; -import { CODE_HIGHLIGHT_CLASSES } from '../lib/code-highlight-theme'; - -export type SerializedInlineCodeNode = Spread< - { - code: string; - }, - SerializedLexicalNode ->; - -interface Token { - type: string; - content: string | Token | (string | Token)[]; -} - -function renderToken(token: string | Token, index: number): React.ReactNode { - if (typeof token === 'string') { - return token; - } - - const className = CODE_HIGHLIGHT_CLASSES[token.type] || ''; - - // Handle nested tokens - let content: React.ReactNode; - if (typeof token.content === 'string') { - content = token.content; - } else if (Array.isArray(token.content)) { - content = token.content.map((t, i) => renderToken(t, i)); - } else { - content = renderToken(token.content, 0); - } - - return ( - - {content} - - ); -} - -function InlineCodeComponent({ code }: { code: string }): JSX.Element { - // Use PrismTokenizer to tokenize the code - const tokens = PrismTokenizer.tokenize(code); - - return ( - - {tokens.map((token, index) => renderToken(token, index))} - - ); -} - -export class InlineCodeNode extends DecoratorNode { - __code: string; - - static getType(): string { - return 'inline-code'; - } - - static clone(node: InlineCodeNode): InlineCodeNode { - return new InlineCodeNode(node.__code, node.__key); - } - - constructor(code: string, key?: NodeKey) { - super(key); - this.__code = code; - } - - createDOM(): HTMLElement { - const span = document.createElement('span'); - return span; - } - - updateDOM(): false { - return false; - } - - static importJSON(serializedNode: SerializedInlineCodeNode): InlineCodeNode { - const { code } = serializedNode; - return $createInlineCodeNode(code); - } - - exportJSON(): SerializedInlineCodeNode { - return { - type: 'inline-code', - version: 1, - code: this.__code, - }; - } - - static importDOM(): DOMConversionMap | null { - return { - code: (domNode: HTMLElement) => { - // Only import inline code elements (not block code) - const isBlock = - domNode.parentElement?.tagName === 'PRE' || - domNode.style.display === 'block'; - if (isBlock) { - return null; - } - return { - conversion: (node: HTMLElement) => { - const code = node.textContent || ''; - return { node: $createInlineCodeNode(code) }; - }, - priority: 0, - }; - }, - }; - } - - exportDOM(): DOMExportOutput { - const code = document.createElement('code'); - code.textContent = this.__code; - return { element: code }; - } - - getCode(): string { - return this.__code; - } - - getTextContent(): string { - return this.__code; - } - - decorate(): JSX.Element { - return ; - } - - isInline(): boolean { - return true; - } - - isKeyboardSelectable(): boolean { - return true; - } -} - -export function $createInlineCodeNode(code: string): InlineCodeNode { - return new InlineCodeNode(code); -} - -export function $isInlineCodeNode( - node: LexicalNode | null | undefined -): node is InlineCodeNode { - return node instanceof InlineCodeNode; -} diff --git a/frontend/src/components/ui/wysiwyg/transformers/inline-code-transformer.ts b/frontend/src/components/ui/wysiwyg/transformers/inline-code-transformer.ts deleted file mode 100644 index 6b45ef41..00000000 --- a/frontend/src/components/ui/wysiwyg/transformers/inline-code-transformer.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TextMatchTransformer } from '@lexical/markdown'; -import { - $createInlineCodeNode, - $isInlineCodeNode, - InlineCodeNode, -} from '../nodes/inline-code-node'; - -export const INLINE_CODE_TRANSFORMER: TextMatchTransformer = { - dependencies: [InlineCodeNode], - export: (node) => { - if ($isInlineCodeNode(node)) { - return '`' + node.getCode() + '`'; - } - return null; - }, - // Match backtick-wrapped code during import (paste) - importRegExp: /`([^`]+)`/, - // Match at end of line while typing - regExp: /`([^`]+)`$/, - replace: (textNode, match) => { - const [, code] = match; - const inlineCodeNode = $createInlineCodeNode(code); - textNode.replace(inlineCodeNode); - }, - trigger: '`', - type: 'text-match', -};