| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801 | 
							- /* eslint-disable @typescript-eslint/no-explicit-any */
 
- import { TLBoxShape, TLBoxShapeProps, TLResizeInfo, validUUID } from '@tldraw/core'
 
- import { HTMLContainer, TLComponentProps, useApp } from '@tldraw/react'
 
- import Vec from '@tldraw/vec'
 
- import { makeObservable, runInAction } from 'mobx'
 
- import { observer } from 'mobx-react-lite'
 
- import * as React from 'react'
 
- import { TablerIcon } from '~components/icons'
 
- import { SwitchInput } from '~components/inputs/SwitchInput'
 
- import { useCameraMovingRef } from '~hooks/useCameraMoving'
 
- import type { Shape } from '~lib'
 
- import { LogseqContext, SearchResult } from '~lib/logseq-context'
 
- import { CustomStyleProps, withClampedStyles } from './style-props'
 
- const HEADER_HEIGHT = 40
 
- export interface LogseqPortalShapeProps extends TLBoxShapeProps, CustomStyleProps {
 
-   type: 'logseq-portal'
 
-   pageId: string // page name or UUID
 
-   blockType?: 'P' | 'B'
 
-   collapsed?: boolean
 
-   compact?: boolean
 
-   collapsedHeight?: number
 
- }
 
- interface LogseqQuickSearchProps {
 
-   onChange: (id: string) => void
 
- }
 
- const LogseqTypeTag = ({
 
-   type,
 
-   active,
 
- }: {
 
-   type: 'B' | 'P' | 'WP' | 'BS' | 'PS'
 
-   active?: boolean
 
- }) => {
 
-   const nameMapping = {
 
-     B: 'block',
 
-     P: 'page',
 
-     WP: 'whiteboard',
 
-     BS: 'block-search',
 
-     PS: 'page-search',
 
-   }
 
-   return (
 
-     <span className="tl-type-tag" data-active={active}>
 
-       <i className={`tie tie-${nameMapping[type]}`} />
 
-     </span>
 
-   )
 
- }
 
- const LogseqPortalShapeHeader = observer(
 
-   ({ type, children }: { type: 'P' | 'B'; children: React.ReactNode }) => {
 
-     return (
 
-       <div className="tl-logseq-portal-header">
 
-         <LogseqTypeTag type={type} />
 
-         {children}
 
-       </div>
 
-     )
 
-   }
 
- )
 
- function escapeRegExp(text: string) {
 
-   return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
 
- }
 
- const highlightedJSX = (input: string, keyword: string) => {
 
-   return (
 
-     <span>
 
-       {input
 
-         .split(new RegExp(`(${escapeRegExp(keyword)})`, 'gi'))
 
-         .map((part, index) => {
 
-           if (index % 2 === 1) {
 
-             return <mark className="tl-highlighted">{part}</mark>
 
-           }
 
-           return part
 
-         })
 
-         .map((frag, idx) => (
 
-           <React.Fragment key={idx}>{frag}</React.Fragment>
 
-         ))}
 
-     </span>
 
-   )
 
- }
 
- const useSearch = (q: string) => {
 
-   const { handlers } = React.useContext(LogseqContext)
 
-   const [results, setResults] = React.useState<SearchResult | null>(null)
 
-   React.useEffect(() => {
 
-     let canceled = false
 
-     const searchHandler = handlers?.search
 
-     if (q.length > 0 && searchHandler) {
 
-       handlers.search(q).then(_results => {
 
-         if (!canceled) {
 
-           setResults(_results)
 
-         }
 
-       })
 
-     } else {
 
-       setResults(null)
 
-     }
 
-     return () => {
 
-       canceled = true
 
-     }
 
-   }, [q, handlers?.search])
 
-   return results
 
- }
 
- export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
 
-   static id = 'logseq-portal'
 
-   static defaultProps: LogseqPortalShapeProps = {
 
-     id: 'logseq-portal',
 
-     type: 'logseq-portal',
 
-     parentId: 'page',
 
-     point: [0, 0],
 
-     size: [400, 50],
 
-     // collapsedHeight is the height before collapsing
 
-     collapsedHeight: 0,
 
-     stroke: 'var(--ls-primary-text-color)',
 
-     fill: 'var(--ls-secondary-background-color)',
 
-     strokeWidth: 2,
 
-     opacity: 1,
 
-     pageId: '',
 
-     collapsed: false,
 
-     compact: false,
 
-   }
 
-   hideRotateHandle = true
 
-   canChangeAspectRatio = true
 
-   canFlip = true
 
-   canEdit = true
 
-   persist: ((replace?: boolean) => void) | null = null
 
-   // For quick add shapes, we want to calculate the page height dynamically
 
-   initialHeightCalculated = true
 
-   getInnerHeight: (() => number) | null = null // will be overridden in the hook
 
-   constructor(props = {} as Partial<LogseqPortalShapeProps>) {
 
-     super(props)
 
-     makeObservable(this)
 
-     if (props.collapsed || props.compact) {
 
-       Object.assign(this.canResize, [true, false])
 
-     }
 
-     if (props.size?.[1] === 0) {
 
-       this.initialHeightCalculated = false
 
-     }
 
-   }
 
-   static isPageOrBlock(id: string): 'P' | 'B' | false {
 
-     const blockRefEg = '((62af02d0-0443-42e8-a284-946c162b0f89))'
 
-     if (id) {
 
-       return /^\(\(.*\)\)$/.test(id) && id.length === blockRefEg.length ? 'B' : 'P'
 
-     }
 
-     return false
 
-   }
 
-   useComponentSize<T extends HTMLElement>(ref: React.RefObject<T> | null, selector = '') {
 
-     const [size, setSize] = React.useState<[number, number]>([0, 0])
 
-     const app = useApp<Shape>()
 
-     React.useEffect(() => {
 
-       if (ref?.current) {
 
-         const el = selector ? ref.current.querySelector<HTMLElement>(selector) : ref.current
 
-         if (el) {
 
-           const updateSize = () => {
 
-             const { width, height } = el.getBoundingClientRect()
 
-             const bound = Vec.div([width, height], app.viewport.camera.zoom) as [number, number]
 
-             setSize(bound)
 
-             return bound
 
-           }
 
-           updateSize()
 
-           // Hacky, I know 🤨
 
-           this.getInnerHeight = () => updateSize()[1] + 2 // 2 is a hack to compensate for the border
 
-           const resizeObserver = new ResizeObserver(() => {
 
-             updateSize()
 
-           })
 
-           resizeObserver.observe(el)
 
-           return () => {
 
-             resizeObserver.disconnect()
 
-           }
 
-         }
 
-       }
 
-       return () => {}
 
-     }, [ref, selector])
 
-     return size
 
-   }
 
-   ReactContextBar = observer(() => {
 
-     const app = useApp<Shape>()
 
-     return (
 
-       <>
 
-         {this.props.blockType !== 'B' && (
 
-           <SwitchInput
 
-             label="Collapsed"
 
-             checked={this.props.collapsed}
 
-             onCheckedChange={collapsing => {
 
-               runInAction(() => {
 
-                 const originalHeight = this.props.size[1]
 
-                 this.canResize[1] = !collapsing
 
-                 this.update({
 
-                   collapsed: collapsing,
 
-                   size: [
 
-                     this.props.size[0],
 
-                     collapsing ? HEADER_HEIGHT : this.props.collapsedHeight,
 
-                   ],
 
-                   collapsedHeight: collapsing ? originalHeight : this.props.collapsedHeight,
 
-                 })
 
-                 app.persist()
 
-               })
 
-             }}
 
-           />
 
-         )}
 
-         {this.props.blockType === 'B' && (
 
-           <SwitchInput
 
-             label="Compact"
 
-             checked={this.props.compact}
 
-             onCheckedChange={compact => {
 
-               runInAction(() => {
 
-                 this.update({ compact })
 
-                 this.canResize[1] = !compact
 
-                 if (!compact) {
 
-                   // this will also persist the state, so we can skip persist call
 
-                   this.autoResizeHeight()
 
-                 } else {
 
-                   app.persist()
 
-                 }
 
-               })
 
-             }}
 
-           />
 
-         )}
 
-       </>
 
-     )
 
-   })
 
