github.com/ngocphuongnb/tetua@v0.0.7-alpha/packages/editor/src/extensions/iframe.ts (about) 1 import { Node } from '@tiptap/core' 2 import { createNodeViewBlock } from '../utils' 3 4 export interface IframeOptions { 5 allowFullscreen: boolean, 6 HTMLAttributes: { 7 [key: string]: any 8 }, 9 } 10 11 declare module '@tiptap/core' { 12 interface Commands<ReturnType> { 13 iframe: { 14 setIframe: (options: { src: string, width?: number, height?: number }) => ReturnType, 15 } 16 } 17 } 18 19 export const Iframe = Node.create<IframeOptions>({ 20 name: 'iframe', 21 group: 'block', 22 atom: true, 23 addOptions() { 24 return { 25 allowFullscreen: true, 26 HTMLAttributes: { 27 class: 'block-iframe', 28 }, 29 } 30 }, 31 addAttributes() { 32 return { 33 src: { 34 default: null, 35 }, 36 frameborder: { 37 default: 0, 38 }, 39 allowfullscreen: { 40 default: this.options.allowFullscreen, 41 parseHTML: () => this.options.allowFullscreen, 42 }, 43 width: { 44 default: 500, 45 }, 46 height: { 47 default: 315, 48 }, 49 title: { 50 default: null, 51 }, 52 allow: { 53 default: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture', 54 } 55 } 56 }, 57 parseHTML() { 58 return [{ 59 tag: 'iframe', 60 }] 61 }, 62 renderHTML({ HTMLAttributes }) { 63 return ['div', this.options.HTMLAttributes, ['iframe', HTMLAttributes]] 64 }, 65 addCommands() { 66 return { 67 setIframe: (options: { src: string, width?: number, height?: number }) => ({ tr, dispatch }) => { 68 const { selection } = tr 69 const node = this.type.create(options) 70 71 if (dispatch) { 72 tr.replaceRangeWith(selection.from, selection.to, node) 73 } 74 75 return true 76 }, 77 } 78 }, 79 80 addNodeView() { 81 return ({ editor, node: _node, getPos, HTMLAttributes: attrs, decorations: _decorations, extension: _extension }) => { 82 const createImageUrlElm = () => { 83 const iframeSrcContainer = document.createElement('div'); 84 const iframeSrcInput = document.createElement('input'); 85 const iframeSrcApplyBtn = document.createElement('button'); 86 87 iframeSrcContainer.className = 'mely-editor-iframe-src'; 88 iframeSrcApplyBtn.innerText = 'Insert'; 89 iframeSrcInput.setAttribute('type', 'text'); 90 iframeSrcInput.setAttribute('placeholder', 'Enter embed URL'); 91 iframeSrcApplyBtn.addEventListener('click', () => { 92 if (typeof getPos === 'function') { 93 let src = iframeSrcInput.value; 94 if (src.startsWith('https://www.youtube.com/watch?v=')) { 95 src = src.replace('https://www.youtube.com/watch?v=', 'https://www.youtube.com/embed/'); 96 src = src.split('&')[0]; 97 } 98 editor.view.dispatch(editor.view.state.tr.setNodeMarkup(getPos(), undefined, { 99 src: src, 100 })) 101 editor.commands.focus(); 102 } 103 }); 104 105 iframeSrcContainer.append(iframeSrcInput, iframeSrcApplyBtn); 106 setTimeout(() => { 107 iframeSrcInput.focus(); 108 }, 0); 109 return iframeSrcContainer; 110 } 111 const contentDomElm = document.createElement('iframe'); 112 contentDomElm.setAttribute('src', attrs.src); 113 contentDomElm.setAttribute('width', attrs.width); 114 contentDomElm.setAttribute('height', attrs.height); 115 contentDomElm.setAttribute('title', attrs.title); 116 contentDomElm.setAttribute('allow', attrs.allow); 117 // contentDomElm.setAttribute('alt', attrs.alt); 118 // contentDomElm.setAttribute('title', attrs.title); 119 120 const viewDomElms: HTMLElement[] = []; 121 const imageUrlElm = createImageUrlElm(); 122 123 viewDomElms.push(imageUrlElm); 124 const { dom } = createNodeViewBlock(contentDomElm, viewDomElms); 125 dom.classList.add('block-iframe'); 126 127 return { 128 dom, 129 contentDOM: contentDomElm, 130 stopEvent: () => !attrs.src, 131 ignoreMutation: _mutation => !attrs.src, 132 } 133 } 134 }, 135 })