| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  | import { | 
					
						
							|  |  |  |     DOMConversion, | 
					
						
							| 
									
										
										
										
											2024-09-08 20:37:13 +08:00
										 |  |  |     DOMConversionMap, DOMConversionOutput, DOMExportOutput, | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |     ElementNode, | 
					
						
							|  |  |  |     LexicalEditor, | 
					
						
							|  |  |  |     LexicalNode, | 
					
						
							| 
									
										
										
										
											2024-09-09 19:28:01 +08:00
										 |  |  |     Spread | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  | } from 'lexical'; | 
					
						
							|  |  |  | import type {EditorConfig} from "lexical/LexicalEditor"; | 
					
						
							| 
									
										
										
										
											2024-08-04 01:14:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-05 02:53:59 +08:00
										 |  |  | import {el, setOrRemoveAttribute, sizeToPixels} from "../../utils/dom"; | 
					
						
							| 
									
										
										
										
											2024-09-06 21:07:10 +08:00
										 |  |  | import { | 
					
						
							| 
									
										
										
										
											2024-09-10 22:55:46 +08:00
										 |  |  |     CommonBlockAlignment, deserializeCommonBlockNode, | 
					
						
							| 
									
										
										
										
											2024-09-06 21:07:10 +08:00
										 |  |  |     setCommonBlockPropsFromElement, | 
					
						
							|  |  |  |     updateElementWithCommonBlockProps | 
					
						
							| 
									
										
										
										
											2024-12-05 02:53:59 +08:00
										 |  |  | } from "lexical/nodes/common"; | 
					
						
							|  |  |  | import {$selectSingleNode} from "../../utils/selection"; | 
					
						
							|  |  |  | import {SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export type MediaNodeTag = 'iframe' | 'embed' | 'object' | 'video' | 'audio'; | 
					
						
							|  |  |  | export type MediaNodeSource = { | 
					
						
							|  |  |  |     src: string; | 
					
						
							|  |  |  |     type: string; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export type SerializedMediaNode = Spread<{ | 
					
						
							|  |  |  |     tag: MediaNodeTag; | 
					
						
							|  |  |  |     attributes: Record<string, string>; | 
					
						
							|  |  |  |     sources: MediaNodeSource[]; | 
					
						
							| 
									
										
										
										
											2024-09-06 21:07:10 +08:00
										 |  |  | }, SerializedCommonBlockNode> | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | const attributeAllowList = [ | 
					
						
							| 
									
										
										
										
											2024-09-06 21:07:10 +08:00
										 |  |  |     'width', 'height', 'style', 'title', 'name', | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |     'src', 'allow', 'allowfullscreen', 'loading', 'sandbox', | 
					
						
							|  |  |  |     'type', 'data', 'controls', 'autoplay', 'controlslist', 'loop', | 
					
						
							|  |  |  |     'muted', 'playsinline', 'poster', 'preload' | 
					
						
							|  |  |  | ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function filterAttributes(attributes: Record<string, string>): Record<string, string> { | 
					
						
							|  |  |  |     const filtered: Record<string, string> = {}; | 
					
						
							| 
									
										
										
										
											2024-07-28 00:25:30 +08:00
										 |  |  |     for (const key of Object.keys(attributes)) { | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |         if (attributeAllowList.includes(key)) { | 
					
						
							|  |  |  |             filtered[key] = attributes[key]; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return filtered; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-06 21:07:10 +08:00
										 |  |  | function domElementToNode(tag: MediaNodeTag, element: HTMLElement): MediaNode { | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |     const node = $createMediaNode(tag); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const attributes: Record<string, string> = {}; | 
					
						
							|  |  |  |     for (const attribute of element.attributes) { | 
					
						
							|  |  |  |         attributes[attribute.name] = attribute.value; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     node.setAttributes(attributes); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const sources: MediaNodeSource[] = []; | 
					
						
							|  |  |  |     if (tag === 'video' || tag === 'audio') { | 
					
						
							|  |  |  |         for (const child of element.children) { | 
					
						
							|  |  |  |             if (child.tagName === 'SOURCE') { | 
					
						
							|  |  |  |                 const src = child.getAttribute('src'); | 
					
						
							|  |  |  |                 const type = child.getAttribute('type'); | 
					
						
							|  |  |  |                 if (src && type) { | 
					
						
							|  |  |  |                     sources.push({ src, type }); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         node.setSources(sources); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-06 21:07:10 +08:00
										 |  |  |     setCommonBlockPropsFromElement(element, node); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |     return node; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class MediaNode extends ElementNode { | 
					
						
							| 
									
										
										
										
											2024-09-06 21:07:10 +08:00
										 |  |  |     __id: string = ''; | 
					
						
							|  |  |  |     __alignment: CommonBlockAlignment = ''; | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |     __tag: MediaNodeTag; | 
					
						
							|  |  |  |     __attributes: Record<string, string> = {}; | 
					
						
							|  |  |  |     __sources: MediaNodeSource[] = []; | 
					
						
							| 
									
										
										
										
											2024-09-10 22:55:46 +08:00
										 |  |  |     __inset: number = 0; | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     static getType() { | 
					
						
							|  |  |  |         return 'media'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     static clone(node: MediaNode) { | 
					
						
							| 
									
										
										
										
											2024-08-11 23:08:51 +08:00
										 |  |  |         const newNode = new MediaNode(node.__tag, node.__key); | 
					
						
							|  |  |  |         newNode.__attributes = Object.assign({}, node.__attributes); | 
					
						
							|  |  |  |         newNode.__sources = node.__sources.map(s => Object.assign({}, s)); | 
					
						
							| 
									
										
										
										
											2024-09-08 20:37:13 +08:00
										 |  |  |         newNode.__id = node.__id; | 
					
						
							|  |  |  |         newNode.__alignment = node.__alignment; | 
					
						
							| 
									
										
										
										
											2024-09-10 22:55:46 +08:00
										 |  |  |         newNode.__inset = node.__inset; | 
					
						
							| 
									
										
										
										
											2024-08-11 23:08:51 +08:00
										 |  |  |         return newNode; | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     constructor(tag: MediaNodeTag, key?: string) { | 
					
						
							|  |  |  |         super(key); | 
					
						
							|  |  |  |         this.__tag = tag; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setTag(tag: MediaNodeTag) { | 
					
						
							|  |  |  |         const self = this.getWritable(); | 
					
						
							|  |  |  |         self.__tag = tag; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     getTag(): MediaNodeTag { | 
					
						
							|  |  |  |         const self = this.getLatest(); | 
					
						
							|  |  |  |         return self.__tag; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setAttributes(attributes: Record<string, string>) { | 
					
						
							|  |  |  |         const self = this.getWritable(); | 
					
						
							|  |  |  |         self.__attributes = filterAttributes(attributes); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     getAttributes(): Record<string, string> { | 
					
						
							|  |  |  |         const self = this.getLatest(); | 
					
						
							|  |  |  |         return self.__attributes; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setSources(sources: MediaNodeSource[]) { | 
					
						
							|  |  |  |         const self = this.getWritable(); | 
					
						
							|  |  |  |         self.__sources = sources; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     getSources(): MediaNodeSource[] { | 
					
						
							|  |  |  |         const self = this.getLatest(); | 
					
						
							|  |  |  |         return self.__sources; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setSrc(src: string): void { | 
					
						
							|  |  |  |         const attrs = Object.assign({}, this.getAttributes()); | 
					
						
							|  |  |  |         if (this.__tag ==='object') { | 
					
						
							|  |  |  |             attrs.data = src; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             attrs.src = src; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this.setAttributes(attrs); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setWidthAndHeight(width: string, height: string): void { | 
					
						
							|  |  |  |         const attrs = Object.assign( | 
					
						
							|  |  |  |             {}, | 
					
						
							|  |  |  |             this.getAttributes(), | 
					
						
							|  |  |  |             {width, height}, | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         this.setAttributes(attrs); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-06 21:07:10 +08:00
										 |  |  |     setId(id: string) { | 
					
						
							|  |  |  |         const self = this.getWritable(); | 
					
						
							|  |  |  |         self.__id = id; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     getId(): string { | 
					
						
							|  |  |  |         const self = this.getLatest(); | 
					
						
							|  |  |  |         return self.__id; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setAlignment(alignment: CommonBlockAlignment) { | 
					
						
							|  |  |  |         const self = this.getWritable(); | 
					
						
							|  |  |  |         self.__alignment = alignment; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     getAlignment(): CommonBlockAlignment { | 
					
						
							|  |  |  |         const self = this.getLatest(); | 
					
						
							|  |  |  |         return self.__alignment; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 22:55:46 +08:00
										 |  |  |     setInset(size: number) { | 
					
						
							|  |  |  |         const self = this.getWritable(); | 
					
						
							|  |  |  |         self.__inset = size; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     getInset(): number { | 
					
						
							|  |  |  |         const self = this.getLatest(); | 
					
						
							|  |  |  |         return self.__inset; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-08 20:37:13 +08:00
										 |  |  |     setHeight(height: number): void { | 
					
						
							|  |  |  |         if (!height) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const attrs = Object.assign({}, this.getAttributes(), {height}); | 
					
						
							|  |  |  |         this.setAttributes(attrs); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     getHeight(): number { | 
					
						
							|  |  |  |         const self = this.getLatest(); | 
					
						
							|  |  |  |         return sizeToPixels(self.__attributes.height || '0'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setWidth(width: number): void { | 
					
						
							|  |  |  |         const attrs = Object.assign({}, this.getAttributes(), {width}); | 
					
						
							|  |  |  |         this.setAttributes(attrs); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     getWidth(): number { | 
					
						
							|  |  |  |         const self = this.getLatest(); | 
					
						
							|  |  |  |         return sizeToPixels(self.__attributes.width || '0'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     isInline(): boolean { | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 19:14:26 +08:00
										 |  |  |     isParentRequired(): boolean { | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-08 20:37:13 +08:00
										 |  |  |     createInnerDOM() { | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |         const sources = (this.__tag === 'video' || this.__tag === 'audio') ? this.__sources : []; | 
					
						
							|  |  |  |         const sourceEls = sources.map(source => el('source', source)); | 
					
						
							| 
									
										
										
										
											2024-09-06 21:07:10 +08:00
										 |  |  |         const element = el(this.__tag, this.__attributes, sourceEls); | 
					
						
							|  |  |  |         updateElementWithCommonBlockProps(element, this); | 
					
						
							|  |  |  |         return element; | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-08 20:37:13 +08:00
										 |  |  |     createDOM(_config: EditorConfig, _editor: LexicalEditor) { | 
					
						
							|  |  |  |         const media = this.createInnerDOM(); | 
					
						
							|  |  |  |         const wrap = el('span', { | 
					
						
							|  |  |  |             class: media.className + ' editor-media-wrap', | 
					
						
							|  |  |  |         }, [media]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         wrap.addEventListener('click', e => { | 
					
						
							|  |  |  |             _editor.update(() => $selectSingleNode(this)); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return wrap; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-09 19:28:01 +08:00
										 |  |  |     updateDOM(prevNode: MediaNode, dom: HTMLElement): boolean { | 
					
						
							|  |  |  |         if (prevNode.__tag !== this.__tag) { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (JSON.stringify(prevNode.__sources) !== JSON.stringify(this.__sources)) { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (JSON.stringify(prevNode.__attributes) !== JSON.stringify(this.__attributes)) { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const mediaEl = dom.firstElementChild as HTMLElement; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (prevNode.__id !== this.__id) { | 
					
						
							|  |  |  |             setOrRemoveAttribute(mediaEl, 'id', this.__id); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (prevNode.__alignment !== this.__alignment) { | 
					
						
							|  |  |  |             if (prevNode.__alignment) { | 
					
						
							|  |  |  |                 dom.classList.remove(`align-${prevNode.__alignment}`); | 
					
						
							|  |  |  |                 mediaEl.classList.remove(`align-${prevNode.__alignment}`); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (this.__alignment) { | 
					
						
							|  |  |  |                 dom.classList.add(`align-${this.__alignment}`); | 
					
						
							|  |  |  |                 mediaEl.classList.add(`align-${this.__alignment}`); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 22:55:46 +08:00
										 |  |  |         if (prevNode.__inset !== this.__inset) { | 
					
						
							|  |  |  |             dom.style.paddingLeft = `${this.__inset}px`; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-09 19:28:01 +08:00
										 |  |  |         return false; | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     static importDOM(): DOMConversionMap|null { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const buildConverter = (tag: MediaNodeTag) => { | 
					
						
							|  |  |  |             return (node: HTMLElement): DOMConversion|null => { | 
					
						
							|  |  |  |                 return { | 
					
						
							|  |  |  |                     conversion: (element: HTMLElement): DOMConversionOutput|null => { | 
					
						
							|  |  |  |                         return { | 
					
						
							|  |  |  |                             node: domElementToNode(tag, element), | 
					
						
							|  |  |  |                         }; | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                     priority: 3, | 
					
						
							|  |  |  |                 }; | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             iframe: buildConverter('iframe'), | 
					
						
							|  |  |  |             embed: buildConverter('embed'), | 
					
						
							|  |  |  |             object: buildConverter('object'), | 
					
						
							|  |  |  |             video: buildConverter('video'), | 
					
						
							|  |  |  |             audio: buildConverter('audio'), | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-08 20:37:13 +08:00
										 |  |  |     exportDOM(editor: LexicalEditor): DOMExportOutput { | 
					
						
							|  |  |  |         const element = this.createInnerDOM(); | 
					
						
							|  |  |  |         return { element }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |     exportJSON(): SerializedMediaNode { | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             ...super.exportJSON(), | 
					
						
							| 
									
										
										
										
											2024-07-28 00:25:30 +08:00
										 |  |  |             type: 'media', | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |             version: 1, | 
					
						
							| 
									
										
										
										
											2024-09-06 21:07:10 +08:00
										 |  |  |             id: this.__id, | 
					
						
							|  |  |  |             alignment: this.__alignment, | 
					
						
							| 
									
										
										
										
											2024-09-10 22:55:46 +08:00
										 |  |  |             inset: this.__inset, | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |             tag: this.__tag, | 
					
						
							|  |  |  |             attributes: this.__attributes, | 
					
						
							|  |  |  |             sources: this.__sources, | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     static importJSON(serializedNode: SerializedMediaNode): MediaNode { | 
					
						
							| 
									
										
										
										
											2024-09-06 21:07:10 +08:00
										 |  |  |         const node = $createMediaNode(serializedNode.tag); | 
					
						
							| 
									
										
										
										
											2024-09-10 22:55:46 +08:00
										 |  |  |         deserializeCommonBlockNode(serializedNode, node); | 
					
						
							| 
									
										
										
										
											2024-09-06 21:07:10 +08:00
										 |  |  |         return node; | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function $createMediaNode(tag: MediaNodeTag) { | 
					
						
							|  |  |  |     return new MediaNode(tag); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function $createMediaNodeFromHtml(html: string): MediaNode | null { | 
					
						
							|  |  |  |     const parser = new DOMParser(); | 
					
						
							|  |  |  |     const doc = parser.parseFromString(`<body>${html}</body>`, 'text/html'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const el = doc.body.children[0]; | 
					
						
							| 
									
										
										
										
											2024-09-06 21:07:10 +08:00
										 |  |  |     if (!(el instanceof HTMLElement)) { | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |         return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const tag = el.tagName.toLowerCase(); | 
					
						
							|  |  |  |     const validTypes = ['embed', 'iframe', 'video', 'audio', 'object']; | 
					
						
							|  |  |  |     if (!validTypes.includes(tag)) { | 
					
						
							|  |  |  |         return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return domElementToNode(tag as MediaNodeTag, el); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-28 00:25:30 +08:00
										 |  |  | const videoExtensions = ['mp4', 'mpeg', 'm4v', 'm4p', 'mov']; | 
					
						
							|  |  |  | const audioExtensions = ['3gp', 'aac', 'flac', 'mp3', 'm4a', 'ogg', 'wav', 'webm']; | 
					
						
							| 
									
										
										
										
											2024-09-10 19:14:26 +08:00
										 |  |  | const iframeExtensions = ['html', 'htm', 'php', 'asp', 'aspx', '']; | 
					
						
							| 
									
										
										
										
											2024-07-28 00:25:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export function $createMediaNodeFromSrc(src: string): MediaNode { | 
					
						
							|  |  |  |     let nodeTag: MediaNodeTag = 'iframe'; | 
					
						
							|  |  |  |     const srcEnd = src.split('?')[0].split('/').pop() || ''; | 
					
						
							| 
									
										
										
										
											2024-09-10 19:14:26 +08:00
										 |  |  |     const srcEndSplit = srcEnd.split('.'); | 
					
						
							|  |  |  |     const extension = (srcEndSplit.length > 1 ? srcEndSplit[srcEndSplit.length - 1] : '').toLowerCase(); | 
					
						
							| 
									
										
										
										
											2024-07-28 00:25:30 +08:00
										 |  |  |     if (videoExtensions.includes(extension)) { | 
					
						
							|  |  |  |         nodeTag = 'video'; | 
					
						
							|  |  |  |     } else if (audioExtensions.includes(extension)) { | 
					
						
							|  |  |  |         nodeTag = 'audio'; | 
					
						
							|  |  |  |     } else if (extension && !iframeExtensions.includes(extension)) { | 
					
						
							|  |  |  |         nodeTag = 'embed'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return new MediaNode(nodeTag); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-11 23:08:51 +08:00
										 |  |  | export function $isMediaNode(node: LexicalNode | null | undefined): node is MediaNode { | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |     return node instanceof MediaNode; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-11 23:08:51 +08:00
										 |  |  | export function $isMediaNodeOfTag(node: LexicalNode | null | undefined, tag: MediaNodeTag): boolean { | 
					
						
							| 
									
										
										
										
											2024-07-25 23:25:08 +08:00
										 |  |  |     return node instanceof MediaNode && (node as MediaNode).getTag() === tag; | 
					
						
							|  |  |  | } |