diff --git a/common/util/iframe.ts b/common/util/iframe.ts new file mode 100644 index 00000000..024ab301 --- /dev/null +++ b/common/util/iframe.ts @@ -0,0 +1,98 @@ +// Adopted from https://github.com/ueberdosis/tiptap/blob/main/demos/src/Experiments/Embeds/Vue/iframe.ts + +import { Node } from '@tiptap/core' +import clsx from 'clsx' + +export interface IframeOptions { + allowFullscreen: boolean + HTMLAttributes: { + [key: string]: any + } +} + +declare module '@tiptap/core' { + interface Commands { + iframe: { + /** + * Add an iframe + */ + setIframe: (options: { src: string }) => ReturnType + } + } +} + +const wrapperClasses = 'relative h-auto w-full overflow-hidden' +const iframeClasses = 'absolute top-0 left-0 h-full w-full' + +export default Node.create({ + name: 'iframe', + + group: 'block', + + atom: true, + + addOptions() { + return { + allowFullscreen: true, + HTMLAttributes: { + class: clsx('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: clsx(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 + }, + } + }, +}) diff --git a/common/util/parse.ts b/common/util/parse.ts index 30dcb952..e4245bbc 100644 --- a/common/util/parse.ts +++ b/common/util/parse.ts @@ -20,6 +20,7 @@ import { Text } from '@tiptap/extension-text' // other tiptap extensions import { Image } from '@tiptap/extension-image' import { Link } from '@tiptap/extension-link' +import Iframe from './iframe' export function parseTags(text: string) { const regex = /(?:^|\s)(?:[#][a-z0-9_]+)/gi @@ -80,6 +81,7 @@ export const exhibitExts = [ Image, Link, + Iframe, ] // export const exhibitExts = [StarterKit as unknown as Extension, Image] diff --git a/web/components/contract/contract-description.tsx b/web/components/contract/contract-description.tsx index f9db0cd9..277d5de0 100644 --- a/web/components/contract/contract-description.tsx +++ b/web/components/contract/contract-description.tsx @@ -99,6 +99,9 @@ function RichEditContract(props: { contract: Contract; isAdmin?: boolean }) { .setContent(contract.description) .focus('end') .insertContent(`

${editTimestamp()}

`) + .insertContent( + `` + ) .run() }} > diff --git a/web/components/editor.tsx b/web/components/editor.tsx index 7063fa42..a4ad758b 100644 --- a/web/components/editor.tsx +++ b/web/components/editor.tsx @@ -19,6 +19,7 @@ import { useMutation } from 'react-query' import { exhibitExts } from 'common/util/parse' import { FileUploadButton } from './file-upload-button' import { linkClass } from './site-link' +import Iframe from 'common/util/iframe' const proseClass = clsx( '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), }, }), + Iframe, ], content: defaultValue, })