From ffd4172a64b11daf3e84006286217cefe301a0a4 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 10 Jul 2026 00:37:05 +0300 Subject: [PATCH] Replace @tiptap/html generateJSON with native DOMParser equivalent (#11098) --- package.json | 1 - pnpm-lock.yaml | 30 --------------- .../com/composer/text-input/TextInput.web.tsx | 37 ++++++++++++++++--- 3 files changed, 31 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index d50caa57ee..7a46301e8d 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,6 @@ "@tiptap/extension-paragraph": "^2.9.1", "@tiptap/extension-placeholder": "^2.9.1", "@tiptap/extension-text": "^2.9.1", - "@tiptap/html": "^2.9.1", "@tiptap/pm": "^2.9.1", "@tiptap/react": "^2.9.1", "@tiptap/suggestion": "^2.9.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f1cbc333bb..6840d80184 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -382,9 +382,6 @@ importers: '@tiptap/extension-text': specifier: ^2.9.1 version: 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) - '@tiptap/html': - specifier: ^2.9.1 - version: 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) '@tiptap/pm': specifier: ^2.9.1 version: 2.27.2 @@ -3450,12 +3447,6 @@ packages: peerDependencies: '@tiptap/core': ^2.7.0 - '@tiptap/html@2.27.2': - resolution: {integrity: sha512-WiZgAvFjUprjyAczxAHLN9k++w7klwsCJ7EJDljtW/QXQ476Q0GqNtaHG4WRuW25cBH7c7AWZFptALeak2OE4Q==} - peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 - '@tiptap/pm@2.27.2': resolution: {integrity: sha512-kaEg7BfiJPDQMKbjVIzEPO3wlcA+pZb2tlcK9gPrdDnEFaec2QTF1sXz2ak2IIb2curvnIrQ4yrfHgLlVA72wA==} @@ -5013,10 +5004,6 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - entities@5.0.0: - resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==} - engines: {node: '>=0.12'} - entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -9432,10 +9419,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zeed-dom@0.15.1: - resolution: {integrity: sha512-dtZ0aQSFyZmoJS0m06/xBN1SazUBPL5HpzlAcs/KcRW0rzadYw12deQBjeMhGKMMeGEp7bA9vmikMLaO4exBcg==} - engines: {node: '>=14.13.1'} - zod-validation-error@3.5.4: resolution: {integrity: sha512-+hEiRIiPobgyuFlEojnqjJnhFvg4r/i3cqgcm67eehZf/WBaK3g6cD02YU9mtdVxZjv8CzCA9n/Rhrs3yAAvAw==} engines: {node: '>=18.0.0'} @@ -12653,12 +12636,6 @@ snapshots: dependencies: '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) - '@tiptap/html@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': - dependencies: - '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) - '@tiptap/pm': 2.27.2 - zeed-dom: 0.15.1 - '@tiptap/pm@2.27.2': dependencies: prosemirror-changeset: 2.4.1 @@ -14354,8 +14331,6 @@ snapshots: entities@4.5.0: {} - entities@5.0.0: {} - entities@6.0.1: {} env-editor@0.4.2: {} @@ -19435,11 +19410,6 @@ snapshots: yocto-queue@0.1.0: {} - zeed-dom@0.15.1: - dependencies: - css-what: 6.2.2 - entities: 5.0.0 - zod-validation-error@3.5.4(zod@3.25.76): dependencies: zod: 3.25.76 diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index bb314f116c..e76d9e3f3d 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -10,6 +10,7 @@ import {StyleSheet, View} from 'react-native' import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' import {AppBskyRichtextFacet, RichText} from '@atproto/api' import {Trans} from '@lingui/react/macro' +import {getSchema} from '@tiptap/core' import {Document} from '@tiptap/extension-document' import Hardbreak from '@tiptap/extension-hard-break' import History from '@tiptap/extension-history' @@ -17,8 +18,12 @@ import {Mention} from '@tiptap/extension-mention' import {Paragraph} from '@tiptap/extension-paragraph' import {Placeholder} from '@tiptap/extension-placeholder' import {Text as TiptapText} from '@tiptap/extension-text' -import {generateJSON} from '@tiptap/html' -import {Fragment, Node, Slice} from '@tiptap/pm/model' +import { + DOMParser as ProseMirrorDOMParser, + Fragment, + Node, + Slice, +} from '@tiptap/pm/model' import {EditorContent, type JSONContent, useEditor} from '@tiptap/react' import {splitGraphemes} from 'unicode-segmenter/grapheme' @@ -242,9 +247,7 @@ export function TextInput({ } }, }, - content: generateJSON(richTextToHTML(richtext), extensions, { - preserveWhitespace: 'full', - }), + content: richTextToEditorJson(richtext, extensions), autofocus: autoFocus ? 'end' : null, editable: true, injectCSS: true, @@ -381,6 +384,26 @@ export function TextInput({ * * It also escapes HTML characters */ +/* + * Equivalent of @tiptap/html's generateJSON, but using the browser's native + * DOMParser. The @tiptap/html version supports SSR via zeed-dom, which pulls + * ~250KB of parser dependencies into the bundle - we are always in a browser + * here, so we don't need it. + */ +function richTextToEditorJson( + richtext: RichText, + extensions: Parameters[0], +): JSONContent { + const schema = getSchema(extensions) + const dom = new DOMParser().parseFromString( + richTextToHTML(richtext), + 'text/html', + ).body + return ProseMirrorDOMParser.fromSchema(schema) + .parse(dom, {preserveWhitespace: 'full'}) + .toJSON() +} + function richTextToHTML(richtext: RichText): string { let html = '' @@ -392,7 +415,9 @@ function richTextToHTML(richtext: RichText): string { } } - return html + // the body wrapper preserves leading whitespace, which the parser + // otherwise strips + return `${html}` } function escapeHTML(str: string): string {