Skip to content

Conversation

@Soxasora
Copy link
Member

@Soxasora Soxasora commented Sep 9, 2025

Description

Introduces Lexical as a Markdown/Rich Text editor and renderer
This PR is ready for review as-is. While structure and cleanup are an on-going work, the editor has been QA'd extensively and logic has been refined across these two months.

Screenshots

Comment editor view:
image

Toplevel editor view:
image

Toolbar

Toolbar with shortcuts
image

Toolbar with dropdowns
image

Floating Toolbar
image

Media

Media captions, resize and DnD

Screen.Recording.2025-11-02.at.18.32.34.mp4
Decorators Table of contents
table-of-contents.mp4

Features

core
  • rich text
  • markdown mode
  • formik integration
  • local-storage draft saving
  • max-length support
  • auto-focus support
  • keyboard shortcuts
  • undo/redo history
  • shared history for nested editors
formatting
  • bold, italic, underline, strikethrough
  • subscript, superscript
  • inline code
  • code blocks
  • block quotes
  • headings (H1-H6)
  • paragraphs
  • text alignment (left, center, right, justify)
  • indentation
lists
  • bullet, numbered, check lists
  • parentheses ordered lists (e.g. 1))
content
  • math equations (KaTeX) - inline and block
  • tables with action menu
  • spoilers (inline and container)
  • table of contents generator
media
  • image and video uploads with fees
  • drag and drop upload
  • image captions (nested editor)
  • image resizing
  • YouTube, Twitter, Spotify, PeerTube, Nostr, Wavlake embeds
  • final media type by checking on insert or db upsert
