Skip to content

Commit ed7786d

Browse files
fix(ui): adjust docx and code rendering (#4334)
* fix(ui): adjust docx and code rendering * fix(ui): address PR feedback on docx fit and Monaco type cast Use computed padding from `.docx-wrapper` instead of a hardcoded 60px so the fit calculation survives docx-preview library changes. Replace the inline `import()` cast for ShowLightbulbIconMode with a top-level type import. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix lint --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e2ded16 commit ed7786d

2 files changed

Lines changed: 72 additions & 13 deletions

File tree

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/docx-preview.tsx

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { memo, useEffect, useRef, useState } from 'react'
3+
import { memo, useCallback, useEffect, useRef, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import { toError } from '@sim/utils/errors'
66
import { cn } from '@/lib/core/utils/cn'
@@ -15,6 +15,42 @@ import {
1515

1616
const logger = createLogger('DocxPreview')
1717

18+
/**
19+
* Fit the rendered docx pages to the host container width using a CSS scale.
20+
* The library renders `<section class="docx">` at the document's natural page
21+
* width (in cm), which overflows narrow panels.
22+
*/
23+
function fitDocxToContainer(host: HTMLElement) {
24+
const wrapper = host.querySelector<HTMLElement>('.docx-wrapper')
25+
if (!wrapper) return
26+
const section = wrapper.querySelector<HTMLElement>('section.docx')
27+
if (!section) return
28+
29+
wrapper.style.transform = ''
30+
wrapper.style.transformOrigin = 'top left'
31+
wrapper.style.width = ''
32+
wrapper.style.marginRight = ''
33+
wrapper.style.marginBottom = ''
34+
35+
const naturalPageWidth = section.offsetWidth
36+
if (!naturalPageWidth) return
37+
38+
const wrapperStyle = window.getComputedStyle(wrapper)
39+
const horizontalPadding =
40+
Number.parseFloat(wrapperStyle.paddingLeft) + Number.parseFloat(wrapperStyle.paddingRight)
41+
const naturalWrapperWidth = naturalPageWidth + horizontalPadding
42+
const available = host.clientWidth
43+
const scale = Math.min(1, available / naturalWrapperWidth)
44+
45+
if (scale >= 1) return
46+
47+
wrapper.style.width = `${naturalWrapperWidth}px`
48+
wrapper.style.transform = `scale(${scale})`
49+
const naturalHeight = wrapper.offsetHeight
50+
wrapper.style.marginRight = `${(scale - 1) * naturalWrapperWidth}px`
51+
wrapper.style.marginBottom = `${(scale - 1) * naturalHeight}px`
52+
}
53+
1854
export const DocxPreview = memo(function DocxPreview({
1955
file,
2056
workspaceId,
@@ -35,6 +71,25 @@ export const DocxPreview = memo(function DocxPreview({
3571
const [rendering, setRendering] = useState(false)
3672
const [hasRenderedPreview, setHasRenderedPreview] = useState(false)
3773

74+
const applyPostRenderStyling = useCallback(() => {
75+
const container = containerRef.current
76+
if (!container) return
77+
const wrapper = container.querySelector<HTMLElement>('.docx-wrapper')
78+
if (wrapper) wrapper.style.background = 'transparent'
79+
container.querySelectorAll<HTMLElement>('section.docx').forEach((page) => {
80+
page.style.boxShadow = 'var(--shadow-medium)'
81+
})
82+
fitDocxToContainer(container)
83+
}, [])
84+
85+
useEffect(() => {
86+
const container = containerRef.current
87+
if (!container) return
88+
const observer = new ResizeObserver(() => fitDocxToContainer(container))
89+
observer.observe(container)
90+
return () => observer.disconnect()
91+
}, [])
92+
3893
useEffect(() => {
3994
if (!containerRef.current || !fileData || streamingContent !== undefined) return
4095

@@ -53,11 +108,7 @@ export const DocxPreview = memo(function DocxPreview({
53108
ignoreHeight: false,
54109
})
55110
if (!cancelled && containerRef.current) {
56-
const wrapper = containerRef.current.querySelector<HTMLElement>('.docx-wrapper')
57-
if (wrapper) wrapper.style.background = 'transparent'
58-
containerRef.current.querySelectorAll<HTMLElement>('section.docx').forEach((page) => {
59-
page.style.boxShadow = 'var(--shadow-medium)'
60-
})
111+
applyPostRenderStyling()
61112
lastSuccessfulHtmlRef.current = containerRef.current.innerHTML
62113
setHasRenderedPreview(true)
63114
}
@@ -78,7 +129,7 @@ export const DocxPreview = memo(function DocxPreview({
78129
return () => {
79130
cancelled = true
80131
}
81-
}, [fileData, streamingContent])
132+
}, [fileData, streamingContent, applyPostRenderStyling])
82133

83134
useEffect(() => {
84135
if (streamingContent === undefined || !containerRef.current) return
@@ -121,18 +172,15 @@ export const DocxPreview = memo(function DocxPreview({
121172
})
122173

123174
if (!cancelled && containerRef.current) {
124-
const wrapper = containerRef.current.querySelector<HTMLElement>('.docx-wrapper')
125-
if (wrapper) wrapper.style.background = 'transparent'
126-
containerRef.current.querySelectorAll<HTMLElement>('section.docx').forEach((page) => {
127-
page.style.boxShadow = 'var(--shadow-medium)'
128-
})
175+
applyPostRenderStyling()
129176
lastSuccessfulHtmlRef.current = containerRef.current.innerHTML
130177
setHasRenderedPreview(true)
131178
}
132179
} catch (err) {
133180
if (!cancelled && !(err instanceof DOMException && err.name === 'AbortError')) {
134181
if (containerRef.current && previousHtml) {
135182
containerRef.current.innerHTML = previousHtml
183+
applyPostRenderStyling()
136184
setHasRenderedPreview(true)
137185
}
138186
const msg = toError(err).message || 'Failed to render document'
@@ -155,7 +203,7 @@ export const DocxPreview = memo(function DocxPreview({
155203
clearTimeout(debounceTimer)
156204
controller.abort()
157205
}
158-
}, [streamingContent, workspaceId])
206+
}, [streamingContent, workspaceId, applyPostRenderStyling])
159207

160208
const error =
161209
hasRenderedPreview && streamingContent !== undefined

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { memo, useCallback, useEffect, useReducer, useRef, useState } from 'react'
44
import type { OnMount } from '@monaco-editor/react'
5+
import type { editor as MonacoEditorTypes } from 'monaco-editor'
56
import dynamic from 'next/dynamic'
67
import { Skeleton } from '@/components/emcn'
78
import { cn } from '@/lib/core/utils/cn'
@@ -645,6 +646,16 @@ export const TextEditor = memo(function TextEditor({
645646
verticalScrollbarSize: 6,
646647
horizontalScrollbarSize: 6,
647648
},
649+
quickSuggestions: false,
650+
suggestOnTriggerCharacters: false,
651+
wordBasedSuggestions: 'off',
652+
parameterHints: { enabled: false },
653+
hover: { enabled: false },
654+
codeLens: false,
655+
lightbulb: {
656+
enabled: 'off' as MonacoEditorTypes.ShowLightbulbIconMode,
657+
},
658+
inlayHints: { enabled: 'off' },
648659
}}
649660
onChange={handleEditorChange}
650661
onMount={handleEditorMount}

0 commit comments

Comments
 (0)