-   shouldAutoResizeHeight() {
 
-     return this.props.blockType === 'B' && this.props.compact
 
-   }
 
-   getHeaderHeight() {
 
-     return this.props.compact ? 0 : HEADER_HEIGHT
 
-   }
 
-   getAutoResizeHeight() {
 
-     if (this.getInnerHeight) {
 
-       return this.getHeaderHeight() + this.getInnerHeight()
 
-     }
 
-     return null
 
-   }
 
-   autoResizeHeight(replace: boolean = false) {
 
-     setTimeout(() => {
 
-       const height = this.getAutoResizeHeight()
 
-       if (height !== null) {
 
-         this.update({
 
-           size: [this.props.size[0], height],
 
-         })
 
-         this.persist?.(replace)
 
-         this.initialHeightCalculated = true
 
-       }
 
-     })
 
-   }
 
-   onResize = (initialProps: any, info: TLResizeInfo): this => {
 
-     const {
 
-       bounds,
 
-       rotation,
 
-       scale: [scaleX, scaleY],
 
-     } = info
 
-     const nextScale = [...this.scale]
 
-     if (scaleX < 0) nextScale[0] *= -1
 
-     if (scaleY < 0) nextScale[1] *= -1
 
-     let height = bounds.height
 
-     if (this.shouldAutoResizeHeight()) {
 
-       height = this.getAutoResizeHeight() ?? height
 
-     }
 
-     return this.update({
 
-       point: [bounds.minX, bounds.minY],
 
-       size: [Math.max(1, bounds.width), Math.max(1, height)],
 
-       scale: nextScale,
 
-       rotation,
 
-     })
 
-   }
 