links
  • autolink detection
  • link editor popup
  • SN item mentions (#123456)
  • link fallback for failed media
mentions
  • user mentions (@username) with popover
  • territory mentions (~territory) with popover
  • item mentions
  • typeahead lazy-loaded suggestions
markdown
  • full markdown ↔ Lexical transformation
  • markdown shortcuts in rich mode
  • high-fidelity round-trip conversion
  • highlighted markdown via Shiki
server
  • headless editor for server operations
  • HTML generation from Lexical state
  • HTML sanitization (DOMPurify)
  • media checks
  • legacy content migration worker
UX
  • floating toolbar for selections
  • fixed toolbar for formatting
  • mode switcher (markdown/rich)
  • upload progress indicators
  • copy to clipboard for math nodes
  • link editing popup
  • character count display

New things

  • KaTeX is used in place of MathJax to render equations and LaTeX.
  • Shiki is used in place of React Syntax Highlighter to highlight code blocks - it is undoubtedly faster and lighter than RSH, reaching 4kB of size in the bundle (this has to be re-verified later)
  • Uploads are now concurrently processed, also featuring a percentage, with placeholder per each upload - the user can continue to write while it uploads
  • MediaOrLink preserves scroll on load, supports captions and resize
  • Embeds refactor with a load timeout that spawns an error component (context)
  • HTML from a lexical JSON state is parsed server-side via Linkedom, a performance-focused DOM simulator
  • TBD, a lot has changed from the last update

Additional Context

TBD, a lot has changed from the last update

Progress

TBD, a lot has changed from the last update

Checklist

Are your changes backward compatible? Please answer below:

For example, a change is not backward compatible if you removed a GraphQL field or dropped a database column.
Yes! Everything has backwards compatibility. But it also does not depend on existing features that are going to be replaced.

On a scale of 1-10 how well and how have you QA'd this change and any features it might affect? Please answer below:
tbd

For frontend changes: Tested on mobile, light and dark mode? Please answer below:
UI is tested on both mobile and desktop.

Did you introduce any new environment variables? If so, call them out explicitly here:
n/a

Did you use AI for this? If so, how much did it assist you?
It's a big PR with Lexical being a novel paradigm.

Ask: better lexical understanding, things that weren't clear in documentation or in code, best practices
Agent: massive restructuring, autocomplete, lexical overrides with lint


Note

Introduce a Lexical-based rich/markdown editor and renderer, add schema/API fields for lexicalState/html, server HTML generation, and a worker-driven migration for legacy content, with broad UI integration and media/mention tooling.

  • Backend:
    • DB: add Item.lexicalState (JSONB) and Item.html; create LexicalMigrationLog/LexicalBatchMigrationLog.
    • GraphQL: add lexicalState/html to Item; accept lexicalState in item/sub/user mutations; new executeConversion mutation.
    • Resolvers: prepare Lexical state, generate sanitized HTML, upload ID extraction from text, SN item link regex.
    • Workers: add partitioned legacy content migration jobs; server utils for headless Lexical and HTML generation (DOMPurify/LinkeDOM).
  • Editor/Renderer:
    • New Lexical editor/reader, contexts, theme, toolbar (floating/fixed), shortcuts, history, drafts, max-length.
    • Plugins: uploads with progress/DnD, link editor, mentions (@, ~, #), math (KaTeX), tables with action menu, spoilers, ToC, code highlighting (Shiki), media embeds.
  • UI Integration:
    • Replace MarkdownInput with LexicalInput across forms (posts, comments, jobs, polls, bounties, bio, sub descriptions).
    • Render comments/items via Lexical when available; dev actions for conversion; truncation and outlawed content handling.
    • Media upload refactor (S3 XHR with progress, filtering, EXIF strip), MediaOrLink scroll-preserving and resizing/captions.
    • Misc: tooltip, copy button tweaks, layout/CSS for editor; route /u/:id redirect.

Written by Cursor Bugbot for commit bc69de5. This will update automatically on new commits. Configure here.

… SN theme, formik bridge touchups, a better toolbar style
…gic, image markdown transformer, testing on more scenarios
…t, re-introduce usage of Lexical in read mode
@Soxasora Soxasora changed the title rich text editor and renderer Hybrid Text Editor Sep 20, 2025
@Soxasora Soxasora added the feature new product features that weren't there before label Sep 20, 2025
Soxasora and others added 18 commits November 15, 2025 12:45
…s, remove more parts of experimental migration strategy
…that many worker instances can pick up, idempotently
…up: remove unused code, improve lexical structure; fix: move embed dimensions to the parent rather than the iframe
combine mdHas and mdGetTypes to avoid double markdown parsing
reduce $isMarkdownMode calls
reduce $getSelection calls
…ables and toolbar buttons; fix: client-side shortcuts to avoid hydration mismatches
@Soxasora Soxasora marked this pull request as ready for review November 16, 2025 16:29
const text = markdownNode?.getTextContent() || ''
const percent = total ? Math.floor((loaded / total) * 100) : 0
const regex = new RegExp(`!\\[Uploading ${file.name}… \\d+%\\]\\(${key}\\)`)
const newText = text.replace(regex, `![Uploading ${file.name}${percent}%](${key})`)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Filenames Corrupt Regex Patterns

In markdown mode, file.name is directly interpolated into a regex pattern without escaping special characters. If the filename contains regex metacharacters like ., [, ], (, ), *, +, ?, etc., the regex will either fail to match or match unintended strings, breaking upload progress updates. The same issue exists in onSuccess at line 172 where the regex is constructed identically.

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually during cleanup I was debating the utility of a progress percentage. If we drop it then we don't have to do regexes.

@huumn
Copy link
Member

huumn commented Nov 17, 2025

Woot! The bug hunt begins! I found a couple just now.

Transforms appear to be unreliable:

buggy.transforms.mov

Sometimes the editor will get into a state where the cursor isn't in a paragraph block and new blocks will be weird (eg code block). I seemed to trigger this behavior after transforming back and forth to markdown mode:

buggy.block.mov

@Soxasora
Copy link
Member Author

Soxasora commented Nov 17, 2025

Transforms appear to be unreliable

wow you spotted a very recent bug haha lexical/7974
I'll get to it 🫡

note: transformations need serious QA because covering a lot of scenarios can be really messy.

the cursor isn't in a paragraph block

Ah, might've reduced selection placement calls too much...

toaster.danger(`upload of '${file.name}' failed: ` + e.message || e.toString?.())
}
continue
toaster.danger(`upload of '${file.name}' failed: ` + e.message || e.toString?.())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Error Message Precedence Drops Context

Operator precedence bug in error message construction. The expression evaluates as (fullString + e.message) || e.toString?.(), so if e.message is an empty string, the entire upload context message is discarded and only e.toString() is shown. The fallback should be e.message || e.toString?.() with the string concatenation wrapping the entire result.

Fix in Cursor Fix in Web


console.log('resolve id', id)
resolve(id)
xhr.send(form)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Stale Callbacks: Incomplete Dependency Array

The s3Upload callback's dependency array on line 94 only includes toaster and getSignedPOST, but the callback also uses avatar, onUpload, onError, onProgress, and onSuccess props. If these props change after the component mounts, the callback will continue using stale values, potentially calling outdated handlers or using incorrect avatar settings.

Fix in Cursor Fix in Web

@Soxasora Soxasora marked this pull request as draft November 18, 2025 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature new product features that weren't there before multimedia ui/ux

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants