office-viewer-react 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +390 -0
- package/dist/client/assets/DocxViewer-NgAiZAEg.css +1 -0
- package/dist/client/assets/DocxViewer-gwdjm0mw.js +60 -0
- package/dist/client/assets/LogoIcon-BcnkueZW.js +1 -0
- package/dist/client/assets/PptxViewer-CLNaZa_4.js +59 -0
- package/dist/client/assets/PptxViewer-CYMXzyIj.css +1 -0
- package/dist/client/assets/XlsxViewer-BNso6L-X.css +1 -0
- package/dist/client/assets/XlsxViewer-C2ErMokS.js +64 -0
- package/dist/client/assets/_commonjs-dynamic-modules-DaXrHM_S.js +1 -0
- package/dist/client/assets/form-C1byQJR4.js +1 -0
- package/dist/client/assets/index-BDMLGHcR.js +2 -0
- package/dist/client/assets/index-CKjGwz9R.js +12 -0
- package/dist/client/assets/jszip.min-BwIaN_vk.js +2 -0
- package/dist/client/assets/login-DEy3R1iD.js +1 -0
- package/dist/client/assets/register-CUUVGLJE.js +1 -0
- package/dist/client/assets/styles-3a3CPFIV.css +1 -0
- package/dist/client/robots.txt +2 -0
- package/dist/index.cjs +1806 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1769 -0
- package/dist/server/assets/DocxViewer-Bm8UJY-7.js +469 -0
- package/dist/server/assets/LogoIcon-Dx0LU3or.js +26 -0
- package/dist/server/assets/PptxViewer-DS7Atucw.js +213 -0
- package/dist/server/assets/XlsxViewer-jzIgKmN2.js +841 -0
- package/dist/server/assets/_tanstack-start-manifest_v-CpFqMvFH.js +4 -0
- package/dist/server/assets/empty-plugin-adapters-BFgPZ6_d.js +6 -0
- package/dist/server/assets/form-CD9otjw-.js +236 -0
- package/dist/server/assets/index-gQHSGxNv.js +365 -0
- package/dist/server/assets/login-DvbAXNSQ.js +81 -0
- package/dist/server/assets/register-C2G9K9kP.js +102 -0
- package/dist/server/assets/router-F5YKPXkV.js +229 -0
- package/dist/server/assets/server-6Sfy37dh.js +1523 -0
- package/dist/server/assets/start-dMGD6DUy.js +56 -0
- package/dist/server/server.js +94 -0
- package/package.json +120 -0
package/README.md
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# office-viewer
|
|
2
|
+
|
|
3
|
+
A zero-configuration React component for rendering Word, Excel, and PowerPoint files directly in the browser. No server required — everything processes locally.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
| Format | Capability |
|
|
10
|
+
|--------|-----------|
|
|
11
|
+
| **Word (.docx)** | Text, tables, images, headers/footers, continuous scroll and page-by-page navigation, DrawingML charts rendered to PNG via ECharts |
|
|
12
|
+
| **Excel (.xlsx)** | Cell styles and colours, merged cells, date and number formatting, formula values, multi-sheet tab bar with overflow scroll, sticky row and column headers |
|
|
13
|
+
| **PowerPoint (.pptx)** | All slide layouts, images, text, shapes, vertical scroll and slide-by-slide navigation with keyboard arrow key support |
|
|
14
|
+
|
|
15
|
+
- **Zero-config CSS** — styles are auto-injected at runtime; no separate CSS import required
|
|
16
|
+
- **Automatic format detection** — determines the file type from `File.name`, `Blob.type`, or a `filename` hint
|
|
17
|
+
- **Built-in toolbar** — view-mode selector (Continuous Scroll / Page Navigation) rendered internally for Word and PowerPoint; consumers own zero UI state
|
|
18
|
+
- **SSR-safe** — style injection is guarded by `typeof document !== "undefined"`
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install office-viewer
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
yarn add office-viewer
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm add office-viewer
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Peer dependencies
|
|
37
|
+
|
|
38
|
+
The following packages must already be present in your project:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install react react-dom lucide-react
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| Peer | Version |
|
|
45
|
+
|------|---------|
|
|
46
|
+
| `react` | `^18.0.0 \|\| ^19.0.0` |
|
|
47
|
+
| `react-dom` | `^18.0.0 \|\| ^19.0.0` |
|
|
48
|
+
| `lucide-react` | `>=0.400.0` |
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { OfficeViewer } from "office-viewer";
|
|
56
|
+
|
|
57
|
+
export default function App() {
|
|
58
|
+
const [file, setFile] = React.useState<File | null>(null);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
<input
|
|
63
|
+
type="file"
|
|
64
|
+
accept=".docx,.xlsx,.pptx"
|
|
65
|
+
onChange={(e) => setFile(e.target.files?.[0] ?? null)}
|
|
66
|
+
/>
|
|
67
|
+
|
|
68
|
+
{file && (
|
|
69
|
+
<div style={{ height: "100vh" }}>
|
|
70
|
+
<OfficeViewer file={file} />
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
No CSS import. No state for view mode or pagination. No configuration.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Usage
|
|
83
|
+
|
|
84
|
+
### From a file input (`File`)
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { OfficeViewer } from "office-viewer";
|
|
88
|
+
|
|
89
|
+
function FileViewer() {
|
|
90
|
+
const [file, setFile] = React.useState<File | null>(null);
|
|
91
|
+
|
|
92
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
93
|
+
setFile(e.target.files?.[0] ?? null);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div style={{ height: "100vh", display: "flex", flexDirection: "column" }}>
|
|
98
|
+
<input type="file" accept=".docx,.xlsx,.pptx" onChange={handleChange} />
|
|
99
|
+
{file && <OfficeViewer file={file} style={{ flex: 1 }} />}
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### From a `Blob` (e.g. a fetch response)
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { OfficeViewer } from "office-viewer";
|
|
109
|
+
|
|
110
|
+
function RemoteViewer({ url, filename }: { url: string; filename: string }) {
|
|
111
|
+
const [blob, setBlob] = React.useState<Blob | null>(null);
|
|
112
|
+
|
|
113
|
+
React.useEffect(() => {
|
|
114
|
+
fetch(url)
|
|
115
|
+
.then((r) => r.blob())
|
|
116
|
+
.then(setBlob);
|
|
117
|
+
}, [url]);
|
|
118
|
+
|
|
119
|
+
if (!blob) return <p>Loading…</p>;
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<div style={{ height: "80vh" }}>
|
|
123
|
+
{/* Pass filename so the component can detect .docx / .xlsx / .pptx */}
|
|
124
|
+
<OfficeViewer file={blob} filename={filename} />
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### From an `ArrayBuffer`
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import { OfficeViewer } from "office-viewer";
|
|
134
|
+
|
|
135
|
+
function BufferViewer({
|
|
136
|
+
buffer,
|
|
137
|
+
filename,
|
|
138
|
+
}: {
|
|
139
|
+
buffer: ArrayBuffer;
|
|
140
|
+
filename: string;
|
|
141
|
+
}) {
|
|
142
|
+
return (
|
|
143
|
+
<div style={{ height: "80vh" }}>
|
|
144
|
+
<OfficeViewer file={buffer} filename={filename} />
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Error handling
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
import { OfficeViewer } from "office-viewer";
|
|
154
|
+
|
|
155
|
+
function SafeViewer({ file }: { file: File }) {
|
|
156
|
+
const handleError = (err: Error) => {
|
|
157
|
+
console.error("Viewer error:", err.message);
|
|
158
|
+
// show a toast, report to Sentry, etc.
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div style={{ height: "80vh" }}>
|
|
163
|
+
<OfficeViewer file={file} onError={handleError} />
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Custom container styling
|
|
170
|
+
|
|
171
|
+
Pass a `className` to size or position the viewer wrapper:
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
<OfficeViewer file={file} className="h-screen w-full rounded-xl shadow-lg" />
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## API
|
|
180
|
+
|
|
181
|
+
### `<OfficeViewer />`
|
|
182
|
+
|
|
183
|
+
The only public component. All internal state (view mode, page/slide index, sheet selection) is managed inside the component.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
import { OfficeViewer, type OfficeViewerProps } from "office-viewer";
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Props
|
|
190
|
+
|
|
191
|
+
| Prop | Type | Required | Default | Description |
|
|
192
|
+
|------|------|----------|---------|-------------|
|
|
193
|
+
| `file` | `File \| Blob \| ArrayBuffer` | Yes | — | Document to render. The type is detected automatically. |
|
|
194
|
+
| `filename` | `string` | No | `""` | Filename hint for type detection when passing a bare `Blob` or `ArrayBuffer` that carries no name. Example: `"report.xlsx"` |
|
|
195
|
+
| `className` | `string` | No | `""` | CSS class applied to the outermost wrapper `<div>` for sizing and positioning. |
|
|
196
|
+
| `onError` | `(e: Error) => void` | No | — | Called when the document fails to load or render. The component also displays an inline error message. |
|
|
197
|
+
|
|
198
|
+
#### File type detection rules
|
|
199
|
+
|
|
200
|
+
| Input type | How the type is resolved |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `File` | `file.name` extension (`.docx`, `.xlsx`, `.pptx`) |
|
|
203
|
+
| `Blob` with MIME type | `blob.type` — checks for `wordprocessingml`, `spreadsheetml`, `presentationml` |
|
|
204
|
+
| `Blob` without MIME type | `filename` prop extension |
|
|
205
|
+
| `ArrayBuffer` | `filename` prop extension |
|
|
206
|
+
|
|
207
|
+
If the type cannot be determined, an "Unsupported file type" message is shown.
|
|
208
|
+
|
|
209
|
+
#### TypeScript
|
|
210
|
+
|
|
211
|
+
The `OfficeViewerProps` interface is exported for use in wrapper components:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import type { OfficeViewerProps } from "office-viewer";
|
|
215
|
+
|
|
216
|
+
interface MyViewerProps extends Omit<OfficeViewerProps, "onError"> {
|
|
217
|
+
onRenderError?: (err: Error) => void;
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Format details
|
|
224
|
+
|
|
225
|
+
### Word (.docx)
|
|
226
|
+
|
|
227
|
+
- Rendered by [docx-preview](https://github.com/VolodymyrBaydalka/docxjs)
|
|
228
|
+
- DrawingML chart elements are pre-processed before rendering: chart XML is parsed, converted to an [ECharts](https://echarts.apache.org) option, rendered off-screen to a PNG, and inlined as an `<img>` in the document — so charts display correctly even though `docx-preview` does not natively support them
|
|
229
|
+
- Built-in toolbar provides two view modes:
|
|
230
|
+
- **Continuous Scroll** — all pages flow vertically
|
|
231
|
+
- **Page Navigation** — one page at a time with prev/next buttons and a page counter
|
|
232
|
+
|
|
233
|
+
### Excel (.xlsx)
|
|
234
|
+
|
|
235
|
+
- Dual-parser strategy: [ExcelJS](https://github.com/exceljs/exceljs) supplies cell styles, fonts, and colours; [SheetJS](https://sheetjs.com) supplies formula-evaluated values
|
|
236
|
+
- Multi-sheet support — tab bar at the bottom with left/right scroll arrows for wide workbooks
|
|
237
|
+
- Sticky first row (header) and first column
|
|
238
|
+
- Merged cell rendering
|
|
239
|
+
- Date, number, currency, and percentage formatting
|
|
240
|
+
- Bold, italic, text colour, and background colour fidelity
|
|
241
|
+
|
|
242
|
+
### PowerPoint (.pptx)
|
|
243
|
+
|
|
244
|
+
- Rendered by [pptx-preview](https://github.com/meienberger/pptx-preview)
|
|
245
|
+
- Slides re-render automatically when the container resizes (250 ms debounce via `ResizeObserver`)
|
|
246
|
+
- Built-in toolbar provides two view modes:
|
|
247
|
+
- **Vertical Scroll** — all slides flow vertically
|
|
248
|
+
- **Slide Navigation** — one slide at a time with prev/next buttons and a slide counter
|
|
249
|
+
- Keyboard navigation in Slide Navigation mode: `←`/`↑` for previous, `→`/`↓` for next
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Sizing
|
|
254
|
+
|
|
255
|
+
The component uses `width: 100%` and `flex: 1` internally. Give the wrapper a defined height so scroll and navigation modes work correctly:
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
{/* Full-page viewer */}
|
|
259
|
+
<div style={{ height: "100vh" }}>
|
|
260
|
+
<OfficeViewer file={file} />
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
{/* Panel inside a layout */}
|
|
264
|
+
<div style={{ height: "calc(100vh - 64px)" }}>
|
|
265
|
+
<OfficeViewer file={file} />
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
{/* With Tailwind CSS */}
|
|
269
|
+
<OfficeViewer file={file} className="h-screen" />
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
> **Note:** A defined height is especially important for PowerPoint (Slide Navigation mode) and Excel (the grid fills the available space with sticky headers).
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Browser support
|
|
277
|
+
|
|
278
|
+
| Browser | Version |
|
|
279
|
+
|---------|---------|
|
|
280
|
+
| Chrome | 90+ |
|
|
281
|
+
| Edge | 90+ |
|
|
282
|
+
| Firefox | 90+ |
|
|
283
|
+
| Safari | 14+ |
|
|
284
|
+
|
|
285
|
+
Requires support for:
|
|
286
|
+
- `ResizeObserver`
|
|
287
|
+
- `ArrayBuffer` / `File.arrayBuffer()`
|
|
288
|
+
- CSS custom properties
|
|
289
|
+
- `oklch()` color function (Chrome 111+, Firefox 113+, Safari 15.4+) — used for design tokens; falls back gracefully in older browsers
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Examples
|
|
294
|
+
|
|
295
|
+
### Next.js (App Router)
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
// app/viewer/page.tsx
|
|
299
|
+
"use client";
|
|
300
|
+
|
|
301
|
+
import { useState } from "react";
|
|
302
|
+
import { OfficeViewer } from "office-viewer";
|
|
303
|
+
|
|
304
|
+
export default function ViewerPage() {
|
|
305
|
+
const [file, setFile] = useState<File | null>(null);
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<div className="flex h-screen flex-col">
|
|
309
|
+
<header className="flex h-16 items-center px-6 border-b">
|
|
310
|
+
<input
|
|
311
|
+
type="file"
|
|
312
|
+
accept=".docx,.xlsx,.pptx"
|
|
313
|
+
onChange={(e) => setFile(e.target.files?.[0] ?? null)}
|
|
314
|
+
/>
|
|
315
|
+
</header>
|
|
316
|
+
<main className="flex-1 overflow-hidden">
|
|
317
|
+
{file && <OfficeViewer file={file} className="h-full" />}
|
|
318
|
+
</main>
|
|
319
|
+
</div>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Vite + React
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
// src/App.tsx
|
|
328
|
+
import { useState } from "react";
|
|
329
|
+
import { OfficeViewer } from "office-viewer";
|
|
330
|
+
|
|
331
|
+
export default function App() {
|
|
332
|
+
const [file, setFile] = useState<File | null>(null);
|
|
333
|
+
|
|
334
|
+
return (
|
|
335
|
+
<div style={{ height: "100vh", display: "flex", flexDirection: "column" }}>
|
|
336
|
+
<div style={{ padding: "1rem", borderBottom: "1px solid #e2e8f0" }}>
|
|
337
|
+
<input
|
|
338
|
+
type="file"
|
|
339
|
+
accept=".docx,.xlsx,.pptx"
|
|
340
|
+
onChange={(e) => setFile(e.target.files?.[0] ?? null)}
|
|
341
|
+
/>
|
|
342
|
+
</div>
|
|
343
|
+
<div style={{ flex: 1, overflow: "hidden" }}>
|
|
344
|
+
{file && <OfficeViewer file={file} />}
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Fetching a document from an API
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
import { useEffect, useState } from "react";
|
|
355
|
+
import { OfficeViewer } from "office-viewer";
|
|
356
|
+
|
|
357
|
+
function DocumentViewer({ documentId }: { documentId: string }) {
|
|
358
|
+
const [buffer, setBuffer] = useState<ArrayBuffer | null>(null);
|
|
359
|
+
const [filename, setFilename] = useState("");
|
|
360
|
+
const [loading, setLoading] = useState(true);
|
|
361
|
+
|
|
362
|
+
useEffect(() => {
|
|
363
|
+
setLoading(true);
|
|
364
|
+
fetch(`/api/documents/${documentId}`)
|
|
365
|
+
.then(async (res) => {
|
|
366
|
+
// Read filename from Content-Disposition header if present
|
|
367
|
+
const disposition = res.headers.get("Content-Disposition") ?? "";
|
|
368
|
+
const match = disposition.match(/filename="?([^"]+)"?/);
|
|
369
|
+
if (match) setFilename(match[1]);
|
|
370
|
+
return res.arrayBuffer();
|
|
371
|
+
})
|
|
372
|
+
.then((ab) => { setBuffer(ab); setLoading(false); });
|
|
373
|
+
}, [documentId]);
|
|
374
|
+
|
|
375
|
+
if (loading) return <p>Loading document…</p>;
|
|
376
|
+
if (!buffer) return <p>Failed to load document.</p>;
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<div style={{ height: "80vh" }}>
|
|
380
|
+
<OfficeViewer file={buffer} filename={filename} />
|
|
381
|
+
</div>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## License
|
|
389
|
+
|
|
390
|
+
MIT © Virtualan Software
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.ov-docx{flex-direction:column;display:flex}.ov-docx__loading{color:var(--ov-muted-fg);flex-direction:column;align-items:center;gap:.5rem;padding-block:4rem;display:flex}.ov-docx__loading-icon{width:1.5rem;height:1.5rem;animation:1s linear infinite ov-spin}.ov-docx__loading-text{font-size:.875rem}.ov-docx__error{margin-inline:auto;border:1px solid color-mix(in oklch,var(--ov-destructive) 30%,transparent);background:color-mix(in oklch,var(--ov-destructive) 5%,transparent);max-width:32rem;color:var(--ov-destructive);border-radius:.5rem;margin-top:3rem;padding:1rem 1.5rem;font-size:.875rem}.ov-docx__body{justify-content:center;padding-block:2rem;display:flex;position:relative}.ov-docx__render{width:100%}.ov-docx__nav{z-index:10;justify-content:center;align-items:center;gap:.75rem;padding-bottom:.5rem;display:flex;position:sticky;bottom:1.5rem}.ov-docx__nav-btn{cursor:pointer;background:#fff;border:2px solid #e2e8f0;border-radius:9999px;justify-content:center;align-items:center;width:3.75rem;height:3.75rem;transition:transform .15s cubic-bezier(.4,0,.2,1),border-color .15s cubic-bezier(.4,0,.2,1);display:flex;box-shadow:0 2px 8px #0f172a24,0 1px 3px #0f172a1a}.ov-docx__nav-btn:hover{border-color:#cbd5e1;transform:scale(1.05)}.ov-docx__nav-btn:disabled{cursor:not-allowed;opacity:.25}.ov-docx__nav-btn-icon{color:#1e293b;width:1.5rem;height:1.5rem}.ov-docx__nav-counter{text-align:center;background:#fff;border:1px solid #e2e8f0;border-radius:9999px;min-width:7.5rem;padding:.4375rem 1.25rem;box-shadow:0 1px 3px #0f172a12}.ov-docx__nav-current{color:#334155;font-size:13px;font-weight:600}.ov-docx__nav-total{color:#94a3b8;font-size:13px}.ov-container .docx-wrapper{background:0 0!important;padding:0!important}.ov-container .docx-wrapper>section.docx{box-shadow:var(--ov-shadow-page);background:var(--ov-page);margin-bottom:1.5rem!important}
|