Render iframes inside the rich text editor (#682)
* Try embedding iframes in tiptap * When iframe code is pasted, inject it into the editor * Code cleanups and comments * Remove clsx dependency Cuz it doesn't exist in `common` anyways * Rename to tiptap-iframe
This commit is contained in:
parent
87170894e2
commit
7cace82b83
|
@ -20,6 +20,7 @@ import { Text } from '@tiptap/extension-text'
|
||||||
// other tiptap extensions
|
// other tiptap extensions
|
||||||
import { Image } from '@tiptap/extension-image'
|
import { Image } from '@tiptap/extension-image'
|
||||||
import { Link } from '@tiptap/extension-link'
|
import { Link } from '@tiptap/extension-link'
|
||||||
|
import Iframe from './tiptap-iframe'
|
||||||
|
|
||||||
export function parseTags(text: string) {
|
export function parseTags(text: string) {
|
||||||
const regex = /(?:^|\s)(?:[#][a-z0-9_]+)/gi
|
const regex = /(?:^|\s)(?:[#][a-z0-9_]+)/gi
|
||||||
|
@ -80,6 +81,7 @@ export const exhibitExts = [
|
||||||
|
|
||||||
Image,
|
Image,
|
||||||
Link,
|
Link,
|
||||||
|
Iframe,
|
||||||
]
|
]
|
||||||
// export const exhibitExts = [StarterKit as unknown as Extension, Image]
|
// export const exhibitExts = [StarterKit as unknown as Extension, Image]
|
||||||
|
|
||||||
|
|
92
common/util/tiptap-iframe.ts
Normal file
92
common/util/tiptap-iframe.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// Adopted from https://github.com/ueberdosis/tiptap/blob/main/demos/src/Experiments/Embeds/Vue/iframe.ts
|
||||||
|
|
||||||
|
import { Node } from '@tiptap/core'
|
||||||
|
|
||||||
|
export interface IframeOptions {
|
||||||
|
allowFullscreen: boolean
|
||||||
|
HTMLAttributes: {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@tiptap/core' {
|
||||||
|
interface Commands<ReturnType> {
|
||||||
|
iframe: {
|
||||||
|
setIframe: (options: { src: string }) => ReturnType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These classes style the outer wrapper and the inner iframe;
|
||||||
|
// Adopted from css in https://github.com/ueberdosis/tiptap/blob/main/demos/src/Experiments/Embeds/Vue/index.vue
|
||||||
|
const wrapperClasses = 'relative h-auto w-full overflow-hidden'
|
||||||
|
const iframeClasses = 'absolute top-0 left-0 h-full w-full'
|
||||||
|
|
||||||
|
export default Node.create<IframeOptions>({
|
||||||
|
name: 'iframe',
|
||||||
|
|
||||||
|
group: 'block',
|
||||||
|
|
||||||
|
atom: true,
|
||||||
|
|
||||||
|
addOptions() {
|
||||||
|
return {
|
||||||
|
allowFullscreen: true,
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: 'iframe-wrapper' + ' ' + wrapperClasses,
|
||||||
|
// Tailwind JIT doesn't seem to pick up `pb-[20rem]`, so we hack this in:
|
||||||
|
style: 'padding-bottom: 20rem;',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
src: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
frameborder: {
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
allowfullscreen: {
|
||||||
|
default: this.options.allowFullscreen,
|
||||||
|
parseHTML: () => this.options.allowFullscreen,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
parseHTML() {
|
||||||
|
return [{ tag: 'iframe' }]
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
return [
|
||||||
|
'div',
|
||||||
|
this.options.HTMLAttributes,
|
||||||
|
[
|
||||||
|
'iframe',
|
||||||
|
{
|
||||||
|
...HTMLAttributes,
|
||||||
|
class: HTMLAttributes.class + ' ' + iframeClasses,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
addCommands() {
|
||||||
|
return {
|
||||||
|
setIframe:
|
||||||
|
(options: { src: string }) =>
|
||||||
|
({ tr, dispatch }) => {
|
||||||
|
const { selection } = tr
|
||||||
|
const node = this.type.create(options)
|
||||||
|
|
||||||
|
if (dispatch) {
|
||||||
|
tr.replaceRangeWith(selection.from, selection.to, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
|
@ -19,6 +19,7 @@ import { useMutation } from 'react-query'
|
||||||
import { exhibitExts } from 'common/util/parse'
|
import { exhibitExts } from 'common/util/parse'
|
||||||
import { FileUploadButton } from './file-upload-button'
|
import { FileUploadButton } from './file-upload-button'
|
||||||
import { linkClass } from './site-link'
|
import { linkClass } from './site-link'
|
||||||
|
import Iframe from 'common/util/tiptap-iframe'
|
||||||
|
|
||||||
const proseClass = clsx(
|
const proseClass = clsx(
|
||||||
'prose prose-p:my-0 prose-li:my-0 prose-blockquote:not-italic max-w-none prose-quoteless leading-relaxed',
|
'prose prose-p:my-0 prose-li:my-0 prose-blockquote:not-italic max-w-none prose-quoteless leading-relaxed',
|
||||||
|
@ -56,6 +57,7 @@ export function useTextEditor(props: {
|
||||||
class: clsx('no-underline !text-indigo-700', linkClass),
|
class: clsx('no-underline !text-indigo-700', linkClass),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
Iframe,
|
||||||
],
|
],
|
||||||
content: defaultValue,
|
content: defaultValue,
|
||||||
})
|
})
|
||||||
|
@ -69,12 +71,20 @@ export function useTextEditor(props: {
|
||||||
(file) => file.type.startsWith('image')
|
(file) => file.type.startsWith('image')
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!imageFiles.length) {
|
if (imageFiles.length) {
|
||||||
return // if no files pasted, use default paste handler
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
upload.mutate(imageFiles)
|
upload.mutate(imageFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the pasted content is iframe code, directly inject it
|
||||||
|
const text = event.clipboardData?.getData('text/plain').trim() ?? ''
|
||||||
|
const isValidIframe = /^<iframe.*<\/iframe>$/.test(text)
|
||||||
|
if (isValidIframe) {
|
||||||
|
editor.chain().insertContent(text).run()
|
||||||
|
return true // Prevent the code from getting pasted as text
|
||||||
|
}
|
||||||
|
|
||||||
|
return // Otherwise, use default paste handler
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue
Block a user