Skip to content

Commit

Permalink
fix: correct implementation of insertImage$ signal
Browse files Browse the repository at this point in the history
Fixes #437
  • Loading branch information
petyosi committed May 1, 2024
1 parent d3ccb5a commit 75a501f
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 15 deletions.
1 change: 0 additions & 1 deletion src/examples/basics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import codeBlocksMarkdown from './assets/code-blocks.md?raw'
import imageMarkdown from './assets/image.md?raw'
import jsxMarkdown from './assets/jsx.md?raw'
import tableMarkdown from './assets/table.md?raw'
import { basicDark } from 'cm6-theme-basic-dark'

import { virtuosoSampleSandpackConfig } from './_boilerplate'

Expand Down
96 changes: 95 additions & 1 deletion src/examples/images.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import React from 'react'
import { DiffSourceToggleWrapper, InsertImage, MDXEditor, diffSourcePlugin, imagePlugin, jsxPlugin, toolbarPlugin } from '../'
import {
DiffSourceToggleWrapper,
InsertImage,
MDXEditor,
diffSourcePlugin,
imagePlugin,
insertImage$,
jsxPlugin,
toolbarPlugin,
usePublisher
} from '../'
import { Story } from '@ladle/react'
import { expressImageUploadHandler } from './_boilerplate'

const markdownWithHtmlImages = `
Hello world
Expand All @@ -18,6 +29,89 @@ some more
some
`

export const ImageWithBackend: Story<{ readOnly: boolean }> = () => {
return (
<>
<MDXEditor
markdown=""
plugins={[
imagePlugin({
imageUploadHandler: expressImageUploadHandler
}),
diffSourcePlugin(),
toolbarPlugin({
toolbarContents: () => (
<DiffSourceToggleWrapper>
<InsertImage />
</DiffSourceToggleWrapper>
)
})
]}
onChange={console.log}
/>
</>
)
}

function InsertCustomImage() {
const insertImage = usePublisher(insertImage$)
return (
<>
<button
onClick={() => {
insertImage({
src: 'https://picsum.photos/200/300',
altText: 'placeholder'
})
}}
>
Insert Image
</button>

<button
onClick={() => {
void urlToObject('http://localhost:61000/uploads/image-1714546302250-916818288.png', 'image.png').then((file) => {
insertImage({ file, altText: 'placeholder' })
})
}}
>
Insert Image as File
</button>
</>
)
}

const urlToObject = async (url: string, name: string) => {
const response = await fetch(url)
const blob = await response.blob()
const file = new File([blob], name, { type: blob.type })
return file
}

export const InsertImageSignal: Story<{ readOnly: boolean }> = () => {
return (
<>
<MDXEditor
markdown=""
plugins={[
imagePlugin({
imageUploadHandler: expressImageUploadHandler
}),
diffSourcePlugin(),
toolbarPlugin({
toolbarContents: () => (
<DiffSourceToggleWrapper>
<InsertCustomImage />
</DiffSourceToggleWrapper>
)
})
]}
onChange={console.log}
/>
</>
)
}

export const HtmlImage: Story<{ readOnly: boolean }> = ({ readOnly }) => {
return (
<>
Expand Down
62 changes: 49 additions & 13 deletions src/plugins/image/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,27 @@ export type ImageUploadHandler = ((image: File) => Promise<string>) | null
*/
export type ImagePreviewHandler = ((imageSource: string) => Promise<string>) | null

interface BaseImageParameters {
altText?: string
title?: string
}

interface FileImageParameters extends BaseImageParameters {
file: File
}
interface SrcImageParameters extends BaseImageParameters {
src: string
}
/**
* @group Image
*/
export interface InsertImageParameters {
export type InsertImageParameters = FileImageParameters | SrcImageParameters

/**
* @group Image
*/
export interface SaveImageParameters extends BaseImageParameters {
src?: string
altText?: string
title?: string
file: FileList
}

Expand All @@ -81,14 +95,42 @@ export type NewImageDialogState = {
export type EditingImageDialogState = {
type: 'editing'
nodeKey: string
initialValues: Omit<InsertImageParameters, 'file'>
initialValues: Omit<SaveImageParameters, 'file'>
}

const internalInsertImage$ = Signal<SrcImageParameters>((r) => {
r.sub(r.pipe(internalInsertImage$, withLatestFrom(activeEditor$)), ([values, theEditor]) => {
theEditor?.update(() => {
const imageNode = $createImageNode({ altText: values.altText ?? '', src: values.src, title: values.title ?? '' })
$insertNodes([imageNode])
if ($isRootOrShadowRoot(imageNode.getParentOrThrow())) {
$wrapNodeInElement(imageNode, $createParagraphNode).selectEnd()
}
})
})
})

/**
* A signal that inserts a new image node with the published payload.
* @group Image
*/
export const insertImage$ = Signal<InsertImageParameters>()
export const insertImage$ = Signal<InsertImageParameters>((r) => {
r.sub(r.pipe(insertImage$, withLatestFrom(imageUploadHandler$)), ([values, imageUploadHandler]) => {
const handler = (src: string) => {
r.pub(internalInsertImage$, { ...values, src })
}

if ('file' in values) {
imageUploadHandler?.(values.file)
.then(handler)
.catch((e) => {
throw e
})
} else {
handler(values.src)
}
})
})
/**
* Holds the autocomplete suggestions for image sources.
* @group Image
Expand Down Expand Up @@ -137,13 +179,7 @@ export const imageDialogState$ = Cell<InactiveImageDialogState | NewImageDialogS
r.pub(imageDialogState$, { type: 'inactive' })
}
: (src: string) => {
theEditor?.update(() => {
const imageNode = $createImageNode({ altText: values.altText ?? '', src, title: values.title ?? '' })
$insertNodes([imageNode])
if ($isRootOrShadowRoot(imageNode.getParentOrThrow())) {
$wrapNodeInElement(imageNode, $createParagraphNode).selectEnd()
}
})
r.pub(internalInsertImage$, { ...values, src })
r.pub(imageDialogState$, { type: 'inactive' })
}

Expand Down Expand Up @@ -270,7 +306,7 @@ export const disableImageSettingsButton$ = Cell<boolean>(false)
* Saves the data from the image dialog
* @group Image
*/
export const saveImage$ = Signal<InsertImageParameters>()
export const saveImage$ = Signal<SaveImageParameters>()

/**
* A plugin that adds support for images.
Expand Down

0 comments on commit 75a501f

Please sign in to comment.