فهرست منبع

feat: showing breadcrumb for page block shape

Peng Xiao 3 سال پیش
والد
کامیت
5542d27680

+ 2 - 2
shadow-cljs.edn

@@ -32,7 +32,7 @@
         :release          {:asset-path "https://asset.logseq.com/static/js"}
         :compiler-options {:infer-externs      :auto
                            :output-feature-set :es-next-in
-                           :source-map         true
+                           :source-map-inline  true
                            :externs            ["datascript/externs.js"
                                                 "externs.js"]
                            :warnings           {:fn-deprecated false
@@ -57,7 +57,7 @@
              :devtools         {:before-load electron.core/stop
                                 :after-load  electron.core/start}
              :compiler-options {:infer-externs                      :auto
-                                :source-map                         true
+                                :source-map-inline  true
                                 :source-map-include-sources-content true
                                 :source-map-detail-level            :all
 

+ 1 - 0
src/main/frontend/components/block.cljs

@@ -43,6 +43,7 @@
             [frontend.handler.repeated :as repeated]
             [frontend.handler.route :as route-handler]
             [frontend.handler.ui :as ui-handler]
+            [frontend.handler.whiteboard :as whiteboard-handler]
             [frontend.mobile.util :as mobile-util]
             [frontend.modules.outliner.tree :as tree]
             [frontend.search :as search]

+ 2 - 2
src/main/frontend/components/page.cljs

@@ -319,8 +319,8 @@
   (rum/local false ::all-collapsed?)
   (rum/local false ::control-show?)
   [state {:keys [repo page-name] :as option}]
+           (println option)
   (when-let [path-page-name (or page-name
-                                (gobj/get option "pageId") ;; FIXME: tldraw-logseq hack
                                 (get-page-name state)
                                 (state/get-current-page))]
     (let [current-repo (state/sub :git/current-repo)
@@ -384,7 +384,7 @@
                 (plugins/hook-ui-slot :page-head-actions-slotted nil)
                 (plugins/hook-ui-items :pagebar))])])
         [:div
-         (when (and block? (not sidebar?))
+         (when (and block? (not sidebar?) (not whiteboard?))
            (let [config {:id "block-parent"
                          :block? true}]
              [:div.mb-4

+ 11 - 2
src/main/frontend/extensions/tldraw.cljs

@@ -1,6 +1,7 @@
 (ns frontend.extensions.tldraw
   (:require ["/tldraw-logseq" :as TldrawLogseq]
-            [frontend.components.page :refer [page]]
+            [frontend.components.page :as page]
+            [frontend.components.block :as block]
             [frontend.handler.whiteboard :refer [page-name->tldr
                                                  transact-tldr!]]
             [frontend.rum :as r]
@@ -14,6 +15,14 @@
 
 (def generate-preview (gobj/get TldrawLogseq "generateJSXFromApp"))
 
+(rum/defc page
+  [props]
+  (page/page {:page-name (gobj/get props "pageName")}))
+
+(rum/defc breadcrumb
+  [props]
+  (block/breadcrumb {} (state/get-current-repo) (uuid (gobj/get props "blockId")) nil))
+
 (rum/defcs tldraw-app < rum/reactive
   (rum/local false ::view-mode?)
   [state name block-id]
@@ -25,7 +34,7 @@
         ;; wheel -> overscroll may cause browser navigation
         :on-wheel util/stop-propagation}
 
-       (tldraw {:PageComponent page
+       (tldraw {:renderers {:Page page :Breadcrumb breadcrumb}
                 :searchHandler (comp clj->js vec search/page-search)
                 :onPersist (fn [app]
                              (let [document (gobj/get app "serialized")]

+ 17 - 16
tldraw/apps/tldraw-logseq/src/app.tsx

@@ -6,7 +6,7 @@ import {
   AppProvider,
   TLReactCallbacks,
   TLReactComponents,
-  TLReactToolConstructor,
+  TLReactToolConstructor
 } from '@tldraw/react'
 import * as React from 'react'
 import { AppUI } from '~components/AppUI'
@@ -17,17 +17,12 @@ import { useQuickAdd } from '~hooks/useQuickAdd'
 import { LogseqContext } from '~lib/logseq-context'
 import { Shape, shapes } from '~lib/shapes'
 import {
-  BoxTool,
-  DotTool,
-  EllipseTool,
   HighlighterTool,
   LineTool,
   LogseqPortalTool,
   NuEraseTool,
-  PencilTool,
-  PolygonTool,
-  TextTool,
-  YouTubeTool,
+  PencilTool, TextTool,
+  YouTubeTool
 } from '~lib/tools'
 
 const components: TLReactComponents<Shape> = {
@@ -49,25 +44,31 @@ const tools: TLReactToolConstructor<Shape>[] = [
 ]
 
 interface LogseqTldrawProps {
-  PageComponent: any
+  renderers: {
+    Page: React.FC
+    Breadcrumb: React.FC
+  }
   searchHandler: (query: string) => string[]
   model?: TLDocumentModel<Shape>
   onMount?: TLReactCallbacks<Shape>['onMount']
   onPersist?: TLReactCallbacks<Shape>['onPersist']
 }
 
-export const App = function App({
-  searchHandler,
-  PageComponent,
-  ...props
-}: LogseqTldrawProps): JSX.Element {
+export const App = function App({ searchHandler, ...props }: LogseqTldrawProps): JSX.Element {
   const onFileDrop = useFileDrop()
   const onPaste = usePaste()
   const onQuickAdd = useQuickAdd()
 
-  const Page = React.useMemo(() => React.memo(PageComponent), [])
+  const renderers: any = React.useMemo(() => {
+    return Object.fromEntries(
+      Object.entries(props.renderers).map(([key, comp]) => {
+        return [key, React.memo(comp)]
+      })
+    )
+  }, [])
+
   return (
-    <LogseqContext.Provider value={{ Page, search: searchHandler }}>
+    <LogseqContext.Provider value={{ renderers, search: searchHandler }}>
       <AppProvider
         Shapes={shapes}
         Tools={tools}

+ 1 - 1
tldraw/apps/tldraw-logseq/src/components/ZoomMenu/ZoomMenu.tsx

@@ -12,7 +12,7 @@ export const ZoomMenu = observer(function ZoomMenu(): JSX.Element {
     <div>
       <DropdownMenuPrimitive.Root>
         <DropdownMenuPrimitive.Trigger>
-          <button>{(app.viewport.camera.zoom * 100).toFixed(0) + '%'} </button>
+          {(app.viewport.camera.zoom * 100).toFixed(0) + '%'}
         </DropdownMenuPrimitive.Trigger>
         <DropdownMenuPrimitive.Content
           className="dropdown-menu-button"

+ 14 - 11
tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts

@@ -6,7 +6,9 @@ import {
   TLBinding,
   TLShapeModel,
   uniqueId,
+  validUUID,
 } from '@tldraw/core'
+import { parse as uuidParse, NIL as NIL_UUID } from 'uuid'
 import type { TLReactCallbacks } from '@tldraw/react'
 import * as React from 'react'
 import { LogseqPortalShape, Shape } from '~lib'
@@ -108,18 +110,19 @@ export function usePaste() {
             return true
           }
         } catch {
-          const blockRefEg = '((62af02d0-0443-42e8-a284-946c162b0f89))'
-          if (/^\(\(.*\)\)$/.test(rawText) && rawText.length === blockRefEg.length) {
+          if (/^\(\(.*\)\)$/.test(rawText) && rawText.length === NIL_UUID.length + 4) {
             const blockRef = rawText.slice(2, -2)
-            shapesToCreate.push({
-              ...LogseqPortalShape.defaultProps,
-              id: uniqueId(),
-              parentId: app.currentPageId,
-              point: [point[0], point[1]],
-              size: [600, 400],
-              pageId: blockRef,
-              blockType: 'B',
-            })
+            if (validUUID(blockRef)) {
+              shapesToCreate.push({
+                ...LogseqPortalShape.defaultProps,
+                id: uniqueId(),
+                parentId: app.currentPageId,
+                point: [point[0], point[1]],
+                size: [600, 400],
+                pageId: blockRef,
+                blockType: 'B',
+              })
+            }
           }
         }
       }

+ 8 - 1
tldraw/apps/tldraw-logseq/src/lib/logseq-context.ts

@@ -1,7 +1,14 @@
 import React from 'react'
 export const LogseqContext = React.createContext<
   Partial<{
-    Page: React.FC<{ pageId: string }>
+    renderers: {
+      Page: React.FC<{
+        pageName: string
+      }>
+      Breadcrumb: React.FC<{
+        blockId: string
+      }>
+    }
     search: (query: string) => string[]
   }>
 >({})

+ 12 - 8
tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx

@@ -1,6 +1,6 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 import { MagnifyingGlassIcon } from '@radix-ui/react-icons'
-import { TLBoxShape, TLBoxShapeProps } from '@tldraw/core'
+import { TLBoxShape, TLBoxShapeProps, validUUID } from '@tldraw/core'
 import { HTMLContainer, TLComponentProps, TLContextBarProps, useApp } from '@tldraw/react'
 import { makeObservable } from 'mobx'
 import { observer } from 'mobx-react-lite'
@@ -81,11 +81,11 @@ const LogseqQuickSearch = observer(({ onChange }: LogseqQuickSearchProps) => {
 })
 
 const LogseqPortalShapeHeader = observer(
-  ({ type, pageId }: { type: 'P' | 'B'; pageId: string }) => {
+  ({ type, children }: { type: 'P' | 'B'; children: React.ReactNode }) => {
     return (
       <div className="tl-logseq-portal-header">
         <span className="type-tag">{type}</span>
-        {pageId}
+        {children}
       </div>
     )
   }
@@ -180,7 +180,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
 
     const app = useApp<Shape>()
     const isMoving = useCameraMovingRef()
-    const { Page } = React.useContext(LogseqContext)
+    const { renderers } = React.useContext(LogseqContext)
     const isSelected = app.selectedIds.has(this.id)
     const isCreating = app.isIn('logseq-portal.creating') && !pageId
     const tlEventsEnabled =
@@ -217,17 +217,19 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
       this.update({
         pageId: id,
         size: [600, 320],
-        blockType: 'P',
+        blockType: validUUID(id) ? 'B' : 'P',
       })
       app.selectTool('select')
       app.history.resume()
       app.history.persist()
     }, [])
 
-    if (!Page) {
+    if (!renderers?.Page || !renderers?.Breadcrumb) {
       return null // not being correctly configured
     }
 
+    const { Page, Breadcrumb } = renderers
+
     return (
       <HTMLContainer
         style={{
@@ -263,7 +265,9 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
                 '--ls-title-text-color': stroke,
               }}
             >
-              <LogseqPortalShapeHeader type={this.props.blockType ?? 'P'} pageId={pageId} />
+              <LogseqPortalShapeHeader type={this.props.blockType ?? 'P'}>
+                {this.props.blockType === 'P' ? pageId : <Breadcrumb blockId={pageId} />}
+              </LogseqPortalShapeHeader>
               {(!this.props.collapsed || isEditing) && (
                 <div
                   style={{
@@ -282,7 +286,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
                       cursor: 'default',
                     }}
                   >
-                    <Page pageId={pageId} />
+                    <Page pageName={pageId} />
                   </div>
                 </div>
               )}

+ 3 - 1
tldraw/apps/tldraw-logseq/src/styles.css

@@ -584,6 +584,9 @@
   padding: 0 1rem;
   align-items: center;
   gap: 0.5em;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
 }
 
 .logseq-tldraw .tl-logseq-portal-header .type-tag {
@@ -599,7 +602,6 @@
   border-radius: 4px;
   order: 0;
   flex-grow: 0;
-  transform: translateY(-1px);
 }
 
 html[data-theme='light'] .logseq-tldraw .tl-logseq-portal-header {

+ 39 - 1
tldraw/demo/src/App.jsx

@@ -23,6 +23,7 @@ const documentModel = onLoad() ?? {
       shapes: [
         {
           scale: [1, 1],
+          blockType: 'P',
           id: 'p6bv7EfoQPIF1eZB1RRO6',
           type: 'logseq-portal',
           parentId: 'page1',
@@ -39,6 +40,29 @@ const documentModel = onLoad() ?? {
       bindings: {},
       nonce: 2,
     },
+    {
+      id: 'page1',
+      name: 'Page',
+      shapes: [
+        {
+          scale: [1, 1],
+          blockType: 'B',
+          id: 'p6bv7EfoQPIF1eZB1RRO6',
+          type: 'logseq-portal',
+          parentId: 'page1',
+          point: [369.109375, 170.5546875],
+          size: [390.671875, 295.3671875],
+          stroke: '#000000',
+          fill: '#ffffff',
+          strokeWidth: 2,
+          opacity: 1,
+          pageId: '',
+          nonce: 1,
+        },
+      ],
+      bindings: {},
+      nonce: 2,
+    },
   ],
 }
 
@@ -56,6 +80,17 @@ const Page = props => {
   )
 }
 
+const Breadcrumb = props => {
+  const [value, setValue] = React.useState(JSON.stringify(props))
+  return (
+    <input
+      className="whitespace-pre w-full h-full font-mono"
+      value={value}
+      onChange={e => setValue(e.target.value)}
+    />
+  )
+}
+
 const ThemeSwitcher = ({ theme, setTheme }) => {
   const [anchor, setAnchor] = React.useState(null)
   React.useEffect(() => {
@@ -104,7 +139,10 @@ export default function App() {
     <div className={`h-screen w-screen`}>
       <ThemeSwitcher theme={theme} setTheme={setTheme} />
       <TldrawApp
-        PageComponent={Page}
+        renderers={{
+          Page,
+          Breadcrumb,
+        }}
         searchHandler={q => (q ? list : [])}
         model={documentModel}
         onPersist={onPersist}

+ 3 - 0
tldraw/package.json

@@ -59,5 +59,8 @@
     "tslib": "^2.4.0",
     "typedoc": "^0.22.17",
     "typescript": "^4.7.3"
+  },
+  "dependencies": {
+    "@types/uuid": "^8.3.4"
   }
 }

+ 9 - 1
tldraw/packages/core/src/utils/index.ts

@@ -1,4 +1,3 @@
-// @ts-expect-error no types for uuid
 import * as uuid from 'uuid'
 export * from './BoundsUtils'
 export * from './PointUtils'
@@ -13,6 +12,15 @@ export function uniqueId() {
   return uuid.v1()
 }
 
+export function validUUID(input: string) {
+  try {
+    uuid.parse(input)
+    return true
+  } catch {
+    return false
+  }
+}
+
 // via https://github.com/bameyrick/throttle-typescript
 export function throttle<T extends (...args: any) => any>(
   func: T,

+ 5 - 0
tldraw/yarn.lock

@@ -2537,6 +2537,11 @@
   resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
   integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
 
+"@types/uuid@^8.3.4":
+  version "8.3.4"
+  resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
+  integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
+
 "@types/which@^2.0.1":
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/@types/which/-/which-2.0.1.tgz#27ecd67f915b7c3d6ba552135bb1eecd66e63501"