Skip to content

Commit

Permalink
feat(core): add JSX transformer for DX of renderHTML method (#5558)
Browse files Browse the repository at this point in the history
  • Loading branch information
nperez0111 authored Jan 22, 2025
1 parent 6b4c67c commit dd0a25f
Show file tree
Hide file tree
Showing 29 changed files with 370 additions and 17 deletions.
13 changes: 13 additions & 0 deletions demos/src/Examples/JSX/React/Paragraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/** @jsxImportSource @tiptap/core */
import { mergeAttributes } from '@tiptap/core'
import { Paragraph as BaseParagraph } from '@tiptap/extension-paragraph'

export const Paragraph = BaseParagraph.extend({
renderHTML({ HTMLAttributes }) {
return (
<p {...mergeAttributes(HTMLAttributes, { style: 'color: red' })}>
<slot />
</p>
)
},
})
Empty file.
16 changes: 16 additions & 0 deletions demos/src/Examples/JSX/React/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
context('/src/Examples/JSX/React/', () => {
beforeEach(() => {
cy.visit('/src/Examples/JSX/React/')
})

it('should have a working tiptap instance', () => {
cy.get('.tiptap').then(([{ editor }]) => {
// eslint-disable-next-line
expect(editor).to.not.be.null
})
})

it('should have paragraphs colored as red', () => {
cy.get('.tiptap p').should('have.css', 'color', 'rgb(255, 0, 0)')
})
})
25 changes: 25 additions & 0 deletions demos/src/Examples/JSX/React/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import './styles.scss'

import { EditorContent, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import React from 'react'

import { Paragraph } from './Paragraph.jsx'

export default () => {
const editor = useEditor({
extensions: [
StarterKit.configure({
paragraph: false,
}),
Paragraph,
],
content: `
<p>
Each paragraph will be red
</p>
`,
})

return <EditorContent editor={editor} />
}
91 changes: 91 additions & 0 deletions demos/src/Examples/JSX/React/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* Basic editor styles */
.tiptap {
:first-child {
margin-top: 0;
}

/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;

li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}

/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}

h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}

h1 {
font-size: 1.4rem;
}

h2 {
font-size: 1.2rem;
}

h3 {
font-size: 1.1rem;
}

h4,
h5,
h6 {
font-size: 1rem;
}

/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}

pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;

code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}

blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}

hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}
13 changes: 13 additions & 0 deletions demos/src/Examples/JSX/Vue/Paragraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/** @jsxImportSource @tiptap/core */
import { mergeAttributes } from '@tiptap/core'
import { Paragraph as BaseParagraph } from '@tiptap/extension-paragraph'

export const Paragraph = BaseParagraph.extend({
renderHTML({ HTMLAttributes }) {
return (
<p {...mergeAttributes(HTMLAttributes, { style: 'color: red' })}>
<slot />
</p>
)
},
})
Empty file.
16 changes: 16 additions & 0 deletions demos/src/Examples/JSX/Vue/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
context('/src/Examples/JSX/Vue/', () => {
beforeEach(() => {
cy.visit('/src/Examples/JSX/Vue/')
})

it('should have a working tiptap instance', () => {
cy.get('.tiptap').then(([{ editor }]) => {
// eslint-disable-next-line
expect(editor).to.not.be.null
})
})

it('should have paragraphs colored as red', () => {
cy.get('.tiptap p').should('have.css', 'color', 'rgb(255, 0, 0)')
})
})
52 changes: 52 additions & 0 deletions demos/src/Examples/JSX/Vue/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<template>
<editor-content :editor="editor" />
</template>

<script>
import StarterKit from '@tiptap/starter-kit'
import { Editor, EditorContent } from '@tiptap/vue-3'
import { Paragraph } from './Paragraph.tsx'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
StarterKit.configure({
paragraph: false,
}),
Paragraph,
],
content: `
<p>
Each paragraph will be red
</p>
`,
})
},
beforeUnmount() {
this.editor.destroy()
},
}
</script>

<style lang="scss">
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
}
}
</style>
n
4 changes: 4 additions & 0 deletions demos/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const getPackageDependencies = () => {
}
})

// Handle the JSX runtime alias
paths.unshift({ find: '@tiptap/core/jsx-runtime', replacement: resolve('../packages/core/src/jsx-runtime.ts') })
paths.unshift({ find: '@tiptap/core/jsx-dev-runtime', replacement: resolve('../packages/core/src/jsx-runtime.ts') })