-   LogseqQuickSearch = observer(({ onChange }: LogseqQuickSearchProps) => {
 
-     const [q, setQ] = React.useState('')
 
-     const rInput = React.useRef<HTMLInputElement>(null)
 
-     const { handlers, renderers } = React.useContext(LogseqContext)
 
-     const app = useApp<Shape>()
 
-     const finishCreating = React.useCallback((id: string) => {
 
-       onChange(id)
 
-       rInput.current?.blur()
 
-     }, [])
 
-     const onAddBlock = React.useCallback((content: string) => {
 
-       const uuid = handlers?.addNewBlock(content)
 
-       if (uuid) {
 
-         finishCreating(uuid)
 
-         // wait until the editor is mounted
 
-         setTimeout(() => {
 
-           // @ts-expect-error ???
 
-           const logseqApi = window.logseq?.api as any
 
-           if (logseqApi) {
 
-             app.setEditingShape(this)
 
-             logseqApi.edit_block(uuid)
 
-           }
 
-         })
 
-       }
 
-     }, [])
 
-     const optionsWrapperRef = React.useRef<HTMLDivElement>(null)
 
-     const [focusedOptionIdx, setFocusedOptionIdx] = React.useState<number>(0)
 
-     const searchResult = useSearch(q)
 
-     const [prefixIcon, setPrefixIcon] = React.useState<string>('circle-plus')
 
-     const [searchFilter, setSearchFilter] = React.useState<'B' | 'P' | null>(null)
 
-     React.useEffect(() => {
 
-       // autofocus seems not to be working
 
-       setTimeout(() => {
 
-         rInput.current?.focus()
 
-       })
 
-     }, [searchFilter])
 
-     type Option = {
 
-       actionIcon: 'search' | 'circle-plus'
 
-       onChosen: () => void
 
-       element: React.ReactNode
 
-     }
 
-     const options: Option[] = React.useMemo(() => {
 
-       const options: Option[] = []
 
-       const Breadcrumb = renderers?.Breadcrumb
 
-       if (!Breadcrumb || !handlers) {
 
-         return []
 
-       }
 
-       // New block option
 
-       options.push({
 
-         actionIcon: 'circle-plus',
 
-         onChosen: () => {
 
-           onAddBlock(q)
 
-         },
 
-         element: (
 
-           <div className="tl-quick-search-option-row">
 
-             <LogseqTypeTag active type="B" />
 
-             {q.length > 0 ? (
 
-               <>
 
-                 <strong>New whiteboard block:</strong>
 
-                 {q}
 
-               </>
 
-             ) : (
 
-               <strong>New whiteboard block</strong>
 
-             )}
 
-           </div>
 
-         ),
 
-       })
 
-       // New page option
 
-       if (searchResult?.pages?.length === 0 && q) {
 
-         options.push({
 
-           actionIcon: 'circle-plus',
 
-           onChosen: () => {
 
-             finishCreating(q)
 
-           },
 
-           element: (
 
-             <div className="tl-quick-search-option-row">
 
-               <LogseqTypeTag active type="P" />
 
-               <strong>New page:</strong>
 
-               {q}
 
-             </div>
 
-           ),
 
-         })
 
-       }
 
-       // search filters
 
-       if (q.length === 0 && searchFilter === null) {
 
-         options.push(
 
-           {
 
-             actionIcon: 'search',
 
-             onChosen: () => {
 
-               setSearchFilter('B')
 
-             },
 
-             element: (
 
-               <div className="tl-quick-search-option-row">
 
-                 <LogseqTypeTag type="BS" />
 
-                 Search only blocks
 
-               </div>
 
-             ),
 
-           },
 
-           {
 
-             actionIcon: 'search',
 
-             onChosen: () => {
 
-               setSearchFilter('P')
 
-             },
 
-             element: (
 
-               <div className="tl-quick-search-option-row">
 
-                 <LogseqTypeTag type="PS" />
 
-                 Search only pages
 
-               </div>
 
-             ),
 
-           }
 
-         )
 
-       }
 
-       // Page results
 
-       if ((!searchFilter || searchFilter === 'P') && searchResult && searchResult.pages) {
 
-         options.push(
 
-           ...searchResult.pages.map(page => {
 
-             return {
 
-               actionIcon: 'search' as 'search',
 
-               onChosen: () => {
 
-                 finishCreating(page)
 
-               },
 
-               element: (
 
-                 <div className="tl-quick-search-option-row">
 
-                   <LogseqTypeTag type={handlers.isWhiteboardPage(page) ? 'WP' : 'P'} />
 
-                   {highlightedJSX(page, q)}
 
-                 </div>
 
-               ),
 
-             }
 
-           })
 
-         )
 
-       }
 
-       // Block results
 
-       if ((!searchFilter || searchFilter === 'B') && searchResult && searchResult.blocks) {
 
-         options.push(
 
-           ...searchResult.blocks
 
-             .filter(block => block.content && block.uuid)
 
-             .map(({ content, uuid }) => {
 
-               const block = handlers.queryBlockByUUID(uuid)
 
-               return {
 
-                 actionIcon: 'search' as 'search',
 
-                 onChosen: () => {
 
-                   if (block) {
 
-                     finishCreating(uuid)
 
-                   }
 
-                 },
 
-                 element: block ? (
 
-                   <>
 
-                     <div className="tl-quick-search-option-row">
 
-                       <LogseqTypeTag type="B" />
 
-                       <div className="tl-quick-search-option-breadcrumb">
 
-                         <Breadcrumb blockId={uuid} />
 
-                       </div>
 
-                     </div>
 
-                     <div className="tl-quick-search-option-row">
 
-                       <div className="tl-quick-search-option-placeholder" />
 
-                       {highlightedJSX(content, q)}
 
-                     </div>
 
-                   </>
 
-                 ) : (
 
-                   <div className="tl-quick-search-option-row">
 
-                     Cache is outdated. Please click the 'Re-index' button in the graph's dropdown
 
-                     menu.
 
-                   </div>
 
-                 ),
 
-               }
 
-             })
 
-         )
 
-       }
 
-       return options
 
-     }, [q, searchFilter, searchResult, renderers?.Breadcrumb, handlers])
 
-     React.useEffect(() => {
 
-       const keydownListener = (e: KeyboardEvent) => {
 
-         let newIndex = focusedOptionIdx
 
-         if (e.key === 'ArrowDown') {
 
-           newIndex = Math.min(options.length - 1, focusedOptionIdx + 1)
 
-         } else if (e.key === 'ArrowUp') {
 
-           newIndex = Math.max(0, focusedOptionIdx - 1)
 
-         } else if (e.key === 'Enter') {
 
-           options[focusedOptionIdx]?.onChosen()
 
-           e.stopPropagation()
 
-           e.preventDefault()
 
-         } else if (e.key === 'Backspace' && q.length === 0) {
 
-           setSearchFilter(null)
 
-         }
 
-         if (newIndex !== focusedOptionIdx) {
 
-           const option = options[newIndex]
 
-           setFocusedOptionIdx(newIndex)
 
-           setPrefixIcon(option.actionIcon)
 
-           e.stopPropagation()
 
-           e.preventDefault()
 
-           const optionElement = optionsWrapperRef.current?.querySelector(
 
-             '.tl-quick-search-option:nth-child(' + (newIndex + 1) + ')'
 
-           )
 
-           if (optionElement) {
 
-             // @ts-expect-error we are using scrollIntoViewIfNeeded, which is not in standards
 
-             optionElement?.scrollIntoViewIfNeeded(false)
 
-           }
 
-         }
 
-       }
 
-       document.addEventListener('keydown', keydownListener, true)
 
-       return () => {
 
-         document.removeEventListener('keydown', keydownListener, true)
 
-       }
 
-     }, [options, focusedOptionIdx, q])
 
-     return (
 
-       <div className="tl-quick-search">
 
-         <div className="tl-quick-search-indicator">
 
-           <TablerIcon name={prefixIcon} className="tl-quick-search-icon" />
 
-         </div>
 
-         <div className="tl-quick-search-input-container">
 
-           {searchFilter && (
 
-             <div className="tl-quick-search-input-filter">
 
-               <LogseqTypeTag type={searchFilter} />
 
-               {searchFilter === 'B' ? 'Search blocks' : 'Search pages'}
 
-               <div
 
-                 className="tl-quick-search-input-filter-remove"
 
-                 onClick={() => setSearchFilter(null)}
 
-               >
 
-                 <TablerIcon name="x" />
 
-               </div>
 
-             </div>
 
-           )}
 
-           <div className="tl-quick-search-input-sizer" data-value={q}>
 
-             <div className="tl-quick-search-input-hidden">{q}</div>
 
-             <input
 
-               ref={rInput}
 
-               type="text"
 
-               value={q}
 
-               placeholder="Create or search your graph..."
 
-               onChange={q => setQ(q.target.value)}
 
-               onKeyDown={e => {
 
-                 if (e.key === 'Enter') {
 
-                   finishCreating(q)
 
-                 }
 
-               }}
 
-               className="tl-quick-search-input"
 
-             />
 
-           </div>
 
-         </div>
 
-         <div className="tl-quick-search-options" ref={optionsWrapperRef}>
 
-           {options.map(({ actionIcon, onChosen, element }, index) => {
 
-             return (
 
-               <div
 
-                 key={index}
 
-                 data-focused={index === focusedOptionIdx}
 
-                 className="tl-quick-search-option"
 
-                 tabIndex={0}
 
-                 onMouseEnter={() => {
 
-                   setPrefixIcon(actionIcon)
 
-                   setFocusedOptionIdx(index)
 
-                 }}
 
-                 onClick={onChosen}
 
-               >
 
-                 {element}
 
-               </div>
 
-             )
 
-           })}
 
-         </div>
 
-       </div>
 
-     )
 
-   })
 
