Example: tweet
Install this example with
shadcn:npx shadcn@latest add @prosekit/react-example-tweetimport 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import {
createEditor,
type Extension,
type NodeJSON,
} from 'prosekit/core'
import {
defineReactNodeView,
ProseKit,
useExtension,
} from 'prosekit/react'
import {
useMemo,
useState,
} from 'react'
import { sampleContent } from '../../sample/sample-doc-tweet'
import { defineExtension } from './extension'
import { MethodSelect } from './method-select'
import { TweetView } from './tweet-view'
interface EditorProps {
initialContent?: NodeJSON
}
export default function Editor(props: EditorProps) {
const defaultContent = props.initialContent ?? sampleContent
const editor = useMemo(() => {
return createEditor({ extension: defineExtension(), defaultContent })
}, [defaultContent])
const [method, setMethod] = useState<'iframe' | 'react'>('iframe')
const reactTweetView: Extension | null = useMemo(() => {
if (method === 'iframe') {
return null
}
return defineReactNodeView({
name: 'tweet',
component: TweetView,
})
}, [method])
useExtension(reactTweetView, { editor })
return (
<ProseKit editor={editor}>
<MethodSelect value={method} onChange={setMethod} />
<div className="box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-white dark:bg-gray-950 text-black dark:text-white">
<div className="relative w-full flex-1 box-border overflow-y-auto">
<div ref={editor.mount} className="ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500"></div>
</div>
</div>
</ProseKit>
)
}import { defineBasicExtension } from 'prosekit/basic'
import {
defineNodeSpec,
union,
} from 'prosekit/core'
function defineTweetSpec() {
return defineNodeSpec({
name: 'tweet',
group: 'block',
attrs: {
tweetId: { default: null },
},
parseDOM: [{
tag: 'iframe[src^="https://platform.twitter.com/embed/Tweet.html]',
getAttrs: (node) => {
const src = node.getAttribute('src')
const match = src?.match(/id=([^&]+)/)
return {
tweetId: match?.[1] ?? null,
}
},
}],
toDOM: (node) => {
return [
'iframe',
{
src: `https://platform.twitter.com/embed/Tweet.html?id=${node.attrs.tweetId}&theme=dark`,
style: 'height: 300px',
},
]
},
})
}
function defineTweet() {
return union(
defineTweetSpec(),
)
}
export function defineExtension() {
return union(
defineBasicExtension(),
defineTweet(),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>'use client'
export { default as ExampleEditor } from './editor'import { useId } from 'react'
export function MethodSelect(props: {
value: 'iframe' | 'react'
onChange: (value: 'iframe' | 'react') => void
}) {
const id = useId()
const iframeId = `${id}-iframe`
const reactId = `${id}-react`
return (
<fieldset className="not-content">
<legend>Select a render method:</legend>
<div>
<input
type="radio"
id={iframeId}
name={id}
value="iframe"
checked={props.value === 'iframe'}
onChange={() => props.onChange('iframe')}
/>
<label htmlFor={iframeId}>iframe</label>
</div>
<div>
<input
type="radio"
id={reactId}
name={id}
value="react"
checked={props.value === 'react'}
onChange={() => props.onChange('react')}
/>
<label htmlFor={reactId}>react-tweet</label>
</div>
</fieldset>
)
}'use client'
import TiltComponent, { type ReactParallaxTiltProps } from 'react-parallax-tilt'
export function Tilt({ children, ...props }: ReactParallaxTiltProps) {
return <TiltComponent {...props}>{children}</TiltComponent>
}import type { ReactNodeViewProps } from 'prosekit/react'
import { Tweet } from 'react-tweet'
import { Tilt } from './tilt'
export function TweetView({ node }: ReactNodeViewProps) {
const tweetId = node.attrs.tweetId as string
return (
<div className="[&_img]:m-0!">
<Tilt
glareEnable={true}
glareMaxOpacity={0.3}
glareColor="#ffffff"
glarePosition="all"
glareBorderRadius="8px"
tiltMaxAngleX={10}
tiltMaxAngleY={10}
>
<Tweet id={tweetId} />
</Tilt>
</div>
)
}import type { NodeJSON } from 'prosekit/core'
export const sampleContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Render a tweet in your document',
},
],
},
{
type: 'tweet',
attrs: {
tweetId: '1598038815599661056',
},
},
],
}