feat: add document conversion support (Markdown, HTML, Plain Text)

- Add marked for Markdown to HTML conversion with GFM support
- Add turndown for HTML to Markdown conversion
- Add DOMPurify for HTML sanitization (security)
- Support Markdown ↔ HTML ↔ Plain Text conversions
- Add styled HTML output with responsive design
- Use client-side only DOMPurify to fix SSR issues

Supported conversions:
- Markdown → HTML (with code syntax, tables, blockquotes)
- HTML → Markdown (clean formatting preservation)
- Markdown/HTML → Plain Text (strip formatting)
- Plain Text → HTML/Markdown (basic formatting)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-17 11:01:08 +01:00
parent 1d9f10fd32
commit 9de639b138
5 changed files with 367 additions and 20 deletions

49
pnpm-lock.yaml generated
View File

@@ -20,12 +20,18 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
dompurify:
specifier: ^3.2.2
version: 3.3.0
fuse.js:
specifier: ^7.1.0
version: 7.1.0
lucide-react:
specifier: ^0.553.0
version: 0.553.0(react@19.2.0)
marked:
specifier: ^15.0.4
version: 15.0.12
next:
specifier: ^16.0.0
version: 16.0.3(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -38,6 +44,9 @@ importers:
tailwind-merge:
specifier: ^3.3.1
version: 3.4.0
turndown:
specifier: ^7.2.0
version: 7.2.2
devDependencies:
'@tailwindcss/postcss':
specifier: ^4.1.17
@@ -51,6 +60,9 @@ importers:
'@types/react-dom':
specifier: ^19
version: 19.2.3(@types/react@19.2.5)
'@types/turndown':
specifier: ^5.0.5
version: 5.0.6
eslint:
specifier: ^9
version: 9.39.1(jiti@2.6.1)
@@ -368,6 +380,9 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@mixmark-io/domino@2.2.0':
resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==}
'@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
@@ -558,6 +573,12 @@ packages:
'@types/react@19.2.5':
resolution: {integrity: sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw==}
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
'@types/turndown@5.0.6':
resolution: {integrity: sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==}
'@typescript-eslint/eslint-plugin@8.46.4':
resolution: {integrity: sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -910,6 +931,9 @@ packages:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
dompurify@3.3.0:
resolution: {integrity: sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -1509,6 +1533,11 @@ packages:
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
marked@15.0.12:
resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
engines: {node: '>= 18'}
hasBin: true
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -1871,6 +1900,9 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
turndown@7.2.2:
resolution: {integrity: sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==}
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -2266,6 +2298,8 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@mixmark-io/domino@2.2.0': {}
'@napi-rs/wasm-runtime@0.2.12':
dependencies:
'@emnapi/core': 1.7.1
@@ -2415,6 +2449,11 @@ snapshots:
dependencies:
csstype: 3.2.2
'@types/trusted-types@2.0.7':
optional: true
'@types/turndown@5.0.6': {}
'@typescript-eslint/eslint-plugin@8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
@@ -2788,6 +2827,10 @@ snapshots:
dependencies:
esutils: 2.0.3
dompurify@3.3.0:
optionalDependencies:
'@types/trusted-types': 2.0.7
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -3520,6 +3563,8 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
marked@15.0.12: {}
math-intrinsics@1.1.0: {}
merge2@1.4.1: {}
@@ -3947,6 +3992,10 @@ snapshots:
tslib@2.8.1: {}
turndown@7.2.2:
dependencies:
'@mixmark-io/domino': 2.2.0
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1