import { Node as ProsemirrorNode } from 'prosemirror-model' ; import { EditorView , NodeView } from 'prosemirror-view' ; import { throttle } from '@remirror/core-helpers' ; import { setStyle } from '@remirror/core-utils' ; import { ResizableHandle , ResizableHandleType } from './resizable-view-handle' ; const MIN_WIDTH = 50 ; export enum ResizableRatioType { Fixed , Flexible } interface OptionShape { [ key ] : any ; } param node param view param getPos param aspectRatio param options param initialSize export abstract class ResizableNodeView implements NodeView { dom : HTMLElement ; #element : HTMLElement ; #node : ProsemirrorNode ; #destroyList : Array < ( ) => void > = [ ] ; readonly aspectRatio : ResizableRatioType ; #width : number | undefined = undefined ; #height : number | undefined = undefined ; constructor ( { node , view , getPos , aspectRatio = ResizableRatioType . Fixed , options , initialSize , } ) { const outer = this . createWrapper ( node , initialSize ) ; const element = this . createElement ( { node , view , getPos , options } ) ; /**
         * Only initialize `handleBottom`, `handleBottomRight`, and `handleBottomLeft` in
         * `ResizableRatioType.Flexible` mode
         */ const types = aspectRatio === ResizableRatioType . Flexible ? [ ResizableHandleType . Right , ResizableHandleType . Left , ResizableHandleType . Bottom , ResizableHandleType . BottomRight , ResizableHandleType . BottomLeft , ] : [ ResizableHandleType . Right , ResizableHandleType . Left ] ; const handles = types . map ( ( type ) => new ResizableHandle ( type ) ) ; for ( const handle of handles ) { const handler = ( e ) => { this . resizeHandler ( e , view , getPos , handle ) ; } ; handle . dom . addEventListener ( 'mousedown' , handler ) ; this . #destroyList . push ( ( ) => handle . dom . removeEventListener ( 'mousedown' , handler ) ) ; outer . append ( handle . dom ) ; } const setHandleVisibe = ( ) => { handles . forEach ( ( handle ) => handle . setHandleVisibility ( true ) ) ; } ; const setHandleInvisible = ( ) => { handles . forEach ( ( handle ) => handle . setHandleVisibility ( false ) ) ; } ; outer . addEventListener ( 'mouseover' , setHandleVisibe ) ; outer . addEventListener ( 'mouseout' , setHandleInvisible ) ; this . #destroyList . push ( ( ) => outer . removeEventListener ( 'mouseover' , setHandleVisibe ) , ( ) => outer . removeEventListener ( 'mouseout' , setHandleInvisible ) ) ; outer . append ( element ) ; this . dom = outer ; this . #node = node ; this . #element = element ; this . aspectRatio = aspectRatio ; } /**
     * `createElement` - a method to produce the element DOM element for this prosemirror node.
     * The subclasses have to implement this abstract method.
     */ abstract createElement ( props ) ; createWrapper ( node , initialSize ? ) { const outer = document . createElement ( 'div' ) ; outer . classList . add ( 'remirror-resizable-view' ) ; outer . style . position = 'relative' ; if ( initialSize ) { setStyle ( outer , { width : normalizeSize ( initialSize . width ) , aspectRatio : `${ initialSize . width } / ${ initialSize . height }` , } ) ; } else { setStyle ( outer , { width : normalizeSize ( node . attrs . width ) , aspectRatio : `${ node . attrs . width } / ${ node . attrs . height }` , } ) ; } setStyle ( outer , { maxWidth : '100%' , minWidth : `${ MIN_WIDTH }px` , verticalAlign : 'bottom' , display : 'inline-block' , lineHeight : '0' , transition : 'width 0.15s ease-out, height 0.15s ease-out' , } ) ; return outer ; } resizeHandler ( e , view , getPos , handle ) { e . preventDefault ( ) ; handle . dataSetDragging ( true ) ; this . #element . style . pointerEvents = 'none' ; const startX = e . pageX ; const startY = e . pageY ; const startWidth = this . #element ?. getBoundingClientRect ( ) . width || 0 ; const startHeight = this . #element ?. getBoundingClientRect ( ) . height || 0 ; const onMouseMove = throttle ( 100 , false , ( e ) => { const currentX = e . pageX ; const currentY = e . pageY ; const diffX = currentX - startX ; const diffY = currentY - startY ; let newWidth = null ; let newHeight = null ; if ( this . aspectRatio === ResizableRatioType . Fixed && startWidth && startHeight ) { switch ( handle . type ) { case ResizableHandleType . Right : case ResizableHandleType . BottomRight : newWidth = startWidth + diffX ; newHeight = ( startHeight / startWidth ) * newWidth ; break ; case ResizableHandleType . Left : case ResizableHandleType . BottomLeft : newWidth = startWidth - diffX ; newHeight = ( startHeight / startWidth ) * newWidth ; break ; case ResizableHandleType . Bottom : newHeight = startHeight + diffY ; newWidth = ( startWidth / startHeight ) * newHeight ; break ; } } else if ( this . aspectRatio === ResizableRatioType . Flexible ) { switch ( handle . type ) { case ResizableHandleType . Right : newWidth = startWidth + diffX ; break ; case ResizableHandleType . Left : newWidth = startWidth - diffX ; break ; case ResizableHandleType . Bottom : newHeight = startHeight + diffY ; break ; case ResizableHandleType . BottomRight : newWidth = startWidth + diffX ; newHeight = startHeight + diffY ; break ; case ResizableHandleType . BottomLeft : newWidth = startWidth - diffX ; newHeight = startHeight + diffY ; break ; } } if ( typeof newWidth === 'number' && newWidth < MIN_WIDTH ) { if ( this . aspectRatio === ResizableRatioType . Fixed && startWidth && startHeight ) { newWidth = MIN_WIDTH ; newHeight = ( startHeight / startWidth ) * newWidth ; } else if ( this . aspectRatio === ResizableRatioType . Flexible ) { newWidth = MIN_WIDTH ; } } if ( newWidth ) { this . #width = Math . round ( newWidth ) ; this . dom . style . width = `${ this . #width }px` ; } if ( newHeight ) { this . #height = Math . round ( newHeight ) ; } if ( newWidth || newHeight ) { this . dom . style . aspectRatio = `${ this . #width } / ${ this . #height }` ; } } ) ; const onMouseUp = ( e ) => { e . preventDefault ( ) ; handle . dataSetDragging ( false ) ; handle . setHandleVisibility ( false ) ; this . #element . style . pointerEvents = 'auto' ; document . removeEventListener ( 'mousemove' , onMouseMove ) ; document . removeEventListener ( 'mouseup' , onMouseUp ) ; const pos = getPos ( ) ; const tr = view . state . tr . setNodeMarkup ( pos , undefined , { ... this . #node . attrs , width : this . #width , height : this . #height , } ) ; view . dispatch ( tr ) ; } ; document . addEventListener ( 'mousemove' , onMouseMove ) ; document . addEventListener ( 'mouseup' , onMouseUp ) ; } /**
     * `update` will be called by Prosemirror, when the view is updating itself.
     */ update ( node ) { if ( node . type !== this . #node . type ) { return false ; } if ( this . aspectRatio === ResizableRatioType . Fixed && node . attrs . width && node . attrs . width !== this . #width ) { return false ; } if ( this . aspectRatio === ResizableRatioType . Flexible && node . attrs . width && node . attrs . height && node . attrs . width !== this . #width && node . attrs . height !== this . #height ) { return false ; } if ( ! isEqualWithoutAttrs ( this . #node , node , [ 'width' , 'height' ] ) ) { return false ; } this . #node = node ; this . #width = node . attrs . width ; this . #height = node . attrs . height ; return true ; } destroy ( ) { this . #destroyList . forEach ( ( removeEventListener ) => removeEventListener ( ) ) ; } } function isEqualWithoutAttrs ( node1 , node2 , ignoreAttrs ) { return ( node1 === node2 || ( sameMarkup ( node1 , node2 , ignoreAttrs ) && node1 . content . eq ( node2 . content ) ) ) ; } function sameMarkup ( node1 , node2 , ignoreAttrs ) { const node1Attrs = node1 . attrs ; const node2Attrs = node2 . attrs ; const deltaAttrs = { } ; for ( const attr of ignoreAttrs ) { deltaAttrs [ attr ] = null ; } node1 . attrs = { ... node1Attrs , ... deltaAttrs } ; node2 . attrs = { ... node2Attrs , ... deltaAttrs } ; const same = node1 . sameMarkup ( node2 ) ; node1 . attrs = node1Attrs ; node2 . attrs = node2Attrs ; return same ; } function normalizeSize ( size ) { if ( typeof size === 'number' ) { return `${ size }px` ; } else if ( size ) { return size ; } else { return undefined ; } }