-   PortalComponent = observer(({}: TLComponentProps) => {
 
-     const {
 
-       props: { pageId },
 
-     } = this
 
-     const { renderers } = React.useContext(LogseqContext)
 
-     const app = useApp<Shape>()
 
-     const cpRefContainer = React.useRef<HTMLDivElement>(null)
 
-     const [, innerHeight] = this.useComponentSize(
 
-       cpRefContainer,
 
-       this.props.compact
 
-         ? '.tl-logseq-cp-container > .single-block'
 
-         : '.tl-logseq-cp-container > .page'
 
-     )
 
-     if (!renderers?.Page) {
 
-       return null // not being correctly configured
 
-     }
 
-     const { Page, Block } = renderers
 
-     React.useEffect(() => {
 
-       if (this.shouldAutoResizeHeight()) {
 
-         const newHeight = innerHeight + this.getHeaderHeight() + 2
 
-         if (innerHeight && newHeight !== this.props.size[1]) {
 
-           this.update({
 
-             size: [this.props.size[0], newHeight],
 
-           })
 
-           app.persist(true)
 
-         }
 
-       }
 
-     }, [innerHeight, this.props.compact])
 
-     React.useEffect(() => {
 
-       if (!this.initialHeightCalculated) {
 
-         this.autoResizeHeight(true)
 
-       }
 
-     }, [this.initialHeightCalculated])
 
-     return (
 
-       <div
 
-         ref={cpRefContainer}
 
-         className="tl-logseq-cp-container"
 
-         style={{
 
-           overflow: this.props.compact ? 'visible' : 'auto',
 
-         }}
 
-       >
 
-         {this.props.blockType === 'B' && this.props.compact ? (
 
-           <Block blockId={pageId} />
 
-         ) : (
 
-           <Page pageName={pageId} />
 
-         )}
 
-       </div>
 
-     )
 
-   })
 
