Skip to content

Commit

Permalink
feat(markup): convert pasted urls to images (#469)
Browse files Browse the repository at this point in the history
Co-authored-by: kseniyakuzina <[email protected]>
  • Loading branch information
kseniya57 and kseniyakuzina authored Nov 12, 2024
1 parent 031504b commit 720215b
Show file tree
Hide file tree
Showing 9 changed files with 50 additions and 3 deletions.
1 change: 1 addition & 0 deletions demo/playground/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
},
markupConfig: {
extensions: markupConfigExtensions,
parseInsertedUrlAsImage,
},
extraExtensions: (builder) => {
builder
Expand Down
3 changes: 3 additions & 0 deletions demo/presets/PresetDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ export const PresetDemo = React.memo<PresetDemoProps>((props) => {
},
},
},
markupConfig: {
parseInsertedUrlAsImage,
},
});

useEffect(() => {
Expand Down
8 changes: 8 additions & 0 deletions src/bundle/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type {
MarkdownEditorMdOptions,
MarkdownEditorOptions,
MarkdownEditorMarkupConfig as MarkupConfig,
ParseInsertedUrlAsImage,
RenderPreview,
MarkdownEditorSplitMode as SplitMode,
} from './types';
Expand Down Expand Up @@ -140,6 +141,7 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
#extensions?: WysiwygEditorOptions['extensions'];
#renderStorage: ReactRenderStorage;
#fileUploadHandler?: FileUploadHandler;
#parseInsertedUrlAsImage?: ParseInsertedUrlAsImage;
#needToSetDimensionsForUploadedImages: boolean;
#prepareRawMarkup?: (value: MarkupString) => MarkupString;
#beforeEditorModeChange?: (
Expand Down Expand Up @@ -261,6 +263,7 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
onScroll: (event) => this.emit('cm-scroll', {event}),
reactRenderer: this.#renderStorage,
uploadHandler: this.fileUploadHandler,
parseInsertedUrlAsImage: this.parseInsertedUrlAsImage,
needImgDimms: this.needToSetDimensionsForUploadedImages,
extensions: this.#markupConfig.extensions,
disabledExtensions: this.#markupConfig.disabledExtensions,
Expand All @@ -282,6 +285,10 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI
return this.#fileUploadHandler;
}

get parseInsertedUrlAsImage() {
return this.#parseInsertedUrlAsImage;
}

get needToSetDimensionsForUploadedImages(): boolean {
return this.#needToSetDimensionsForUploadedImages;
}
Expand Down Expand Up @@ -313,6 +320,7 @@ export class EditorImpl extends SafeEventEmitter<EventMapInt> implements EditorI

this.#renderStorage = opts.renderStorage;
this.#fileUploadHandler = handlers.uploadFile;
this.#parseInsertedUrlAsImage = markupConfig.parseInsertedUrlAsImage;
this.#needToSetDimensionsForUploadedImages = Boolean(
experimental.needToSetDimensionsForUploadedImages,
);
Expand Down
6 changes: 6 additions & 0 deletions src/bundle/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export type RenderPreviewParams = {
};
export type RenderPreview = (params: RenderPreviewParams) => ReactNode;

export type ParseInsertedUrlAsImage = (text: string) => {imageUrl: string; title?: string} | null;

export type MarkdownEditorMdOptions = {
html?: boolean;
breaks?: boolean;
Expand Down Expand Up @@ -98,6 +100,10 @@ export type MarkdownEditorMarkupConfig = {
languageData?: YfmLangOptions['languageData'];
/** Config for @codemirror/autocomplete https://codemirror.net/docs/ref/#autocomplete.autocompletion%5Econfig */
autocompletion?: CreateCodemirrorParams['autocompletion'];
/**
* The function, used to determine if the pasted text is the image url and should be inserted as an image
*/
parseInsertedUrlAsImage?: ParseInsertedUrlAsImage;
};

// do not export this type
Expand Down
3 changes: 2 additions & 1 deletion src/extensions/markdown/Image/imageUrlPaste/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Plugin} from 'prosemirror-state';

import type {ParseInsertedUrlAsImage} from '../../../../bundle';
import {ExtensionAuto} from '../../../../core';
import {DataTransferType} from '../../../behavior/Clipboard/utils';
import {imageType} from '../ImageSpecs';
Expand All @@ -8,7 +9,7 @@ export type ImageUrlPasteOptions = {
/**
* The function, used to determine if the pasted text is the image url and should be inserted as an image
*/
parseInsertedUrlAsImage?: (text: string) => {imageUrl: string; title?: string} | null;
parseInsertedUrlAsImage?: ParseInsertedUrlAsImage;
};

