From 613c5015a636a98e16d82a7defa9496a47ebc43a Mon Sep 17 00:00:00 2001 From: Abreham Gezahegn Date: Wed, 11 Jun 2025 14:07:54 +0300 Subject: [PATCH 1/8] truncate image caption --- typescript/package-lock.json | 2 +- typescript/package.json | 2 +- .../components/blocks/ImageBlockRenderer.tsx | 21 +++++---- typescript/src/renderer/styles/media.css | 45 +++++++++++++++++++ typescript/src/renderer/styles/typography.css | 6 ++- typescript/src/renderer/styles/variables.css | 2 +- 6 files changed, 66 insertions(+), 12 deletions(-) diff --git a/typescript/package-lock.json b/typescript/package-lock.json index a26f857..84ab0a8 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -14,7 +14,7 @@ "json5": "^2.2.3", "katex": "^0.16.22", "puppeteer": "^24.9.0", - "react-intersection-observer": "^9.16.0", + "react-intersection-observer": "^9.13.0", "strip-json-comments": "^5.0.2" }, "devDependencies": { diff --git a/typescript/package.json b/typescript/package.json index cab8efa..e282998 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -68,7 +68,7 @@ "json5": "^2.2.3", "katex": "^0.16.22", "puppeteer": "^24.9.0", - "react-intersection-observer": "^9.16.0", + "react-intersection-observer": "^9.13.0", "strip-json-comments": "^5.0.2" }, "peerDependencies": { diff --git a/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx index 73dc0cb..1dd6114 100644 --- a/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx @@ -40,6 +40,7 @@ export const ImageBlockRenderer: React.FC = ({ const [url, setUrl] = useState(); const [isLoading, setIsLoading] = useState(false); const [hasError, setHasError] = useState(false); + const [showFullCaption, setShowFullCaption] = useState(false); const { ref, inView } = useInView({ threshold: 0.1, triggerOnce: true }); const getImageUrl = () => { @@ -95,13 +96,7 @@ export const ImageBlockRenderer: React.FC = ({
{imageUrl && ( -
+
{(isLoading || (!url && resolveImageUrl)) && !hasError && (
@@ -131,7 +126,17 @@ export const ImageBlockRenderer: React.FC = ({ {imageData?.caption && imageData.caption.length > 0 && (
- +
+ +
+
)} diff --git a/typescript/src/renderer/styles/media.css b/typescript/src/renderer/styles/media.css index e5770fd..6baa92e 100644 --- a/typescript/src/renderer/styles/media.css +++ b/typescript/src/renderer/styles/media.css @@ -129,3 +129,48 @@ font-style: italic; max-width: 100%; } + +/* Image container styles */ +.notion-image-container { + position: relative; + width: 100%; + display: flex; + justify-content: center; +} + +/* Image Caption */ +.caption-content { + transition: all 0.2s ease; +} + +.caption-truncated { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; +} + +.caption-expanded { + overflow: visible; + text-overflow: unset; + white-space: normal; + display: block; +} + +button.caption-toggle-btn { + background: none; + border: none; + color: var(--jsondoc-text-muted); + cursor: pointer; + font-size: 0.875rem; + margin-top: 4px; + padding: 2px 4px; + text-decoration: underline; + transition: color 0.2s ease; +} + +.caption-toggle-btn:hover { + color: var(--jsondoc-text-muted); +} diff --git a/typescript/src/renderer/styles/typography.css b/typescript/src/renderer/styles/typography.css index 797066b..9622f00 100644 --- a/typescript/src/renderer/styles/typography.css +++ b/typescript/src/renderer/styles/typography.css @@ -30,7 +30,7 @@ line-height: var(--jsondoc-line-height-relaxed); } -.notion-link { +.notion-link a { color: inherit; word-wrap: break-word; cursor: pointer; @@ -39,6 +39,10 @@ transition: text-decoration-color var(--jsondoc-transition-fast); } +.notion-text-block div { + color: var(--jsondoc-text-primary); +} + .notion-link:hover { text-decoration-color: var(--jsondoc-text-primary); } diff --git a/typescript/src/renderer/styles/variables.css b/typescript/src/renderer/styles/variables.css index ffbb218..1fecad8 100644 --- a/typescript/src/renderer/styles/variables.css +++ b/typescript/src/renderer/styles/variables.css @@ -102,7 +102,7 @@ /* Theme Override Classes */ .jsondoc-theme-light { - --jsondoc-text-primary: #37352f; + --jsondoc-text-primary: #121414; --jsondoc-text-secondary: rgba(55, 53, 47, 0.65); --jsondoc-text-muted: rgba(55, 53, 47, 0.45); --jsondoc-border-light: rgba(55, 53, 47, 0.16); From a14395b3b865046cf482ebe162d32fb3bc1e5b00 Mon Sep 17 00:00:00 2001 From: Abreham Gezahegn Date: Wed, 11 Jun 2025 14:28:58 +0300 Subject: [PATCH 2/8] make tables horizontally scrollable --- .../blocks/ParagraphBlockRenderer.tsx | 8 +++-- typescript/src/renderer/styles/table.css | 32 ++++++++++++++++--- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx index 3834ab7..e54d156 100644 --- a/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx @@ -23,9 +23,11 @@ export const ParagraphBlockRenderer: React.FC = ({ className={`notion-selectable notion-text-block ${className || ""}`.trim()} data-block-id={block.id} > -
- -
+ {block.paragraph?.rich_text && ( +
+ +
+ )} {/* Render children blocks recursively */} {block.children && block.children.length > 0 && ( diff --git a/typescript/src/renderer/styles/table.css b/typescript/src/renderer/styles/table.css index b97b70c..099512a 100644 --- a/typescript/src/renderer/styles/table.css +++ b/typescript/src/renderer/styles/table.css @@ -5,17 +5,41 @@ width: 100%; } -.notion-table { +.notion-scroller.horizontal { + overflow-x: auto; + overflow-y: hidden; + width: 100%; +} + +.notion-table-content { + min-width: 100%; + width: max-content; +} + +.notion-table-content table { width: 100%; border-collapse: collapse; border-spacing: 0; - table-layout: fixed; + table-layout: auto; + min-width: 600px; } .notion-table-row th, .notion-table-row td { border: 1px solid var(--jsondoc-border-medium); - padding: 6px var(--jsondoc-spacing-md); + padding: 8px 12px; vertical-align: top; - word-wrap: break-word; + white-space: nowrap; + min-width: 120px; +} + +.notion-table-row th { + background-color: var(--jsondoc-bg-code); + font-weight: var(--jsondoc-font-weight-semibold); +} + +.notion-table-cell-text { + overflow: hidden; + text-overflow: ellipsis; + max-width: 300px; } From 572efb5dc25f8f0c16233a292bbd83aa1e33b79a Mon Sep 17 00:00:00 2001 From: Abreham Gezahegn Date: Wed, 11 Jun 2025 16:17:53 +0300 Subject: [PATCH 3/8] dev mode --- typescript/src/examples/index.tsx | 4 +- typescript/src/renderer/JsonDocRenderer.tsx | 3 + .../src/renderer/components/BlockRenderer.tsx | 47 ++- .../components/blocks/CodeBlockRenderer.tsx | 6 + .../blocks/ColumnListBlockRenderer.tsx | 16 +- .../blocks/DividerBlockRenderer.tsx | 6 + .../blocks/EquationBlockRenderer.tsx | 6 + .../blocks/HeadingBlockRenderer.tsx | 6 + .../components/blocks/ImageBlockRenderer.tsx | 4 + .../blocks/ListItemBlockRenderer.tsx | 6 + .../blocks/ParagraphBlockRenderer.tsx | 6 + .../components/blocks/QuoteBlockRenderer.tsx | 6 + .../components/blocks/TableBlockRenderer.tsx | 6 + .../components/blocks/ToDoBlockRenderer.tsx | 6 + .../components/blocks/ToggleBlockRenderer.tsx | 6 + .../renderer/components/dev/DevOverlay.tsx | 335 ++++++++++++++++++ .../src/renderer/components/dev/DevPortal.tsx | 30 ++ .../renderer/components/dev/DevWrapper.tsx | 83 +++++ 18 files changed, 563 insertions(+), 19 deletions(-) create mode 100644 typescript/src/renderer/components/dev/DevOverlay.tsx create mode 100644 typescript/src/renderer/components/dev/DevPortal.tsx create mode 100644 typescript/src/renderer/components/dev/DevWrapper.tsx diff --git a/typescript/src/examples/index.tsx b/typescript/src/examples/index.tsx index fd05cfb..b0c3f00 100644 --- a/typescript/src/examples/index.tsx +++ b/typescript/src/examples/index.tsx @@ -6,7 +6,7 @@ import { HeadingBlockRenderer } from "@/renderer/components/blocks/HeadingBlockR import { JsonDocRenderer } from "../renderer/JsonDocRenderer"; -import testPage from "./testJsonDocs/ex1_success.json"; +import testPage from "./testJsonDocs/test_document.json"; const App = () => { return ( @@ -23,12 +23,12 @@ const App = () => { { return ; }, paragraph: (props) => , - // paragraph: (props) => }} />
diff --git a/typescript/src/renderer/JsonDocRenderer.tsx b/typescript/src/renderer/JsonDocRenderer.tsx index 5a4d3cb..cc93494 100644 --- a/typescript/src/renderer/JsonDocRenderer.tsx +++ b/typescript/src/renderer/JsonDocRenderer.tsx @@ -9,6 +9,7 @@ interface JsonDocRendererProps { components?: React.ComponentProps["components"]; theme?: "light" | "dark"; resolveImageUrl?: (url: string) => Promise; + devMode?: boolean; } export const JsonDocRenderer = ({ @@ -17,6 +18,7 @@ export const JsonDocRenderer = ({ components, theme = "light", resolveImageUrl, + devMode = false, }: JsonDocRendererProps) => { return (
@@ -45,6 +47,7 @@ export const JsonDocRenderer = ({ depth={0} components={components} resolveImageUrl={resolveImageUrl} + devMode={devMode} /> ))}
diff --git a/typescript/src/renderer/components/BlockRenderer.tsx b/typescript/src/renderer/components/BlockRenderer.tsx index f34e140..d15ebb0 100644 --- a/typescript/src/renderer/components/BlockRenderer.tsx +++ b/typescript/src/renderer/components/BlockRenderer.tsx @@ -1,5 +1,6 @@ import React from "react"; +import { DevWrapper } from "./dev/DevWrapper"; import { ParagraphBlockRenderer } from "./blocks/ParagraphBlockRenderer"; import { HeadingBlockRenderer } from "./blocks/HeadingBlockRenderer"; import { ListItemBlockRenderer } from "./blocks/ListItemBlockRenderer"; @@ -57,6 +58,7 @@ interface BlockRendererProps { depth?: number; components?: BlockComponents; resolveImageUrl?: (url: string) => Promise; + devMode?: boolean; } export const BlockRenderer: React.FC = ({ @@ -64,51 +66,64 @@ export const BlockRenderer: React.FC = ({ depth = 0, components, resolveImageUrl, + devMode = false, }) => { - const commonProps = { block, depth, components }; + const commonProps = { block, depth, components, devMode }; + + // Helper function to wrap component with DevWrapper if devMode is enabled + const wrapWithDev = (component: React.ReactElement) => { + if (devMode) { + return {component}; + } + return component; + }; // Paragraph block if (block?.type === "paragraph") { const ParagraphComponent = components?.paragraph || ParagraphBlockRenderer; - return ; + return wrapWithDev(); } // Heading blocks if (block?.type === "heading_1") { const HeadingComponent = components?.heading_1 || HeadingBlockRenderer; - return ; + return wrapWithDev(); } if (block?.type === "heading_2") { const HeadingComponent = components?.heading_2 || HeadingBlockRenderer; - return ; + return wrapWithDev(); } if (block?.type === "heading_3") { const HeadingComponent = components?.heading_3 || HeadingBlockRenderer; - return ; + return wrapWithDev(); } // List item blocks if (block?.type === "bulleted_list_item") { const BulletedListItemComponent = components?.bulleted_list_item || ListItemBlockRenderer; - return ; + return wrapWithDev( + + ); } if (block?.type === "numbered_list_item") { const NumberedListItemComponent = components?.numbered_list_item || ListItemBlockRenderer; - return ; + return wrapWithDev( + + ); } // Code block if (block?.type === "code") { const CodeComponent = components?.code || CodeBlockRenderer; - return ; + return wrapWithDev(); } // Image block if (block?.type === "image") { const ImageComponent = components?.image || ImageBlockRenderer; - return ( + return wrapWithDev( ); } @@ -116,44 +131,44 @@ export const BlockRenderer: React.FC = ({ // Table blocks if (block?.type === "table") { const TableComponent = components?.table || TableBlockRenderer; - return ; + return wrapWithDev(); } // Quote block if (block?.type === "quote") { const QuoteComponent = components?.quote || QuoteBlockRenderer; - return ; + return wrapWithDev(); } // Divider block if (block?.type === "divider") { const DividerComponent = components?.divider || DividerBlockRenderer; - return ; + return wrapWithDev(); } // To-do block if (block?.type === "to_do") { const ToDoComponent = components?.to_do || ToDoBlockRenderer; - return ; + return wrapWithDev(); } // Toggle block if (block?.type === "toggle") { const ToggleComponent = components?.toggle || ToggleBlockRenderer; - return ; + return wrapWithDev(); } // Column list and column blocks if (block?.type === "column_list") { const ColumnListComponent = components?.column_list || ColumnListBlockRenderer; - return ; + return wrapWithDev(); } // Equation block if (block?.type === "equation") { const EquationComponent = components?.equation || EquationBlockRenderer; - return ; + return wrapWithDev(); } // Fallback for unsupported block types diff --git a/typescript/src/renderer/components/blocks/CodeBlockRenderer.tsx b/typescript/src/renderer/components/blocks/CodeBlockRenderer.tsx index 3abcf01..e43e905 100644 --- a/typescript/src/renderer/components/blocks/CodeBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/CodeBlockRenderer.tsx @@ -7,6 +7,8 @@ interface CodeBlockRendererProps extends React.HTMLAttributes { block: any; depth?: number; components?: React.ComponentProps["components"]; + devMode?: boolean; + resolveImageUrl?: (url: string) => Promise; } export const CodeBlockRenderer: React.FC = ({ @@ -14,6 +16,8 @@ export const CodeBlockRenderer: React.FC = ({ depth = 0, className, components, + devMode, + resolveImageUrl, ...props }) => { const codeData = block.code; @@ -51,6 +55,8 @@ export const CodeBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} + devMode={devMode} + resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/ColumnListBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ColumnListBlockRenderer.tsx index 9373d19..320c621 100644 --- a/typescript/src/renderer/components/blocks/ColumnListBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ColumnListBlockRenderer.tsx @@ -7,11 +7,21 @@ interface ColumnListBlockRendererProps block: any; depth?: number; components?: React.ComponentProps["components"]; + devMode?: boolean; + resolveImageUrl?: (url: string) => Promise; } export const ColumnListBlockRenderer: React.FC< ColumnListBlockRendererProps -> = ({ block, depth = 0, className, components, ...props }) => { +> = ({ + block, + depth = 0, + className, + components, + devMode, + resolveImageUrl, + ...props +}) => { return (
) )} @@ -61,6 +73,8 @@ export const ColumnListBlockRenderer: React.FC< block={child} depth={depth + 1} components={components} + devMode={devMode} + resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/DividerBlockRenderer.tsx b/typescript/src/renderer/components/blocks/DividerBlockRenderer.tsx index c4a98a7..8dd9b3b 100644 --- a/typescript/src/renderer/components/blocks/DividerBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/DividerBlockRenderer.tsx @@ -7,6 +7,8 @@ interface DividerBlockRendererProps block: any; depth?: number; components?: React.ComponentProps["components"]; + devMode?: boolean; + resolveImageUrl?: (url: string) => Promise; } export const DividerBlockRenderer: React.FC = ({ @@ -14,6 +16,8 @@ export const DividerBlockRenderer: React.FC = ({ depth = 0, className, components, + devMode, + resolveImageUrl, ...props }) => { return ( @@ -38,6 +42,8 @@ export const DividerBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} + devMode={devMode} + resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/EquationBlockRenderer.tsx b/typescript/src/renderer/components/blocks/EquationBlockRenderer.tsx index 8ad2c0a..cc1c1a7 100644 --- a/typescript/src/renderer/components/blocks/EquationBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/EquationBlockRenderer.tsx @@ -7,6 +7,8 @@ interface EquationBlockRendererProps block: any; depth?: number; components?: React.ComponentProps["components"]; + devMode?: boolean; + resolveImageUrl?: (url: string) => Promise; } export const EquationBlockRenderer: React.FC = ({ @@ -14,6 +16,8 @@ export const EquationBlockRenderer: React.FC = ({ depth = 0, className, components, + devMode, + resolveImageUrl, ...props }) => { const equationData = block.equation; @@ -44,6 +48,8 @@ export const EquationBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} + devMode={devMode} + resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/HeadingBlockRenderer.tsx b/typescript/src/renderer/components/blocks/HeadingBlockRenderer.tsx index 68da465..4cbc499 100644 --- a/typescript/src/renderer/components/blocks/HeadingBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/HeadingBlockRenderer.tsx @@ -9,6 +9,8 @@ interface HeadingBlockRendererProps level: 1 | 2 | 3; depth?: number; components?: React.ComponentProps["components"]; + devMode?: boolean; + resolveImageUrl?: (url: string) => Promise; } export const HeadingBlockRenderer: React.FC = ({ @@ -17,6 +19,8 @@ export const HeadingBlockRenderer: React.FC = ({ depth = 0, className, components, + devMode, + resolveImageUrl, ...props }) => { const getHeadingData = () => { @@ -72,6 +76,8 @@ export const HeadingBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} + devMode={devMode} + resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx index 1dd6114..07a5ec8 100644 --- a/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx @@ -26,6 +26,7 @@ interface ImageBlockRendererProps extends React.HTMLAttributes { depth?: number; components?: React.ComponentProps["components"]; resolveImageUrl?: (url: string) => Promise; + devMode?: boolean; } export const ImageBlockRenderer: React.FC = ({ @@ -34,6 +35,7 @@ export const ImageBlockRenderer: React.FC = ({ className, components, resolveImageUrl, + devMode, ...props }) => { const imageData = block.image; @@ -155,6 +157,8 @@ export const ImageBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} + devMode={devMode} + resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/ListItemBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ListItemBlockRenderer.tsx index 7d73d34..1057b7e 100644 --- a/typescript/src/renderer/components/blocks/ListItemBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ListItemBlockRenderer.tsx @@ -9,6 +9,8 @@ interface ListItemBlockRendererProps type: "bulleted" | "numbered"; depth?: number; components?: React.ComponentProps["components"]; + devMode?: boolean; + resolveImageUrl?: (url: string) => Promise; } export const ListItemBlockRenderer: React.FC = ({ @@ -17,6 +19,8 @@ export const ListItemBlockRenderer: React.FC = ({ depth = 0, className, components, + devMode, + resolveImageUrl, ...props }) => { const listData = @@ -47,6 +51,8 @@ export const ListItemBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} + devMode={devMode} + resolveImageUrl={resolveImageUrl} /> ))} diff --git a/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx index e54d156..dce0350 100644 --- a/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx @@ -8,6 +8,8 @@ interface ParagraphBlockRendererProps block: any; depth?: number; components?: React.ComponentProps["components"]; + devMode?: boolean; + resolveImageUrl?: (url: string) => Promise; } export const ParagraphBlockRenderer: React.FC = ({ @@ -15,6 +17,8 @@ export const ParagraphBlockRenderer: React.FC = ({ depth = 0, className, components, + devMode, + resolveImageUrl, ...props }) => { return ( @@ -41,6 +45,8 @@ export const ParagraphBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} + devMode={devMode} + resolveImageUrl={resolveImageUrl} /> ))} diff --git a/typescript/src/renderer/components/blocks/QuoteBlockRenderer.tsx b/typescript/src/renderer/components/blocks/QuoteBlockRenderer.tsx index 5a8cf2e..909a4e7 100644 --- a/typescript/src/renderer/components/blocks/QuoteBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/QuoteBlockRenderer.tsx @@ -7,6 +7,8 @@ interface QuoteBlockRendererProps extends React.HTMLAttributes { block: any; depth?: number; components?: React.ComponentProps["components"]; + devMode?: boolean; + resolveImageUrl?: (url: string) => Promise; } export const QuoteBlockRenderer: React.FC = ({ @@ -14,6 +16,8 @@ export const QuoteBlockRenderer: React.FC = ({ depth = 0, className, components, + devMode, + resolveImageUrl, ...props }) => { const quoteData = block.quote; @@ -44,6 +48,8 @@ export const QuoteBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} + devMode={devMode} + resolveImageUrl={resolveImageUrl} /> ))} diff --git a/typescript/src/renderer/components/blocks/TableBlockRenderer.tsx b/typescript/src/renderer/components/blocks/TableBlockRenderer.tsx index 14d85fc..e6f8179 100644 --- a/typescript/src/renderer/components/blocks/TableBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/TableBlockRenderer.tsx @@ -7,6 +7,8 @@ interface TableBlockRendererProps extends React.HTMLAttributes { block: any; depth?: number; components?: React.ComponentProps["components"]; + devMode?: boolean; + resolveImageUrl?: (url: string) => Promise; } export const TableBlockRenderer: React.FC = ({ @@ -14,6 +16,8 @@ export const TableBlockRenderer: React.FC = ({ depth = 0, className, components, + devMode, + resolveImageUrl, ...props }) => { const tableData = block.table; @@ -84,6 +88,8 @@ export const TableBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} + devMode={devMode} + resolveImageUrl={resolveImageUrl} /> ))} diff --git a/typescript/src/renderer/components/blocks/ToDoBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ToDoBlockRenderer.tsx index dfcb356..0d4624f 100644 --- a/typescript/src/renderer/components/blocks/ToDoBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ToDoBlockRenderer.tsx @@ -7,6 +7,8 @@ interface ToDoBlockRendererProps extends React.HTMLAttributes { block: any; depth?: number; components?: React.ComponentProps["components"]; + devMode?: boolean; + resolveImageUrl?: (url: string) => Promise; } export const ToDoBlockRenderer: React.FC = ({ @@ -14,6 +16,8 @@ export const ToDoBlockRenderer: React.FC = ({ depth = 0, className, components, + devMode, + resolveImageUrl, ...props }) => { const todoData = block.to_do; @@ -87,6 +91,8 @@ export const ToDoBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} + devMode={devMode} + resolveImageUrl={resolveImageUrl} /> ))} diff --git a/typescript/src/renderer/components/blocks/ToggleBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ToggleBlockRenderer.tsx index 6721f16..1b66859 100644 --- a/typescript/src/renderer/components/blocks/ToggleBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ToggleBlockRenderer.tsx @@ -8,6 +8,8 @@ interface ToggleBlockRendererProps block: any; depth?: number; components?: React.ComponentProps["components"]; + devMode?: boolean; + resolveImageUrl?: (url: string) => Promise; } export const ToggleBlockRenderer: React.FC = ({ @@ -15,6 +17,8 @@ export const ToggleBlockRenderer: React.FC = ({ depth = 0, className, components, + devMode, + resolveImageUrl, ...props }) => { const [isOpen, setIsOpen] = useState(false); @@ -83,6 +87,8 @@ export const ToggleBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} + devMode={devMode} + resolveImageUrl={resolveImageUrl} /> ))} diff --git a/typescript/src/renderer/components/dev/DevOverlay.tsx b/typescript/src/renderer/components/dev/DevOverlay.tsx new file mode 100644 index 0000000..a53cb52 --- /dev/null +++ b/typescript/src/renderer/components/dev/DevOverlay.tsx @@ -0,0 +1,335 @@ +import React, { useEffect, useRef, useState } from "react"; + +interface DevOverlayProps { + block: any; + position: { x: number; y: number }; + onClose: () => void; +} + +export const DevOverlay: React.FC = ({ + block, + position, + onClose, +}) => { + const overlayRef = useRef(null); + const [isDragging, setIsDragging] = useState(false); + const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); + const [currentPosition, setCurrentPosition] = useState(position); + const [isResizing, setIsResizing] = useState(false); + const [resizeDirection, setResizeDirection] = useState(""); + const [size, setSize] = useState({ width: 500, height: 400 }); + + // Handle dragging and resizing + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (isDragging) { + setCurrentPosition({ + x: e.clientX - dragOffset.x, + y: e.clientY - dragOffset.y, + }); + } else if (isResizing && overlayRef.current) { + const rect = overlayRef.current.getBoundingClientRect(); + const newSize = { ...size }; + const newPosition = { ...currentPosition }; + + if (resizeDirection.includes("right")) { + newSize.width = Math.max(300, e.clientX - rect.left); + } + if (resizeDirection.includes("left")) { + const newWidth = Math.max(300, rect.right - e.clientX); + const widthDiff = newWidth - size.width; + newSize.width = newWidth; + newPosition.x = currentPosition.x - widthDiff; + } + if (resizeDirection.includes("bottom")) { + newSize.height = Math.max(200, e.clientY - rect.top); + } + if (resizeDirection.includes("top")) { + const newHeight = Math.max(200, rect.bottom - e.clientY); + const heightDiff = newHeight - size.height; + newSize.height = newHeight; + newPosition.y = currentPosition.y - heightDiff; + } + + setSize(newSize); + setCurrentPosition(newPosition); + } + }; + + const handleMouseUp = () => { + setIsDragging(false); + setIsResizing(false); + setResizeDirection(""); + }; + + if (isDragging || isResizing) { + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + document.body.style.userSelect = "none"; + } + + return () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + document.body.style.userSelect = ""; + }; + }, [ + isDragging, + isResizing, + dragOffset, + resizeDirection, + size, + currentPosition, + ]); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (isDragging || isResizing) return; // Don't close while dragging or resizing + if ( + overlayRef.current && + !overlayRef.current.contains(event.target as Node) + ) { + onClose(); + } + }; + + const handleEscape = (event: KeyboardEvent) => { + if (event.key === "Escape") { + onClose(); + } + }; + + // Add a small delay to prevent immediate closing from the opening click + const timer = setTimeout(() => { + document.addEventListener("mousedown", handleClickOutside); + document.addEventListener("keydown", handleEscape); + }, 100); + + return () => { + clearTimeout(timer); + document.removeEventListener("mousedown", handleClickOutside); + document.removeEventListener("keydown", handleEscape); + }; + }, [onClose, isDragging, isResizing]); + + const adjustedPosition = { + x: Math.min(Math.max(0, currentPosition.x), window.innerWidth - size.width), + y: Math.min( + Math.max(0, currentPosition.y), + window.innerHeight - size.height + ), + }; + + const handleMouseDown = (e: React.MouseEvent) => { + if (e.button !== 0) return; // Only handle left click + const rect = overlayRef.current?.getBoundingClientRect(); + if (rect) { + setDragOffset({ + x: e.clientX - rect.left, + y: e.clientY - rect.top, + }); + setIsDragging(true); + } + }; + + const handleResizeStart = (direction: string) => (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setResizeDirection(direction); + setIsResizing(true); + }; + + return ( +
e.stopPropagation()} + style={{ + position: "fixed", + top: adjustedPosition.y, + left: adjustedPosition.x, + width: size.width, + height: size.height, + zIndex: 9999, + background: "#1a1a1a", + border: "1px solid #333", + borderRadius: "8px", + padding: "16px", + boxShadow: "0 8px 32px rgba(0, 0, 0, 0.3)", + fontFamily: "monospace", + fontSize: "12px", + color: "#ffffff", + display: "flex", + flexDirection: "column", + }} + onMouseDown={(e) => e.stopPropagation()} // Prevent bubbling to DevPortal + > +
+ + + + + Block JSON ({block.type || "unknown"}) + + +
+
+        {JSON.stringify(block, null, 2)}
+      
+ + {/* Resize handles */} +
+
+
+
+ {/* Corner resize handles */} +
+
+
+
+
+ ); +}; diff --git a/typescript/src/renderer/components/dev/DevPortal.tsx b/typescript/src/renderer/components/dev/DevPortal.tsx new file mode 100644 index 0000000..5f61b74 --- /dev/null +++ b/typescript/src/renderer/components/dev/DevPortal.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { createPortal } from "react-dom"; + +interface DevPortalProps { + children: React.ReactNode; + onBackdropClick?: (e: React.MouseEvent) => void; +} + +export const DevPortal: React.FC = ({ + children, + onBackdropClick, +}) => { + return createPortal( +
+
{children}
+
, + document.body + ); +}; diff --git a/typescript/src/renderer/components/dev/DevWrapper.tsx b/typescript/src/renderer/components/dev/DevWrapper.tsx new file mode 100644 index 0000000..206c04a --- /dev/null +++ b/typescript/src/renderer/components/dev/DevWrapper.tsx @@ -0,0 +1,83 @@ +import React, { useState } from "react"; + +import { DevPortal } from "./DevPortal"; +import { DevOverlay } from "./DevOverlay"; + +interface DevWrapperProps { + block: any; + children: React.ReactNode; +} + +export const DevWrapper: React.FC = ({ block, children }) => { + const [showJson, setShowJson] = useState(false); + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [isHovered, setIsHovered] = useState(false); + + const handleClick = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (!showJson) { + setPosition({ x: e.clientX, y: e.clientY }); + setShowJson(true); + } + }; + + return ( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + onClick={handleClick} + style={{ + position: "relative", + cursor: "pointer", + outline: isHovered ? "2px dashed #60a5fa" : "none", + outlineOffset: "2px", + transition: "outline 0.2s ease", + }} + > + {children} + {isHovered && ( +
+ {block.type || "unknown"} +
+ )} + {showJson && ( + { + // Don't close if clicking on resize handles (they extend outside the popup) + const target = e.target as HTMLElement; + if (target.hasAttribute?.('data-resize-handle') || + target.style?.cursor?.includes('resize') || + target.style?.zIndex === '10' || + target.style?.zIndex === '11') { + return; + } + setShowJson(false); + }} + > + setShowJson(false)} + /> + + )} +
+ ); +}; From 901891aac0a54e9612ad06f98d519812e3b46645 Mon Sep 17 00:00:00 2001 From: Abreham Gezahegn Date: Wed, 11 Jun 2025 16:27:28 +0300 Subject: [PATCH 4/8] use context for resolveImage and dev mode --- typescript/src/renderer/JsonDocRenderer.tsx | 61 ++++++++++--------- .../src/renderer/components/BlockRenderer.tsx | 13 ++-- .../components/blocks/CodeBlockRenderer.tsx | 6 -- .../blocks/ColumnListBlockRenderer.tsx | 8 --- .../blocks/DividerBlockRenderer.tsx | 6 -- .../blocks/EquationBlockRenderer.tsx | 6 -- .../blocks/HeadingBlockRenderer.tsx | 6 -- .../components/blocks/ImageBlockRenderer.tsx | 8 +-- .../blocks/ListItemBlockRenderer.tsx | 6 -- .../blocks/ParagraphBlockRenderer.tsx | 6 -- .../components/blocks/QuoteBlockRenderer.tsx | 6 -- .../components/blocks/TableBlockRenderer.tsx | 6 -- .../components/blocks/ToDoBlockRenderer.tsx | 6 -- .../components/blocks/ToggleBlockRenderer.tsx | 6 -- .../src/renderer/context/RendererContext.tsx | 28 +++++++++ typescript/src/renderer/context/index.ts | 1 + typescript/src/renderer/index.ts | 3 + 17 files changed, 70 insertions(+), 112 deletions(-) create mode 100644 typescript/src/renderer/context/RendererContext.tsx create mode 100644 typescript/src/renderer/context/index.ts diff --git a/typescript/src/renderer/JsonDocRenderer.tsx b/typescript/src/renderer/JsonDocRenderer.tsx index cc93494..d35c96a 100644 --- a/typescript/src/renderer/JsonDocRenderer.tsx +++ b/typescript/src/renderer/JsonDocRenderer.tsx @@ -2,6 +2,7 @@ import "./styles/index.css"; import React from "react"; import { BlockRenderer } from "./components/BlockRenderer"; +import { RendererProvider } from "./context/RendererContext"; interface JsonDocRendererProps { page: any; @@ -21,38 +22,38 @@ export const JsonDocRenderer = ({ devMode = false, }: JsonDocRendererProps) => { return ( -
-
- {/* Page icon */} - {page.icon && ( -
- {page.icon.type === "emoji" && page.icon.emoji} -
- )} + +
+
+ {/* Page icon */} + {page.icon && ( +
+ {page.icon.type === "emoji" && page.icon.emoji} +
+ )} - {/* Page title */} - {page.properties?.title && ( -

- {page.properties.title.title?.[0]?.plain_text || "Untitled"} -

- )} + {/* Page title */} + {page.properties?.title && ( +

+ {page.properties.title.title?.[0]?.plain_text || "Untitled"} +

+ )} - {/* Page children blocks */} - {page.children && page.children.length > 0 && ( -
- {page.children.map((block: any, index: number) => ( - - ))} -
- )} + {/* Page children blocks */} + {page.children && page.children.length > 0 && ( +
+ {page.children.map((block: any, index: number) => ( + + ))} +
+ )} +
-
+ ); }; diff --git a/typescript/src/renderer/components/BlockRenderer.tsx b/typescript/src/renderer/components/BlockRenderer.tsx index d15ebb0..a4d0113 100644 --- a/typescript/src/renderer/components/BlockRenderer.tsx +++ b/typescript/src/renderer/components/BlockRenderer.tsx @@ -1,5 +1,7 @@ import React from "react"; +import { useRenderer } from "../context/RendererContext"; + import { DevWrapper } from "./dev/DevWrapper"; import { ParagraphBlockRenderer } from "./blocks/ParagraphBlockRenderer"; import { HeadingBlockRenderer } from "./blocks/HeadingBlockRenderer"; @@ -57,18 +59,15 @@ interface BlockRendererProps { block: any; depth?: number; components?: BlockComponents; - resolveImageUrl?: (url: string) => Promise; - devMode?: boolean; } export const BlockRenderer: React.FC = ({ block, depth = 0, components, - resolveImageUrl, - devMode = false, }) => { - const commonProps = { block, depth, components, devMode }; + const { devMode = false } = useRenderer(); + const commonProps = { block, depth, components }; // Helper function to wrap component with DevWrapper if devMode is enabled const wrapWithDev = (component: React.ReactElement) => { @@ -123,9 +122,7 @@ export const BlockRenderer: React.FC = ({ // Image block if (block?.type === "image") { const ImageComponent = components?.image || ImageBlockRenderer; - return wrapWithDev( - - ); + return wrapWithDev(); } // Table blocks diff --git a/typescript/src/renderer/components/blocks/CodeBlockRenderer.tsx b/typescript/src/renderer/components/blocks/CodeBlockRenderer.tsx index e43e905..3abcf01 100644 --- a/typescript/src/renderer/components/blocks/CodeBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/CodeBlockRenderer.tsx @@ -7,8 +7,6 @@ interface CodeBlockRendererProps extends React.HTMLAttributes { block: any; depth?: number; components?: React.ComponentProps["components"]; - devMode?: boolean; - resolveImageUrl?: (url: string) => Promise; } export const CodeBlockRenderer: React.FC = ({ @@ -16,8 +14,6 @@ export const CodeBlockRenderer: React.FC = ({ depth = 0, className, components, - devMode, - resolveImageUrl, ...props }) => { const codeData = block.code; @@ -55,8 +51,6 @@ export const CodeBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} - devMode={devMode} - resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/ColumnListBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ColumnListBlockRenderer.tsx index 320c621..813cd68 100644 --- a/typescript/src/renderer/components/blocks/ColumnListBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ColumnListBlockRenderer.tsx @@ -7,8 +7,6 @@ interface ColumnListBlockRendererProps block: any; depth?: number; components?: React.ComponentProps["components"]; - devMode?: boolean; - resolveImageUrl?: (url: string) => Promise; } export const ColumnListBlockRenderer: React.FC< @@ -18,8 +16,6 @@ export const ColumnListBlockRenderer: React.FC< depth = 0, className, components, - devMode, - resolveImageUrl, ...props }) => { return ( @@ -47,8 +43,6 @@ export const ColumnListBlockRenderer: React.FC< block={columnChild} depth={depth + 1} components={components} - devMode={devMode} - resolveImageUrl={resolveImageUrl} /> ) )} @@ -73,8 +67,6 @@ export const ColumnListBlockRenderer: React.FC< block={child} depth={depth + 1} components={components} - devMode={devMode} - resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/DividerBlockRenderer.tsx b/typescript/src/renderer/components/blocks/DividerBlockRenderer.tsx index 8dd9b3b..c4a98a7 100644 --- a/typescript/src/renderer/components/blocks/DividerBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/DividerBlockRenderer.tsx @@ -7,8 +7,6 @@ interface DividerBlockRendererProps block: any; depth?: number; components?: React.ComponentProps["components"]; - devMode?: boolean; - resolveImageUrl?: (url: string) => Promise; } export const DividerBlockRenderer: React.FC = ({ @@ -16,8 +14,6 @@ export const DividerBlockRenderer: React.FC = ({ depth = 0, className, components, - devMode, - resolveImageUrl, ...props }) => { return ( @@ -42,8 +38,6 @@ export const DividerBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} - devMode={devMode} - resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/EquationBlockRenderer.tsx b/typescript/src/renderer/components/blocks/EquationBlockRenderer.tsx index cc1c1a7..8ad2c0a 100644 --- a/typescript/src/renderer/components/blocks/EquationBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/EquationBlockRenderer.tsx @@ -7,8 +7,6 @@ interface EquationBlockRendererProps block: any; depth?: number; components?: React.ComponentProps["components"]; - devMode?: boolean; - resolveImageUrl?: (url: string) => Promise; } export const EquationBlockRenderer: React.FC = ({ @@ -16,8 +14,6 @@ export const EquationBlockRenderer: React.FC = ({ depth = 0, className, components, - devMode, - resolveImageUrl, ...props }) => { const equationData = block.equation; @@ -48,8 +44,6 @@ export const EquationBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} - devMode={devMode} - resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/HeadingBlockRenderer.tsx b/typescript/src/renderer/components/blocks/HeadingBlockRenderer.tsx index 4cbc499..68da465 100644 --- a/typescript/src/renderer/components/blocks/HeadingBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/HeadingBlockRenderer.tsx @@ -9,8 +9,6 @@ interface HeadingBlockRendererProps level: 1 | 2 | 3; depth?: number; components?: React.ComponentProps["components"]; - devMode?: boolean; - resolveImageUrl?: (url: string) => Promise; } export const HeadingBlockRenderer: React.FC = ({ @@ -19,8 +17,6 @@ export const HeadingBlockRenderer: React.FC = ({ depth = 0, className, components, - devMode, - resolveImageUrl, ...props }) => { const getHeadingData = () => { @@ -76,8 +72,6 @@ export const HeadingBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} - devMode={devMode} - resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx index 07a5ec8..c6b6dd9 100644 --- a/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ImageBlockRenderer.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from "react"; import { useInView } from "react-intersection-observer"; +import { useRenderer } from "../../context/RendererContext"; import { RichTextRenderer } from "../RichTextRenderer"; import { BlockRenderer } from "../BlockRenderer"; @@ -25,8 +26,6 @@ interface ImageBlockRendererProps extends React.HTMLAttributes { block: any; depth?: number; components?: React.ComponentProps["components"]; - resolveImageUrl?: (url: string) => Promise; - devMode?: boolean; } export const ImageBlockRenderer: React.FC = ({ @@ -34,10 +33,9 @@ export const ImageBlockRenderer: React.FC = ({ depth = 0, className, components, - resolveImageUrl, - devMode, ...props }) => { + const { resolveImageUrl } = useRenderer(); const imageData = block.image; const [url, setUrl] = useState(); const [isLoading, setIsLoading] = useState(false); @@ -157,8 +155,6 @@ export const ImageBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} - devMode={devMode} - resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/ListItemBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ListItemBlockRenderer.tsx index 1057b7e..7d73d34 100644 --- a/typescript/src/renderer/components/blocks/ListItemBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ListItemBlockRenderer.tsx @@ -9,8 +9,6 @@ interface ListItemBlockRendererProps type: "bulleted" | "numbered"; depth?: number; components?: React.ComponentProps["components"]; - devMode?: boolean; - resolveImageUrl?: (url: string) => Promise; } export const ListItemBlockRenderer: React.FC = ({ @@ -19,8 +17,6 @@ export const ListItemBlockRenderer: React.FC = ({ depth = 0, className, components, - devMode, - resolveImageUrl, ...props }) => { const listData = @@ -51,8 +47,6 @@ export const ListItemBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} - devMode={devMode} - resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx index dce0350..e54d156 100644 --- a/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx @@ -8,8 +8,6 @@ interface ParagraphBlockRendererProps block: any; depth?: number; components?: React.ComponentProps["components"]; - devMode?: boolean; - resolveImageUrl?: (url: string) => Promise; } export const ParagraphBlockRenderer: React.FC = ({ @@ -17,8 +15,6 @@ export const ParagraphBlockRenderer: React.FC = ({ depth = 0, className, components, - devMode, - resolveImageUrl, ...props }) => { return ( @@ -45,8 +41,6 @@ export const ParagraphBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} - devMode={devMode} - resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/QuoteBlockRenderer.tsx b/typescript/src/renderer/components/blocks/QuoteBlockRenderer.tsx index 909a4e7..5a8cf2e 100644 --- a/typescript/src/renderer/components/blocks/QuoteBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/QuoteBlockRenderer.tsx @@ -7,8 +7,6 @@ interface QuoteBlockRendererProps extends React.HTMLAttributes { block: any; depth?: number; components?: React.ComponentProps["components"]; - devMode?: boolean; - resolveImageUrl?: (url: string) => Promise; } export const QuoteBlockRenderer: React.FC = ({ @@ -16,8 +14,6 @@ export const QuoteBlockRenderer: React.FC = ({ depth = 0, className, components, - devMode, - resolveImageUrl, ...props }) => { const quoteData = block.quote; @@ -48,8 +44,6 @@ export const QuoteBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} - devMode={devMode} - resolveImageUrl={resolveImageUrl} /> ))}
diff --git a/typescript/src/renderer/components/blocks/TableBlockRenderer.tsx b/typescript/src/renderer/components/blocks/TableBlockRenderer.tsx index e6f8179..14d85fc 100644 --- a/typescript/src/renderer/components/blocks/TableBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/TableBlockRenderer.tsx @@ -7,8 +7,6 @@ interface TableBlockRendererProps extends React.HTMLAttributes { block: any; depth?: number; components?: React.ComponentProps["components"]; - devMode?: boolean; - resolveImageUrl?: (url: string) => Promise; } export const TableBlockRenderer: React.FC = ({ @@ -16,8 +14,6 @@ export const TableBlockRenderer: React.FC = ({ depth = 0, className, components, - devMode, - resolveImageUrl, ...props }) => { const tableData = block.table; @@ -88,8 +84,6 @@ export const TableBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} - devMode={devMode} - resolveImageUrl={resolveImageUrl} /> ))} diff --git a/typescript/src/renderer/components/blocks/ToDoBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ToDoBlockRenderer.tsx index 0d4624f..dfcb356 100644 --- a/typescript/src/renderer/components/blocks/ToDoBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ToDoBlockRenderer.tsx @@ -7,8 +7,6 @@ interface ToDoBlockRendererProps extends React.HTMLAttributes { block: any; depth?: number; components?: React.ComponentProps["components"]; - devMode?: boolean; - resolveImageUrl?: (url: string) => Promise; } export const ToDoBlockRenderer: React.FC = ({ @@ -16,8 +14,6 @@ export const ToDoBlockRenderer: React.FC = ({ depth = 0, className, components, - devMode, - resolveImageUrl, ...props }) => { const todoData = block.to_do; @@ -91,8 +87,6 @@ export const ToDoBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} - devMode={devMode} - resolveImageUrl={resolveImageUrl} /> ))} diff --git a/typescript/src/renderer/components/blocks/ToggleBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ToggleBlockRenderer.tsx index 1b66859..6721f16 100644 --- a/typescript/src/renderer/components/blocks/ToggleBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ToggleBlockRenderer.tsx @@ -8,8 +8,6 @@ interface ToggleBlockRendererProps block: any; depth?: number; components?: React.ComponentProps["components"]; - devMode?: boolean; - resolveImageUrl?: (url: string) => Promise; } export const ToggleBlockRenderer: React.FC = ({ @@ -17,8 +15,6 @@ export const ToggleBlockRenderer: React.FC = ({ depth = 0, className, components, - devMode, - resolveImageUrl, ...props }) => { const [isOpen, setIsOpen] = useState(false); @@ -87,8 +83,6 @@ export const ToggleBlockRenderer: React.FC = ({ block={child} depth={depth + 1} components={components} - devMode={devMode} - resolveImageUrl={resolveImageUrl} /> ))} diff --git a/typescript/src/renderer/context/RendererContext.tsx b/typescript/src/renderer/context/RendererContext.tsx new file mode 100644 index 0000000..7782c4e --- /dev/null +++ b/typescript/src/renderer/context/RendererContext.tsx @@ -0,0 +1,28 @@ +import React, { createContext, useContext, ReactNode } from 'react'; + +interface RendererContextValue { + devMode?: boolean; + resolveImageUrl?: (url: string) => Promise; +} + +const RendererContext = createContext({}); + +export const useRenderer = () => { + return useContext(RendererContext); +}; + +interface RendererProviderProps { + children: ReactNode; + value: RendererContextValue; +} + +export const RendererProvider: React.FC = ({ + children, + value +}) => { + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/typescript/src/renderer/context/index.ts b/typescript/src/renderer/context/index.ts new file mode 100644 index 0000000..3226f9f --- /dev/null +++ b/typescript/src/renderer/context/index.ts @@ -0,0 +1 @@ +export { RendererProvider, useRenderer } from './RendererContext'; \ No newline at end of file diff --git a/typescript/src/renderer/index.ts b/typescript/src/renderer/index.ts index c67aef1..a574467 100644 --- a/typescript/src/renderer/index.ts +++ b/typescript/src/renderer/index.ts @@ -16,6 +16,9 @@ export { ToggleBlockRenderer } from "./components/blocks/ToggleBlockRenderer"; export { ColumnListBlockRenderer } from "./components/blocks/ColumnListBlockRenderer"; export { EquationBlockRenderer } from "./components/blocks/EquationBlockRenderer"; +// Export context +export { RendererProvider, useRenderer } from "./context"; + // Export types export type { BlockComponents } from "./components/BlockRenderer"; export type { JsonDocRendererProps, BlockRendererProps } from "./types"; From 7190496f2603356b437de6ad5312767ff762a524 Mon Sep 17 00:00:00 2001 From: Abreham Gezahegn Date: Wed, 11 Jun 2025 16:31:51 +0300 Subject: [PATCH 5/8] checkpoint --- .../src/renderer/components/blocks/ParagraphBlockRenderer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx b/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx index e54d156..c937bfa 100644 --- a/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx +++ b/typescript/src/renderer/components/blocks/ParagraphBlockRenderer.tsx @@ -23,7 +23,7 @@ export const ParagraphBlockRenderer: React.FC = ({ className={`notion-selectable notion-text-block ${className || ""}`.trim()} data-block-id={block.id} > - {block.paragraph?.rich_text && ( + {block.paragraph?.rich_text.length > 0 && (
From 92ce32762a5c90ff1f8b13e008637ea4b61b7c38 Mon Sep 17 00:00:00 2001 From: Abreham Gezahegn Date: Wed, 11 Jun 2025 16:53:08 +0300 Subject: [PATCH 6/8] add page delimiter --- typescript/src/renderer/JsonDocRenderer.tsx | 34 +++++++++++---- .../src/renderer/components/PageDelimiter.tsx | 21 ++++++++++ typescript/src/renderer/styles/layout.css | 42 +++++++++++++++++++ 3 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 typescript/src/renderer/components/PageDelimiter.tsx diff --git a/typescript/src/renderer/JsonDocRenderer.tsx b/typescript/src/renderer/JsonDocRenderer.tsx index d35c96a..e3771a0 100644 --- a/typescript/src/renderer/JsonDocRenderer.tsx +++ b/typescript/src/renderer/JsonDocRenderer.tsx @@ -2,6 +2,7 @@ import "./styles/index.css"; import React from "react"; import { BlockRenderer } from "./components/BlockRenderer"; +import { PageDelimiter } from "./components/PageDelimiter"; import { RendererProvider } from "./context/RendererContext"; interface JsonDocRendererProps { @@ -42,14 +43,31 @@ export const JsonDocRenderer = ({ {/* Page children blocks */} {page.children && page.children.length > 0 && (
- {page.children.map((block: any, index: number) => ( - - ))} + {page.children.map((block: any, index: number) => { + const currentPageNum = block.metadata?.origin?.page_num; + const nextPageNum = + index < page.children.length - 1 + ? page.children[index + 1]?.metadata?.origin?.page_num + : null; + + // Show delimiter after the last block of each page + const showPageDelimiter = + currentPageNum && + (nextPageNum !== currentPageNum || index === page.children.length - 1); + + return ( + + + {showPageDelimiter && ( + + )} + + ); + })}
)} diff --git a/typescript/src/renderer/components/PageDelimiter.tsx b/typescript/src/renderer/components/PageDelimiter.tsx new file mode 100644 index 0000000..32d727f --- /dev/null +++ b/typescript/src/renderer/components/PageDelimiter.tsx @@ -0,0 +1,21 @@ +import React from "react"; + +interface PageDelimiterProps { + pageNumber: number; + className?: string; +} + +export const PageDelimiter: React.FC = ({ + pageNumber, + className, +}) => { + return ( +
+
+ + Page {pageNumber} + +
+
+ ); +}; \ No newline at end of file diff --git a/typescript/src/renderer/styles/layout.css b/typescript/src/renderer/styles/layout.css index 2b282bd..6d391ba 100644 --- a/typescript/src/renderer/styles/layout.css +++ b/typescript/src/renderer/styles/layout.css @@ -1,5 +1,47 @@ /* Layout Components */ +/* Page Delimiter */ +.jsondoc-page-delimiter { + margin: var(--jsondoc-spacing-xl) 0; + margin-bottom: var(--jsondoc-spacing-3xl); + position: relative; + display: flex; + align-items: center; + justify-content: center; +} + +.jsondoc-page-delimiter-line { + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 1px; + background: var(--jsondoc-border-light); + z-index: var(--jsondoc-z-base); +} + +.jsondoc-page-number { + background: white; + padding: var(--jsondoc-spacing-xs) var(--jsondoc-spacing-md); + color: var(--jsondoc-text-muted); + font-size: var(--jsondoc-font-size-caption); + font-weight: var(--jsondoc-font-weight-normal); + border-radius: var(--jsondoc-radius-sm); + position: relative; + z-index: var(--jsondoc-z-elevated); + display: inline-block; +} + +/* Dark theme support for page delimiter */ +.jsondoc-theme-dark .jsondoc-page-number { + background: var(--jsondoc-bg-code); + color: var(--jsondoc-text-muted); +} + +.jsondoc-theme-dark .jsondoc-page-delimiter-line { + background: var(--jsondoc-border-light); +} + /* Column Layout */ .notion-column_list-block { margin: var(--jsondoc-spacing-sm) 0; From f7df2ae760fc797b5d7188ccfd03b0d036285a6f Mon Sep 17 00:00:00 2001 From: Abreham Gezahegn Date: Wed, 11 Jun 2025 17:00:18 +0300 Subject: [PATCH 7/8] checkpoint --- typescript/src/renderer/components/dev/DevWrapper.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/typescript/src/renderer/components/dev/DevWrapper.tsx b/typescript/src/renderer/components/dev/DevWrapper.tsx index 206c04a..7141e9f 100644 --- a/typescript/src/renderer/components/dev/DevWrapper.tsx +++ b/typescript/src/renderer/components/dev/DevWrapper.tsx @@ -13,11 +13,11 @@ export const DevWrapper: React.FC = ({ block, children }) => { const [position, setPosition] = useState({ x: 0, y: 0 }); const [isHovered, setIsHovered] = useState(false); - const handleClick = (e: React.MouseEvent) => { + const handleBadgeClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (!showJson) { - setPosition({ x: e.clientX, y: e.clientY }); + setPosition({ x: e.clientX, y: e.clientY + 20 }); setShowJson(true); } }; @@ -27,10 +27,8 @@ export const DevWrapper: React.FC = ({ block, children }) => { className="dev-block-wrapper" onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} - onClick={handleClick} style={{ position: "relative", - cursor: "pointer", outline: isHovered ? "2px dashed #60a5fa" : "none", outlineOffset: "2px", transition: "outline 0.2s ease", @@ -39,6 +37,7 @@ export const DevWrapper: React.FC = ({ block, children }) => { {children} {isHovered && (
= ({ block, children }) => { fontFamily: "monospace", fontWeight: "bold", zIndex: 10, - pointerEvents: "none", + cursor: "pointer", + pointerEvents: "auto", }} > {block.type || "unknown"} From 7eb9d448860bdd6d062cf010033810184912ac37 Mon Sep 17 00:00:00 2001 From: Abreham Gezahegn Date: Wed, 11 Jun 2025 17:11:31 +0300 Subject: [PATCH 8/8] add json viewer --- typescript/src/renderer/JsonDocRenderer.tsx | 104 ++++++++++-------- .../renderer/components/dev/JsonViewPanel.tsx | 24 ++++ 2 files changed, 84 insertions(+), 44 deletions(-) create mode 100644 typescript/src/renderer/components/dev/JsonViewPanel.tsx diff --git a/typescript/src/renderer/JsonDocRenderer.tsx b/typescript/src/renderer/JsonDocRenderer.tsx index e3771a0..ebe34b5 100644 --- a/typescript/src/renderer/JsonDocRenderer.tsx +++ b/typescript/src/renderer/JsonDocRenderer.tsx @@ -3,6 +3,7 @@ import React from "react"; import { BlockRenderer } from "./components/BlockRenderer"; import { PageDelimiter } from "./components/PageDelimiter"; +import { JsonViewPanel } from "./components/dev/JsonViewPanel"; import { RendererProvider } from "./context/RendererContext"; interface JsonDocRendererProps { @@ -12,6 +13,7 @@ interface JsonDocRendererProps { theme?: "light" | "dark"; resolveImageUrl?: (url: string) => Promise; devMode?: boolean; + viewJson?: boolean; } export const JsonDocRenderer = ({ @@ -21,56 +23,70 @@ export const JsonDocRenderer = ({ theme = "light", resolveImageUrl, devMode = false, + viewJson = false, }: JsonDocRendererProps) => { - return ( - -
-
- {/* Page icon */} - {page.icon && ( -
- {page.icon.type === "emoji" && page.icon.emoji} -
- )} + const renderedContent = ( +
+ {/* Page icon */} + {page.icon && ( +
+ {page.icon.type === "emoji" && page.icon.emoji} +
+ )} - {/* Page title */} - {page.properties?.title && ( -

- {page.properties.title.title?.[0]?.plain_text || "Untitled"} -

- )} + {/* Page title */} + {page.properties?.title && ( +

+ {page.properties.title.title?.[0]?.plain_text || "Untitled"} +

+ )} - {/* Page children blocks */} - {page.children && page.children.length > 0 && ( -
- {page.children.map((block: any, index: number) => { - const currentPageNum = block.metadata?.origin?.page_num; - const nextPageNum = - index < page.children.length - 1 - ? page.children[index + 1]?.metadata?.origin?.page_num - : null; + {/* Page children blocks */} + {page.children && page.children.length > 0 && ( +
+ {page.children.map((block: any, index: number) => { + const currentPageNum = block.metadata?.origin?.page_num; + const nextPageNum = + index < page.children.length - 1 + ? page.children[index + 1]?.metadata?.origin?.page_num + : null; - // Show delimiter after the last block of each page - const showPageDelimiter = - currentPageNum && - (nextPageNum !== currentPageNum || index === page.children.length - 1); + // Show delimiter after the last block of each page + const showPageDelimiter = + currentPageNum && + (nextPageNum !== currentPageNum || index === page.children.length - 1); - return ( - - - {showPageDelimiter && ( - - )} - - ); - })} -
- )} + return ( + + + {showPageDelimiter && ( + + )} + + ); + })}
+ )} +
+ ); + + return ( + +
+ {viewJson ? ( +
+
+ {renderedContent} +
+ +
+ ) : ( + renderedContent + )}
); diff --git a/typescript/src/renderer/components/dev/JsonViewPanel.tsx b/typescript/src/renderer/components/dev/JsonViewPanel.tsx new file mode 100644 index 0000000..80be956 --- /dev/null +++ b/typescript/src/renderer/components/dev/JsonViewPanel.tsx @@ -0,0 +1,24 @@ +import React from "react"; + +interface JsonViewPanelProps { + data: any; + title?: string; +} + +export const JsonViewPanel: React.FC = ({ + data, + title = "Raw JSON Data" +}) => { + return ( +
+
+

+ {title} +

+
+          {data ? JSON.stringify(data, null, 2) : 'No data available'}
+        
+
+
+ ); +}; \ No newline at end of file