Skip to content

Commit 07e431a

Browse files
Renderer improvements (#21)
* truncate image caption * make tables horizontally scrollable * dev mode * use context for resolveImage and dev mode * checkpoint * add page delimiter * checkpoint * add json viewer
1 parent 74588a6 commit 07e431a

21 files changed

+774
-71
lines changed

typescript/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

typescript/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"json5": "^2.2.3",
6969
"katex": "^0.16.22",
7070
"puppeteer": "^24.9.0",
71-
"react-intersection-observer": "^9.16.0",
71+
"react-intersection-observer": "^9.13.0",
7272
"strip-json-comments": "^5.0.2"
7373
},
7474
"peerDependencies": {

typescript/src/examples/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { HeadingBlockRenderer } from "@/renderer/components/blocks/HeadingBlockR
66

77
import { JsonDocRenderer } from "../renderer/JsonDocRenderer";
88

9-
import testPage from "./testJsonDocs/ex1_success.json";
9+
import testPage from "./testJsonDocs/test_document.json";
1010

1111
const App = () => {
1212
return (
@@ -23,12 +23,12 @@ const App = () => {
2323
<JsonDocRenderer
2424
page={testPage}
2525
theme="dark"
26+
devMode={true}
2627
components={{
2728
heading_1: (props) => {
2829
return <HeadingBlockRenderer {...props} />;
2930
},
3031
paragraph: (props) => <ParagraphBlockRenderer {...props} />,
31-
// paragraph: (props) =>
3232
}}
3333
/>
3434
</div>

typescript/src/renderer/JsonDocRenderer.tsx

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@ import "./styles/index.css";
22
import React from "react";
33

44
import { BlockRenderer } from "./components/BlockRenderer";
5+
import { PageDelimiter } from "./components/PageDelimiter";
6+
import { JsonViewPanel } from "./components/dev/JsonViewPanel";
7+
import { RendererProvider } from "./context/RendererContext";
58

69
interface JsonDocRendererProps {
710
page: any;
811
className?: string;
912
components?: React.ComponentProps<typeof BlockRenderer>["components"];
1013
theme?: "light" | "dark";
1114
resolveImageUrl?: (url: string) => Promise<string>;
15+
devMode?: boolean;
16+
viewJson?: boolean;
1217
}
1318

1419
export const JsonDocRenderer = ({
@@ -17,39 +22,72 @@ export const JsonDocRenderer = ({
1722
components,
1823
theme = "light",
1924
resolveImageUrl,
25+
devMode = false,
26+
viewJson = false,
2027
}: JsonDocRendererProps) => {
21-
return (
22-
<div className={`json-doc-renderer jsondoc-theme-${theme} ${className}`}>
23-
<div className="json-doc-page">
24-
{/* Page icon */}
25-
{page.icon && (
26-
<div className="json-doc-page-icon">
27-
{page.icon.type === "emoji" && page.icon.emoji}
28-
</div>
29-
)}
28+
const renderedContent = (
29+
<div className="json-doc-page">
30+
{/* Page icon */}
31+
{page.icon && (
32+
<div className="json-doc-page-icon">
33+
{page.icon.type === "emoji" && page.icon.emoji}
34+
</div>
35+
)}
3036

31-
{/* Page title */}
32-
{page.properties?.title && (
33-
<h1 className="json-doc-page-title">
34-
{page.properties.title.title?.[0]?.plain_text || "Untitled"}
35-
</h1>
36-
)}
37+
{/* Page title */}
38+
{page.properties?.title && (
39+
<h1 className="json-doc-page-title">
40+
{page.properties.title.title?.[0]?.plain_text || "Untitled"}
41+
</h1>
42+
)}
43+
44+
{/* Page children blocks */}
45+
{page.children && page.children.length > 0 && (
46+
<div className="json-doc-page-content">
47+
{page.children.map((block: any, index: number) => {
48+
const currentPageNum = block.metadata?.origin?.page_num;
49+
const nextPageNum =
50+
index < page.children.length - 1
51+
? page.children[index + 1]?.metadata?.origin?.page_num
52+
: null;
53+
54+
// Show delimiter after the last block of each page
55+
const showPageDelimiter =
56+
currentPageNum &&
57+
(nextPageNum !== currentPageNum || index === page.children.length - 1);
3758

38-
{/* Page children blocks */}
39-
{page.children && page.children.length > 0 && (
40-
<div className="json-doc-page-content">
41-
{page.children.map((block: any, index: number) => (
42-
<BlockRenderer
43-
key={block.id || index}
44-
block={block}
45-
depth={0}
46-
components={components}
47-
resolveImageUrl={resolveImageUrl}
48-
/>
49-
))}
59+
return (
60+
<React.Fragment key={block.id || index}>
61+
<BlockRenderer
62+
block={block}
63+
depth={0}
64+
components={components}
65+
/>
66+
{showPageDelimiter && (
67+
<PageDelimiter pageNumber={currentPageNum} />
68+
)}
69+
</React.Fragment>
70+
);
71+
})}
72+
</div>
73+
)}
74+
</div>
75+
);
76+
77+
return (
78+
<RendererProvider value={{ devMode, resolveImageUrl }}>
79+
<div className={`json-doc-renderer jsondoc-theme-${theme} ${className}`}>
80+
{viewJson ? (
81+
<div className="flex h-screen">
82+
<div className="w-1/2 overflow-y-auto">
83+
{renderedContent}
84+
</div>
85+
<JsonViewPanel data={page} />
5086
</div>
87+
) : (
88+
renderedContent
5189
)}
5290
</div>
53-
</div>
91+
</RendererProvider>
5492
);
5593
};

typescript/src/renderer/components/BlockRenderer.tsx

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import React from "react";
22

3+
import { useRenderer } from "../context/RendererContext";
4+
5+
import { DevWrapper } from "./dev/DevWrapper";
36
import { ParagraphBlockRenderer } from "./blocks/ParagraphBlockRenderer";
47
import { HeadingBlockRenderer } from "./blocks/HeadingBlockRenderer";
58
import { ListItemBlockRenderer } from "./blocks/ListItemBlockRenderer";
@@ -56,104 +59,113 @@ interface BlockRendererProps {
5659
block: any;
5760
depth?: number;
5861
components?: BlockComponents;
59-
resolveImageUrl?: (url: string) => Promise<string>;
6062
}
6163

6264
export const BlockRenderer: React.FC<BlockRendererProps> = ({
6365
block,
6466
depth = 0,
6567
components,
66-
resolveImageUrl,
6768
}) => {
69+
const { devMode = false } = useRenderer();
6870
const commonProps = { block, depth, components };
6971

72+
// Helper function to wrap component with DevWrapper if devMode is enabled
73+
const wrapWithDev = (component: React.ReactElement) => {
74+
if (devMode) {
75+
return <DevWrapper block={block}>{component}</DevWrapper>;
76+
}
77+
return component;
78+
};
79+
7080
// Paragraph block
7181
if (block?.type === "paragraph") {
7282
const ParagraphComponent = components?.paragraph || ParagraphBlockRenderer;
73-
return <ParagraphComponent {...commonProps} />;
83+
return wrapWithDev(<ParagraphComponent {...commonProps} />);
7484
}
7585

7686
// Heading blocks
7787
if (block?.type === "heading_1") {
7888
const HeadingComponent = components?.heading_1 || HeadingBlockRenderer;
79-
return <HeadingComponent {...commonProps} level={1} />;
89+
return wrapWithDev(<HeadingComponent {...commonProps} level={1} />);
8090
}
8191
if (block?.type === "heading_2") {
8292
const HeadingComponent = components?.heading_2 || HeadingBlockRenderer;
83-
return <HeadingComponent {...commonProps} level={2} />;
93+
return wrapWithDev(<HeadingComponent {...commonProps} level={2} />);
8494
}
8595
if (block?.type === "heading_3") {
8696
const HeadingComponent = components?.heading_3 || HeadingBlockRenderer;
87-
return <HeadingComponent {...commonProps} level={3} />;
97+
return wrapWithDev(<HeadingComponent {...commonProps} level={3} />);
8898
}
8999

90100
// List item blocks
91101
if (block?.type === "bulleted_list_item") {
92102
const BulletedListItemComponent =
93103
components?.bulleted_list_item || ListItemBlockRenderer;
94-
return <BulletedListItemComponent {...commonProps} type="bulleted" />;
104+
return wrapWithDev(
105+
<BulletedListItemComponent {...commonProps} type="bulleted" />
106+
);
95107
}
96108
if (block?.type === "numbered_list_item") {
97109
const NumberedListItemComponent =
98110
components?.numbered_list_item || ListItemBlockRenderer;
99-
return <NumberedListItemComponent {...commonProps} type="numbered" />;
111+
return wrapWithDev(
112+
<NumberedListItemComponent {...commonProps} type="numbered" />
113+
);
100114
}
101115

102116
// Code block
103117
if (block?.type === "code") {
104118
const CodeComponent = components?.code || CodeBlockRenderer;
105-
return <CodeComponent {...commonProps} />;
119+
return wrapWithDev(<CodeComponent {...commonProps} />);
106120
}
107121

108122
// Image block
109123
if (block?.type === "image") {
110124
const ImageComponent = components?.image || ImageBlockRenderer;
111-
return (
112-
<ImageComponent {...commonProps} resolveImageUrl={resolveImageUrl} />
113-
);
125+
return wrapWithDev(<ImageComponent {...commonProps} />);
114126
}
115127

116128
// Table blocks
117129
if (block?.type === "table") {
118130
const TableComponent = components?.table || TableBlockRenderer;
119-
return <TableComponent {...commonProps} />;
131+
return wrapWithDev(<TableComponent {...commonProps} />);
120132
}
121133

122134
// Quote block
123135
if (block?.type === "quote") {
124136
const QuoteComponent = components?.quote || QuoteBlockRenderer;
125-
return <QuoteComponent {...commonProps} />;
137+
return wrapWithDev(<QuoteComponent {...commonProps} />);
126138
}
127139

128140
// Divider block
129141
if (block?.type === "divider") {
130142
const DividerComponent = components?.divider || DividerBlockRenderer;
131-
return <DividerComponent {...commonProps} />;
143+
return wrapWithDev(<DividerComponent {...commonProps} />);
132144
}
133145

134146
// To-do block
135147
if (block?.type === "to_do") {
136148
const ToDoComponent = components?.to_do || ToDoBlockRenderer;
137-
return <ToDoComponent {...commonProps} />;
149+
return wrapWithDev(<ToDoComponent {...commonProps} />);
138150
}
139151

140152
// Toggle block
141153
if (block?.type === "toggle") {
142154
const ToggleComponent = components?.toggle || ToggleBlockRenderer;
143-
return <ToggleComponent {...commonProps} />;
155+
return wrapWithDev(<ToggleComponent {...commonProps} />);
144156
}
145157

146158
// Column list and column blocks
147159
if (block?.type === "column_list") {
148160
const ColumnListComponent =
149161
components?.column_list || ColumnListBlockRenderer;
150-
return <ColumnListComponent {...commonProps} />;
162+
return wrapWithDev(<ColumnListComponent {...commonProps} />);
151163
}
152164

153165
// Equation block
154166
if (block?.type === "equation") {
155167
const EquationComponent = components?.equation || EquationBlockRenderer;
156-
return <EquationComponent {...commonProps} />;
168+
return wrapWithDev(<EquationComponent {...commonProps} />);
157169
}
158170

159171
// Fallback for unsupported block types
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from "react";
2+
3+
interface PageDelimiterProps {
4+
pageNumber: number;
5+
className?: string;
6+
}
7+
8+
export const PageDelimiter: React.FC<PageDelimiterProps> = ({
9+
pageNumber,
10+
className,
11+
}) => {
12+
return (
13+
<div className={`jsondoc-page-delimiter ${className || ""}`.trim()}>
14+
<div className="jsondoc-page-delimiter-line">
15+
<span className="jsondoc-page-number">
16+
Page {pageNumber}
17+
</span>
18+
</div>
19+
</div>
20+
);
21+
};

typescript/src/renderer/components/blocks/ColumnListBlockRenderer.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ interface ColumnListBlockRendererProps
1111

1212
export const ColumnListBlockRenderer: React.FC<
1313
ColumnListBlockRendererProps
14-
> = ({ block, depth = 0, className, components, ...props }) => {
14+
> = ({
15+
block,
16+
depth = 0,
17+
className,
18+
components,
19+
...props
20+
}) => {
1521
return (
1622
<div
1723
{...props}

0 commit comments

Comments
 (0)