[ENG-1368] Drag and drop links from Base and File explorer into canvas#780
[ENG-1368] Drag and drop links from Base and File explorer into canvas#780trangdoan982 wants to merge 2 commits intomainfrom
Conversation
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
apps/obsidian/src/components/canvas/utils/externalContentHandlers.ts
Outdated
Show resolved
Hide resolved
…ers.ts Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
| editor.registerExternalContentHandler("url", (externalContent) => { | ||
| void handleExternalUrlContent({ | ||
| editor, | ||
| url: externalContent.url, | ||
| point: externalContent.point, | ||
| plugin, | ||
| canvasFile: file, | ||
| defaultHandlerOpts: { | ||
| toasts: { | ||
| addToast: (t: Omit<TLUiToast, "id"> & { id?: string }) => { | ||
| new Notice(t.description ?? t.title ?? "Error"); | ||
| return ""; | ||
| }, | ||
| removeToast: () => "", | ||
| clearToasts: () => {}, | ||
| toasts: { get: () => [], update: () => {} }, | ||
| }, | ||
| msg: (key?: string) => key ?? "", | ||
| } as unknown as TLDefaultExternalContentHandlerOpts, | ||
| }); | ||
| }); |
There was a problem hiding this comment.
The void keyword is used with an async function inside a handler, which means unhandled promise rejections could occur if handleExternalUrlContent throws an error outside of its try-catch blocks. While the function has internal error handling, any unexpected errors in the async chain (like in ensureBlockRefForFile, getFirstImageSrcForFile, or calcDiscourseNodeSize) that aren't caught will result in unhandled promise rejections.
editor.registerExternalContentHandler("url", async (externalContent) => {
try {
await handleExternalUrlContent({...});
} catch (error) {
console.error('Error handling external URL content:', error);
new Notice('Failed to add content to canvas');
}
});Alternatively, if the handler must be synchronous, ensure handleExternalUrlContent has comprehensive error handling for all async operations.
| editor.registerExternalContentHandler("url", (externalContent) => { | |
| void handleExternalUrlContent({ | |
| editor, | |
| url: externalContent.url, | |
| point: externalContent.point, | |
| plugin, | |
| canvasFile: file, | |
| defaultHandlerOpts: { | |
| toasts: { | |
| addToast: (t: Omit<TLUiToast, "id"> & { id?: string }) => { | |
| new Notice(t.description ?? t.title ?? "Error"); | |
| return ""; | |
| }, | |
| removeToast: () => "", | |
| clearToasts: () => {}, | |
| toasts: { get: () => [], update: () => {} }, | |
| }, | |
| msg: (key?: string) => key ?? "", | |
| } as unknown as TLDefaultExternalContentHandlerOpts, | |
| }); | |
| }); | |
| editor.registerExternalContentHandler("url", async (externalContent) => { | |
| try { | |
| await handleExternalUrlContent({ | |
| editor, | |
| url: externalContent.url, | |
| point: externalContent.point, | |
| plugin, | |
| canvasFile: file, | |
| defaultHandlerOpts: { | |
| toasts: { | |
| addToast: (t: Omit<TLUiToast, "id"> & { id?: string }) => { | |
| new Notice(t.description ?? t.title ?? "Error"); | |
| return ""; | |
| }, | |
| removeToast: () => "", | |
| clearToasts: () => {}, | |
| toasts: { get: () => [], update: () => {} }, | |
| }, | |
| msg: (key?: string) => key ?? "", | |
| } as unknown as TLDefaultExternalContentHandlerOpts, | |
| }); | |
| } catch (error) { | |
| console.error('Error handling external URL content:', error); | |
| new Notice('Failed to add content to canvas'); | |
| } | |
| }); |
Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
| editor.registerExternalContentHandler("url", (externalContent) => { | ||
| void handleExternalUrlContent({ | ||
| editor, | ||
| url: externalContent.url, | ||
| point: externalContent.point, | ||
| plugin, | ||
| canvasFile: file, | ||
| defaultHandlerOpts: { | ||
| toasts: { | ||
| addToast: (t: Omit<TLUiToast, "id"> & { id?: string }) => { | ||
| new Notice(t.description ?? t.title ?? "Error"); | ||
| return ""; | ||
| }, | ||
| removeToast: () => "", | ||
| clearToasts: () => {}, | ||
| toasts: { get: () => [], update: () => {} }, | ||
| }, | ||
| msg: (key?: string) => key ?? "", | ||
| } as unknown as TLDefaultExternalContentHandlerOpts, | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🔴 External content handler discards promise with void, breaking tldraw's async handler contract
The registerExternalContentHandler callback uses void handleExternalUrlContent(...) which discards the returned promise, causing the handler to return undefined synchronously instead of a Promise<void>.
Root Cause and Impact
Tldraw's registerExternalContentHandler expects the handler to return a Promise<void> so it can await the handler's completion. By using void (TldrawViewComponent.tsx:271), the promise from handleExternalUrlContent is discarded and the callback returns undefined synchronously.
When tldraw does await handler(content), it gets undefined, which resolves immediately. This means tldraw will consider the content handling "done" before the async work actually completes. Concretely:
- For obsidian:// URLs:
ensureBlockRefForFileatexternalContentHandlers.ts:170is async and must complete before the shape can be created. Since tldraw isn't waiting, drag indicators and other UI may dismiss before the node appears on canvas. - For non-obsidian URLs: The fallback call to
defaultHandleExternalUrlContentatexternalContentHandlers.ts:145also runs fire-and-forget, so bookmark/embed creation may not complete properly. - Error handling: Errors thrown after the first
awaitinsidehandleExternalUrlContentbecome unhandled promise rejections instead of being caught by tldraw's error handling.
The fix is to return the promise instead of discarding it, e.g. return handleExternalUrlContent(...) instead of void handleExternalUrlContent(...).
| editor.registerExternalContentHandler("url", (externalContent) => { | |
| void handleExternalUrlContent({ | |
| editor, | |
| url: externalContent.url, | |
| point: externalContent.point, | |
| plugin, | |
| canvasFile: file, | |
| defaultHandlerOpts: { | |
| toasts: { | |
| addToast: (t: Omit<TLUiToast, "id"> & { id?: string }) => { | |
| new Notice(t.description ?? t.title ?? "Error"); | |
| return ""; | |
| }, | |
| removeToast: () => "", | |
| clearToasts: () => {}, | |
| toasts: { get: () => [], update: () => {} }, | |
| }, | |
| msg: (key?: string) => key ?? "", | |
| } as unknown as TLDefaultExternalContentHandlerOpts, | |
| }); | |
| }); | |
| editor.registerExternalContentHandler("url", (externalContent) => { | |
| return handleExternalUrlContent({ | |
| editor, | |
| url: externalContent.url, | |
| point: externalContent.point, | |
| plugin, | |
| canvasFile: file, | |
| defaultHandlerOpts: { | |
| toasts: { | |
| addToast: (t: Omit<TLUiToast, "id"> & { id?: string }) => { | |
| new Notice(t.description ?? t.title ?? "Error"); | |
| return ""; | |
| }, | |
| removeToast: () => "", | |
| clearToasts: () => {}, | |
| toasts: { get: () => [], update: () => {} }, | |
| }, | |
| msg: (key?: string) => key ?? "", | |
| } as unknown as TLDefaultExternalContentHandlerOpts, | |
| }); | |
| }); |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
👍
Feel free to merge after dealing with #780 (comment)
https://www.loom.com/share/f0520b78435c4caabac0f6b6871b4fa3