mirror of https://github.com/logseq/logseq
Enhance (Whiteboards): Paste and dnd behavior (also add a placeholder to shape labels) (#8753)
* fix: paste shape * enhance: add label placeholder * fix: don't create portal on ref click * enhance: allow ref dragging * fix: create line binding on drop * enhance: allow creating url based elements on droppull/8768/head
parent
91e89ef2d6
commit
82e5abf9e0
|
@ -511,11 +511,6 @@
|
||||||
(:db/id page-entity)
|
(:db/id page-entity)
|
||||||
:page))
|
:page))
|
||||||
|
|
||||||
(whiteboard-handler/inside-portal? (.-target e))
|
|
||||||
(whiteboard-handler/add-new-block-portal-shape!
|
|
||||||
page-name
|
|
||||||
(whiteboard-handler/closest-shape (.-target e)))
|
|
||||||
|
|
||||||
whiteboard-page?
|
whiteboard-page?
|
||||||
(route-handler/redirect-to-whiteboard! page-name)
|
(route-handler/redirect-to-whiteboard! page-name)
|
||||||
|
|
||||||
|
@ -546,7 +541,9 @@
|
||||||
(str " page-property-key block-property")
|
(str " page-property-key block-property")
|
||||||
untitled? (str " opacity-50"))
|
untitled? (str " opacity-50"))
|
||||||
:data-ref page-name
|
:data-ref page-name
|
||||||
:on-mouse-down (fn [e] (open-page-ref e page-name redirect-page-name page-name-in-block contents-page? whiteboard-page?))
|
:draggable true
|
||||||
|
:on-drag-start (fn [e] (editor-handler/block->data-transfer! page-name e))
|
||||||
|
:on-mouse-up (fn [e] (open-page-ref e page-name redirect-page-name page-name-in-block contents-page? whiteboard-page?))
|
||||||
:on-key-up (fn [e] (when (and e (= (.-key e) "Enter"))
|
:on-key-up (fn [e] (when (and e (= (.-key e) "Enter"))
|
||||||
(open-page-ref e page-name redirect-page-name page-name-in-block contents-page? whiteboard-page?)))}
|
(open-page-ref e page-name redirect-page-name page-name-in-block contents-page? whiteboard-page?)))}
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,8 @@ import { LogseqContext, LogseqContextValue } from '../lib/logseq-context'
|
||||||
|
|
||||||
const isValidURL = (url: string) => {
|
const isValidURL = (url: string) => {
|
||||||
try {
|
try {
|
||||||
new URL(url)
|
const parsedUrl = new URL(url)
|
||||||
return true
|
return parsedUrl.host && ['http:', 'https:'].includes(parsedUrl.protocol)
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -105,17 +105,17 @@ const handleCreatingShapes = async (
|
||||||
const existingAsset = Object.values(app.assets).find(asset => asset.src === url)
|
const existingAsset = Object.values(app.assets).find(asset => asset.src === url)
|
||||||
if (existingAsset) {
|
if (existingAsset) {
|
||||||
return existingAsset as VideoImageAsset
|
return existingAsset as VideoImageAsset
|
||||||
} else {
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
async function createAssetsFromFiles(files: File[]) {
|
async function createAssetsFromFiles(files: File[]) {
|
||||||
const tasks = files
|
const tasks = files
|
||||||
|
@ -272,7 +272,7 @@ const handleCreatingShapes = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tryCreateShapeFromURL(rawText: string) {
|
async function tryCreateShapeFromURL(rawText: string) {
|
||||||
if (isValidURL(rawText) && !(shiftKey || fromDrop)) {
|
if (isValidURL(rawText) && !shiftKey) {
|
||||||
if (YOUTUBE_REGEX.test(rawText)) {
|
if (YOUTUBE_REGEX.test(rawText)) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -415,7 +415,7 @@ const handleCreatingShapes = async (
|
||||||
}
|
}
|
||||||
app.currentPage.updateBindings(Object.fromEntries(bindingsToCreate.map(b => [b.id, b])))
|
app.currentPage.updateBindings(Object.fromEntries(bindingsToCreate.map(b => [b.id, b])))
|
||||||
|
|
||||||
if (app.selectedShapesArray.length === 1 && allShapesToAdd.length === 1 && !fromDrop) {
|
if (app.selectedShapesArray.length === 1 && allShapesToAdd.length === 1 && fromDrop) {
|
||||||
const source = app.selectedShapesArray[0]
|
const source = app.selectedShapesArray[0]
|
||||||
const target = app.getShapeById(allShapesToAdd[0].id!)!
|
const target = app.getShapeById(allShapesToAdd[0].id!)!
|
||||||
app.createNewLineBinding(source, target)
|
app.createNewLineBinding(source, target)
|
||||||
|
|
|
@ -77,7 +77,7 @@ export class LineShape extends TLLineShape<LineShapeProps> {
|
||||||
const labelSize =
|
const labelSize =
|
||||||
label || isEditing
|
label || isEditing
|
||||||
? getTextLabelSize(
|
? getTextLabelSize(
|
||||||
label,
|
label || "Enter text",
|
||||||
{ fontFamily: 'var(--ls-font-family)', fontSize, lineHeight: 1, fontWeight },
|
{ fontFamily: 'var(--ls-font-family)', fontSize, lineHeight: 1, fontWeight },
|
||||||
6
|
6
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { TextAreaUtils } from './TextAreaUtils'
|
||||||
|
|
||||||
const stopPropagation = (e: KeyboardEvent | React.SyntheticEvent<any, Event>) => e.stopPropagation()
|
const stopPropagation = (e: KeyboardEvent | React.SyntheticEvent<any, Event>) => e.stopPropagation()
|
||||||
|
|
||||||
|
const placeholder = "Enter text"
|
||||||
export interface TextLabelProps {
|
export interface TextLabelProps {
|
||||||
font: string
|
font: string
|
||||||
text: string
|
text: string
|
||||||
|
@ -57,11 +58,7 @@ export const TextLabel = React.memo(function TextLabel({
|
||||||
if (!(e.key === 'Meta' || e.metaKey)) {
|
if (!(e.key === 'Meta' || e.metaKey)) {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
} else if (e.key === 'z' && e.metaKey) {
|
} else if (e.key === 'z' && e.metaKey) {
|
||||||
if (e.shiftKey) {
|
document.execCommand(e.shiftKey ? 'redo' : 'undo', false)
|
||||||
document.execCommand('redo', false)
|
|
||||||
} else {
|
|
||||||
document.execCommand('undo', false)
|
|
||||||
}
|
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return
|
return
|
||||||
|
@ -92,8 +89,7 @@ export const TextLabel = React.memo(function TextLabel({
|
||||||
|
|
||||||
const handleFocus = React.useCallback(
|
const handleFocus = React.useCallback(
|
||||||
(e: React.FocusEvent<HTMLTextAreaElement>) => {
|
(e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||||
if (!isEditing) return
|
if (!isEditing || !rIsMounted.current) return
|
||||||
if (!rIsMounted.current) return
|
|
||||||
|
|
||||||
if (document.activeElement === e.currentTarget) {
|
if (document.activeElement === e.currentTarget) {
|
||||||
e.currentTarget.select()
|
e.currentTarget.select()
|
||||||
|
@ -130,7 +126,7 @@ export const TextLabel = React.memo(function TextLabel({
|
||||||
const elm = rInnerWrapper.current
|
const elm = rInnerWrapper.current
|
||||||
if (!elm) return
|
if (!elm) return
|
||||||
const size = getTextLabelSize(
|
const size = getTextLabelSize(
|
||||||
text,
|
text || placeholder,
|
||||||
{ fontFamily: 'var(--ls-font-family)', fontSize, lineHeight: 1, fontWeight },
|
{ fontFamily: 'var(--ls-font-family)', fontSize, lineHeight: 1, fontWeight },
|
||||||
4
|
4
|
||||||
)
|
)
|
||||||
|
@ -172,7 +168,7 @@ export const TextLabel = React.memo(function TextLabel({
|
||||||
autoCorrect="false"
|
autoCorrect="false"
|
||||||
autoSave="false"
|
autoSave="false"
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder=""
|
placeholder={placeholder}
|
||||||
spellCheck="true"
|
spellCheck="true"
|
||||||
wrap="off"
|
wrap="off"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
|
|
|
@ -122,9 +122,8 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
|
||||||
private parseShapesArg<S>(shapes: S[] | string[]) {
|
private parseShapesArg<S>(shapes: S[] | string[]) {
|
||||||
if (typeof shapes[0] === 'string') {
|
if (typeof shapes[0] === 'string') {
|
||||||
return this.shapes.filter(shape => (shapes as string[]).includes(shape.id))
|
return this.shapes.filter(shape => (shapes as string[]).includes(shape.id))
|
||||||
} else {
|
|
||||||
return shapes as S[]
|
|
||||||
}
|
}
|
||||||
|
return shapes as S[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@action removeShapes(...shapes: S[] | string[]) {
|
@action removeShapes(...shapes: S[] | string[]) {
|
||||||
|
|
Loading…
Reference in New Issue