import { Editor } from 'roosterjs-editor-core' ; import { Base64Svgs } from '../resources/Images' ; import * as Styles from '../scss/core.scss.g' ; import { css } from '../utils/ReactUtil' ; export interface ImageManagerInteface { upload : ( editor : Editor , image : File , forceFallbackAltValue ? : boolean ) => HTMLElement ; updatePlaceholders : ( html : string ) => UpdatePlaceholdersResult ; } export interface ImageManagerOptions { uploadImage : ( file : File ) => Promise < string > ; createImagePlaceholder ? : ( editor : Editor , image : File ) => HTMLImageElement ; placeholderImageClassName ? : string ; fallbackAltValue ? : string ; } const PlaceholderDataName = 'paste-image-placeholder-804b751e' ; export const PlaceholderDataAttribute = `data-${ PlaceholderDataName }` ; export function hasPlaceholder ( html ) { return html . indexOf ( PlaceholderDataAttribute ) > - 1 ; } export interface UpdatePlaceholdersResult { resolvedAll : boolean ; html : string ; } export default class ImageManager implements ImageManagerInteface { private static Id : number = 0 ; private idToUrlImageCache : { [ id ] : string ; } = { } ; private options : ImageManagerOptions ; private placeholderImageClasses : string [ ] ; constructor ( options ) { this . options = { ... options } ; this . options . createImagePlaceholder = this . options . createImagePlaceholder || this . defaultCreateImagePlaceholder ; this . placeholderImageClasses = this . options . placeholderImageClassName ? this . options . placeholderImageClassName . split ( ' ' ) : undefined ; } public upload ( editor , image , forceFallbackAltValue ? ) { if ( ! image || image . size === 0 ) { return null ; } const placeholder = this . options . createImagePlaceholder ( editor , image ) ; if ( placeholder === null ) { return null ; } let altText = "" ; if ( forceFallbackAltValue ) { altText = this . options . fallbackAltValue ; } else { altText = image . name || this . options . fallbackAltValue ; } const placeholdId = ( ImageManager . Id ++ ) . toString ( 10 ) ; placeholder . setAttribute ( PlaceholderDataAttribute , placeholdId ) ; this . options . uploadImage ( image ) . then ( ( url ) => { this . idToUrlImageCache [ placeholdId ] = url ; if ( editor . isDisposed ( ) || ! editor . contains ( placeholder ) ) { return ; } this . replacePlaceholder ( placeholder , url , editor , altText || "Image" ) ; this . triggerChangeEvent ( editor ) ; } , ( ) => { if ( editor . isDisposed ( ) || ! editor . contains ( placeholder ) ) { return ; } this . idToUrlImageCache [ placeholdId ] = null ; this . removePlaceholder ( placeholder , editor ) ; this . triggerChangeEvent ( editor ) ; } ) ; return placeholder ; } public updatePlaceholders ( html ) { const container = document . createElement ( 'div' ) ; container . innerHTML = html ; const placeholders = container . querySelectorAll ( `[${ PlaceholderDataAttribute }]` ) ; let resolvedAll = true ; for ( let i = 0 ; i < placeholders . length ; ++ i ) { const placeholder = placeholders [ i ] ; const id = placeholder . getAttribute ( PlaceholderDataAttribute ) ; const url = this . idToUrlImageCache [ id ] ; if ( url === undefined ) { resolvedAll = false ; continue ; } if ( url === null ) { this . removePlaceholder ( placeholder as HTMLElement ) ; } else { this . replacePlaceholder ( placeholder as HTMLElement , url ) ; } } return { html : container . innerHTML , resolvedAll } ; } private triggerChangeEvent ( editor ) { editor . triggerContentChangedEvent ( 'ImageManager' ) ; } private removePlaceholder ( placeholder , editor ? ) { if ( editor ) { editor . deleteNode ( placeholder ) ; editor . addUndoSnapshot ( ) ; } else { const parent = placeholder . parentNode ; if ( parent ) { parent . removeChild ( placeholder ) ; } } } private replacePlaceholder ( placeholder , url , editor ? , altText ? ) { let img ; if ( placeholder . tagName === 'IMG' ) { img = placeholder as HTMLImageElement ; img . src = url ; img . classList . remove ( Styles . roosterjsReactSpinner , ... this . placeholderImageClasses ) ; placeholder . removeAttribute ( PlaceholderDataAttribute ) ; if ( img . classList . length === 0 ) { placeholder . removeAttribute ( 'class' ) ; } } else { const doc = editor ? editor . getDocument ( ) : document ; img = doc . createElement ( 'img' ) as HTMLImageElement ; img . src = url ; if ( editor ) { editor . replaceNode ( placeholder , img ) ; editor . addUndoSnapshot ( ) ; } else { doc . replaceChild ( img , placeholder ) ; } } img . setAttribute ( "alt" , altText ) ; } private defaultCreateImagePlaceholder = ( editor , image ) : HTMLImageElement => { if ( editor . isDisposed ( ) ) { return null ; } const result = editor . getDocument ( ) . createElement ( 'img' ) as HTMLImageElement ; result . src = Base64Svgs . RoosterJsReactSpinner ; result . className = css ( Styles . roosterjsReactSpinner , this . options . placeholderImageClassName ) ; return result ; } ; }