-   ReactComponent = observer((componentProps: TLComponentProps) => {
 
-     const { events, isErasing, isEditing, isBinding } = componentProps
 
-     const {
 
-       props: { opacity, pageId, stroke, fill },
 
-     } = this
 
-     const app = useApp<Shape>()
 
-     const { renderers, handlers } = React.useContext(LogseqContext)
 
-     this.persist = () => app.persist()
 
-     const isMoving = useCameraMovingRef()
 
-     const isSelected = app.selectedIds.has(this.id)
 
-     const isCreating = app.isIn('logseq-portal.creating') && !pageId
 
-     const tlEventsEnabled =
 
-       (isMoving || (isSelected && !isEditing) || app.selectedTool.id !== 'select') && !isCreating
 
-     const stop = React.useCallback(
 
-       e => {
 
-         if (!tlEventsEnabled) {
 
-           // TODO: pinching inside Logseq Shape issue
 
-           e.stopPropagation()
 
-         }
 
-       },
 
-       [tlEventsEnabled]
 
-     )
 
-     // It is a bit weird to update shapes here. Is there a better place?
 
-     React.useEffect(() => {
 
-       if (this.props.collapsed && isEditing) {
 
-         // Should temporarily disable collapsing
 
-         this.update({
 
-           size: [this.props.size[0], this.props.collapsedHeight],
 
-         })
 
-         return () => {
 
-           this.update({
 
-             size: [this.props.size[0], HEADER_HEIGHT],
 
-           })
 
-         }
 
-       }
 
-       return () => {
 
-         // no-ops
 
-       }
 
-     }, [isEditing, this.props.collapsed])
 
-     const onPageNameChanged = React.useCallback((id: string) => {
 
-       this.initialHeightCalculated = false
 
-       const blockType = validUUID(id) ? 'B' : 'P'
 
-       this.update({
 
-         pageId: id,
 
-         size: [400, 320],
 
-         blockType: blockType,
 
-         compact: blockType === 'B',
 
-       })
 
-       app.selectTool('select')
 
-       app.history.resume()
 
-       app.history.persist()
 
-     }, [])
 
-     const PortalComponent = this.PortalComponent
 
-     const LogseqQuickSearch = this.LogseqQuickSearch
 
-     const blockContent = React.useMemo(() => {
 
-       if (pageId && this.props.blockType === 'B') {
 
-         return handlers?.queryBlockByUUID(pageId)?.content
 
-       }
 
-     }, [handlers?.queryBlockByUUID, pageId])
 
-     const targetNotFound = this.props.blockType === 'B' && typeof blockContent !== 'string'
 
-     const showingPortal = (!this.props.collapsed || isEditing) && !targetNotFound
 
-     if (!renderers?.Page) {
 
-       return null // not being correctly configured
 
-     }
 
-     const { Breadcrumb, PageNameLink } = renderers
 
-     return (
 
-       <HTMLContainer
 
-         style={{
 
-           pointerEvents: 'all',
 
-           opacity: isErasing ? 0.2 : opacity,
 
-         }}
 
-         {...events}
 
-       >
 
-         <div
 
-           onWheelCapture={stop}
 
-           onPointerDown={stop}
 
-           onPointerUp={stop}
 
-           style={{
 
-             width: '100%',
 
-             height: '100%',
 
-             pointerEvents: isEditing ? 'all' : 'none',
 
-           }}
 
-         >
 
-           {isCreating ? (
 
-             <LogseqQuickSearch onChange={onPageNameChanged} />
 
-           ) : (
 
-             <div
 
-               className="tl-logseq-portal-container"
 
-               style={{
 
-                 background: this.props.compact ? 'transparent' : fill,
 
-                 boxShadow: isBinding
 
-                   ? '0px 0px 0 var(--tl-binding-distance) var(--tl-binding)'
 
-                   : 'none',
 
-                 color: stroke,
 
-                 // @ts-expect-error ???
 
-                 '--ls-primary-background-color': !fill?.startsWith('var') ? fill : undefined,
 
-                 '--ls-primary-text-color': !stroke?.startsWith('var') ? stroke : undefined,
 
-                 '--ls-title-text-color': !stroke?.startsWith('var') ? stroke : undefined,
 
-               }}
 
-             >
 
-               {!this.props.compact && !targetNotFound && (
 
-                 <LogseqPortalShapeHeader type={this.props.blockType ?? 'P'}>
 
-                   {this.props.blockType === 'P' ? (
 
-                     <PageNameLink pageName={pageId} />
 
-                   ) : (
 
-                     <Breadcrumb blockId={pageId} />
 
-                   )}
 
-                 </LogseqPortalShapeHeader>
 
-               )}
 
-               {targetNotFound && <div className="tl-target-not-found">Target not found</div>}
 
-               {showingPortal && <PortalComponent {...componentProps} />}
 
-             </div>
 
-           )}
 
-         </div>
 
-       </HTMLContainer>
 
-     )
 
-   })
 
