[feature] Include sources in TLExternalContent (#1925)

This PR adds the source items from a paste event to the data shared with
external content handlers. This allows developers to customize the way
certain content is handled.

For example, pasting text sometimes incudes additional clipboard items,
such as the HTML representation of that text. We wouldn't want to create
two shapes—one for the text and one for the HTML—so we still treat this
as a single text paste. The `registerExternalContentHandler` API allows
a developer to change how that text is handled, and the new `sources`
API will now allow the developer to take into consideration all of the
items that were on the clipboard.
 
![Kapture 2023-09-19 at 12 25
52](https://github.com/tldraw/tldraw/assets/23072548/fa976320-cfec-4921-b481-10cae0d4043e)

### Change Type

- [x] `minor` — New feature

### Test Plan

1. Try the external content source example.
2. Paste text that includes HTML (e.g. from VS Code)

### Release Notes

- [editor / tldraw] add `sources` to `TLExternalContent`
This commit is contained in:
Steve Ruiz 2023-09-19 16:33:54 +01:00 committed by GitHub
parent b6ebe1e274
commit 5cd74f4bd6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 171 additions and 101 deletions

View file

@ -623,16 +623,16 @@ export class Editor extends EventEmitter<TLEventMap> {
get erasingShapes(): NonNullable<TLShape | undefined>[];
// @internal (undocumented)
externalAssetContentHandlers: {
[K in TLExternalAssetContent_2['type']]: {
[Key in K]: ((info: TLExternalAssetContent_2 & {
[K in TLExternalAssetContent['type']]: {
[Key in K]: ((info: TLExternalAssetContent & {
type: Key;
}) => Promise<TLAsset | undefined>) | null;
}[K];
};
// @internal (undocumented)
externalContentHandlers: {
[K in TLExternalContent_2['type']]: {
[Key in K]: ((info: TLExternalContent_2 & {
[K in TLExternalContent['type']]: {
[Key in K]: ((info: TLExternalContent & {
type: Key;
}) => void) | null;
}[K];
@ -649,7 +649,7 @@ export class Editor extends EventEmitter<TLEventMap> {
handleId: "end" | "start";
}[];
getAsset(asset: TLAsset | TLAssetId): TLAsset | undefined;
getAssetForExternalContent(info: TLExternalAssetContent_2): Promise<TLAsset | undefined>;
getAssetForExternalContent(info: TLExternalAssetContent): Promise<TLAsset | undefined>;
getContainer: () => HTMLElement;
getContentFromCurrentPage(shapes: TLShape[] | TLShapeId[]): TLContent | undefined;
getDroppingOverShape(point: VecLike, droppingShapes?: TLShape[]): TLShape | undefined;
@ -770,14 +770,14 @@ export class Editor extends EventEmitter<TLEventMap> {
preservePosition?: boolean;
preserveIds?: boolean;
}): this;
putExternalContent(info: TLExternalContent_2): Promise<void>;
putExternalContent(info: TLExternalContent): Promise<void>;
redo(): this;
registerExternalAssetHandler<T extends TLExternalAssetContent_2['type']>(type: T, handler: ((info: TLExternalAssetContent_2 & {
registerExternalAssetHandler<T extends TLExternalAssetContent['type']>(type: T, handler: ((info: TLExternalAssetContent & {
type: T;
}) => Promise<TLAsset>) | null): this;
registerExternalContentHandler<T extends TLExternalContent_2['type']>(type: T, handler: ((info: T extends TLExternalContent_2['type'] ? TLExternalContent_2 & {
registerExternalContentHandler<T extends TLExternalContent['type']>(type: T, handler: ((info: T extends TLExternalContent['type'] ? TLExternalContent & {
type: T;
} : TLExternalContent_2) => void) | null): this;
} : TLExternalContent) => void) | null): this;
renamePage(page: TLPage | TLPageId, name: string, historyOptions?: TLCommandHistoryOptions): this;
get renderingBounds(): Box2d;
get renderingBoundsExpanded(): Box2d;
@ -2147,27 +2147,42 @@ export type TLExternalAssetContent = {
// @public (undocumented)
export type TLExternalContent = {
sources?: TLExternalContentSource[];
point?: VecLike;
} & ({
type: 'embed';
url: string;
point?: VecLike;
embed: EmbedDefinition;
} | {
type: 'files';
files: File[];
point?: VecLike;
ignoreParent: boolean;
} | {
type: 'svg-text';
text: string;
point?: VecLike;
} | {
type: 'text';
point?: VecLike;
text: string;
} | {
type: 'url';
url: string;
point?: VecLike;
});
// @public (undocumented)
export type TLExternalContentSource = {
type: 'error';
data: null | string;
reason: string;
} | {
type: 'excalidraw';
data: any;
} | {
type: 'text';
data: string;
subtype: 'html' | 'json' | 'text' | 'url';
} | {
type: 'tldraw';
data: TLContent;
};
// @public (undocumented)

View file

@ -230,6 +230,7 @@ export {
export {
type TLExternalAssetContent,
type TLExternalContent,
type TLExternalContentSource,
} from './lib/editor/types/external-content'
export {
type TLCommand,

View file

@ -2,7 +2,6 @@ import { EMPTY_ARRAY, atom, computed, transact } from '@tldraw/state'
import { ComputedCache, RecordType } from '@tldraw/store'
import {
CameraRecordType,
EmbedDefinition,
InstancePageStateRecordType,
PageRecordType,
StyleProp,
@ -122,6 +121,7 @@ import { SvgExportContext, SvgExportDef } from './types/SvgExportContext'
import { TLContent } from './types/clipboard-types'
import { TLEventMap } from './types/emit-types'
import { TLEventInfo, TLPinchEventInfo, TLPointerEventInfo } from './types/event-types'
import { TLExternalAssetContent, TLExternalContent } from './types/external-content'
import { TLCommandHistoryOptions } from './types/history-types'
import { OptionalKeys, RequiredKeys } from './types/misc-types'
import { TLResizeHandle } from './types/selection-types'
@ -8908,36 +8908,3 @@ function alertMaxShapes(editor: Editor, pageId = editor.currentPageId) {
const name = editor.getPage(pageId)!.name
editor.emit('max-shapes', { name, pageId, count: MAX_SHAPES_PER_PAGE })
}
/** @public */
export type TLExternalContent =
| {
type: 'text'
point?: VecLike
text: string
}
| {
type: 'files'
files: File[]
point?: VecLike
ignoreParent: boolean
}
| {
type: 'url'
url: string
point?: VecLike
}
| {
type: 'svg-text'
text: string
point?: VecLike
}
| {
type: 'embed'
url: string
point?: VecLike
embed: EmbedDefinition
}
/** @public */
export type TLExternalAssetContent = { type: 'file'; file: File } | { type: 'url'; url: string }

View file

@ -1,35 +1,56 @@
import { EmbedDefinition } from '@tldraw/tlschema'
import { VecLike } from '../../primitives/Vec2d'
import { TLContent } from './clipboard-types'
/** @public */
export type TLExternalContent =
export type TLExternalContentSource =
| {
type: 'tldraw'
data: TLContent
}
| {
type: 'excalidraw'
data: any
}
| {
type: 'text'
data: string
subtype: 'json' | 'html' | 'text' | 'url'
}
| {
type: 'error'
data: string | null
reason: string
}
/** @public */
export type TLExternalContent = {
sources?: TLExternalContentSource[]
point?: VecLike
} & (
| {
type: 'text'
point?: VecLike
text: string
}
| {
type: 'files'
files: File[]
point?: VecLike
ignoreParent: boolean
}
| {
type: 'url'
url: string
point?: VecLike
}
| {
type: 'svg-text'
text: string
point?: VecLike
}
| {
type: 'embed'
url: string
point?: VecLike
embed: EmbedDefinition
}
)
/** @public */
export type TLExternalAssetContent = { type: 'file'; file: File } | { type: 'url'; url: string }