return paths
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/jsx-dev-runtime/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../dist/jsx-runtime/jsx-runtime.cjs'
1 change: 1 addition & 0 deletions packages/core/jsx-dev-runtime/index.d.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../src/jsx-runtime.ts'
1 change: 1 addition & 0 deletions packages/core/jsx-dev-runtime/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type * from '../src/jsx-runtime.js'
1 change: 1 addition & 0 deletions packages/core/jsx-dev-runtime/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../dist/jsx-runtime/jsx-runtime.js'
1 change: 1 addition & 0 deletions packages/core/jsx-runtime/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../dist/jsx-runtime/jsx-runtime.cjs'
1 change: 1 addition & 0 deletions packages/core/jsx-runtime/index.d.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../src/jsx-runtime.ts'
1 change: 1 addition & 0 deletions packages/core/jsx-runtime/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type * from '../src/jsx-runtime.ts'
1 change: 1 addition & 0 deletions packages/core/jsx-runtime/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../dist/jsx-runtime/jsx-runtime.js'
19 changes: 18 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,31 @@
},
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./jsx-runtime": {
"types": {
"import": "./jsx-runtime/index.d.ts",
"require": "./jsx-runtime/index.d.cts"
},
"import": "./jsx-runtime/index.js",
"require": "./jsx-runtime/index.cjs"
},
"./jsx-dev-runtime": {
"types": {
"import": "./jsx-dev-runtime/index.d.ts",
"require": "./jsx-dev-runtime/index.d.cts"
},
"import": "./jsx-dev-runtime/index.js",
"require": "./jsx-dev-runtime/index.cjs"
}
},
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"src",
"dist"
"dist",
"jsx-runtime"
],
"devDependencies": {
"@tiptap/pm": "^3.0.0-next.4"
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * as extensions from './extensions/index.js'
export * from './helpers/index.js'
export * from './InputRule.js'
export * from './inputRules/index.js'
export { createElement, Fragment, createElement as h } from './jsx-runtime.js'
export * from './Mark.js'
export * from './Node.js'
export * from './NodePos.js'
Expand Down
64 changes: 64 additions & 0 deletions packages/core/src/jsx-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
export type Attributes = Record<string, any>

export type DOMOutputSpecElement = 0 | Attributes | DOMOutputSpecArray
/**
* Better describes the output of a `renderHTML` function in prosemirror
* @see https://prosemirror.net/docs/ref/#model.DOMOutputSpec
*/
export type DOMOutputSpecArray =
| [string]
| [string, Attributes]
| [string, 0]
| [string, Attributes, 0]
| [string, Attributes, DOMOutputSpecArray | 0]
| [string, DOMOutputSpecArray]

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace JSX {
// @ts-ignore - conflict with React typings
type Element = [string, ...any[]]
// @ts-ignore - conflict with React typings
interface IntrinsicElements {
// @ts-ignore - conflict with React typings
[key: string]: any
}
}
}

export type JSXRenderer = (
tag: 'slot' | string | ((props?: Attributes) => DOMOutputSpecArray | DOMOutputSpecElement),
props?: Attributes,
...children: JSXRenderer[]
) => DOMOutputSpecArray | DOMOutputSpecElement

export function Fragment(props: { children: JSXRenderer[] }) {
return props.children
}

export const h: JSXRenderer = (tag, attributes) => {
// Treat the slot tag as the Prosemirror hole to render content into
if (tag === 'slot') {
return 0
}

// If the tag is a function, call it with the props
if (tag instanceof Function) {
return tag(attributes)
}

const { children, ...rest } = attributes ?? {}

if (tag === 'svg') {
throw new Error('SVG elements are not supported in the JSX syntax, use the array syntax instead')
}

// Otherwise, return the tag, attributes, and children
return [tag, rest, children]
}

// See
// https://esbuild.github.io/api/#jsx-import-source
// https://www.typescriptlang.org/tsconfig/#jsxImportSource

export { h as createElement, h as jsx, h as jsxDEV, h as jsxs }
29 changes: 20 additions & 9 deletions packages/core/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import { defineConfig } from 'tsup'

export default defineConfig({
entry: ['src/index.ts'],
// purposefully not using the build tsconfig, so @tiptap/core's types can be resolved correctly
outDir: 'dist',
dts: true,
clean: true,
sourcemap: true,
format: ['esm', 'cjs'],
})
export default defineConfig([
{
entry: ['src/index.ts'],
// purposefully not using the build tsconfig, so @tiptap/core's types can be resolved correctly
outDir: 'dist',
dts: true,
clean: true,
sourcemap: true,
format: ['esm', 'cjs'],
},
{
entry: ['src/jsx-runtime.ts'],
tsconfig: '../../tsconfig.build.json',
outDir: 'dist/jsx-runtime',
dts: true,
clean: true,
sourcemap: true,
format: ['esm', 'cjs'],
},
])
Loading

0 comments on commit dd0a25f

Please sign in to comment.