export const imageUrlPaste: ExtensionAuto<ImageUrlPasteOptions> = (builder, opts) => {
Expand Down
1 change: 1 addition & 0 deletions src/extensions/markdown/Image/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const Image: ExtensionAuto<ImageOptions | undefined> = (builder, opts) =>
declare global {
namespace WysiwygEditor {
interface Actions {
// @ts-expect-error
[addImageAction]: Action<AddImageAttrs>;
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/extensions/yfm/ImgSize/ImagePaste/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {Fragment, Node, Schema, Slice} from 'prosemirror-model';
import {Plugin} from 'prosemirror-state';
import {dropPoint} from 'prosemirror-transform';

import type {ParseInsertedUrlAsImage} from '../../../../bundle';
import {ExtensionAuto} from '../../../../core';
import {isFunction} from '../../../../lodash';
import {FileUploadHandler} from '../../../../utils/upload';
Expand All @@ -20,7 +21,7 @@ export type ImagePasteOptions = Pick<CreateImageNodeOptions, 'needDimmensions'>
/**
* The function, used to determine if the pasted text is the image url and should be inserted as an image
*/
parseInsertedUrlAsImage?: (text: string) => {imageUrl: string; title?: string} | null;
parseInsertedUrlAsImage?: ParseInsertedUrlAsImage;
};

export const ImagePaste: ExtensionAuto<ImagePasteOptions> = (builder, opts) => {
Expand Down
1 change: 0 additions & 1 deletion src/extensions/yfm/ImgSize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export const ImgSize: ExtensionAuto<ImgSizeOptions> = (builder, opts) => {
declare global {
namespace WysiwygEditor {
interface Actions {
// @ts-expect-error
[addImageAction]: Action<AddImageAttrs>;
}
}
Expand Down
27 changes: 27 additions & 0 deletions src/markup/codemirror/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ import {syntaxHighlighting} from '@codemirror/language';
import type {Extension, StateCommand} from '@codemirror/state';
import {EditorView, EditorViewConfig, KeyBinding, keymap, placeholder} from '@codemirror/view';

import type {ParseInsertedUrlAsImage} from '../../bundle';
import {EventMap} from '../../bundle/Editor';
import {ActionName} from '../../bundle/config/action-names';
import {ReactRenderStorage} from '../../extensions';
import {DataTransferType} from '../../extensions/behavior/Clipboard/utils';
import {logger} from '../../logger';
import {Action as A, formatter as f} from '../../shortcuts';
import {Receiver} from '../../utils';
import {
insertImages,
insertLink,
toH1,
toH2,
Expand Down Expand Up @@ -56,6 +59,7 @@ export type CreateCodemirrorParams = {
onScroll: (event: Event) => void;
reactRenderer: ReactRenderStorage;
uploadHandler?: FileUploadHandler;
parseInsertedUrlAsImage?: ParseInsertedUrlAsImage;
needImgDimms?: boolean;
extensions?: Extension[];
disabledExtensions?: {
Expand Down Expand Up @@ -83,6 +87,7 @@ export function createCodemirror(params: CreateCodemirrorParams) {
extensions: extraExtensions,
placeholder: placeholderContent,
autocompletion: autocompletionConfig,
parseInsertedUrlAsImage,
} = params;

const extensions: Extension[] = [gravityTheme, placeholder(placeholderContent)];
Expand Down Expand Up @@ -145,6 +150,28 @@ export function createCodemirror(params: CreateCodemirrorParams) {
scroll(event) {
onScroll(event);
},
paste(event, editor) {
if (event.clipboardData && parseInsertedUrlAsImage) {
const {imageUrl, title} =
parseInsertedUrlAsImage(
event.clipboardData.getData(DataTransferType.Text) ?? '',
) || {};

if (!imageUrl) {
return;
}

event.preventDefault();

insertImages([
{
url: imageUrl,
alt: title,
title,
},
])(editor);
}
},
}),
SearchPanelPlugin({
anchorSelector: '.g-md-search-anchor',
Expand Down

0 comments on commit 720215b

Please sign in to comment.