-   ReactIndicator = observer(() => {
 
-     const bounds = this.getBounds()
 
-     return <rect width={bounds.width} height={bounds.height} fill="transparent" />
 
-   })
 
-   validateProps = (props: Partial<LogseqPortalShapeProps>) => {
 
-     if (props.size !== undefined) {
 
-       props.size[0] = Math.max(props.size[0], 240)
 
-       props.size[1] = Math.max(props.size[1], HEADER_HEIGHT)
 
-     }
 
-     return withClampedStyles(props)
 
-   }
 
-   getShapeSVGJsx({ preview }: any) {
 
-     // Do not need to consider the original point here
 
-     const bounds = this.getBounds()
 
-     return (
 
-       <>
 
-         <rect
 
-           stroke={this.props.stroke}
 
-           strokeWidth={this.props.strokeWidth ?? 2}
 
-           fill="#aaa"
 
-           width={bounds.width}
 
-           height={HEADER_HEIGHT}
 
-         />
 
-         <rect
 
-           y={HEADER_HEIGHT}
 
-           fill={this.props.fill}
 
-           stroke={this.props.stroke}
 
-           strokeWidth={this.props.strokeWidth ?? 2}
 
-           fillOpacity={this.props.opacity ?? 0.2}
 
-           width={bounds.width}
 
-           height={bounds.height - HEADER_HEIGHT}
 
-         />
 
-         <text
 
-           style={{
 
-             transformOrigin: 'top left',
 
-           }}
 
-           transform={`translate(${bounds.width / 2}, ${10 + bounds.height / 2})`}
 
-           textAnchor="middle"
 
-           fontFamily="var(--ls-font-family)"
 
-           fontSize="32"
 
-           fill={this.props.stroke}
 
-           stroke={this.props.stroke}
 
-         >
 
-           {this.props.blockType === 'P' ? this.props.pageId : ''}
 
-         </text>
 
-       </>
 
-     )
 
-   }
 
- }
 
 
  |