11import { type NodeViewProps , NodeViewWrapper } from '@tiptap/react'
2- import { useCallback , useRef , useState } from 'react'
2+ import { useCallback , useEffect , useRef , useState } from 'react'
3+ import type { AssetScopeEnum } from '~/__generated__/useEmbedUserAssetMutation.graphql'
4+ import useAtmosphere from '~/hooks/useAtmosphere'
5+ import { useEmbedUserAsset } from '~/mutations/useEmbedUserAsset'
6+ import { GQLID } from '~/utils/GQLID'
37import { useBlockResizer } from '../../../hooks/useBlockResizer'
48import { cn } from '../../../ui/cn'
59import { BlockResizer } from './BlockResizer'
610import { ImageBlockBubbleMenu } from './ImageBlockBubbleMenu'
11+
12+ const getRelativeSrc = ( src : string ) => {
13+ if ( src . startsWith ( '/' ) ) return src
14+ try {
15+ const url = new URL ( src )
16+ return url . pathname
17+ } catch {
18+ return ''
19+ }
20+ }
21+ const getIsHosted = ( src : string , scopeKey : string , assetScope : AssetScopeEnum ) => {
22+ const relativeSrc = getRelativeSrc ( src )
23+ const scopeCode = assetScope === 'Page' ? GQLID . fromKey ( scopeKey ) [ 0 ] : scopeKey
24+ const hostedPath = `/assets/${ assetScope } /${ scopeCode } `
25+ return relativeSrc . startsWith ( hostedPath )
26+ }
727export const ImageBlockView = ( props : NodeViewProps ) => {
828 const { editor, getPos, node, updateAttributes} = props
929 const imageWrapperRef = useRef < HTMLDivElement > ( null )
1030 const { attrs} = node
1131 const { src, align, height, width} = attrs
1232 const alignClass =
1333 align === 'left' ? 'justify-start' : align === 'right' ? 'justify-end' : 'justify-center'
14-
34+ const { scopeKey, assetScope} = editor . extensionStorage . imageUpload
35+ const isHosted = getIsHosted ( src , scopeKey , assetScope )
1536 const onClick = useCallback ( ( ) => {
1637 const pos = getPos ( )
1738 if ( ! pos ) return
@@ -32,13 +53,41 @@ export const ImageBlockView = (props: NodeViewProps) => {
3253 )
3354 const onMouseDownLeft = onMouseDown ( 'left' )
3455 const onMouseDownRight = onMouseDown ( 'right' )
56+ const atmosphere = useAtmosphere ( )
57+ const [ commit ] = useEmbedUserAsset ( )
58+ useEffect ( ( ) => {
59+ if ( isHosted ) return
60+ commit ( {
61+ variables : { url : src , scope : assetScope , scopeKey} ,
62+ onCompleted : ( res , error ) => {
63+ const { embedUserAsset} = res
64+ if ( ! embedUserAsset ) {
65+ // Since this is triggered without user input, we log it silently
66+ console . error ( error ?. [ 0 ] ?. message )
67+ return
68+ }
69+ const { url} = embedUserAsset
70+ const message = embedUserAsset ?. error ?. message
71+ if ( message ) {
72+ atmosphere . eventEmitter . emit ( 'addSnackbar' , {
73+ key : 'errorEmbeddingAsset' ,
74+ message,
75+ autoDismiss : 5
76+ } )
77+ return
78+ }
79+ updateAttributes ( { src : url } )
80+ }
81+ } )
82+ } , [ isHosted ] )
3583 return (
3684 < NodeViewWrapper >
3785 < div className = { cn ( 'flex' , alignClass ) } >
3886 < div contentEditable = { false } ref = { imageWrapperRef } className = 'group relative w-fit' >
3987 < img
4088 draggable = { false }
41- className = 'block'
89+ data-uploading = { isHosted ? undefined : '' }
90+ className = 'block data-uploading:animate-shimmer data-uploading:[mask:linear-gradient(-60deg,#000_30%,#0005,#000_70%)_right/350%_100%]'
4291 src = { src }
4392 alt = ''
4493 onClick = { onClick }
0 commit comments