|
|
@@ -1,7 +1,7 @@
|
|
|
-import { Item, Label } from '@radix-ui/react-select'
|
|
|
import {
|
|
|
BoundsUtils,
|
|
|
getSizeFromSrc,
|
|
|
+ isNonNullable,
|
|
|
TLAsset,
|
|
|
TLBinding,
|
|
|
TLCursor,
|
|
|
@@ -14,17 +14,15 @@ import Vec from '@tldraw/vec'
|
|
|
import * as React from 'react'
|
|
|
import { NIL as NIL_UUID } from 'uuid'
|
|
|
import {
|
|
|
- type Shape,
|
|
|
HTMLShape,
|
|
|
- YouTubeShape,
|
|
|
+ IFrameShape,
|
|
|
+ ImageShape,
|
|
|
LogseqPortalShape,
|
|
|
VideoShape,
|
|
|
- TextShape,
|
|
|
- LineShape,
|
|
|
- ImageShape,
|
|
|
- IFrameShape,
|
|
|
+ YouTubeShape,
|
|
|
+ type Shape,
|
|
|
} from '../lib'
|
|
|
-import type { LogseqContextValue } from '../lib/logseq-context'
|
|
|
+import { LogseqContext } from '../lib/logseq-context'
|
|
|
|
|
|
const isValidURL = (url: string) => {
|
|
|
try {
|
|
|
@@ -43,197 +41,218 @@ const safeParseJson = (json: string) => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+interface VideoImageAsset extends TLAsset {
|
|
|
+ size?: number[]
|
|
|
+}
|
|
|
+
|
|
|
const IMAGE_EXTENSIONS = ['.png', '.svg', '.jpg', '.jpeg', '.gif']
|
|
|
const VIDEO_EXTENSIONS = ['.mp4', '.webm', '.ogg']
|
|
|
|
|
|
+function getFileType(filename: string) {
|
|
|
+ // Get extension, verify that it's an image
|
|
|
+ const extensionMatch = filename.match(/\.[0-9a-z]+$/i)
|
|
|
+ if (!extensionMatch) {
|
|
|
+ return 'unknown'
|
|
|
+ }
|
|
|
+ const extension = extensionMatch[0].toLowerCase()
|
|
|
+ if (IMAGE_EXTENSIONS.includes(extension)) {
|
|
|
+ return 'image'
|
|
|
+ }
|
|
|
+ if (VIDEO_EXTENSIONS.includes(extension)) {
|
|
|
+ return 'video'
|
|
|
+ }
|
|
|
+ return 'unknown'
|
|
|
+}
|
|
|
+
|
|
|
+type MaybeShapes = Shape['props'][] | null | undefined
|
|
|
+
|
|
|
+type CreateShapeFN<Args extends any[]> = (...args: Args) => Promise<MaybeShapes> | MaybeShapes
|
|
|
+
|
|
|
+/**
|
|
|
+ * Try create a shape from a list of create shape functions. If one of the functions returns a
|
|
|
+ * shape, return it, otherwise try again for the next one until all have been tried.
|
|
|
+ */
|
|
|
+async function tryCreateShapeHelper<Args extends any[]>(fns: CreateShapeFN<Args>[], ...args: Args) {
|
|
|
+ for (const fn of fns) {
|
|
|
+ const result = await fn(...(args as any))
|
|
|
+ if (result && result.length > 0) {
|
|
|
+ return result
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null
|
|
|
+}
|
|
|
+
|
|
|
// FIXME: for assets, we should prompt the user a loading spinner
|
|
|
-export function usePaste(context: LogseqContextValue) {
|
|
|
- const { handlers } = context
|
|
|
+export function usePaste() {
|
|
|
+ const { handlers } = React.useContext(LogseqContext)
|
|
|
|
|
|
return React.useCallback<TLReactCallbacks<Shape>['onPaste']>(
|
|
|
async (app, { point, shiftKey, dataTransfer }) => {
|
|
|
- interface VideoImageAsset extends TLAsset {
|
|
|
- size: number[]
|
|
|
- }
|
|
|
-
|
|
|
- const imageAssetsToCreate: VideoImageAsset[] = []
|
|
|
+ let imageAssetsToCreate: VideoImageAsset[] = []
|
|
|
let assetsToClone: TLAsset[] = []
|
|
|
const shapesToCreate: Shape['props'][] = []
|
|
|
const bindingsToCreate: TLBinding[] = []
|
|
|
|
|
|
- async function createAsset(file: File): Promise<string | null> {
|
|
|
- return await handlers.saveAsset(file)
|
|
|
- }
|
|
|
-
|
|
|
- async function handleAssetUrl(url: string, isVideo: boolean) {
|
|
|
+ async function createAssetsFromURL(url: string, isVideo: boolean): Promise<VideoImageAsset> {
|
|
|
// Do we already have an asset for this image?
|
|
|
const existingAsset = Object.values(app.assets).find(asset => asset.src === url)
|
|
|
if (existingAsset) {
|
|
|
- imageAssetsToCreate.push(existingAsset as VideoImageAsset)
|
|
|
- return true
|
|
|
+ return existingAsset as VideoImageAsset
|
|
|
} else {
|
|
|
- try {
|
|
|
- // Create a new asset for this image
|
|
|
- const asset: VideoImageAsset = {
|
|
|
- id: uniqueId(),
|
|
|
- type: isVideo ? 'video' : 'image',
|
|
|
- src: url,
|
|
|
- size: await getSizeFromSrc(handlers.makeAssetUrl(url), isVideo),
|
|
|
- }
|
|
|
- imageAssetsToCreate.push(asset)
|
|
|
- return true
|
|
|
- } catch {
|
|
|
- return false
|
|
|
+ // Create a new asset for this image
|
|
|
+ const asset: VideoImageAsset = {
|
|
|
+ id: uniqueId(),
|
|
|
+ type: isVideo ? 'video' : 'image',
|
|
|
+ src: url,
|
|
|
+ size: await getSizeFromSrc(handlers.makeAssetUrl(url), isVideo),
|
|
|
}
|
|
|
+ return asset
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // TODO: handle PDF?
|
|
|
- async function handleFiles(files: File[]) {
|
|
|
- let added = false
|
|
|
- for (const file of files) {
|
|
|
- // Get extension, verify that it's an image
|
|
|
- const extensionMatch = file.name.match(/\.[0-9a-z]+$/i)
|
|
|
- if (!extensionMatch) {
|
|
|
- continue
|
|
|
- }
|
|
|
- const extension = extensionMatch[0].toLowerCase()
|
|
|
- if (![...IMAGE_EXTENSIONS, ...VIDEO_EXTENSIONS].includes(extension)) {
|
|
|
- continue
|
|
|
- }
|
|
|
- const isVideo = VIDEO_EXTENSIONS.includes(extension)
|
|
|
- try {
|
|
|
- // Turn the image into a base64 dataurl
|
|
|
- const dataurl = await createAsset(file)
|
|
|
- if (!dataurl) {
|
|
|
- continue
|
|
|
- }
|
|
|
- if (await handleAssetUrl(dataurl, isVideo)) {
|
|
|
- added = true
|
|
|
+ async function createAssetsFromFiles(files: File[]) {
|
|
|
+ const tasks = files
|
|
|
+ .filter(file => getFileType(file.name) !== 'unknown')
|
|
|
+ .map(async file => {
|
|
|
+ try {
|
|
|
+ const dataurl = await handlers.saveAsset(file)
|
|
|
+ return await createAssetsFromURL(dataurl, getFileType(file.name) === 'video')
|
|
|
+ } catch (err) {
|
|
|
+ console.error(err)
|
|
|
}
|
|
|
- } catch (error) {
|
|
|
- console.error(error)
|
|
|
- }
|
|
|
- }
|
|
|
- return added
|
|
|
+ return null
|
|
|
+ })
|
|
|
+ return (await Promise.all(tasks)).filter(isNonNullable)
|
|
|
}
|
|
|
|
|
|
- async function handleItems(items: any) {
|
|
|
- for (const item of items) {
|
|
|
- if (await handleDroppedItem(item)) {
|
|
|
- const lineId = uniqueId()
|
|
|
-
|
|
|
- const startBinding: TLBinding = {
|
|
|
- id: uniqueId(),
|
|
|
- distance: 200,
|
|
|
- handleId: 'start',
|
|
|
- fromId: lineId,
|
|
|
- toId: app.selectedShapesArray[app.selectedShapesArray.length - 1].id,
|
|
|
- point: [point[0], point[1]],
|
|
|
- }
|
|
|
- bindingsToCreate.push(startBinding)
|
|
|
-
|
|
|
- const endBinding: TLBinding = {
|
|
|
- id: uniqueId(),
|
|
|
- distance: 200,
|
|
|
- handleId: 'end',
|
|
|
- fromId: lineId,
|
|
|
- toId: shapesToCreate[shapesToCreate.length - 1].id,
|
|
|
- point: [point[0], point[1]],
|
|
|
- }
|
|
|
- bindingsToCreate.push(endBinding)
|
|
|
-
|
|
|
- shapesToCreate.push({
|
|
|
- ...LineShape.defaultProps,
|
|
|
- id: lineId,
|
|
|
- handles: {
|
|
|
- start: { id: 'start', canBind: true, point: app.selectedShapesArray[0].getCenter(), bindingId: startBinding.id },
|
|
|
- end: { id: 'end', canBind: true, point: [point[0], point[1]], bindingId: endBinding.id },
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- return true
|
|
|
- }
|
|
|
+ function createHTMLShape(text: string) {
|
|
|
+ return {
|
|
|
+ ...HTMLShape.defaultProps,
|
|
|
+ html: text,
|
|
|
+ point: [point[0], point[1]],
|
|
|
}
|
|
|
- return false
|
|
|
}
|
|
|
|
|
|
- async function handleTransfer(dataTransfer: DataTransfer) {
|
|
|
- let added = false
|
|
|
-
|
|
|
- if ((dataTransfer.files?.length && await handleFiles(Array.from(dataTransfer.files))) ||
|
|
|
- (dataTransfer.items?.length && await handleItems(Array.from(dataTransfer.items).map((item: any) => ({type: item.type, text: dataTransfer.getData(item.type)})))) ) {
|
|
|
- added = true
|
|
|
- }
|
|
|
-
|
|
|
- return added
|
|
|
+ // async function handleItems(items: any) {
|
|
|
+ // for (const item of items) {
|
|
|
+ // if (await handleDroppedItem(item)) {
|
|
|
+ // const lineId = uniqueId()
|
|
|
+
|
|
|
+ // const startBinding: TLBinding = {
|
|
|
+ // id: uniqueId(),
|
|
|
+ // distance: 200,
|
|
|
+ // handleId: 'start',
|
|
|
+ // fromId: lineId,
|
|
|
+ // toId: app.selectedShapesArray[app.selectedShapesArray.length - 1].id,
|
|
|
+ // point: [point[0], point[1]],
|
|
|
+ // }
|
|
|
+ // bindingsToCreate.push(startBinding)
|
|
|
+
|
|
|
+ // const endBinding: TLBinding = {
|
|
|
+ // id: uniqueId(),
|
|
|
+ // distance: 200,
|
|
|
+ // handleId: 'end',
|
|
|
+ // fromId: lineId,
|
|
|
+ // toId: shapesToCreate[shapesToCreate.length - 1].id,
|
|
|
+ // point: [point[0], point[1]],
|
|
|
+ // }
|
|
|
+ // bindingsToCreate.push(endBinding)
|
|
|
+
|
|
|
+ // shapesToCreate.push({
|
|
|
+ // ...LineShape.defaultProps,
|
|
|
+ // id: lineId,
|
|
|
+ // handles: {
|
|
|
+ // start: {
|
|
|
+ // id: 'start',
|
|
|
+ // canBind: true,
|
|
|
+ // point: app.selectedShapesArray[0].getCenter(),
|
|
|
+ // bindingId: startBinding.id,
|
|
|
+ // },
|
|
|
+ // end: {
|
|
|
+ // id: 'end',
|
|
|
+ // canBind: true,
|
|
|
+ // point: [point[0], point[1]],
|
|
|
+ // bindingId: endBinding.id,
|
|
|
+ // },
|
|
|
+ // },
|
|
|
+ // })
|
|
|
+
|
|
|
+ // return true
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // return false
|
|
|
+ // }
|
|
|
+
|
|
|
+ async function tryCreateShapesFromDataTransfer(dataTransfer: DataTransfer) {
|
|
|
+ return tryCreateShapeHelper(
|
|
|
+ [tryCreateShapeFromFiles, tryCreateShapeFromTextHTML, tryCreateShapeFromTextPlain],
|
|
|
+ dataTransfer
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
- async function handleHTML(item: ClipboardItem) {
|
|
|
- if (item.types.includes('text/html')) {
|
|
|
- const blob = await item.getType('text/html')
|
|
|
- const rawText = (await blob.text()).trim()
|
|
|
-
|
|
|
- shapesToCreate.push({
|
|
|
- ...HTMLShape.defaultProps,
|
|
|
- html: rawText,
|
|
|
- point: [point[0], point[1]],
|
|
|
- })
|
|
|
- return true
|
|
|
- }
|
|
|
- return false
|
|
|
- }
|
|
|
+ async function tryCreateShapeFromFiles(dataTransfer: DataTransfer) {
|
|
|
+ const files = Array.from(dataTransfer.files)
|
|
|
+ if (files.length > 0) {
|
|
|
+ const assets = await createAssetsFromFiles(files)
|
|
|
+ // ? could we get rid of this side effect?
|
|
|
+ imageAssetsToCreate = assets
|
|
|
+
|
|
|
+ return assets.map((asset, i) => {
|
|
|
+ const defaultProps =
|
|
|
+ asset.type === 'video' ? VideoShape.defaultProps : ImageShape.defaultProps
|
|
|
+ const newShape = {
|
|
|
+ ...defaultProps,
|
|
|
+ // TODO: Should be place near the last edited shape
|
|
|
+ assetId: asset.id,
|
|
|
+ opacity: 1,
|
|
|
+ }
|
|
|
|
|
|
- async function handleDroppedItem(item: any) {
|
|
|
- switch(item.type) {
|
|
|
- case 'text/html':
|
|
|
- shapesToCreate.push({
|
|
|
- ...HTMLShape.defaultProps,
|
|
|
- html: item.text,
|
|
|
- point: [point[0], point[1]],
|
|
|
- })
|
|
|
- return true
|
|
|
- case 'text/plain':
|
|
|
- if (await handleURL(item.text)) {
|
|
|
- return true
|
|
|
+ if (asset.size) {
|
|
|
+ Object.assign(newShape, {
|
|
|
+ point: [
|
|
|
+ point[0] - asset.size[0] / 4 + i * 16,
|
|
|
+ point[1] - asset.size[1] / 4 + i * 16,
|
|
|
+ ],
|
|
|
+ size: Vec.div(asset.size, 2),
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
- shapesToCreate.push({
|
|
|
- ...TextShape.defaultProps,
|
|
|
- text: item.text,
|
|
|
- point: [point[0], point[1]],
|
|
|
- })
|
|
|
- return true
|
|
|
- default:
|
|
|
- return false
|
|
|
+ return newShape
|
|
|
+ })
|
|
|
}
|
|
|
+ return null
|
|
|
}
|
|
|
|
|
|
- async function handleTextPlain(item: ClipboardItem) {
|
|
|
- if (item.types.includes('text/plain')) {
|
|
|
- const blob = await item.getType('text/plain')
|
|
|
- const rawText = (await blob.text()).trim()
|
|
|
-
|
|
|
- if (await handleURL(rawText)) {
|
|
|
- return true
|
|
|
- }
|
|
|
+ function tryCreateShapeFromTextHTML(dataTransfer: DataTransfer) {
|
|
|
+ if (dataTransfer.types.includes('text/html') && !shiftKey) {
|
|
|
+ const html = dataTransfer.getData('text/html')
|
|
|
|
|
|
- if (handleIframe(rawText)) {
|
|
|
- return true
|
|
|
+ if (html) {
|
|
|
+ return [createHTMLShape(html)]
|
|
|
}
|
|
|
+ }
|
|
|
+ return null
|
|
|
+ }
|
|
|
|
|
|
- if (handleTldrawShapes(rawText)) {
|
|
|
- return true
|
|
|
- }
|
|
|
- if (await handleLogseqPortalShapes(rawText)) {
|
|
|
- return true
|
|
|
- }
|
|
|
+ async function tryCreateShapeFromTextPlain(dataTransfer: DataTransfer) {
|
|
|
+ if (dataTransfer.types.includes('text/plain')) {
|
|
|
+ const text = dataTransfer.getData('text/plain').trim()
|
|
|
+
|
|
|
+ return tryCreateShapeHelper(
|
|
|
+ [
|
|
|
+ tryCreateShapeFromURL,
|
|
|
+ tryCreateShapeFromIframeString,
|
|
|
+ tryCreateClonedShapesFromJSON,
|
|
|
+ tryCreateLogseqPortalShapesFromString,
|
|
|
+ ],
|
|
|
+ text
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
- return false
|
|
|
+ return null
|
|
|
}
|
|
|
|
|
|
- function handleTldrawShapes(rawText: string) {
|
|
|
+ function tryCreateClonedShapesFromJSON(rawText: string) {
|
|
|
const data = safeParseJson(rawText)
|
|
|
try {
|
|
|
if (data?.type === 'logseq/whiteboard-shapes') {
|
|
|
@@ -249,7 +268,7 @@ export function usePaste(context: LogseqContextValue) {
|
|
|
maxY: (shape.point?.[1] ?? point[1]) + (shape.size?.[1] ?? 4),
|
|
|
}))
|
|
|
)
|
|
|
- const clonedShapes = shapes.map(shape => {
|
|
|
+ const shapesToCreate = shapes.map(shape => {
|
|
|
return {
|
|
|
...shape,
|
|
|
point: [
|
|
|
@@ -258,49 +277,48 @@ export function usePaste(context: LogseqContextValue) {
|
|
|
],
|
|
|
}
|
|
|
})
|
|
|
- // @ts-expect-error - This is a valid shape
|
|
|
- shapesToCreate.push(...clonedShapes)
|
|
|
|
|
|
// Try to rebinding the shapes to the new assets
|
|
|
- shapesToCreate.forEach((s, idx) => {
|
|
|
- if (s.handles) {
|
|
|
- Object.values(s.handles).forEach(h => {
|
|
|
- if (h.bindingId) {
|
|
|
- // try to bind the new shape
|
|
|
- const binding = app.currentPage.bindings[h.bindingId]
|
|
|
- // FIXME: if copy from a different whiteboard, the binding info
|
|
|
- // will not be available
|
|
|
- if (binding) {
|
|
|
- // if the copied binding from/to is in the source
|
|
|
- const oldFromIdx = shapes.findIndex(s => s.id === binding.fromId)
|
|
|
- const oldToIdx = shapes.findIndex(s => s.id === binding.toId)
|
|
|
- if (binding && oldFromIdx !== -1 && oldToIdx !== -1) {
|
|
|
- const newBinding: TLBinding = {
|
|
|
- ...binding,
|
|
|
- id: uniqueId(),
|
|
|
- fromId: shapesToCreate[oldFromIdx].id,
|
|
|
- toId: shapesToCreate[oldToIdx].id,
|
|
|
- }
|
|
|
- bindingsToCreate.push(newBinding)
|
|
|
- h.bindingId = newBinding.id
|
|
|
- } else {
|
|
|
- h.bindingId = undefined
|
|
|
- }
|
|
|
+ shapesToCreate
|
|
|
+ .flatMap(s => Object.values(s.handles ?? {}))
|
|
|
+ .forEach(h => {
|
|
|
+ if (!h.bindingId) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // try to bind the new shape
|
|
|
+ const binding = app.currentPage.bindings[h.bindingId]
|
|
|
+ // FIXME: if copy from a different whiteboard, the binding info
|
|
|
+ // will not be available
|
|
|
+ if (binding) {
|
|
|
+ // if the copied binding from/to is in the source
|
|
|
+ const oldFromIdx = shapes.findIndex(s => s.id === binding.fromId)
|
|
|
+ const oldToIdx = shapes.findIndex(s => s.id === binding.toId)
|
|
|
+ if (binding && oldFromIdx !== -1 && oldToIdx !== -1) {
|
|
|
+ const newBinding: TLBinding = {
|
|
|
+ ...binding,
|
|
|
+ id: uniqueId(),
|
|
|
+ fromId: shapesToCreate[oldFromIdx].id,
|
|
|
+ toId: shapesToCreate[oldToIdx].id,
|
|
|
}
|
|
|
+ bindingsToCreate.push(newBinding)
|
|
|
+ h.bindingId = newBinding.id
|
|
|
+ } else {
|
|
|
+ h.bindingId = undefined
|
|
|
}
|
|
|
- })
|
|
|
- }
|
|
|
- })
|
|
|
+ } else {
|
|
|
+ console.warn('binding not found', h.bindingId)
|
|
|
+ }
|
|
|
+ })
|
|
|
|
|
|
- return true
|
|
|
+ return shapesToCreate as Shape['props'][]
|
|
|
}
|
|
|
} catch (err) {
|
|
|
console.error(err)
|
|
|
}
|
|
|
- return false
|
|
|
+ return null
|
|
|
}
|
|
|
|
|
|
- async function handleURL(rawText: string) {
|
|
|
+ async function tryCreateShapeFromURL(rawText: string) {
|
|
|
if (isValidURL(rawText)) {
|
|
|
const isYoutubeUrl = (url: string) => {
|
|
|
const youtubeRegex =
|
|
|
@@ -308,116 +326,108 @@ export function usePaste(context: LogseqContextValue) {
|
|
|
return youtubeRegex.test(url)
|
|
|
}
|
|
|
if (isYoutubeUrl(rawText)) {
|
|
|
- shapesToCreate.push({
|
|
|
- ...YouTubeShape.defaultProps,
|
|
|
- url: rawText,
|
|
|
- point: [point[0], point[1]],
|
|
|
- })
|
|
|
- return true
|
|
|
- }
|
|
|
- const extension = rawText.match(/\.[0-9a-z]+$/i)?.[0].toLowerCase()
|
|
|
- if (
|
|
|
- extension &&
|
|
|
- [...IMAGE_EXTENSIONS, ...VIDEO_EXTENSIONS].includes(extension) &&
|
|
|
- (await handleAssetUrl(rawText, VIDEO_EXTENSIONS.includes(extension)))
|
|
|
- ) {
|
|
|
- return true
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ ...YouTubeShape.defaultProps,
|
|
|
+ url: rawText,
|
|
|
+ point: [point[0], point[1]],
|
|
|
+ },
|
|
|
+ ]
|
|
|
}
|
|
|
|
|
|
- shapesToCreate.push({
|
|
|
- ...IFrameShape.defaultProps,
|
|
|
- url: rawText,
|
|
|
- point: [point[0], point[1]],
|
|
|
- })
|
|
|
- return true
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ ...IFrameShape.defaultProps,
|
|
|
+ url: rawText,
|
|
|
+ point: [point[0], point[1]],
|
|
|
+ },
|
|
|
+ ]
|
|
|
}
|
|
|
- return false
|
|
|
+ return null
|
|
|
}
|
|
|
|
|
|
- function handleIframe(rawText: string) {
|
|
|
+ function tryCreateShapeFromIframeString(rawText: string) {
|
|
|
// if rawText is iframe text
|
|
|
if (rawText.startsWith('<iframe')) {
|
|
|
- shapesToCreate.push({
|
|
|
- ...HTMLShape.defaultProps,
|
|
|
- html: rawText,
|
|
|
- point: [point[0], point[1]],
|
|
|
- })
|
|
|
- return true
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ ...HTMLShape.defaultProps,
|
|
|
+ html: rawText,
|
|
|
+ point: [point[0], point[1]],
|
|
|
+ },
|
|
|
+ ]
|
|
|
}
|
|
|
- return false
|
|
|
+ return null
|
|
|
}
|
|
|
|
|
|
- async function handleLogseqPortalShapes(rawText: string) {
|
|
|
+ async function tryCreateLogseqPortalShapesFromString(rawText: string) {
|
|
|
if (/^\(\(.*\)\)$/.test(rawText) && rawText.length === NIL_UUID.length + 4) {
|
|
|
const blockRef = rawText.slice(2, -2)
|
|
|
if (validUUID(blockRef)) {
|
|
|
- shapesToCreate.push({
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ ...LogseqPortalShape.defaultProps,
|
|
|
+ point: [point[0], point[1]],
|
|
|
+ size: [400, 0], // use 0 here to enable auto-resize
|
|
|
+ pageId: blockRef,
|
|
|
+ blockType: 'B' as 'B',
|
|
|
+ },
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // [[page name]] ?
|
|
|
+ else if (/^\[\[.*\]\]$/.test(rawText)) {
|
|
|
+ const pageName = rawText.slice(2, -2)
|
|
|
+ return [
|
|
|
+ {
|
|
|
...LogseqPortalShape.defaultProps,
|
|
|
point: [point[0], point[1]],
|
|
|
size: [400, 0], // use 0 here to enable auto-resize
|
|
|
- pageId: blockRef,
|
|
|
- blockType: 'B',
|
|
|
- })
|
|
|
- return true
|
|
|
- }
|
|
|
- } else if (/^\[\[.*\]\]$/.test(rawText)) {
|
|
|
- const pageName = rawText.slice(2, -2)
|
|
|
- shapesToCreate.push({
|
|
|
- ...LogseqPortalShape.defaultProps,
|
|
|
- point: [point[0], point[1]],
|
|
|
- size: [400, 0], // use 0 here to enable auto-resize
|
|
|
- pageId: pageName,
|
|
|
- blockType: 'P',
|
|
|
- })
|
|
|
- return true
|
|
|
+ pageId: pageName,
|
|
|
+ blockType: 'P' as 'P',
|
|
|
+ },
|
|
|
+ ]
|
|
|
}
|
|
|
|
|
|
+ // Otherwise, creating a new block that belongs to the current whiteboard
|
|
|
const uuid = handlers?.addNewBlock(rawText)
|
|
|
if (uuid) {
|
|
|
// create text shape
|
|
|
- shapesToCreate.push({
|
|
|
- ...LogseqPortalShape.defaultProps,
|
|
|
- id: uniqueId(),
|
|
|
- size: [400, 0], // use 0 here to enable auto-resize
|
|
|
- point: [point[0], point[1]],
|
|
|
- pageId: uuid,
|
|
|
- blockType: 'B',
|
|
|
- compact: true,
|
|
|
- })
|
|
|
- return true
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ ...LogseqPortalShape.defaultProps,
|
|
|
+ id: uniqueId(),
|
|
|
+ size: [400, 0], // use 0 here to enable auto-resize
|
|
|
+ point: [point[0], point[1]],
|
|
|
+ pageId: uuid,
|
|
|
+ blockType: 'B' as 'B',
|
|
|
+ compact: true,
|
|
|
+ },
|
|
|
+ ]
|
|
|
}
|
|
|
- return false
|
|
|
+
|
|
|
+ return null
|
|
|
}
|
|
|
|
|
|
app.cursors.setCursor(TLCursor.Progress)
|
|
|
|
|
|
try {
|
|
|
- if (dataTransfer) {
|
|
|
- await handleTransfer(dataTransfer)
|
|
|
+ const shapesFromDataTransfer = dataTransfer
|
|
|
+ ? await tryCreateShapesFromDataTransfer(dataTransfer)
|
|
|
+ : null
|
|
|
+ if (shapesFromDataTransfer) {
|
|
|
+ shapesToCreate.push(...shapesFromDataTransfer)
|
|
|
} else {
|
|
|
- for (const item of await navigator.clipboard.read()) {
|
|
|
- let handled = !shiftKey ? await handleHTML(item) : false
|
|
|
- if (!handled) {
|
|
|
- await handleTextPlain(item)
|
|
|
- }
|
|
|
- }
|
|
|
+ // from Clipboard app or Shift copy etc
|
|
|
+ // in this case, we do not have the dataTransfer object
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error(error)
|
|
|
}
|
|
|
|
|
|
- const allShapesToAdd: TLShapeModel[] = [
|
|
|
- // assets to images
|
|
|
- ...imageAssetsToCreate.map((asset, i) => ({
|
|
|
- ...(asset.type === 'video' ? VideoShape : ImageShape).defaultProps,
|
|
|
- // TODO: Should be place near the last edited shape
|
|
|
- point: [point[0] - asset.size[0] / 4 + i * 16, point[1] - asset.size[1] / 4 + i * 16],
|
|
|
- size: Vec.div(asset.size, 2),
|
|
|
- assetId: asset.id,
|
|
|
- opacity: 1,
|
|
|
- })),
|
|
|
- ...shapesToCreate,
|
|
|
- ].map(shape => {
|
|
|
+ console.log(bindingsToCreate)
|
|
|
+
|
|
|
+ const allShapesToAdd: TLShapeModel[] = shapesToCreate.map(shape => {
|
|
|
return {
|
|
|
...shape,
|
|
|
parentId: app.currentPageId,
|