From 1958e62ee6579e703fd43eb5fffb81ea53a66dcc Mon Sep 17 00:00:00 2001
From: electroluxcode <3451613934@qq.com>
Date: Sat, 11 Jul 2026 18:35:56 +0800
Subject: [PATCH 1/4] perf: refactor demo and internal & add logger & fix
resource error in console
---
README.md | 32 +-
README.zh.md | 32 +-
public/packages/onlyoffice/9.3.0/plugins.json | 3 +
.../sdkjs-plugins/marketplace/3rd-Party.txt | 7 -
.../sdkjs-plugins/marketplace/CHANGELOG.md | 29 -
.../sdkjs-plugins/marketplace/config.json | 88 -
.../sdkjs-plugins/marketplace/developer.html | 38 -
.../sdkjs-plugins/marketplace/index.html | 38 -
.../marketplace/licenses/Select2.license | 21 -
.../marketplace/licenses/jQuery.license | 20 -
.../marketplace/resources/css/styles.css | 74 -
.../marketplace/resources/dark/icon.png | Bin 320 -> 0 bytes
.../marketplace/resources/dark/icon.svg | 3 -
.../marketplace/resources/dark/icon@1.25x.png | Bin 397 -> 0 bytes
.../marketplace/resources/dark/icon@1.5x.png | Bin 446 -> 0 bytes
.../marketplace/resources/dark/icon@1.75x.png | Bin 486 -> 0 bytes
.../marketplace/resources/dark/icon@2x.png | Bin 560 -> 0 bytes
.../marketplace/resources/img/warnings_s.svg | 6 -
.../marketplace/resources/light/icon.png | Bin 410 -> 0 bytes
.../marketplace/resources/light/icon.svg | 3 -
.../resources/light/icon@1.25x.png | Bin 505 -> 0 bytes
.../marketplace/resources/light/icon@1.5x.png | Bin 566 -> 0 bytes
.../resources/light/icon@1.75x.png | Bin 701 -> 0 bytes
.../marketplace/resources/light/icon@2x.png | Bin 768 -> 0 bytes
.../sdkjs-plugins/marketplace/scripts/code.js | 338 ----
.../marketplace/scripts/developer.js | 49 -
.../marketplace/scripts/warning.js | 34 -
.../marketplace/translations/cs-CZ.json | 14 -
.../marketplace/translations/de-DE.json | 14 -
.../marketplace/translations/es-ES.json | 14 -
.../marketplace/translations/fr-FR.json | 14 -
.../marketplace/translations/it-IT.json | 14 -
.../marketplace/translations/ja-JA.json | 14 -
.../marketplace/translations/langs.json | 14 -
.../marketplace/translations/pt-BR.json | 14 -
.../marketplace/translations/ru-RU.json | 14 -
.../marketplace/translations/si-SI.json | 11 -
.../marketplace/translations/sq-AL.json | 14 -
.../marketplace/translations/sr-Cyrl-RS.json | 14 -
.../marketplace/translations/sr-Latn-RS.json | 14 -
.../marketplace/translations/zh-ZH.json | 14 -
.../vendor/jquery/jquery-3.7.1.min.js | 2 -
.../sdkjs-plugins/marketplace/warning.html | 38 -
.../sdkjs-plugins/plugin-list-default.json | 13 -
.../9.3.0/sdkjs-plugins/pluginBase.js | 95 -
.../9.3.0/sdkjs-plugins/plugins.css | 678 -------
.../9.3.0/sdkjs-plugins/v1/plugins-ui.js | 54 -
.../9.3.0/sdkjs-plugins/v1/plugins.css | 678 -------
.../9.3.0/sdkjs-plugins/v1/plugins.js | 39 -
public/packages/onlyoffice/9.3.0/themes.json | 3 +
src/app/docs/demos/multi/page.tsx | 19 +-
src/app/docs/demos/page.tsx | 40 +-
src/app/docs/demos/single/page.tsx | 26 +-
src/app/examples/page.tsx | 2 +-
src/app/multi/page.tsx | 2 +-
.../onlyoffice-web-comp/const/index.ts | 35 +-
.../core/editor-manager.ts | 35 +-
.../core/onlyoffice-manager.ts | 23 +-
.../docs/00-\346\246\202\350\277\260.md" | 68 -
.../docs/API\345\217\202\350\200\203.md" | 15 +-
...13\344\273\266\347\263\273\347\273\237.md" | 6 +-
...36\344\276\213\347\244\272\344\276\213.md" | 9 +-
...36\344\276\213\347\244\272\344\276\213.md" | 7 +-
...27\344\275\223\351\205\215\347\275\256.md" | 4 +-
...14\346\225\264\347\244\272\344\276\213.md" | 7 +-
...53\351\200\237\345\274\200\345\247\213.md" | 8 +-
...7\256\350\256\242\344\270\216-Word-API.md" | 4 +-
.../docs/\346\240\270\345\277\203API.md" | 15 +-
.../docs/\346\246\202\350\277\260.md" | 68 +
...57\346\214\201\346\240\274\345\274\217.md" | 10 +-
src/components/onlyoffice-web-comp/index.ts | 8 +-
.../internal/editor/cross-origin-bridge.ts | 596 -------
.../internal/editor/cross-origin-protocol.ts | 41 -
.../internal/editor/csv-to-xlsx.ts | 27 -
.../internal/editor/fetch.ts | 53 -
.../internal/editor/install-proxies.ts | 350 ----
.../internal/editor/logger.ts | 135 ++
.../internal/editor/office-format.ts | 602 +++++++
.../internal/editor/plain-text-office.ts | 236 ---
.../internal/editor/plugins.ts | 75 -
.../internal/editor/runtime-bridge.ts | 1581 +++++++++++++++++
.../internal/editor/server.ts | 655 +++++--
.../internal/editor/socket.ts | 236 ---
.../internal/editor/types.ts | 72 +-
.../internal/editor/utils.ts | 53 +-
.../editor/{fetch-brotli.ts => x2t-assets.ts} | 84 +-
.../internal/editor/x2t-pdf-fonts.ts | 70 -
.../internal/editor/x2t.ts | 94 +-
.../internal/editor/x2t.worker.ts | 55 +-
.../internal/editor/xhr.ts | 312 ----
.../internal/editor/zip.ts | 333 ----
src/components/onlyoffice-web-comp/readme.md | 38 +-
.../onlyoffice-web-comp/readme.zh.md | 38 +-
.../onlyoffice-web-comp/store/document.ts | 16 +-
.../onlyoffice-web-comp/type/sdk-internal.ts | 8 +-
.../onlyoffice-web-comp/util/x2t.ts | 23 +-
src/features/demo/office-preview-page.tsx | 7 +-
src/features/demo/tabs-multi-page.tsx | 9 +-
.../docs/components/docs-demo-page.tsx | 50 +
.../docs/components/docs-demos-tabs-page.tsx | 86 -
src/features/docs/components/docs-shell.tsx | 11 +-
src/features/docs/config/site-map.ts | 36 +-
src/features/marketing/home-page.tsx | 2 +-
src/features/shell/site-header.tsx | 4 +-
104 files changed, 3527 insertions(+), 5480 deletions(-)
create mode 100644 public/packages/onlyoffice/9.3.0/plugins.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/3rd-Party.txt
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/CHANGELOG.md
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/config.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/developer.html
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/index.html
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/licenses/Select2.license
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/licenses/jQuery.license
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/css/styles.css
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon.png
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon.svg
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon@1.25x.png
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon@1.5x.png
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon@1.75x.png
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon@2x.png
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/img/warnings_s.svg
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/light/icon.png
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/light/icon.svg
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/light/icon@1.25x.png
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/light/icon@1.5x.png
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/light/icon@1.75x.png
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/light/icon@2x.png
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/scripts/code.js
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/scripts/developer.js
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/scripts/warning.js
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/cs-CZ.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/de-DE.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/es-ES.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/fr-FR.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/it-IT.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/ja-JA.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/langs.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/pt-BR.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/ru-RU.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/si-SI.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/sq-AL.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/sr-Cyrl-RS.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/sr-Latn-RS.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/zh-ZH.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/vendor/jquery/jquery-3.7.1.min.js
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/warning.html
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/plugin-list-default.json
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/pluginBase.js
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/plugins.css
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/v1/plugins-ui.js
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/v1/plugins.css
delete mode 100644 public/packages/onlyoffice/9.3.0/sdkjs-plugins/v1/plugins.js
create mode 100644 public/packages/onlyoffice/9.3.0/themes.json
delete mode 100644 "src/components/onlyoffice-web-comp/docs/00-\346\246\202\350\277\260.md"
rename "src/components/onlyoffice-web-comp/docs/05-API\345\217\202\350\200\203.md" => "src/components/onlyoffice-web-comp/docs/API\345\217\202\350\200\203.md" (92%)
rename "src/components/onlyoffice-web-comp/docs/03-\344\272\213\344\273\266\347\263\273\347\273\237.md" => "src/components/onlyoffice-web-comp/docs/\344\272\213\344\273\266\347\263\273\347\273\237.md" (96%)
rename "src/components/onlyoffice-web-comp/docs/08-\345\215\225\345\256\236\344\276\213\347\244\272\344\276\213.md" => "src/components/onlyoffice-web-comp/docs/\345\215\225\345\256\236\344\276\213\347\244\272\344\276\213.md" (92%)
rename "src/components/onlyoffice-web-comp/docs/09-\345\244\232\345\256\236\344\276\213\347\244\272\344\276\213.md" => "src/components/onlyoffice-web-comp/docs/\345\244\232\345\256\236\344\276\213\347\244\272\344\276\213.md" (98%)
rename "src/components/onlyoffice-web-comp/docs/10-\345\255\227\344\275\223\351\205\215\347\275\256.md" => "src/components/onlyoffice-web-comp/docs/\345\255\227\344\275\223\351\205\215\347\275\256.md" (97%)
rename "src/components/onlyoffice-web-comp/docs/04-\345\256\214\346\225\264\347\244\272\344\276\213.md" => "src/components/onlyoffice-web-comp/docs/\345\256\214\346\225\264\347\244\272\344\276\213.md" (95%)
rename "src/components/onlyoffice-web-comp/docs/01-\345\277\253\351\200\237\345\274\200\345\247\213.md" => "src/components/onlyoffice-web-comp/docs/\345\277\253\351\200\237\345\274\200\345\247\213.md" (92%)
rename "src/components/onlyoffice-web-comp/docs/07-\346\211\271\346\263\250\344\277\256\350\256\242\344\270\216-Word-API.md" => "src/components/onlyoffice-web-comp/docs/\346\211\271\346\263\250\344\277\256\350\256\242\344\270\216-Word-API.md" (97%)
rename "src/components/onlyoffice-web-comp/docs/02-\346\240\270\345\277\203API.md" => "src/components/onlyoffice-web-comp/docs/\346\240\270\345\277\203API.md" (92%)
create mode 100644 "src/components/onlyoffice-web-comp/docs/\346\246\202\350\277\260.md"
rename "src/components/onlyoffice-web-comp/docs/06-\346\263\250\346\204\217\344\272\213\351\241\271\344\270\216\346\224\257\346\214\201\346\240\274\345\274\217.md" => "src/components/onlyoffice-web-comp/docs/\346\263\250\346\204\217\344\272\213\351\241\271\344\270\216\346\224\257\346\214\201\346\240\274\345\274\217.md" (82%)
delete mode 100644 src/components/onlyoffice-web-comp/internal/editor/cross-origin-bridge.ts
delete mode 100644 src/components/onlyoffice-web-comp/internal/editor/cross-origin-protocol.ts
delete mode 100644 src/components/onlyoffice-web-comp/internal/editor/csv-to-xlsx.ts
delete mode 100644 src/components/onlyoffice-web-comp/internal/editor/fetch.ts
delete mode 100644 src/components/onlyoffice-web-comp/internal/editor/install-proxies.ts
create mode 100644 src/components/onlyoffice-web-comp/internal/editor/logger.ts
create mode 100644 src/components/onlyoffice-web-comp/internal/editor/office-format.ts
delete mode 100644 src/components/onlyoffice-web-comp/internal/editor/plain-text-office.ts
delete mode 100644 src/components/onlyoffice-web-comp/internal/editor/plugins.ts
create mode 100644 src/components/onlyoffice-web-comp/internal/editor/runtime-bridge.ts
delete mode 100644 src/components/onlyoffice-web-comp/internal/editor/socket.ts
rename src/components/onlyoffice-web-comp/internal/editor/{fetch-brotli.ts => x2t-assets.ts} (53%)
delete mode 100644 src/components/onlyoffice-web-comp/internal/editor/x2t-pdf-fonts.ts
delete mode 100644 src/components/onlyoffice-web-comp/internal/editor/xhr.ts
delete mode 100644 src/components/onlyoffice-web-comp/internal/editor/zip.ts
create mode 100644 src/features/docs/components/docs-demo-page.tsx
delete mode 100644 src/features/docs/components/docs-demos-tabs-page.tsx
diff --git a/README.md b/README.md
index 1e77adac..d27fe1cf 100644
--- a/README.md
+++ b/README.md
@@ -38,32 +38,32 @@ pnpm dev
|-------|-------------|
| `/` | Product landing page |
| `/docs` | Component library documentation (rendered from Markdown) |
-| `/docs/demos?tab=single` | Single-instance editor demo |
-| `/docs/demos?tab=multi` | Multi-instance Tab demo |
+| `/docs/demos/single` | Single-instance editor demo |
+| `/docs/demos/multi` | Multi-instance Tab demo |
3. Upload a file → edit → export
-Legacy routes `/examples` and `/multi` redirect to the multi-instance demo tab.
+Legacy routes `/examples` and `/multi` redirect to the multi-instance demo.
## Component Library Docs
**API details live in the component library docs**, not duplicated here.
- **Entry**: [Component README (English)](src/components/onlyoffice-web-comp/readme.md)
-- **Overview**: [docs/00-概述.md](src/components/onlyoffice-web-comp/docs/00-概述.md)
+- **Overview**: [docs/概述.md](src/components/onlyoffice-web-comp/docs/概述.md)
| Doc | Topic |
|-----|-------|
-| [01 Quick Start](src/components/onlyoffice-web-comp/docs/01-快速开始.md) | Init and container mount |
-| [02 Core API](src/components/onlyoffice-web-comp/docs/02-核心API.md) | `OnlyOfficeManager`, multi-instance |
-| [03 Events](src/components/onlyoffice-web-comp/docs/03-事件系统.md) | EventBus |
-| [04 Examples](src/components/onlyoffice-web-comp/docs/04-完整示例.md) | React integration patterns |
-| [05 Reference](src/components/onlyoffice-web-comp/docs/05-API参考.md) | Constants and types |
-| [06 Notes & Formats](src/components/onlyoffice-web-comp/docs/06-注意事项与支持格式.md) | Prerequisites and formats |
-| [10 Fonts](src/components/onlyoffice-web-comp/docs/10-字体配置.md) | Custom font registration |
-| [07 Comments & Revisions](src/components/onlyoffice-web-comp/docs/07-批注修订与-Word-API.md) | Comments and revisions |
-| [08 Single-instance Demo](src/components/onlyoffice-web-comp/docs/08-单实例示例.md) | Single editor demo + source |
-| [09 Multi-instance Demo](src/components/onlyoffice-web-comp/docs/09-多实例示例.md) | Full Tab demo source |
+| [Quick Start](src/components/onlyoffice-web-comp/docs/快速开始.md) | Init and container mount |
+| [Core API](src/components/onlyoffice-web-comp/docs/核心API.md) | `OnlyOfficeManager`, multi-instance |
+| [Events](src/components/onlyoffice-web-comp/docs/事件系统.md) | EventBus |
+| [Examples](src/components/onlyoffice-web-comp/docs/完整示例.md) | React integration patterns |
+| [Reference](src/components/onlyoffice-web-comp/docs/API参考.md) | Constants and types |
+| [Notes & Formats](src/components/onlyoffice-web-comp/docs/注意事项与支持格式.md) | Prerequisites and formats |
+| [Fonts](src/components/onlyoffice-web-comp/docs/字体配置.md) | Custom font registration |
+| [Comments & Revisions](src/components/onlyoffice-web-comp/docs/批注修订与-Word-API.md) | Comments and revisions |
+| [Single-instance Demo](src/components/onlyoffice-web-comp/docs/单实例示例.md) | Single editor demo + source |
+| [Multi-instance Demo](src/components/onlyoffice-web-comp/docs/多实例示例.md) | Full Tab demo source |
```typescript
import { OnlyOfficeManager, FILE_TYPE, ONLYOFFICE_ID } from "@/components/onlyoffice-web-comp";
@@ -79,7 +79,7 @@ onlyoffice-web-comp/
│ │ ├── docs/ # Documentation site
│ │ │ ├── page.tsx # /docs (overview md)
│ │ │ ├── [slug]/page.tsx # /docs/*
-│ │ │ └── demos/page.tsx # /docs/demos?tab=single|multi
+│ │ │ └── demos/ # /docs/demos/single|multi
│ │ └── examples/ # → redirect to multi demo
│ ├── features/
│ │ ├── docs/ # Docs shell, markdown renderer, site-map
@@ -143,7 +143,7 @@ OnlyOfficeManager.registerStaticResource({
## Fonts
-Custom fonts are registered via **`__custom_font_registry__`**, with **`ttf-to-catalog-font.mjs`** producing OnlyOffice catalog wire-format files. See **[10 - Fonts](src/components/onlyoffice-web-comp/docs/10-字体配置.md)** in the component docs for the full guide.
+Custom fonts are registered via **`__custom_font_registry__`**, with **`ttf-to-catalog-font.mjs`** producing OnlyOffice catalog wire-format files. See **[Fonts](src/components/onlyoffice-web-comp/docs/字体配置.md)** in the component docs for the full guide.
Quick outline:
diff --git a/README.zh.md b/README.zh.md
index b5f45e57..4c0b9a2f 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -38,32 +38,32 @@ pnpm dev
|------|------|
| `/` | 产品主页 |
| `/docs` | 组件库文档(直接渲染 Markdown) |
-| `/docs/demos?tab=single` | 单实例在线示例 |
-| `/docs/demos?tab=multi` | 多实例 Tab 在线示例 |
+| `/docs/demos/single` | 单实例在线示例 |
+| `/docs/demos/multi` | 多实例 Tab 在线示例 |
3. 上传本地文件 → 编辑 → 导出
-旧路由 `/examples`、`/multi` 会重定向到多实例示例 Tab。
+旧路由 `/examples`、`/multi` 会重定向到多实例示例。
## 组件库文档
**API 与接入说明不在本 README 重复**,请阅读组件库文档:
- **入口**:[组件库 README(中文)](src/components/onlyoffice-web-comp/readme.zh.md)
-- **概述**:[docs/00-概述.md](src/components/onlyoffice-web-comp/docs/00-概述.md)
+- **概述**:[docs/概述.md](src/components/onlyoffice-web-comp/docs/概述.md)
| 文档 | 内容 |
|------|------|
-| [01-快速开始](src/components/onlyoffice-web-comp/docs/01-快速开始.md) | 初始化与容器挂载 |
-| [02-核心API](src/components/onlyoffice-web-comp/docs/02-核心API.md) | `OnlyOfficeManager`、多实例 |
-| [03-事件系统](src/components/onlyoffice-web-comp/docs/03-事件系统.md) | EventBus |
-| [04-完整示例](src/components/onlyoffice-web-comp/docs/04-完整示例.md) | React 集成模式 |
-| [05-API参考](src/components/onlyoffice-web-comp/docs/05-API参考.md) | 常量与类型 |
-| [06-注意事项与格式](src/components/onlyoffice-web-comp/docs/06-注意事项与支持格式.md) | 前置条件与格式 |
-| [10-字体配置](src/components/onlyoffice-web-comp/docs/10-字体配置.md) | 自定义字体注册 |
-| [07-批注修订](src/components/onlyoffice-web-comp/docs/07-批注修订与-Word-API.md) | 批注、修订 |
-| [08-单实例示例](src/components/onlyoffice-web-comp/docs/08-单实例示例.md) | 单实例 Demo 与源码说明 |
-| [09-多实例示例](src/components/onlyoffice-web-comp/docs/09-多实例示例.md) | Tab 多实例完整源码 |
+| [快速开始](src/components/onlyoffice-web-comp/docs/快速开始.md) | 初始化与容器挂载 |
+| [核心API](src/components/onlyoffice-web-comp/docs/核心API.md) | `OnlyOfficeManager`、多实例 |
+| [事件系统](src/components/onlyoffice-web-comp/docs/事件系统.md) | EventBus |
+| [完整示例](src/components/onlyoffice-web-comp/docs/完整示例.md) | React 集成模式 |
+| [API参考](src/components/onlyoffice-web-comp/docs/API参考.md) | 常量与类型 |
+| [注意事项与格式](src/components/onlyoffice-web-comp/docs/注意事项与支持格式.md) | 前置条件与格式 |
+| [字体配置](src/components/onlyoffice-web-comp/docs/字体配置.md) | 自定义字体注册 |
+| [批注修订](src/components/onlyoffice-web-comp/docs/批注修订与-Word-API.md) | 批注、修订 |
+| [单实例示例](src/components/onlyoffice-web-comp/docs/单实例示例.md) | 单实例 Demo 与源码说明 |
+| [多实例示例](src/components/onlyoffice-web-comp/docs/多实例示例.md) | Tab 多实例完整源码 |
```typescript
import { OnlyOfficeManager, FILE_TYPE, ONLYOFFICE_ID } from "@/components/onlyoffice-web-comp";
@@ -79,7 +79,7 @@ onlyoffice-web-comp/
│ │ ├── docs/ # 文档站
│ │ │ ├── page.tsx # /docs(概述 md)
│ │ │ ├── [slug]/page.tsx # /docs/*
-│ │ │ └── demos/page.tsx # /docs/demos?tab=single|multi
+│ │ │ └── demos/ # /docs/demos/single|multi
│ │ └── examples/ # → 重定向至多实例示例
│ ├── features/
│ │ ├── docs/ # 文档壳、Markdown 渲染、site-map
@@ -143,7 +143,7 @@ OnlyOfficeManager.registerStaticResource({
## 字体配置
-自定义字体通过 **`__custom_font_registry__`** 注册,配合 **`ttf-to-catalog-font.mjs`** 生成 OnlyOffice catalog 线格式。完整步骤见组件库文档 **[10 - 字体配置](src/components/onlyoffice-web-comp/docs/10-字体配置.md)**。
+自定义字体通过 **`__custom_font_registry__`** 注册,配合 **`ttf-to-catalog-font.mjs`** 生成 OnlyOffice catalog 线格式。完整步骤见组件库文档 **[字体配置](src/components/onlyoffice-web-comp/docs/字体配置.md)**。
简要流程:
diff --git a/public/packages/onlyoffice/9.3.0/plugins.json b/public/packages/onlyoffice/9.3.0/plugins.json
new file mode 100644
index 00000000..62895290
--- /dev/null
+++ b/public/packages/onlyoffice/9.3.0/plugins.json
@@ -0,0 +1,3 @@
+{
+ "pluginsData": []
+}
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/3rd-Party.txt b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/3rd-Party.txt
deleted file mode 100644
index aa030cb0..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/3rd-Party.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This plugin uses code from the following 3rd party projects.
-
-
-1. jQuery - Query is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. (http://jquery.com/)
-
-License: MIT License
-License File: jQuery.license
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/CHANGELOG.md b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/CHANGELOG.md
deleted file mode 100644
index acbc2006..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/CHANGELOG.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# Change Log
-
-## 1.0.0
-
-* Initial release.
-
-## 1.0.1
-
-* Add loader.
-* Fix problem with internet checking.
-* Fix problem for Windows XP.
-
-## 1.0.2
-
-* Enable plugin for viewer mode.
-* Add button and new window for developer mode.
-* Rename window for notification when removeing plugin into desktop.
-
-## 1.0.3
-
-* Add "pdf" to the supported editors.
-
-## 1.0.4
-
-* Change developer window (add warnign - bug 66451).
-
-## 1.0.5
-
-* Fix problem into desktop with deletting manually installed plugins.
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/config.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/config.json
deleted file mode 100644
index 59519625..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/config.json
+++ /dev/null
@@ -1,88 +0,0 @@
-{
- "name" : "Plugin Manager",
- "nameLocale": {
- "ru": "Менеджер плагинов",
- "fr": "Gestionnaire de Plugins",
- "it": "Plugin Manager",
- "ja": "プラグインマネージャー",
- "es": "Administrador de Complementos",
- "de": "Plugin-Verwaltung",
- "cs": "Správce Pluginů",
- "si": "පේනු කළමනාකරු",
- "zh": "插件管理器",
- "pt-BR": "Gerenciador de plug-ins",
- "sq-AL": "Menaxheri i shtojcës",
- "sr-Cyrl-RS": "Менаџер додатака",
- "sr-Latn-RS": "Menadžer dodataka"
- },
- "guid" : "asc.{AA2EA9B6-9EC2-415F-9762-634EE8D9A95E}",
- "version": "1.0.5",
- "group" : {
- "name" : "Plugin Manager",
- "rank" : 1
- },
- "loader": false,
- "variations" : [
- {
- "description" : "Plugin Manager",
- "url" : "index.html",
- "icons": [ "resources/light/icon.png", "resources/light/icon@2x.png" ],
- "icons2": [
- {
- "style" : "light",
-
- "100%": {
- "normal": "resources/light/icon.png"
- },
- "125%": {
- "normal": "resources/light/icon@1.25x.png"
- },
- "150%": {
- "normal": "resources/light/icon@1.5x.png"
- },
- "175%": {
- "normal": "resources/light/icon@1.75x.png"
- },
- "200%": {
- "normal": "resources/light/icon@2x.png"
- },
- "*": {
- "normal": "resources/light/icon.svg"
- }
- },
- {
- "style" : "dark",
-
- "100%": {
- "normal": "resources/dark/icon.png"
- },
- "125%": {
- "normal": "resources/dark/icon@1.25x.png"
- },
- "150%": {
- "normal": "resources/dark/icon@1.5x.png"
- },
- "175%": {
- "normal": "resources/dark/icon@1.75x.png"
- },
- "200%": {
- "normal": "resources/dark/icon@2x.png"
- },
- "*": {
- "normal": "resources/dark/icon.svg"
- }
- }
- ],
- "isViewer" : true,
- "EditorsSupport" : ["word", "cell", "slide", "pdf"],
- "isVisual" : true,
- "isModal" : true,
- "isInsideMode" : false,
- "isSystem" : false,
- "initDataType" : "none",
- "isUpdateOleOnResize" : false,
- "size" : [608, 600],
- "buttons" : []
- }
- ]
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/developer.html b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/developer.html
deleted file mode 100644
index a93bee8c..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/developer.html
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
- Developer Mode
-
-
-
-
-
-
-
-
-
-
-
Be careful! Avoid using the untrusted URLs.
-
-
Enter the URL for your marketplace page here
-
-
Return to default settings
-
-
-
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/index.html b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/index.html
deleted file mode 100644
index 5a93aa3b..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/index.html
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
- Marketplace
-
-
-
-
-
-
-
-
-
-
-
-
-
- No Internet Connection.
-
-
-
-
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/licenses/Select2.license b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/licenses/Select2.license
deleted file mode 100644
index 8cb8a2b1..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/licenses/Select2.license
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2012-2017 Kevin Brown, Igor Vaynberg, and Select2 contributors
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/licenses/jQuery.license b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/licenses/jQuery.license
deleted file mode 100644
index 1e8b433d..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/licenses/jQuery.license
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2009 John Resig, http://jquery.com/
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/css/styles.css b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/css/styles.css
deleted file mode 100644
index 478050a3..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/css/styles.css
+++ /dev/null
@@ -1,74 +0,0 @@
-iframe {
- width: 100%;
- height: 100%;
- border: none;
-}
-html, body {
- margin: 0px !important;
- width: 100%;
- height: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
- flex-flow: column;
-}
-.notification {
- display: flex;
- justify-content: center;
- align-items: center;
-}
-.hidden {
- display: none !important;
-}
-.lab{
- border: none !important;
- background-color: transparent !important;
- font-size: 15px !important;
-}
-.img_warning {
- background: url(../img/warnings_s.svg) no-repeat center;
- width: 40px;
- height: 40px;
-}
-.modal {
- margin: 10px;
-}
-.warning {
- display: flex;
- /* justify-content: center; */
- align-items: center;
-}
-.question {
- margin-left: 5px;
- color: red;
-}
-.info {
- margin-top: 10px;
-}
-.loader {
- margin: 0 !important;
- position: absolute;
- right: 0;
- top: 0;
- width: 100%;
- height: 100% !important;
- background-color: rgba(128, 128, 128, 0.5);
-}
-.div_developer {
- display: flex;
- flex-flow: column;
- width: calc(100% - 40px);
- margin: 20px;
-}
-.link_submit {
- text-decoration: underline dotted !important;
- text-underline-offset: 4px;
- margin-top: 15px;
- cursor: pointer;
-}
-.link_submit:hover {
- text-decoration: underline dotted !important;
-}
-.lab_developer {
- margin-bottom: 5px;
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon.png b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon.png
deleted file mode 100644
index 44ff857d95b54215de9aed7fd10c22fd983d11df..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 320
zcmeAS@N?(olHy`uVBq!ia0vp^G9b*s1|*Ak?@s|zoCO|{#S9GG!XV7ZFl&wkP>``W
z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{#8#G$B+ufx6>Z-9#Rl!?e_~XzaThg!`%f|
zjD>8FL)wJg}MdyD*Ts!o4^5m#V%l%R4TUe&z_SjvEu-tyx$S$INAMu(4F-?TYp7
zJ0FC;b=}=tt6ChYozhg1pXm4K+r&K?)t5MR0vZI3J2qM#o7BQFb;3rS^c6|oI~KJ>
z<^OTf3OmevZ;#*|kRG#3`HTYkK`MAh~F
z?na@~HTypY#ng08ymcwxyC>@UUe8Y#&Ldm8!j2;YeEcXJoJpy`=
N!PC{xWt~$(69D4Idno__
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon.svg b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon.svg
deleted file mode 100644
index ced49ca5..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon@1.25x.png b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon@1.25x.png
deleted file mode 100644
index c3e3e74ca9b4c06560d2944117aa2bd89c2d06ac..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 397
zcmeAS@N?(olHy`uVBq!ia0vp^${@_a1|;QfZjJ;}oCO|{#S9GG!XV7ZFl&wkP>``W
z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBIVDdQ$B+ufx6>~QHW~1=F$-VeJ(bd@ApOE^
zj&OxvgY*lw4UWF&ZJsQg+QR0?zi;=Pn4Eg2U@xW(n++6M`pcZ954G$4GLbTsa4>&+
z@m&V*t;TiLxej6nkA!9j|MQN~jS%+g_Wlv;Bh6vxDQr~t^L(Sg>IAur+(Y#)yARE}
zFx%uqZS(c?Pn9eER3%%Q{QrEMv{BTlnCV2t!zpY_G+M3}^hRnAJ-
pytYfhcXPDpy1(^nPpMThUpjK=fMW31g}~5b@O1TaS?83{1OS|;mg4{b
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon@1.5x.png b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon@1.5x.png
deleted file mode 100644
index d8d0b970f548e4de5c4052949bb75859b60274dd..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 446
zcmV;v0YUzWP)Eh5>NsTzyUY_6d(Z!NZ@b)3dXz8BbN)itfl2Idy?jI>y0*ByOk0Z
zzJL(I05XRh!;K@q4@gO$jA3W?kUz+{2k@LgMsfTZXHDUiTPJiY$bvor-sb;=ZW#6S
z3B-VWMxVkwMnM%wCE$auN(l03S{D
zd}(Y}m_8yY+9c4@w&xL}E)++tDUMoG9JS^<9rdWpyO7xHHz!d?WlqMZ?PXNvrdO!i
zURP!Af!dz+idoxhCR4I9cS2;Z>1eVt#}95CR$OPVS((?d!~YuV%G^4cME340iqqkY
z#~5;g?D)fYOk8-}6gPYCPBxb-G?d;`oB1AB=iW>QECnn!Is>p!f6&M@@Q9uSllS
od&)wvWhT9+w!Po$Jr#b9D@G~Y>cZ77WdHyG07*qoM6N<$f-6e6^8f$<
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon@1.75x.png b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon@1.75x.png
deleted file mode 100644
index 391619e8cb5e669e79e29b4502b9dfd9ea309180..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 486
zcmV@P)dju?g%Qs$ZnyYult2(AdLFq&GiFc@4zc0?o4*f
zv^j=X4ZJdElzF!9;FYj~mu&YA?S(zgnF%VcP=U4+zhr%;P1v3O!7HoSodP7VXQ4w<
zKX82ymF!9yhlf(pfe`-C#lC75^Od^#0136b$Wo*_)Q=q&G$*{!obW<(!VAp_FRafA
z?Wn|DN_FgCZgdfq5bG#7_p2TNt8^4ZCA6K8>+te-Ri#6Fgy$mF5f}F8VJ1MRBRY|d
zO7K6i_KeAOtYUZK22)fbPAPXfMI|iOPPGnhe2(7o7xVPenaKvAs>8~O9H|bI10c3c
z(ei?dUMEzF#hR`ORLT@tJCPk_6l995olwaXS?^9QQ>4NscPGAQItwNB0}3-msgCVs
ciW-VM0Bsx7f6q#2=Kufz07*qoM6N<$g1a%x8UO$Q
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon@2x.png b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/dark/icon@2x.png
deleted file mode 100644
index f8fab56555cbd2b843f9f1598a057fb81a5331db..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 560
zcmeAS@N?(olHy`uVBq!ia0vp^79h;Q1|(OsS<3+_&H|6fVg?2=RS;(M3{v?36l5$8
za(7}_cTVOdki(Mh=j5
zrXusoj0T!|iH*JUzy5uWehi
zVzu|vj>a=R(x*C%%j=luJ^0DBUEoC9j2>y_4RbPXq;niBWA{qFJk_{M%#o+a9SJ=@
zrVzF(U(!{4*W2i{|C?ofL5yhz66?7Ce$(A7*|UsW_M5J^fN%Ia>&IRz*4@upn5wi&ujWhZJ&Ay|w(Q9ba_6Jp
zyYBy&axwJM10l26PrF2<*Vvv`dLnqVB~nar*CO5)&doYq(npR&ikV9I%t{SxKDhgN
z;(@9(>-8QYTen_3ep=YYZIe!#%bMDjZ*q>us(sfa@~NHOwBcpc2MQgM
u=lz`|7h%k9f5jm{Udy4jQGp}$Bmb5;u^Zzzd|wWXU
-
-
-
-
-
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/light/icon.png b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/light/icon.png
deleted file mode 100644
index a913ffaeaccca3dd7d130e2ee37247dc05faddab..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 410
zcmV;L0cHM)P)!VWi68=8R*7zik!ot)+KYu;$86Z3+|of*c<5rbXvV;z=<~!6C*7QTEh?fbOpM!
z4(CwN^-yZH))d2jzHHn6&+}YJpMD`LXdx|Nr5
zH{kaXL&@_WF_fPa0!!UW#dF#GG;xG~PtNVHN{e2GExAs)>R$7
-
-
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/light/icon@1.25x.png b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/light/icon@1.25x.png
deleted file mode 100644
index b08dc574a9941549d4e1c16ce9340358f687caab..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 505
zcmV500009a7bBm000XU
z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yP9J@i)2}mbMIzh5QJ3@Oyf&-m^ZV)yocM$Y<1&AU{UxF+3
z;7_vb*yiV#d9j~~G~*bg2;cWtWm!I>xk0f|UQN?{48t(vYAi?rjgb`1chqZ?tE#F*
zI~8MG9g`GL6ve?XjHft`kG<}r>$+C6TDKe>B`X6XcX;W`upfQG+=iGaF$406F^ia}
zGV_68-3~jK%jG?)H)5j13_iyLssJ(48A5x9&$q|emaD`1YkQt|s_S~inTsGc8Z$L#
zJ`8736s?I1SNIb8+Nxqk3EGSdZAQkwGV|l0l%)aaqq%Uj7lX;8X#mZHtKF^4R2qPK
zM>9BETkM3kZL?3~oG{5i#a=%dzB
z!axwmH<|FR9Y6(GLKvU`D-bJ?4x|I20-*v60LGM%4vZD3&-wDdPLg3VA>__I0m<*>
zlG{mkZ*On2S)vwMEHZ%Wx>t_lJXn@BNz=58F~GQtqG;iH-Y02blQ5Z)uQ2R748y@@
zv#~JV;=af8ET>aoG#Wh)hr{P88RqAZ&qxDtkMefgw!Ns5#c@1`{Ekimcr5*md>nd}^%u?Fps``rb
z#FaOnHV$z9i$u}t^NO>V^+_02eT7H4gMo(!*pher+r03KI-L%ui7%cfVT|cJ_F;3#
z02keR689*35=Qyk?RJ{a!HYhI34d`Q=lK~aayGpu6P=PSrM#(;ClL0$8xVn5_nqK@0Ey-63Fr;l8?@}Y?5^AP1Z!5&}%F!nMCg16)PMx$|2EEWgwNK?ln
zqMm^9ciZju`IG>Lzr!QXJk&@7Q4xhg;U9+3Q(POa-^GfVwA<}%mhFB*IpUgQRdQM2nKCl-ic!Ct|h(sjGO7NLj+GDDAL?V)A
zB}f=mJECmIaaN*UuYb&Dv&glzTJ3tDmB8~qG8S6tl{i%G_+IR$)9Di$G|>|tFDmRZ
zxb_w0KfKs*J>`J79j4A=F6Dr|Dax9(!-+kxTZK=~HvAnT-nkv6AlKxXy(vobKO_hz
zpV8Z4T$^2enKZCBMdz?LMJW&!!Q&kw`dHWoy!$+?+m#0_;9T6CBh`7C=X5^bk|J5%
j6mbkqCKI)rB8wnjeGwXKP4{$-00000NkvXXu0mjfQB5}@
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/light/icon@2x.png b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/resources/light/icon@2x.png
deleted file mode 100644
index d04a5cd67c3ec04c646afa0474288b3a73b1ad40..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 768
zcmV+b1ONPqP)X
z(sR%AX617EdN3Hw$Py(IL@USFjI-B^#o~N48hM<*K^dOm9axzxA*Nuf)wtUHFkKA*LV;4!=V;eAJ4CK^Z^0SJ^AFgk%@3BVo&Z?s|c{>{Q+Q
zWC>9p1pE9e$+D(MDi|PP|ElkLq0wk`)$NolA*tXu685v#BrBqf=fW4I*IjEin`=r57744>suypZ
z)E4A!)FT7s@&XN{8a_g53+k4}3WO|TTA#A|R>O-=6Vf;Zi;h#U=r{$7j#IGcI0cK&
z)>>3LZh0tK<%%qD4~bjmwbRzz;{hOa5so@f@Ii{a
zlm$)Y+GO~93Sk5Ugu44ed(xQ774D*c#x0M8kz8BTgw1^cn`D(MtYCm-U)=IY1W2;V
z6`ltXxAaxqGSQiJXb*Y8X~IaZjRoUz%XYin)fKR#`{7C?F_kN#E&r&-L++Oza^v8S
zq^Ai}xgvuMn;%i-3l5-S8!Nm&5b*Vkd5DgHs9cCYW{EuK%`UnA-`7OmNPn$}S`c3a
zYGPu!TEi;Z9j(!=19QNPxthvLJWLA{ALyGVqf#ll5pG
yrz}(*vv^r*3-XHNDXF3pV9s(qlf&USDSiPC?n?;_8g|kE0000= 200 && this.status < 300) {
- endInternetChecking(true);
- }
- }
- };
-
- xhr.onerror = function (err) {
- endInternetChecking(false);
- };
-
- xhr.send(null);
- } catch (error) {
- endInternetChecking(false);
- }
- if (bSetTimeout) {
- errTimeout = setTimeout(function() {
- // if loading is too long show the error (because sometimes requests can not send error)
- endInternetChecking(false);
- }, 15000);
- }
- };
-
- function endInternetChecking(isOnline) {
- clearTimeout(errTimeout);
- errTimeout = null;
- destroyLoader();
- if (isOnline) {
- initPlugin();
- } else {
- document.getElementById('div_noIternet').classList.remove('hidden');
- if (!interval) {
- interval = setInterval(function() {
- checkInternet(false);
- }, 5000);
- }
- }
- };
-
- function createWindow(type) {
- let fileName = type + '.html';
- let description = window.Asc.plugin.tr('Warning');
- let size = [350, 100];
- let buttons = [
- {
- 'text': window.Asc.plugin.tr('Yes'),
- 'primary': true
- },
- {
- 'text': window.Asc.plugin.tr('No'),
- 'primary': false
- }
- ];
- if (type == 'developer') {
- fileName = 'developer.html';
- description = window.Asc.plugin.tr('Developer Mode');
- size = [500, 150];
- buttons = [
- {
- 'text': window.Asc.plugin.tr('Ok'),
- 'primary': true
- },
- {
- 'text': window.Asc.plugin.tr('Cancel'),
- 'primary': false
- }
- ];
- }
- let location = window.location;
- let start = location.pathname.lastIndexOf('/') + 1;
- let file = location.pathname.substring(start);
-
- let variation = {
- url : location.href.replace(file, fileName),
- description : description,
- isVisual : true,
- isModal : true,
- isViewer: true,
- EditorsSupport : ['word', 'cell', 'slide', 'pdf'],
- size : size,
- buttons : buttons
- };
-
- if (type == 'warning') {
- if (!warningWindow) {
- warningWindow = new window.Asc.PluginWindow();
- }
- warningWindow.show(variation);
- } else {
- if (!developerWindow) {
- developerWindow = new window.Asc.PluginWindow();
- developerWindow.attachEvent('onWindowMessage', function(message) {
- if (message.type == 'SetURL') {
- if (message.url.length) {
- marketplaceURl = message.url;
- localStorage.setItem('DeveloperMarketplaceUrl', marketplaceURl);
- document.getElementById('notification').classList.remove('hidden');
- } else {
- marketplaceURl = OOMarketplaceUrl;
- localStorage.removeItem('DeveloperMarketplaceUrl');
- document.getElementById('notification').classList.add('hidden');
- }
- iframe.src = marketplaceURl + window.location.search;
- }
- window.Asc.plugin.executeMethod('CloseWindow', [developerWindow.id]);
- });
- }
- developerWindow.show(variation);
- }
-
- };
-
- function removePlugin(backup) {
- if (removeGuid)
- window.Asc.plugin.executeMethod('RemovePlugin', [removeGuid, backup], function(result) {
- postMessage(result);
- });
-
- removeGuid = null;
- };
-
- window.onresize = function() {
- // zoom for all elements in plugin window
- let zoom = 1;
- if (window.devicePixelRatio < 1)
- zoom = 1 / window.devicePixelRatio;
-
- document.getElementsByTagName('html')[0].style.zoom = zoom;
- };
-
- function createLoader() {
- $('#loader-container').removeClass( 'hidden' );
- loader && (loader.remove ? loader.remove() : $('#loader-container')[0].removeChild(loader));
- loader = showLoader($('#loader-container')[0], window.Asc.plugin.tr('Checking internet connection...'));
- };
-
- function destroyLoader() {
- clearTimeout(loaderTimeout);
- loaderTimeout = null;
- $('#loader-container').addClass( 'hidden' )
- loader && (loader.remove ? loader.remove() : $('#loader-container')[0].removeChild(loader));
- loader = undefined;
- };
-
-})(window, undefined);
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/scripts/developer.js b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/scripts/developer.js
deleted file mode 100644
index 44737596..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/scripts/developer.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- *
- * (c) Copyright Ascensio System SIA 2020
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-(function(window, undefined) {
-
- window.Asc.plugin.init = function() {
- document.getElementById('inp_url').value = localStorage.getItem('DeveloperMarketplaceUrl') || '';
- document.getElementById('lbl_reset').onclick = function() {
- document.getElementById('inp_url').value = '';
- };
- };
-
- window.Asc.plugin.onThemeChanged = function(theme) {
- window.Asc.plugin.onThemeChangedBase(theme);
- };
-
- window.Asc.plugin.onTranslate = function() {
- let elements = document.getElementsByClassName('i18n');
-
- for (let index = 0; index < elements.length; index++) {
- const element = elements[index];
- element.innerHTML = window.Asc.plugin.tr(element.innerHTML);
- }
- document.getElementById('inp_url').setAttribute('placeholder', window.Asc.plugin.tr('Enter URL'));
- };
-
- window.Asc.plugin.attachEvent("onClickBtn", function(){
- sendPluginMessage({type: 'SetURL', url: document.getElementById('inp_url').value.trim()});
- });
-
- function sendPluginMessage(message) {
- window.Asc.plugin.sendToPlugin("onWindowMessage", message);
- };
-
-})(window, undefined);
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/scripts/warning.js b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/scripts/warning.js
deleted file mode 100644
index ec0e0692..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/scripts/warning.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- *
- * (c) Copyright Ascensio System SIA 2020
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-(function(window, undefined) {
-
- window.Asc.plugin.init = function() {};
-
- window.Asc.plugin.onThemeChanged = function(theme) {
- window.Asc.plugin.onThemeChangedBase(theme);
- };
-
- window.Asc.plugin.onTranslate = function() {
- let elements = document.getElementsByClassName('i18n');
-
- for (let index = 0; index < elements.length; index++) {
- const element = elements[index];
- element.innerHTML = window.Asc.plugin.tr(element.innerHTML);
- }
- };
-})(window, undefined);
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/cs-CZ.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/cs-CZ.json
deleted file mode 100644
index 1f4cbd34..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/cs-CZ.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "This version of \"Plugin Manager\" is not official." : "Tato verze \"Správce pluginů\" není oficiální.",
- "No Internet Connection.": "Žádné Připojení K Internetu.",
- "Warning" : "Varování",
- "Yes" : "Ano",
- "No" : "Ne",
- "Do you want to completely remove this plugin?" : "Chcete tento plugin úplně odstranit?",
- "If you choose \"No\", you will be able to restore this plugin later in the marketplace." : "Pokud zvolíte \"Ne\", budete moci tento plugin obnovit později na Správce Pluginů.",
- "Enter the URL for your marketplace page here" : "Zde zadejte adresu URL své stránky marketplace",
- "Return to default settings" : "Návrat na výchozí nastavení",
- "Enter URL" : "Zadejte URL",
- "Cancel" : "Zrušit",
- "Be careful! Avoid using the untrusted URLs." : "Buď opatrný! Nepoužívejte nedůvěryhodné adresy URL."
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/de-DE.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/de-DE.json
deleted file mode 100644
index 7d7e7bf0..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/de-DE.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "This version of \"Plugin Manager\" is not official." : "Diese Version von \"Plugin Manager\" ist nicht offiziell.",
- "No Internet Connection.": "Keine Internetverbindung.",
- "Warning" : "Warnung",
- "Yes" : "Ja",
- "No" : "Nein",
- "Do you want to completely remove this plugin?" : "Möchten Sie dieses Plugin vollständig entfernen?",
- "If you choose \"No\", you will be able to restore this plugin later in the marketplace." : "Wenn Sie \"Nein\" wählen, können Sie dieses Plugin später auf dem Plugin-Verwaltung wiederherstellen.",
- "Enter the URL for your marketplace page here" : "Geben Sie hier die URL für Ihre Marktplatz-Seite ein",
- "Return to default settings" : "Zu den Standardeinstellungen zurückkehren",
- "Enter URL" : "URL eingeben",
- "Cancel" : "Stornieren",
- "Be careful! Avoid using the untrusted URLs." : "Sei vorsichtig! Vermeiden Sie die Verwendung nicht vertrauenswürdiger URLs."
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/es-ES.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/es-ES.json
deleted file mode 100644
index 55eb1c90..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/es-ES.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "This version of \"Plugin Manager\" is not official." : "Esta versión de \"Plugin Manager\" no es oficial.",
- "No Internet Connection.": "Sin Conexión a Internet.",
- "Warning" : "Advertencia",
- "Yes" : "Sí",
- "No" : "No",
- "Do you want to completely remove this plugin?" : "¿Quieres eliminar por completo este plugin?",
- "If you choose \"No\", you will be able to restore this plugin later in the marketplace." : "Si elige \"No\", podrá restaurar este complemento más adelante en el Administrador de Complementos.",
- "Enter the URL for your marketplace page here" : "Ingrese la URL de su página de mercado aquí",
- "Return to default settings" : "Volver a la configuración predeterminada",
- "Enter URL" : "Introduzca la URL",
- "Cancel" : "Cancelar",
- "Be careful! Avoid using the untrusted URLs." : "¡Ten cuidado! Evite usar URL que no sean de confianza."
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/fr-FR.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/fr-FR.json
deleted file mode 100644
index c57cab4d..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/fr-FR.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "This version of \"Plugin Manager\" is not official." : "Cette version de \"Plugin Manager\" n'est pas officielle.",
- "No Internet Connection.": "Pas De Connexion Internet.",
- "Warning" : "Avertissement",
- "Yes" : "Oui",
- "No" : "Aucun",
- "Do you want to completely remove this plugin?" : "Voulez-vous supprimer complètement ce plugin?",
- "If you choose \"No\", you will be able to restore this plugin later in the marketplace." : "Si vous choisissez \"Aucun\", vous pourrez restaurer ce plugin plus tard sur le Gestionnaire de Plugins.",
- "Enter the URL for your marketplace page here" : "Entrez l'URL de votre page marketplace ici",
- "Return to default settings" : "Retour aux paramètres par défaut",
- "Enter URL" : "Entrez l'URL",
- "Cancel" : "Annuler",
- "Be careful! Avoid using the untrusted URLs." : "Soyez prudent! Évitez d'utiliser les URL non fiables."
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/it-IT.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/it-IT.json
deleted file mode 100644
index 0440cce9..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/it-IT.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "This version of \"Plugin Manager\" is not official." : "Questa versione di \"Plugin Manager\" non è ufficiale.",
- "No Internet Connection.": "Nessuna connessione internet.",
- "Warning" : "Avviso",
- "Yes" : "Sì",
- "No" : "No",
- "Do you want to completely remove this plugin?" : "Vuoi rimuovere completamente questo plugin?",
- "If you choose \"No\", you will be able to restore this plugin later in the marketplace." : "Se scegli \"No\", potrai ripristinare questo plugin più tardi nel Marketplace.",
- "Enter the URL for your marketplace page here" : "Inserisci qui l'URL della pagina del tuo marketplace",
- "Return to default settings" : "Ritorno alle impostazioni predefinite",
- "Enter URL" : "Inserisci URL",
- "Cancel" : "Annulla",
- "Be careful! Avoid using the untrusted URLs." : "Attenzione! Evita di utilizzare URL non affidabili."
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/ja-JA.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/ja-JA.json
deleted file mode 100644
index 894880b2..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/ja-JA.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "This version of \"Plugin Manager\" is not official." : "この\"Plugin Manager\"のバージョンは正式なものではありません。",
- "No Internet Connection.": "インターネットに接続されていません。",
- "Warning" : "注意",
- "Yes" : "はい",
- "No" : "いいえ",
- "Do you want to completely remove this plugin?" : "このプラグインを完全に削除しますか?",
- "If you choose \"No\", you will be able to restore this plugin later in the marketplace." : "\"No\" を選んでも、後でマーケットプレイスでこのプラグインを復元できます。",
- "Enter the URL for your marketplace page here" : "マーケットプレイスページのURLをここに入力してください",
- "Return to default settings" : "デフォルト設定に戻す",
- "Enter URL" : "URLを入力する",
- "Cancel" : "キャンセル",
- "Be careful! Avoid using the untrusted URLs." : "ご注意!信頼できないURLの使用は避けてください。"
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/langs.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/langs.json
deleted file mode 100644
index 89559faf..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/langs.json
+++ /dev/null
@@ -1,14 +0,0 @@
-[
- "cs-CZ",
- "de-DE",
- "es-ES",
- "fr-FR",
- "it-IT",
- "ja-JA",
- "ru-RU",
- "si-SI",
- "zh-ZH",
- "pt-BR",
- "sr-Cyrl-RS",
- "sr-Latn-RS"
-]
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/pt-BR.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/pt-BR.json
deleted file mode 100644
index dd664c71..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/pt-BR.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "This version of \"Plugin Manager\" is not official." : "Esta versão do \"Plugin Manager\" não é oficial.",
- "No Internet Connection.": "Sem conexão com a Internet.",
- "Warning" : "Aviso",
- "Yes" : "Sim",
- "No" : "No",
- "Do you want to completely remove this plugin?" : "Deseja remover completamente este plugin?",
- "If you choose \"No\", you will be able to restore this plugin later in the marketplace." : "Se você escolher \"Não\", você poderá restaurar este plugin mais tarde no mercado.",
- "Enter the URL for your marketplace page here" : "Insira o URL da sua página de mercado aqui",
- "Return to default settings" : "Retornar às configurações padrão",
- "Enter URL" : "Insira o URL",
- "Cancel" : "Cancelar",
- "Be careful! Avoid using the untrusted URLs." : "Tenha cuidado! Evite usar URLs não confiáveis."
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/ru-RU.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/ru-RU.json
deleted file mode 100644
index 16520316..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/ru-RU.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "This version of \"Plugin Manager\" is not official." : "Эта версия \"Менеджера Плагинов\" не является официальной.",
- "No Internet Connection.": "Отсутствует соединение с интернетом.",
- "Warning" : "Предупреждение",
- "Yes" : "Да",
- "No" : "Нет",
- "Do you want to completely remove this plugin?" : "Вы хотите полностью удалить этот плагин?",
- "If you choose \"No\", you will be able to restore this plugin later in the marketplace." : "Если вы выберете \"Нет\", вы сможете восстановить этот плагин позже в Менеджере плагинов.",
- "Enter the URL for your marketplace page here" : "Введите URL для вашей страницы менеджера плагинов здесь",
- "Return to default settings" : "Вернуть настройки по умолчанию",
- "Enter URL" : "Введите URL",
- "Cancel" : "Отмена",
- "Be careful! Avoid using the untrusted URLs." : "Будьте осторожны! Избегайте использования ненадежных URL-адресов."
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/si-SI.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/si-SI.json
deleted file mode 100644
index 06e2f8ed..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/si-SI.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "This version of \"Plugin Manager\" is not official." : "මෙය නිල නොවන \"පේනු කළමනාකරු\" අනුවාදයකි.",
- "No Internet Connection.": "අන්තර්ජාල සම්බන්ධතාවයක් නැත.",
- "Warning" : "අවවාදයයි",
- "Yes" : "ඔව්",
- "No" : "නැහැ",
- "Do you want to completely remove this plugin?" : "ඔබට මෙම පේනුව සම්පූර්ණයෙන්ම ඉවත් කිරීමට වුවමනා ද?",
- "If you choose \"No\", you will be able to restore this plugin later in the marketplace." : "ඔබ \"නැත\" තෝරන්නේ නම්, මෙම පේනුව පසුව වෙළඳපොළෙන් ප්රත්යර්පණයට හැකි වනු ඇත.",
- "Checking internet connection..." : "සම්බන්ධතාවය පරීක්ෂා වෙමින්...",
- "Be careful! Avoid using the untrusted URLs." : "පරිස්සමින්! විශ්වාස කළ නොහැකි Url භාවිතා කිරීමෙන් වළකින්න."
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/sq-AL.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/sq-AL.json
deleted file mode 100644
index 3d0403f8..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/sq-AL.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "This version of \"Plugin Manager\" is not official." : "Ky version i \"Menaxherit të shtojcës\" is nuk është zyrtar.",
- "No Internet Connection.": "Nuk ka lidhje interneti.",
- "Warning" : "Paralajmërim",
- "Yes" : "Po",
- "No" : "Jo",
- "Do you want to completely remove this plugin?" : "Dëshironi që të fshini këtë shtojcë komplet.",
- "If you choose \"No\", you will be able to restore this plugin later in the marketplace." : "Në qoftë se ju zgjidhni \"Jo\", do të mund të riktheni këtë shtojcë më vonë në marketplace.",
- "Enter the URL for your marketplace page here" : "Futni URL-në e faqes suaj të marketplace-it këtu",
- "Return to default settings" : "Kthehuni në cilësimet e parazgjedhura",
- "Enter URL" : "Futni URL",
- "Cancel" : "Anuloni",
- "Be careful! Avoid using the untrusted URLs." : "Bëni kujdes! Shmangni përdorimin e URL-ve të pa besueshme."
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/sr-Cyrl-RS.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/sr-Cyrl-RS.json
deleted file mode 100644
index f2066d3e..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/sr-Cyrl-RS.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "This version of \"Plugin Manager\" is not official." : "Ова верзија \"Менаџера додатака\" није званична.",
- "No Internet Connection.": "Нема Интернет конекције.",
- "Warning" : "Упозорење",
- "Yes" : "Да",
- "No" : "Не",
- "Do you want to completely remove this plugin?" : "Да ли желите да потпуно уклоните овај додатак?",
- "If you choose \"No\", you will be able to restore this plugin later in the marketplace." : "Ако изаберете \"Не\", моћи ћете да обновите овај додатак касније на тржишту.",
- "Enter the URL for your marketplace page here" : "Унесите URL за вашу страницу тржишта овде",
- "Return to default settings" : "Вратите се на подразумевана подешавања",
- "Enter URL" : "Унесите URL",
- "Cancel" : "Откажи",
- "Be careful! Avoid using the untrusted URLs." : "Пазите! Избегавајте да користите непоуздане URL-ове."
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/sr-Latn-RS.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/sr-Latn-RS.json
deleted file mode 100644
index 3ba1620d..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/sr-Latn-RS.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "This version of \"Plugin Manager\" is not official." : "Ova verzija \"Menadžera dodataka\" nije zvanična.",
- "No Internet Connection.": "Nema Internet konekcije.",
- "Warning" : "Upozorenje",
- "Yes" : "Da",
- "No" : "Ne",
- "Do you want to completely remove this plugin?" : "Da li želite da potpuno uklonite ovaj dodatak?",
- "If you choose \"No\", you will be able to restore this plugin later in the marketplace." : "Ako izaberete \"Ne\", moći ćete da obnovite ovaj dodatak kasnije na tržištu.",
- "Enter the URL for your marketplace page here" : "Unesite URL za vašu stranicu tržišta ovde",
- "Return to default settings" : "Vratite se na podrazumevana podešavanja",
- "Enter URL" : "Unesite URL",
- "Cancel" : "Otkaži",
- "Be careful! Avoid using the untrusted URLs." : "Pazite! Izbegavajte da koristite nepouzdane URL-ove."
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/zh-ZH.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/zh-ZH.json
deleted file mode 100644
index 2beaa50c..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/translations/zh-ZH.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "This version of \"Plugin Manager\" is not official." : "此版本的\"插件管理器\"不是官方的。",
- "No Internet Connection.": "没有网络连接。",
- "Warning" : "警告",
- "Yes" : "是",
- "No" : "否",
- "Do you want to completely remove this plugin?" : "您想彻底删除这个插件吗?",
- "If you choose \"No\", you will be able to restore this plugin later in the marketplace." : "如果您选择\"否\",稍后可以在市场中恢复此插件。",
- "Enter the URL for your marketplace page here" : "在此处输入市场页面的网址",
- "Return to default settings" : "返回默认设置",
- "Enter URL" : "输入网址",
- "Cancel" : "取消",
- "Be careful! Avoid using the untrusted URLs." : "当心!避免使用不受信任的网址"
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/vendor/jquery/jquery-3.7.1.min.js b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/vendor/jquery/jquery-3.7.1.min.js
deleted file mode 100644
index 7f37b5d9..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/marketplace/vendor/jquery/jquery-3.7.1.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */
-!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML=" ",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML=" ",le.option=!!xe.lastChild;var ke={thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0
-
-
-
-
- Notification
-
-
-
-
-
-
-
-
-
-
-
Do you want to completely remove this plugin?
-
-
- If you choose "No", you will be able to restore this plugin later in the marketplace.
-
-
-
-
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/plugin-list-default.json b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/plugin-list-default.json
deleted file mode 100644
index 39943421..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/plugin-list-default.json
+++ /dev/null
@@ -1,13 +0,0 @@
-[
- "ai",
- "highlightcode",
- "mendeley",
- "ocr",
- "photoeditor",
- "speech",
- "speechrecognition",
- "thesaurus",
- "translator",
- "youtube",
- "zotero"
-]
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/pluginBase.js b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/pluginBase.js
deleted file mode 100644
index bd3065b9..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/pluginBase.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/**
- *
- * (c) Copyright Ascensio System SIA 2021
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-(function(a,u){function w(c){a.Asc.plugin.ie_channel?a.Asc.plugin.ie_channel.postMessage(c):a.parent.postMessage(c,"*")}function r(c,d){if(!c||"object"!=typeof c&&"array"!=typeof c)return c;d=d===u?{}:d;for(var h in c)c.hasOwnProperty(h)&&(d[h]=c[h]&&"object"===typeof c[h]?r(c[h]):c[h]);return d}function p(c){var d=a.location.search,h=c+"=";c=d.indexOf(h);return 0<=c?(c+=h.length,h=d.indexOf("&",c),0>h&&(h=d.length),d.substring(c,h)):u}function n(){var c=p("windowID");c&&(a.Asc.plugin.windowID=c,
-a.Asc.plugin.guid||(a.Asc.plugin.guid=decodeURIComponent(p("guid"))));return u!==c?!0:!1}function q(c){if(a.Asc&&a.Asc.plugin)if(a.plugin_onMessage)a.Asc.supportOrigins[c.origin]&&a.plugin_onMessage(c);else if(a.Asc.plugin._initInternal&&"string"==typeof c.data){var d={};try{d=JSON.parse(c.data)}catch(h){d={}}"plugin_init"==d.type&&(a.Asc.supportOrigins[c.origin]=!0,a.Asc.plugin.ie_channel_check(c),eval(d.data))}}a.Asc=a.Asc||{};a.Asc.plugin={};a.Asc.plugin.ie_channel=null;a.Asc.plugin.ie_channel_check=
-function(c){var d=navigator.userAgent.toLowerCase();(-1t[n]-t[o]-1&&(t.reach[h]="end"),e&&(f.dispatchEvent(c("ps-scroll-"+h)),e<0?f.dispatchEvent(c("ps-scroll-"+u)):e>0&&f.dispatchEvent(c("ps-scroll-"+d)),r&&a(t,h)),t.reach[h]&&(e||l)&&f.dispatchEvent(c("ps-"+h+"-reach-"+t.reach[h]))}function u(t){return parseInt(t,10)||0}function d(t){return r(t,"input,[contenteditable]")||r(t,"select,[contenteditable]")||r(t,"textarea,[contenteditable]")||r(t,"button,[contenteditable]")}function f(e){var i=t(e);return u(i.width)+u(i.paddingLeft)+u(i.paddingRight)+u(i.borderLeftWidth)+u(i.borderRightWidth)}function p(t,e){return t.settings.minScrollbarLength&&(e=Math.max(e,t.settings.minScrollbarLength)),t.settings.maxScrollbarLength&&(e=Math.min(e,t.settings.maxScrollbarLength)),e}function b(t,i){var r={width:i.railXWidth},l=Math.floor(t.scrollTop);i.isRtl?r.left=i.negativeScrollAdjustment+t.scrollLeft+i.containerWidth-i.contentWidth:r.left=t.scrollLeft,i.isScrollbarXUsingBottom?r.bottom=i.scrollbarXBottom-l:r.top=i.scrollbarXTop+l,e(i.scrollbarXRail,r);var n={top:l,height:i.railYHeight};i.isScrollbarYUsingRight?i.isRtl?n.right=i.contentWidth-(i.negativeScrollAdjustment+t.scrollLeft)-i.scrollbarYRight-i.scrollbarYOuterWidth:n.right=i.scrollbarYRight-t.scrollLeft:i.isRtl?n.left=i.negativeScrollAdjustment+t.scrollLeft+2*i.containerWidth-i.contentWidth-i.scrollbarYLeft-i.scrollbarYOuterWidth:n.left=i.scrollbarYLeft+t.scrollLeft,e(i.scrollbarYRail,n),e(i.scrollbarX,{left:i.scrollbarXLeft,width:i.scrollbarXWidth-i.railBorderXWidth}),e(i.scrollbarY,{top:i.scrollbarYTop,height:i.scrollbarYHeight-i.railBorderYWidth})}function g(t,e){function i(e){b[d]=g+Y*(e[a]-v),o(t,f),R(t),e.stopPropagation(),e.preventDefault()}function r(){s(t,f),t[p].classList.remove(m.state.clicking),t.event.unbind(t.ownerDocument,"mousemove",i)}var l=e[0],n=e[1],a=e[2],c=e[3],h=e[4],u=e[5],d=e[6],f=e[7],p=e[8],b=t.element,g=null,v=null,Y=null;t.event.bind(t[h],"mousedown",function(e){g=b[d],v=e[a],Y=(t[n]-t[l])/(t[c]-t[u]),t.event.bind(t.ownerDocument,"mousemove",i),t.event.once(t.ownerDocument,"mouseup",r),t[p].classList.add(m.state.clicking),e.stopPropagation()/*,e.preventDefault()*/})}var v="undefined"!=typeof Element&&(Element.prototype.matches||Element.prototype.webkitMatchesSelector||Element.prototype.mozMatchesSelector||Element.prototype.msMatchesSelector),m={main:"ps",element:{thumb:function(t){return"ps__thumb-"+t},rail:function(t){return"ps__rail-"+t},consuming:"ps__child--consume"},state:{focus:"ps--focus",clicking:"ps--clicking",active:function(t){return"ps--active-"+t},scrolling:function(t){return"ps--scrolling-"+t}}},Y={x:null,y:null},X=function(t){this.element=t,this.handlers={}},w={isEmpty:{configurable:!0}};X.prototype.bind=function(t,e){void 0===this.handlers[t]&&(this.handlers[t]=[]),this.handlers[t].push(e),this.element.addEventListener(t,e,!1)},X.prototype.unbind=function(t,e){var i=this;this.handlers[t]=this.handlers[t].filter(function(r){return!(!e||r===e)||(i.element.removeEventListener(t,r,!1),!1)})},X.prototype.unbindAll=function(){var t=this;for(var e in t.handlers)t.unbind(e)},w.isEmpty.get=function(){var t=this;return Object.keys(this.handlers).every(function(e){return 0===t.handlers[e].length})},Object.defineProperties(X.prototype,w);var y=function(){this.eventElements=[]};y.prototype.eventElement=function(t){var e=this.eventElements.filter(function(e){return e.element===t})[0];return e||(e=new X(t),this.eventElements.push(e)),e},y.prototype.bind=function(t,e,i){this.eventElement(t).bind(e,i)},y.prototype.unbind=function(t,e,i){var r=this.eventElement(t);r.unbind(e,i),r.isEmpty&&this.eventElements.splice(this.eventElements.indexOf(r),1)},y.prototype.unbindAll=function(){this.eventElements.forEach(function(t){return t.unbindAll()}),this.eventElements=[]},y.prototype.once=function(t,e,i){var r=this.eventElement(t),l=function(t){r.unbind(e,l),i(t)};r.bind(e,l)};var W=function(t,e,i,r,l){void 0===r&&(r=!0),void 0===l&&(l=!1);var n;if("top"===e)n=["contentHeight","containerHeight","scrollTop","y","up","down"];else{if("left"!==e)throw new Error("A proper axis should be provided");n=["contentWidth","containerWidth","scrollLeft","x","left","right"]}h(t,i,n,r,l)},L={isWebKit:"undefined"!=typeof document&&"WebkitAppearance"in document.documentElement.style,supportsTouch:"undefined"!=typeof window&&("ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch),supportsIePointer:"undefined"!=typeof navigator&&navigator.msMaxTouchPoints,isChrome:"undefined"!=typeof navigator&&/Chrome/i.test(navigator&&navigator.userAgent)},R=function(t){var e=t.element,i=Math.floor(e.scrollTop);t.containerWidth=e.clientWidth,t.containerHeight=e.clientHeight,t.contentWidth=e.scrollWidth,t.contentHeight=e.scrollHeight,e.contains(t.scrollbarXRail)||(n(e,m.element.rail("x")).forEach(function(t){return l(t)}),e.appendChild(t.scrollbarXRail)),e.contains(t.scrollbarYRail)||(n(e,m.element.rail("y")).forEach(function(t){return l(t)}),e.appendChild(t.scrollbarYRail)),!t.settings.suppressScrollX&&t.containerWidth+t.settings.scrollXMarginOffset=t.railXWidth-t.scrollbarXWidth&&(t.scrollbarXLeft=t.railXWidth-t.scrollbarXWidth),t.scrollbarYTop>=t.railYHeight-t.scrollbarYHeight&&(t.scrollbarYTop=t.railYHeight-t.scrollbarYHeight),b(e,t),t.scrollbarXActive?e.classList.add(m.state.active("x")):(e.classList.remove(m.state.active("x")),t.scrollbarXWidth=0,t.scrollbarXLeft=0,e.scrollLeft=0),t.scrollbarYActive?e.classList.add(m.state.active("y")):(e.classList.remove(m.state.active("y")),t.scrollbarYHeight=0,t.scrollbarYTop=0,e.scrollTop=0)},T={"click-rail":function(t){t.event.bind(t.scrollbarY,"mousedown",function(t){return t.stopPropagation()}),t.event.bind(t.scrollbarYRail,"mousedown",function(e){var i=e.pageY-window.pageYOffset-t.scrollbarYRail.getBoundingClientRect().top>t.scrollbarYTop?1:-1;t.element.scrollTop+=i*t.containerHeight,R(t),e.stopPropagation()}),t.event.bind(t.scrollbarX,"mousedown",function(t){return t.stopPropagation()}),t.event.bind(t.scrollbarXRail,"mousedown",function(e){var i=e.pageX-window.pageXOffset-t.scrollbarXRail.getBoundingClientRect().left>t.scrollbarXLeft?1:-1;t.element.scrollLeft+=i*t.containerWidth,R(t),e.stopPropagation()})},"drag-thumb":function(t){g(t,["containerWidth","contentWidth","pageX","railXWidth","scrollbarX","scrollbarXWidth","scrollLeft","x","scrollbarXRail"]),g(t,["containerHeight","contentHeight","pageY","railYHeight","scrollbarY","scrollbarYHeight","scrollTop","y","scrollbarYRail"])},keyboard:function(t){function e(e,r){var l=Math.floor(i.scrollTop);if(0===e){if(!t.scrollbarYActive)return!1;if(0===l&&r>0||l>=t.contentHeight-t.containerHeight&&r<0)return!t.settings.wheelPropagation}var n=i.scrollLeft;if(0===r){if(!t.scrollbarXActive)return!1;if(0===n&&e<0||n>=t.contentWidth-t.containerWidth&&e>0)return!t.settings.wheelPropagation}return!0}var i=t.element,l=function(){return r(i,":hover")},n=function(){return r(t.scrollbarX,":focus")||r(t.scrollbarY,":focus")};t.event.bind(t.ownerDocument,"keydown",function(r){if(!(r.isDefaultPrevented&&r.isDefaultPrevented()||r.defaultPrevented)&&(l()||n())){var o=document.activeElement?document.activeElement:t.ownerDocument.activeElement;if(o){if("IFRAME"===o.tagName)o=o.contentDocument.activeElement;else for(;o.shadowRoot;)o=o.shadowRoot.activeElement;if(d(o))return}var s=0,a=0;switch(r.which){case 37:s=r.metaKey?-t.contentWidth:r.altKey?-t.containerWidth:-30;break;case 38:a=r.metaKey?t.contentHeight:r.altKey?t.containerHeight:30;break;case 39:s=r.metaKey?t.contentWidth:r.altKey?t.containerWidth:30;break;case 40:a=r.metaKey?-t.contentHeight:r.altKey?-t.containerHeight:-30;break;case 32:a=r.shiftKey?t.containerHeight:-t.containerHeight;break;case 33:a=t.containerHeight;break;case 34:a=-t.containerHeight;break;case 36:a=t.contentHeight;break;case 35:a=-t.contentHeight;break;default:return}t.settings.suppressScrollX&&0!==s||t.settings.suppressScrollY&&0!==a||(i.scrollTop-=a,i.scrollLeft+=s,R(t),e(s,a)&&r.preventDefault())}})},wheel:function(e){function i(t,i){var r=Math.floor(o.scrollTop),l=0===o.scrollTop,n=r+o.offsetHeight===o.scrollHeight,s=0===o.scrollLeft,a=o.scrollLeft+o.offsetWidth===o.scrollWidth;return!(Math.abs(i)>Math.abs(t)?l||n:s||a)||!e.settings.wheelPropagation}function r(t){var e=t.deltaX,i=-1*t.deltaY;return void 0!==e&&void 0!==i||(e=-1*t.wheelDeltaX/6,i=t.wheelDeltaY/6),t.deltaMode&&1===t.deltaMode&&(e*=10,i*=10),e!==e&&i!==i&&(e=0,i=t.wheelDelta),t.shiftKey?[-i,-e]:[e,i]}function l(e,i,r){if(!L.isWebKit&&o.querySelector("select:focus"))return!0;if(!o.contains(e))return!1;for(var l=e;l&&l!==o;){if(l.classList.contains(m.element.consuming))return!0;var n=t(l);if([n.overflow,n.overflowX,n.overflowY].join("").match(/(scroll|auto)/)){var s=l.scrollHeight-l.clientHeight;if(s>0&&!(0===l.scrollTop&&r>0||l.scrollTop===s&&r<0))return!0;var a=l.scrollWidth-l.clientWidth;if(a>0&&!(0===l.scrollLeft&&i<0||l.scrollLeft===a&&i>0))return!0}l=l.parentNode}return!1}function n(t){var n=r(t),s=n[0],a=n[1];if(!l(t.target,s,a)){var c=!1;e.settings.useBothWheelAxes?e.scrollbarYActive&&!e.scrollbarXActive?(a?o.scrollTop-=a*e.settings.wheelSpeed:o.scrollTop+=s*e.settings.wheelSpeed,c=!0):e.scrollbarXActive&&!e.scrollbarYActive&&(s?o.scrollLeft+=s*e.settings.wheelSpeed:o.scrollLeft-=a*e.settings.wheelSpeed,c=!0):(o.scrollTop-=a*e.settings.wheelSpeed,o.scrollLeft+=s*e.settings.wheelSpeed),R(e),(c=c||i(s,a))&&!t.ctrlKey&&(t.stopPropagation(),t.preventDefault())}}var o=e.element;void 0!==window.onwheel?e.event.bind(o,"wheel",n):void 0!==window.onmousewheel&&e.event.bind(o,"mousewheel",n)},touch:function(e){function i(t,i){var r=Math.floor(h.scrollTop),l=h.scrollLeft,n=Math.abs(t),o=Math.abs(i);if(o>n){if(i<0&&r===e.contentHeight-e.containerHeight||i>0&&0===r)return 0===window.scrollY&&i>0&&L.isChrome}else if(n>o&&(t<0&&l===e.contentWidth-e.containerWidth||t>0&&0===l))return!0;return!0}function r(t,i){h.scrollTop-=i,h.scrollLeft-=t,R(e)}function l(t){return t.targetTouches?t.targetTouches[0]:t}function n(t){return!(t.pointerType&&"pen"===t.pointerType&&0===t.buttons||(!t.targetTouches||1!==t.targetTouches.length)&&(!t.pointerType||"mouse"===t.pointerType||t.pointerType===t.MSPOINTER_TYPE_MOUSE))}function o(t){if(n(t)){var e=l(t);u.pageX=e.pageX,u.pageY=e.pageY,d=(new Date).getTime(),null!==p&&clearInterval(p)}}function s(e,i,r){if(!h.contains(e))return!1;for(var l=e;l&&l!==h;){if(l.classList.contains(m.element.consuming))return!0;var n=t(l);if([n.overflow,n.overflowX,n.overflowY].join("").match(/(scroll|auto)/)){var o=l.scrollHeight-l.clientHeight;if(o>0&&!(0===l.scrollTop&&r>0||l.scrollTop===o&&r<0))return!0;var s=l.scrollLeft-l.clientWidth;if(s>0&&!(0===l.scrollLeft&&i<0||l.scrollLeft===s&&i>0))return!0}l=l.parentNode}return!1}function a(t){if(n(t)){var e=l(t),o={pageX:e.pageX,pageY:e.pageY},a=o.pageX-u.pageX,c=o.pageY-u.pageY;if(s(t.target,a,c))return;r(a,c),u=o;var h=(new Date).getTime(),p=h-d;p>0&&(f.x=a/p,f.y=c/p,d=h),i(a,c)&&t.preventDefault()}}function c(){e.settings.swipeEasing&&(clearInterval(p),p=setInterval(function(){e.isInitialized?clearInterval(p):f.x||f.y?Math.abs(f.x)<.01&&Math.abs(f.y)<.01?clearInterval(p):(r(30*f.x,30*f.y),f.x*=.8,f.y*=.8):clearInterval(p)},10))}if(L.supportsTouch||L.supportsIePointer){var h=e.element,u={},d=0,f={},p=null;L.supportsTouch?(e.event.bind(h,"touchstart",o),e.event.bind(h,"touchmove",a),e.event.bind(h,"touchend",c)):L.supportsIePointer&&(window.PointerEvent?(e.event.bind(h,"pointerdown",o),e.event.bind(h,"pointermove",a),e.event.bind(h,"pointerup",c)):window.MSPointerEvent&&(e.event.bind(h,"MSPointerDown",o),e.event.bind(h,"MSPointerMove",a),e.event.bind(h,"MSPointerUp",c)))}}},H=function(r,l){var n=this;if(void 0===l&&(l={}),"string"==typeof r&&(r=document.querySelector(r)),!r||!r.nodeName)throw new Error("no element is specified to initialize PerfectScrollbar");this.element=r,r.classList.add(m.main),this.settings={handlers:["click-rail","drag-thumb","keyboard","wheel","touch"],maxScrollbarLength:null,minScrollbarLength:null,scrollingThreshold:1e3,scrollXMarginOffset:0,scrollYMarginOffset:0,suppressScrollX:!1,suppressScrollY:!1,swipeEasing:!0,useBothWheelAxes:!1,wheelPropagation:!0,wheelSpeed:0.35};for(var o in l)n.settings[o]=l[o];this.containerWidth=null,this.containerHeight=null,this.contentWidth=null,this.contentHeight=null;var s=function(){return r.classList.add(m.state.focus)},a=function(){return r.classList.remove(m.state.focus)};this.isRtl="rtl"===t(r).direction,this.isNegativeScroll=function(){var t=r.scrollLeft,e=null;return r.scrollLeft=-1,e=r.scrollLeft<0,r.scrollLeft=t,e}(),this.negativeScrollAdjustment=this.isNegativeScroll?r.scrollWidth-r.clientWidth:0,this.event=new y,this.ownerDocument=r.ownerDocument||document,this.scrollbarXRail=i(m.element.rail("x")),r.appendChild(this.scrollbarXRail),this.scrollbarX=i(m.element.thumb("x")),this.scrollbarXRail.appendChild(this.scrollbarX),this.scrollbarX.setAttribute("tabindex",0),this.event.bind(this.scrollbarX,"focus",s),this.event.bind(this.scrollbarX,"blur",a),this.scrollbarXActive=null,this.scrollbarXWidth=null,this.scrollbarXLeft=null;var c=t(this.scrollbarXRail);this.scrollbarXBottom=parseInt(c.bottom,10),isNaN(this.scrollbarXBottom)?(this.isScrollbarXUsingBottom=!1,this.scrollbarXTop=u(c.top)):this.isScrollbarXUsingBottom=!0,this.railBorderXWidth=u(c.borderLeftWidth)+u(c.borderRightWidth),e(this.scrollbarXRail,{display:"block"}),this.railXMarginWidth=u(c.marginLeft)+u(c.marginRight),e(this.scrollbarXRail,{display:""}),this.railXWidth=null,this.railXRatio=null,this.scrollbarYRail=i(m.element.rail("y")),r.appendChild(this.scrollbarYRail),this.scrollbarY=i(m.element.thumb("y")),this.scrollbarYRail.appendChild(this.scrollbarY),this.scrollbarY.setAttribute("tabindex",0),this.event.bind(this.scrollbarY,"focus",s),this.event.bind(this.scrollbarY,"blur",a),this.scrollbarYActive=null,this.scrollbarYHeight=null,this.scrollbarYTop=null;var h=t(this.scrollbarYRail);this.scrollbarYRight=parseInt(h.right,10),isNaN(this.scrollbarYRight)?(this.isScrollbarYUsingRight=!1,this.scrollbarYLeft=u(h.left)):this.isScrollbarYUsingRight=!0,this.scrollbarYOuterWidth=this.isRtl?f(this.scrollbarY):null,this.railBorderYWidth=u(h.borderTopWidth)+u(h.borderBottomWidth),e(this.scrollbarYRail,{display:"block"}),this.railYMarginHeight=u(h.marginTop)+u(h.marginBottom),e(this.scrollbarYRail,{display:""}),this.railYHeight=null,this.railYRatio=null,this.reach={x:r.scrollLeft<=0?"start":r.scrollLeft>=this.contentWidth-this.containerWidth?"end":null,y:r.scrollTop<=0?"start":r.scrollTop>=this.contentHeight-this.containerHeight?"end":null},this.isAlive=!0,this.settings.handlers.forEach(function(t){return T[t](n)}),this.lastScrollTop=Math.floor(r.scrollTop),this.lastScrollLeft=r.scrollLeft,this.event.bind(this.element,"scroll",function(t){return n.onScroll(t)}),R(this)};return H.prototype.update=function(){this.isAlive&&(this.negativeScrollAdjustment=this.isNegativeScroll?this.element.scrollWidth-this.element.clientWidth:0,e(this.scrollbarXRail,{display:"block"}),e(this.scrollbarYRail,{display:"block"}),this.railXMarginWidth=u(t(this.scrollbarXRail).marginLeft)+u(t(this.scrollbarXRail).marginRight),this.railYMarginHeight=u(t(this.scrollbarYRail).marginTop)+u(t(this.scrollbarYRail).marginBottom),e(this.scrollbarXRail,{display:"none"}),e(this.scrollbarYRail,{display:"none"}),R(this),W(this,"top",0,!1,!0),W(this,"left",0,!1,!0),e(this.scrollbarXRail,{display:""}),e(this.scrollbarYRail,{display:""}))},H.prototype.onScroll=function(t){this.isAlive&&(R(this),W(this,"top",this.element.scrollTop-this.lastScrollTop),W(this,"left",this.element.scrollLeft-this.lastScrollLeft),this.lastScrollTop=Math.floor(this.element.scrollTop),this.lastScrollLeft=this.element.scrollLeft)},H.prototype.destroy=function(){this.isAlive&&(this.event.unbindAll(),l(this.scrollbarX),l(this.scrollbarY),l(this.scrollbarXRail),l(this.scrollbarYRail),this.removePsClasses(),this.element=null,this.scrollbarX=null,this.scrollbarY=null,this.scrollbarXRail=null,this.scrollbarYRail=null,this.isAlive=!1)},H.prototype.removePsClasses=function(){this.element.className=this.element.className.split(" ").filter(function(t){return!t.match(/^ps([-_].+|)$/)}).join(" ")},H});
-var Ps = null;
-/**
- *
- * var loader = showLoader(document.getElementById("parent-id"))
- * loader.remove()
- *
- * parent must have position: relative
- */
-showLoader = function (parent, text) {
- var themeType = window.Asc && window.Asc.plugin && window.Asc.plugin.theme && window.Asc.plugin.theme.type;
- var loader = document.createElement('div');
- loader.className = 'asc-plugin-loader';
- loader.innerHTML = '
' + (text || "Loading") + '
';
- parent.append ? parent.append(loader) : parent.appendChild(loader);
- return loader;
-};
-// check css load
-if (document.currentScript && document.currentScript.src) {
- if (document.currentScript.getAttribute("loadCss") === "true") {
- var scriptDirectory = document.currentScript.src;
- var lastSlash = scriptDirectory.lastIndexOf("/");
- if (0 < lastSlash) scriptDirectory = scriptDirectory.substr(0, lastSlash);
-
- var link = document.createElement("link");
- link.rel = "stylesheet";
- link.type = "text/css";
- link.href = scriptDirectory + "/plugins.css";
- document.getElementsByTagName('HEAD')[0].appendChild(link);
- }
-}
-// escape and unescape func
-!function(e){function n(n){function t(e){return n[e]}var e="(?:"+Object.keys(n).join("|")+")",u=RegExp(e),c=RegExp(e,"g");return function(e){return e=null==e?"":""+e,u.test(e)?e.replace(c,t):e}}escape=n({"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"}),unescape=n({"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"}),e.escape=escape,e.unescape=unescape}(window);
-
-// select2 fix search field
-var select2_amd,select2_defaults;"undefined"!=typeof jQuery&&null!=$.fn.select2&&(select2_amd=$.fn.select2.amd,select2_defaults=$.fn.select2.defaults,$.fn.select2.amd.define("SearchableSingleSelection",["select2/utils","select2/selection/single","select2/selection/eventRelay","select2/dropdown/search"],function(e,t,n,l){var s=e.Decorate(t,l);(s=e.Decorate(s,n)).prototype.render=function(){var e=l.prototype.render.call(this,t.prototype.render);return this.$searchContainer.hide(),this.$element.siblings(".select2").find(".selection").prepend(this.$searchContainer),e};var c=s.prototype.bind;return s.prototype.bind=function(e){var t=this;c.apply(this,arguments),e.on("open",function(){t.$selection.hide(),t.$searchContainer.show()}),e.on("close",function(){t.$searchContainer.hide(),t.$selection.show()})},s}),$.fn.select2.amd.define("UnsearchableDropdown",["select2/utils","select2/dropdown","select2/dropdown/attachBody","select2/dropdown/closeOnSelect"],function(e,t,n,l){n=e.Decorate(t,n);return n=e.Decorate(n,l)}),select2_amd.define("jquery.select2_",["jquery","jquery-mousewheel","./select2/core","./select2/defaults","./select2/utils"],function(s,e,c,t,o){var r;return null!=s.fn.select2&&(r=["open","close","destroy"],s.fn.select2=function(l){if((l=l||{}).selectionAdapter=!1!==l.selectionAdapter?select2_amd.require("SearchableSingleSelection"):void 0,l.dropdownAdapter=!1!==l.dropdownAdapter?select2_amd.require("UnsearchableDropdown"):void 0,"object"==typeof l)return this.each(function(){var e=s.extend(!0,{},l),t=new c(s(this),e),n=s(this).next().find(".select2-search.select2-search--dropdown"),e=s(this).next().find("span.selection").find(".select2-selection__arrow").clone();s(e).click(function(){t.close()}),e.appendTo(n)}),this;if("string"!=typeof l)throw new Error("Invalid arguments for Select2: "+l);var t,n=Array.prototype.slice.call(arguments,1);return this.each(function(){var e=o.GetData(this,"select2");null==e&&window.console&&console.error&&console.error("The select2('"+l+"') method was called on an element that is not using Select2."),t=e[l].apply(e,n)}),-1 .ps__rail-x,
-.ps--active-y > .ps__rail-y {
- display: block;
- background-color: transparent;
-}
-
-.ps .ps__rail-x:hover,
-.ps .ps__rail-y:hover,
-.ps .ps__rail-x:focus,
-.ps .ps__rail-y:focus,
-.ps .ps__rail-x.ps--clicking,
-.ps .ps__rail-y.ps--clicking {
- background-color: #EEEEEE;
-}
-
-/*
- * Scrollbar thumb styles
- */
-.ps__thumb-x {
- position: absolute; /* please don't change 'position' */
- bottom: 0; /* there must be 'bottom' for ps-scrollbar-x */
- height: 9px;
- background-color: rgb(241, 241, 241);
- -webkit-border-radius: 2px;
- -moz-border-radius: 2px;
- border-radius: 2px;
- visibility: visible;
- display: block;
- box-sizing: border-box;
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAOCAYAAAD0f5bSAAAACXBIWXMAAB2HAAAdhwGP5fFlAAAALElEQVQokWNgoBs4f/78f1JpJnIsIkvTsAQfP378TypNxyD/+PEjyRrJsgkAKS81km7nDNQAAAAASUVORK5CYII=);
- image-rendering: pixelated;
- background-repeat: no-repeat;
- background-position: center 0;
- border: 1px solid #cfcfcf;
-}
-
-.ps__thumb-y {
- position: absolute; /* please don't change 'position' */
- right: 0; /* there must be 'right' for ps-scrollbar-y */
- width: 9px;
- background-color: rgb(241, 241, 241);
- -webkit-border-radius: 2px;
- -moz-border-radius: 2px;
- border-radius: 2px;
- visibility: visible;
- display: block;
- box-sizing: border-box;
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAQAAAAz1Zf0AAAAIUlEQVR42mNgAILz/0GQAQo+/gdBBqLAqE5ydH5k+sgEANHgUH2JtDRHAAAAAElFTkSuQmCC);
- image-rendering: pixelated;
- background-repeat: no-repeat;
- background-position: 0 center;
- border: 1px solid #cfcfcf;
-}
-
-.ps__rail-x:hover > .ps__thumb-x {
- background-color: rgb(207, 207, 207);
-}
-.ps__thumb-x:hover {
- background-position: center -7px;
-}
-.ps__rail-x:focus > .ps__thumb-x,
-.ps__rail-x.ps--clicking .ps__thumb-x {
- background-color: #adadad;
- border-color: #adadad;
- background-position: center -7px;
-}
-
-.ps__rail-y:hover > .ps__thumb-y {
- background-color: rgb(207, 207, 207);
-}
-.ps__thumb-y:hover {
- background-position: -7px center;
-}
-.ps__rail-y:focus > .ps__thumb-y,
-.ps__rail-y.ps--clicking .ps__thumb-y {
- background-color: #adadad;
- border-color: #adadad;
- background-position: -7px center;
-}
-
-/* MS supports */
-@supports (-ms-overflow-style: none) {
- .ps {
- overflow: auto !important;
- }
-}
-
-@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
- .ps {
- overflow: auto !important;
- }
-}
-
-/* loader */
-@keyframes rotation {
- from {
- transform: rotate(0deg);
- }
-
- to {
- transform: rotate(360deg);
- }
-}
-.asc-loader-container {
- position: relative;
-}
-.asc-plugin-loader {
- position: absolute;
- left: 50%;
- top: 50%;
- margin-top: -10px;
- z-index: 10000;
- line-height: 20px;
- background-image: none;
- background-color: transparent;
- color: #444444;
- transform: translate(-50%, 0);
-}
-.asc-plugin-loader .asc-loader-image {
- height: 20px;
- width: 20px;
- float: left;
- margin-top: -1px;
-
-
- animation-duration: .8s;
- animation-name: rotation;
- animation-iteration-count: infinite;
- animation-timing-function: linear;
-}
-.asc-loader-image-light {
- background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyOCAyOCI+PGNpcmNsZSBjeD0iMTQiIGN5PSIxNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEuNSIgcj0iMTAuMjUiIHN0cm9rZS1kYXNoYXJyYXk9IjE2MCUsIDQwJSIgLz48L3N2Zz4=);
-}
-.asc-loader-image-dark {
- background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+PGNpcmNsZSBjeD0iMTAiIGN5PSIxMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNDQ0IiBzdHJva2Utd2lkdGg9IjEuNSIgcj0iNy4yNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTYwJSwgNDAlIiAvPjwvc3ZnPg==);
-}
-
-.asc-plugin-loader .asc-loader-title {
- font-size: 13px;
- padding-left: 25px;
- min-width: 100px;
-}
-
-/* default scroll */
-
-/* * {
- scrollbar-width: thin;
-} */
-
-*::-webkit-scrollbar {
- width: 9px;
- height: 9px;
-}
-
-*::-webkit-scrollbar-thumb {
- border-radius: 2px;
- border: 1px solid;
- image-rendering: pixelated;
- background-repeat: no-repeat;
- cursor: default;
-}
-
-*::-webkit-scrollbar-track {
- cursor: default;
-}
-
-*::-webkit-scrollbar-thumb:vertical {
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAQAAAAz1Zf0AAAAIUlEQVR42mNgAILz/0GQAQo+/gdBBqLAqE5ydH5k+sgEANHgUH2JtDRHAAAAAElFTkSuQmCC);
- background-position: 0px center;
-}
-
-*::-webkit-scrollbar-thumb:horizontal {
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAOCAYAAAD0f5bSAAAACXBIWXMAAB2HAAAdhwGP5fFlAAAALElEQVQokWNgoBs4f/78f1JpJnIsIkvTsAQfP378TypNxyD/+PEjyRrJsgkAKS81km7nDNQAAAAASUVORK5CYII=);
- background-position: center 0px;
-}
-
-*::-webkit-scrollbar-thumb:vertical:hover {
- background-position: -7px center;
-}
-*::-webkit-scrollbar-thumb:horizontal:hover {
- background-position: center -7px;
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/v1/plugins-ui.js b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/v1/plugins-ui.js
deleted file mode 100644
index 9490dfba..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/v1/plugins-ui.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- *
- * (c) Copyright Ascensio System SIA 2021
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.PerfectScrollbar=e()}(this,function(){"use strict";function t(t){return getComputedStyle(t)}function e(t,e){for(var i in e){var r=e[i];"number"==typeof r&&(r+="px"),t.style[i]=r}return t}function i(t){var e=document.createElement("div");return e.className=t,e}function r(t,e){if(!v)throw new Error("No element matching method supported");return v.call(t,e)}function l(t){t.remove?t.remove():t.parentNode&&t.parentNode.removeChild(t)}function n(t,e){return Array.prototype.filter.call(t.children,function(t){return r(t,e)})}function o(t,e){var i=t.element.classList,r=m.state.scrolling(e);i.contains(r)?clearTimeout(Y[e]):i.add(r)}function s(t,e){Y[e]=setTimeout(function(){return t.isAlive&&t.element.classList.remove(m.state.scrolling(e))},t.settings.scrollingThreshold)}function a(t,e){o(t,e),s(t,e)}function c(t){if("function"==typeof window.CustomEvent)return new CustomEvent(t);var e=document.createEvent("CustomEvent");return e.initCustomEvent(t,!1,!1,void 0),e}function h(t,e,i,r,l){var n=i[0],o=i[1],s=i[2],h=i[3],u=i[4],d=i[5];void 0===r&&(r=!0),void 0===l&&(l=!1);var f=t.element;t.reach[h]=null,f[s]<1&&(t.reach[h]="start"),f[s]>t[n]-t[o]-1&&(t.reach[h]="end"),e&&(f.dispatchEvent(c("ps-scroll-"+h)),e<0?f.dispatchEvent(c("ps-scroll-"+u)):e>0&&f.dispatchEvent(c("ps-scroll-"+d)),r&&a(t,h)),t.reach[h]&&(e||l)&&f.dispatchEvent(c("ps-"+h+"-reach-"+t.reach[h]))}function u(t){return parseInt(t,10)||0}function d(t){return r(t,"input,[contenteditable]")||r(t,"select,[contenteditable]")||r(t,"textarea,[contenteditable]")||r(t,"button,[contenteditable]")}function f(e){var i=t(e);return u(i.width)+u(i.paddingLeft)+u(i.paddingRight)+u(i.borderLeftWidth)+u(i.borderRightWidth)}function p(t,e){return t.settings.minScrollbarLength&&(e=Math.max(e,t.settings.minScrollbarLength)),t.settings.maxScrollbarLength&&(e=Math.min(e,t.settings.maxScrollbarLength)),e}function b(t,i){var r={width:i.railXWidth},l=Math.floor(t.scrollTop);i.isRtl?r.left=i.negativeScrollAdjustment+t.scrollLeft+i.containerWidth-i.contentWidth:r.left=t.scrollLeft,i.isScrollbarXUsingBottom?r.bottom=i.scrollbarXBottom-l:r.top=i.scrollbarXTop+l,e(i.scrollbarXRail,r);var n={top:l,height:i.railYHeight};i.isScrollbarYUsingRight?i.isRtl?n.right=i.contentWidth-(i.negativeScrollAdjustment+t.scrollLeft)-i.scrollbarYRight-i.scrollbarYOuterWidth:n.right=i.scrollbarYRight-t.scrollLeft:i.isRtl?n.left=i.negativeScrollAdjustment+t.scrollLeft+2*i.containerWidth-i.contentWidth-i.scrollbarYLeft-i.scrollbarYOuterWidth:n.left=i.scrollbarYLeft+t.scrollLeft,e(i.scrollbarYRail,n),e(i.scrollbarX,{left:i.scrollbarXLeft,width:i.scrollbarXWidth-i.railBorderXWidth}),e(i.scrollbarY,{top:i.scrollbarYTop,height:i.scrollbarYHeight-i.railBorderYWidth})}function g(t,e){function i(e){b[d]=g+Y*(e[a]-v),o(t,f),R(t),e.stopPropagation(),e.preventDefault()}function r(){s(t,f),t[p].classList.remove(m.state.clicking),t.event.unbind(t.ownerDocument,"mousemove",i)}var l=e[0],n=e[1],a=e[2],c=e[3],h=e[4],u=e[5],d=e[6],f=e[7],p=e[8],b=t.element,g=null,v=null,Y=null;t.event.bind(t[h],"mousedown",function(e){g=b[d],v=e[a],Y=(t[n]-t[l])/(t[c]-t[u]),t.event.bind(t.ownerDocument,"mousemove",i),t.event.once(t.ownerDocument,"mouseup",r),t[p].classList.add(m.state.clicking),e.stopPropagation()/*,e.preventDefault()*/})}var v="undefined"!=typeof Element&&(Element.prototype.matches||Element.prototype.webkitMatchesSelector||Element.prototype.mozMatchesSelector||Element.prototype.msMatchesSelector),m={main:"ps",element:{thumb:function(t){return"ps__thumb-"+t},rail:function(t){return"ps__rail-"+t},consuming:"ps__child--consume"},state:{focus:"ps--focus",clicking:"ps--clicking",active:function(t){return"ps--active-"+t},scrolling:function(t){return"ps--scrolling-"+t}}},Y={x:null,y:null},X=function(t){this.element=t,this.handlers={}},w={isEmpty:{configurable:!0}};X.prototype.bind=function(t,e){void 0===this.handlers[t]&&(this.handlers[t]=[]),this.handlers[t].push(e),this.element.addEventListener(t,e,!1)},X.prototype.unbind=function(t,e){var i=this;this.handlers[t]=this.handlers[t].filter(function(r){return!(!e||r===e)||(i.element.removeEventListener(t,r,!1),!1)})},X.prototype.unbindAll=function(){var t=this;for(var e in t.handlers)t.unbind(e)},w.isEmpty.get=function(){var t=this;return Object.keys(this.handlers).every(function(e){return 0===t.handlers[e].length})},Object.defineProperties(X.prototype,w);var y=function(){this.eventElements=[]};y.prototype.eventElement=function(t){var e=this.eventElements.filter(function(e){return e.element===t})[0];return e||(e=new X(t),this.eventElements.push(e)),e},y.prototype.bind=function(t,e,i){this.eventElement(t).bind(e,i)},y.prototype.unbind=function(t,e,i){var r=this.eventElement(t);r.unbind(e,i),r.isEmpty&&this.eventElements.splice(this.eventElements.indexOf(r),1)},y.prototype.unbindAll=function(){this.eventElements.forEach(function(t){return t.unbindAll()}),this.eventElements=[]},y.prototype.once=function(t,e,i){var r=this.eventElement(t),l=function(t){r.unbind(e,l),i(t)};r.bind(e,l)};var W=function(t,e,i,r,l){void 0===r&&(r=!0),void 0===l&&(l=!1);var n;if("top"===e)n=["contentHeight","containerHeight","scrollTop","y","up","down"];else{if("left"!==e)throw new Error("A proper axis should be provided");n=["contentWidth","containerWidth","scrollLeft","x","left","right"]}h(t,i,n,r,l)},L={isWebKit:"undefined"!=typeof document&&"WebkitAppearance"in document.documentElement.style,supportsTouch:"undefined"!=typeof window&&("ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch),supportsIePointer:"undefined"!=typeof navigator&&navigator.msMaxTouchPoints,isChrome:"undefined"!=typeof navigator&&/Chrome/i.test(navigator&&navigator.userAgent)},R=function(t){var e=t.element,i=Math.floor(e.scrollTop);t.containerWidth=e.clientWidth,t.containerHeight=e.clientHeight,t.contentWidth=e.scrollWidth,t.contentHeight=e.scrollHeight,e.contains(t.scrollbarXRail)||(n(e,m.element.rail("x")).forEach(function(t){return l(t)}),e.appendChild(t.scrollbarXRail)),e.contains(t.scrollbarYRail)||(n(e,m.element.rail("y")).forEach(function(t){return l(t)}),e.appendChild(t.scrollbarYRail)),!t.settings.suppressScrollX&&t.containerWidth+t.settings.scrollXMarginOffset=t.railXWidth-t.scrollbarXWidth&&(t.scrollbarXLeft=t.railXWidth-t.scrollbarXWidth),t.scrollbarYTop>=t.railYHeight-t.scrollbarYHeight&&(t.scrollbarYTop=t.railYHeight-t.scrollbarYHeight),b(e,t),t.scrollbarXActive?e.classList.add(m.state.active("x")):(e.classList.remove(m.state.active("x")),t.scrollbarXWidth=0,t.scrollbarXLeft=0,e.scrollLeft=0),t.scrollbarYActive?e.classList.add(m.state.active("y")):(e.classList.remove(m.state.active("y")),t.scrollbarYHeight=0,t.scrollbarYTop=0,e.scrollTop=0)},T={"click-rail":function(t){t.event.bind(t.scrollbarY,"mousedown",function(t){return t.stopPropagation()}),t.event.bind(t.scrollbarYRail,"mousedown",function(e){var i=e.pageY-window.pageYOffset-t.scrollbarYRail.getBoundingClientRect().top>t.scrollbarYTop?1:-1;t.element.scrollTop+=i*t.containerHeight,R(t),e.stopPropagation()}),t.event.bind(t.scrollbarX,"mousedown",function(t){return t.stopPropagation()}),t.event.bind(t.scrollbarXRail,"mousedown",function(e){var i=e.pageX-window.pageXOffset-t.scrollbarXRail.getBoundingClientRect().left>t.scrollbarXLeft?1:-1;t.element.scrollLeft+=i*t.containerWidth,R(t),e.stopPropagation()})},"drag-thumb":function(t){g(t,["containerWidth","contentWidth","pageX","railXWidth","scrollbarX","scrollbarXWidth","scrollLeft","x","scrollbarXRail"]),g(t,["containerHeight","contentHeight","pageY","railYHeight","scrollbarY","scrollbarYHeight","scrollTop","y","scrollbarYRail"])},keyboard:function(t){function e(e,r){var l=Math.floor(i.scrollTop);if(0===e){if(!t.scrollbarYActive)return!1;if(0===l&&r>0||l>=t.contentHeight-t.containerHeight&&r<0)return!t.settings.wheelPropagation}var n=i.scrollLeft;if(0===r){if(!t.scrollbarXActive)return!1;if(0===n&&e<0||n>=t.contentWidth-t.containerWidth&&e>0)return!t.settings.wheelPropagation}return!0}var i=t.element,l=function(){return r(i,":hover")},n=function(){return r(t.scrollbarX,":focus")||r(t.scrollbarY,":focus")};t.event.bind(t.ownerDocument,"keydown",function(r){if(!(r.isDefaultPrevented&&r.isDefaultPrevented()||r.defaultPrevented)&&(l()||n())){var o=document.activeElement?document.activeElement:t.ownerDocument.activeElement;if(o){if("IFRAME"===o.tagName)o=o.contentDocument.activeElement;else for(;o.shadowRoot;)o=o.shadowRoot.activeElement;if(d(o))return}var s=0,a=0;switch(r.which){case 37:s=r.metaKey?-t.contentWidth:r.altKey?-t.containerWidth:-30;break;case 38:a=r.metaKey?t.contentHeight:r.altKey?t.containerHeight:30;break;case 39:s=r.metaKey?t.contentWidth:r.altKey?t.containerWidth:30;break;case 40:a=r.metaKey?-t.contentHeight:r.altKey?-t.containerHeight:-30;break;case 32:a=r.shiftKey?t.containerHeight:-t.containerHeight;break;case 33:a=t.containerHeight;break;case 34:a=-t.containerHeight;break;case 36:a=t.contentHeight;break;case 35:a=-t.contentHeight;break;default:return}t.settings.suppressScrollX&&0!==s||t.settings.suppressScrollY&&0!==a||(i.scrollTop-=a,i.scrollLeft+=s,R(t),e(s,a)&&r.preventDefault())}})},wheel:function(e){function i(t,i){var r=Math.floor(o.scrollTop),l=0===o.scrollTop,n=r+o.offsetHeight===o.scrollHeight,s=0===o.scrollLeft,a=o.scrollLeft+o.offsetWidth===o.scrollWidth;return!(Math.abs(i)>Math.abs(t)?l||n:s||a)||!e.settings.wheelPropagation}function r(t){var e=t.deltaX,i=-1*t.deltaY;return void 0!==e&&void 0!==i||(e=-1*t.wheelDeltaX/6,i=t.wheelDeltaY/6),t.deltaMode&&1===t.deltaMode&&(e*=10,i*=10),e!==e&&i!==i&&(e=0,i=t.wheelDelta),t.shiftKey?[-i,-e]:[e,i]}function l(e,i,r){if(!L.isWebKit&&o.querySelector("select:focus"))return!0;if(!o.contains(e))return!1;for(var l=e;l&&l!==o;){if(l.classList.contains(m.element.consuming))return!0;var n=t(l);if([n.overflow,n.overflowX,n.overflowY].join("").match(/(scroll|auto)/)){var s=l.scrollHeight-l.clientHeight;if(s>0&&!(0===l.scrollTop&&r>0||l.scrollTop===s&&r<0))return!0;var a=l.scrollWidth-l.clientWidth;if(a>0&&!(0===l.scrollLeft&&i<0||l.scrollLeft===a&&i>0))return!0}l=l.parentNode}return!1}function n(t){var n=r(t),s=n[0],a=n[1];if(!l(t.target,s,a)){var c=!1;e.settings.useBothWheelAxes?e.scrollbarYActive&&!e.scrollbarXActive?(a?o.scrollTop-=a*e.settings.wheelSpeed:o.scrollTop+=s*e.settings.wheelSpeed,c=!0):e.scrollbarXActive&&!e.scrollbarYActive&&(s?o.scrollLeft+=s*e.settings.wheelSpeed:o.scrollLeft-=a*e.settings.wheelSpeed,c=!0):(o.scrollTop-=a*e.settings.wheelSpeed,o.scrollLeft+=s*e.settings.wheelSpeed),R(e),(c=c||i(s,a))&&!t.ctrlKey&&(t.stopPropagation(),t.preventDefault())}}var o=e.element;void 0!==window.onwheel?e.event.bind(o,"wheel",n):void 0!==window.onmousewheel&&e.event.bind(o,"mousewheel",n)},touch:function(e){function i(t,i){var r=Math.floor(h.scrollTop),l=h.scrollLeft,n=Math.abs(t),o=Math.abs(i);if(o>n){if(i<0&&r===e.contentHeight-e.containerHeight||i>0&&0===r)return 0===window.scrollY&&i>0&&L.isChrome}else if(n>o&&(t<0&&l===e.contentWidth-e.containerWidth||t>0&&0===l))return!0;return!0}function r(t,i){h.scrollTop-=i,h.scrollLeft-=t,R(e)}function l(t){return t.targetTouches?t.targetTouches[0]:t}function n(t){return!(t.pointerType&&"pen"===t.pointerType&&0===t.buttons||(!t.targetTouches||1!==t.targetTouches.length)&&(!t.pointerType||"mouse"===t.pointerType||t.pointerType===t.MSPOINTER_TYPE_MOUSE))}function o(t){if(n(t)){var e=l(t);u.pageX=e.pageX,u.pageY=e.pageY,d=(new Date).getTime(),null!==p&&clearInterval(p)}}function s(e,i,r){if(!h.contains(e))return!1;for(var l=e;l&&l!==h;){if(l.classList.contains(m.element.consuming))return!0;var n=t(l);if([n.overflow,n.overflowX,n.overflowY].join("").match(/(scroll|auto)/)){var o=l.scrollHeight-l.clientHeight;if(o>0&&!(0===l.scrollTop&&r>0||l.scrollTop===o&&r<0))return!0;var s=l.scrollLeft-l.clientWidth;if(s>0&&!(0===l.scrollLeft&&i<0||l.scrollLeft===s&&i>0))return!0}l=l.parentNode}return!1}function a(t){if(n(t)){var e=l(t),o={pageX:e.pageX,pageY:e.pageY},a=o.pageX-u.pageX,c=o.pageY-u.pageY;if(s(t.target,a,c))return;r(a,c),u=o;var h=(new Date).getTime(),p=h-d;p>0&&(f.x=a/p,f.y=c/p,d=h),i(a,c)&&t.preventDefault()}}function c(){e.settings.swipeEasing&&(clearInterval(p),p=setInterval(function(){e.isInitialized?clearInterval(p):f.x||f.y?Math.abs(f.x)<.01&&Math.abs(f.y)<.01?clearInterval(p):(r(30*f.x,30*f.y),f.x*=.8,f.y*=.8):clearInterval(p)},10))}if(L.supportsTouch||L.supportsIePointer){var h=e.element,u={},d=0,f={},p=null;L.supportsTouch?(e.event.bind(h,"touchstart",o),e.event.bind(h,"touchmove",a),e.event.bind(h,"touchend",c)):L.supportsIePointer&&(window.PointerEvent?(e.event.bind(h,"pointerdown",o),e.event.bind(h,"pointermove",a),e.event.bind(h,"pointerup",c)):window.MSPointerEvent&&(e.event.bind(h,"MSPointerDown",o),e.event.bind(h,"MSPointerMove",a),e.event.bind(h,"MSPointerUp",c)))}}},H=function(r,l){var n=this;if(void 0===l&&(l={}),"string"==typeof r&&(r=document.querySelector(r)),!r||!r.nodeName)throw new Error("no element is specified to initialize PerfectScrollbar");this.element=r,r.classList.add(m.main),this.settings={handlers:["click-rail","drag-thumb","keyboard","wheel","touch"],maxScrollbarLength:null,minScrollbarLength:null,scrollingThreshold:1e3,scrollXMarginOffset:0,scrollYMarginOffset:0,suppressScrollX:!1,suppressScrollY:!1,swipeEasing:!0,useBothWheelAxes:!1,wheelPropagation:!0,wheelSpeed:0.35};for(var o in l)n.settings[o]=l[o];this.containerWidth=null,this.containerHeight=null,this.contentWidth=null,this.contentHeight=null;var s=function(){return r.classList.add(m.state.focus)},a=function(){return r.classList.remove(m.state.focus)};this.isRtl="rtl"===t(r).direction,this.isNegativeScroll=function(){var t=r.scrollLeft,e=null;return r.scrollLeft=-1,e=r.scrollLeft<0,r.scrollLeft=t,e}(),this.negativeScrollAdjustment=this.isNegativeScroll?r.scrollWidth-r.clientWidth:0,this.event=new y,this.ownerDocument=r.ownerDocument||document,this.scrollbarXRail=i(m.element.rail("x")),r.appendChild(this.scrollbarXRail),this.scrollbarX=i(m.element.thumb("x")),this.scrollbarXRail.appendChild(this.scrollbarX),this.scrollbarX.setAttribute("tabindex",0),this.event.bind(this.scrollbarX,"focus",s),this.event.bind(this.scrollbarX,"blur",a),this.scrollbarXActive=null,this.scrollbarXWidth=null,this.scrollbarXLeft=null;var c=t(this.scrollbarXRail);this.scrollbarXBottom=parseInt(c.bottom,10),isNaN(this.scrollbarXBottom)?(this.isScrollbarXUsingBottom=!1,this.scrollbarXTop=u(c.top)):this.isScrollbarXUsingBottom=!0,this.railBorderXWidth=u(c.borderLeftWidth)+u(c.borderRightWidth),e(this.scrollbarXRail,{display:"block"}),this.railXMarginWidth=u(c.marginLeft)+u(c.marginRight),e(this.scrollbarXRail,{display:""}),this.railXWidth=null,this.railXRatio=null,this.scrollbarYRail=i(m.element.rail("y")),r.appendChild(this.scrollbarYRail),this.scrollbarY=i(m.element.thumb("y")),this.scrollbarYRail.appendChild(this.scrollbarY),this.scrollbarY.setAttribute("tabindex",0),this.event.bind(this.scrollbarY,"focus",s),this.event.bind(this.scrollbarY,"blur",a),this.scrollbarYActive=null,this.scrollbarYHeight=null,this.scrollbarYTop=null;var h=t(this.scrollbarYRail);this.scrollbarYRight=parseInt(h.right,10),isNaN(this.scrollbarYRight)?(this.isScrollbarYUsingRight=!1,this.scrollbarYLeft=u(h.left)):this.isScrollbarYUsingRight=!0,this.scrollbarYOuterWidth=this.isRtl?f(this.scrollbarY):null,this.railBorderYWidth=u(h.borderTopWidth)+u(h.borderBottomWidth),e(this.scrollbarYRail,{display:"block"}),this.railYMarginHeight=u(h.marginTop)+u(h.marginBottom),e(this.scrollbarYRail,{display:""}),this.railYHeight=null,this.railYRatio=null,this.reach={x:r.scrollLeft<=0?"start":r.scrollLeft>=this.contentWidth-this.containerWidth?"end":null,y:r.scrollTop<=0?"start":r.scrollTop>=this.contentHeight-this.containerHeight?"end":null},this.isAlive=!0,this.settings.handlers.forEach(function(t){return T[t](n)}),this.lastScrollTop=Math.floor(r.scrollTop),this.lastScrollLeft=r.scrollLeft,this.event.bind(this.element,"scroll",function(t){return n.onScroll(t)}),R(this)};return H.prototype.update=function(){this.isAlive&&(this.negativeScrollAdjustment=this.isNegativeScroll?this.element.scrollWidth-this.element.clientWidth:0,e(this.scrollbarXRail,{display:"block"}),e(this.scrollbarYRail,{display:"block"}),this.railXMarginWidth=u(t(this.scrollbarXRail).marginLeft)+u(t(this.scrollbarXRail).marginRight),this.railYMarginHeight=u(t(this.scrollbarYRail).marginTop)+u(t(this.scrollbarYRail).marginBottom),e(this.scrollbarXRail,{display:"none"}),e(this.scrollbarYRail,{display:"none"}),R(this),W(this,"top",0,!1,!0),W(this,"left",0,!1,!0),e(this.scrollbarXRail,{display:""}),e(this.scrollbarYRail,{display:""}))},H.prototype.onScroll=function(t){this.isAlive&&(R(this),W(this,"top",this.element.scrollTop-this.lastScrollTop),W(this,"left",this.element.scrollLeft-this.lastScrollLeft),this.lastScrollTop=Math.floor(this.element.scrollTop),this.lastScrollLeft=this.element.scrollLeft)},H.prototype.destroy=function(){this.isAlive&&(this.event.unbindAll(),l(this.scrollbarX),l(this.scrollbarY),l(this.scrollbarXRail),l(this.scrollbarYRail),this.removePsClasses(),this.element=null,this.scrollbarX=null,this.scrollbarY=null,this.scrollbarXRail=null,this.scrollbarYRail=null,this.isAlive=!1)},H.prototype.removePsClasses=function(){this.element.className=this.element.className.split(" ").filter(function(t){return!t.match(/^ps([-_].+|)$/)}).join(" ")},H});
-var Ps = null;
-/**
- *
- * var loader = showLoader(document.getElementById("parent-id"))
- * loader.remove()
- *
- * parent must have position: relative
- */
-showLoader = function (parent, text) {
- var themeType = window.Asc && window.Asc.plugin && window.Asc.plugin.theme && window.Asc.plugin.theme.type;
- var loader = document.createElement('div');
- loader.className = 'asc-plugin-loader';
- loader.innerHTML = '
' + (text || "Loading") + '
';
- parent.append ? parent.append(loader) : parent.appendChild(loader);
- return loader;
-};
-// check css load
-if (document.currentScript && document.currentScript.src) {
- if (document.currentScript.getAttribute("loadCss") === "true") {
- var scriptDirectory = document.currentScript.src;
- var lastSlash = scriptDirectory.lastIndexOf("/");
- if (0 < lastSlash) scriptDirectory = scriptDirectory.substr(0, lastSlash);
-
- var link = document.createElement("link");
- link.rel = "stylesheet";
- link.type = "text/css";
- link.href = scriptDirectory + "/plugins.css";
- document.getElementsByTagName('HEAD')[0].appendChild(link);
- }
-}
-// escape and unescape func
-!function(e){function n(n){function t(e){return n[e]}var e="(?:"+Object.keys(n).join("|")+")",u=RegExp(e),c=RegExp(e,"g");return function(e){return e=null==e?"":""+e,u.test(e)?e.replace(c,t):e}}escape=n({"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"}),unescape=n({"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"}),e.escape=escape,e.unescape=unescape}(window);
-
-// select2 fix search field
-var select2_amd,select2_defaults;"undefined"!=typeof jQuery&&null!=$.fn.select2&&(select2_amd=$.fn.select2.amd,select2_defaults=$.fn.select2.defaults,$.fn.select2.amd.define("SearchableSingleSelection",["select2/utils","select2/selection/single","select2/selection/eventRelay","select2/dropdown/search"],function(e,t,n,l){var s=e.Decorate(t,l);(s=e.Decorate(s,n)).prototype.render=function(){var e=l.prototype.render.call(this,t.prototype.render);return this.$searchContainer.hide(),this.$element.siblings(".select2").find(".selection").prepend(this.$searchContainer),e};var c=s.prototype.bind;return s.prototype.bind=function(e){var t=this;c.apply(this,arguments),e.on("open",function(){t.$selection.hide(),t.$searchContainer.show()}),e.on("close",function(){t.$searchContainer.hide(),t.$selection.show()})},s}),$.fn.select2.amd.define("UnsearchableDropdown",["select2/utils","select2/dropdown","select2/dropdown/attachBody","select2/dropdown/closeOnSelect"],function(e,t,n,l){n=e.Decorate(t,n);return n=e.Decorate(n,l)}),select2_amd.define("jquery.select2_",["jquery","jquery-mousewheel","./select2/core","./select2/defaults","./select2/utils"],function(s,e,c,t,o){var r;return null!=s.fn.select2&&(r=["open","close","destroy"],s.fn.select2=function(l){if((l=l||{}).selectionAdapter=!1!==l.selectionAdapter?select2_amd.require("SearchableSingleSelection"):void 0,l.dropdownAdapter=!1!==l.dropdownAdapter?select2_amd.require("UnsearchableDropdown"):void 0,"object"==typeof l)return this.each(function(){var e=s.extend(!0,{},l),t=new c(s(this),e),n=s(this).next().find(".select2-search.select2-search--dropdown"),e=s(this).next().find("span.selection").find(".select2-selection__arrow").clone();s(e).click(function(){t.close()}),e.appendTo(n)}),this;if("string"!=typeof l)throw new Error("Invalid arguments for Select2: "+l);var t,n=Array.prototype.slice.call(arguments,1);return this.each(function(){var e=o.GetData(this,"select2");null==e&&window.console&&console.error&&console.error("The select2('"+l+"') method was called on an element that is not using Select2."),t=e[l].apply(e,n)}),-1 .ps__rail-x,
-.ps--active-y > .ps__rail-y {
- display: block;
- background-color: transparent;
-}
-
-.ps .ps__rail-x:hover,
-.ps .ps__rail-y:hover,
-.ps .ps__rail-x:focus,
-.ps .ps__rail-y:focus,
-.ps .ps__rail-x.ps--clicking,
-.ps .ps__rail-y.ps--clicking {
- background-color: #EEEEEE;
-}
-
-/*
- * Scrollbar thumb styles
- */
-.ps__thumb-x {
- position: absolute; /* please don't change 'position' */
- bottom: 0; /* there must be 'bottom' for ps-scrollbar-x */
- height: 9px;
- background-color: rgb(241, 241, 241);
- -webkit-border-radius: 2px;
- -moz-border-radius: 2px;
- border-radius: 2px;
- visibility: visible;
- display: block;
- box-sizing: border-box;
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAOCAYAAAD0f5bSAAAACXBIWXMAAB2HAAAdhwGP5fFlAAAALElEQVQokWNgoBs4f/78f1JpJnIsIkvTsAQfP378TypNxyD/+PEjyRrJsgkAKS81km7nDNQAAAAASUVORK5CYII=);
- image-rendering: pixelated;
- background-repeat: no-repeat;
- background-position: center 0;
- border: 1px solid #cfcfcf;
-}
-
-.ps__thumb-y {
- position: absolute; /* please don't change 'position' */
- right: 0; /* there must be 'right' for ps-scrollbar-y */
- width: 9px;
- background-color: rgb(241, 241, 241);
- -webkit-border-radius: 2px;
- -moz-border-radius: 2px;
- border-radius: 2px;
- visibility: visible;
- display: block;
- box-sizing: border-box;
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAQAAAAz1Zf0AAAAIUlEQVR42mNgAILz/0GQAQo+/gdBBqLAqE5ydH5k+sgEANHgUH2JtDRHAAAAAElFTkSuQmCC);
- image-rendering: pixelated;
- background-repeat: no-repeat;
- background-position: 0 center;
- border: 1px solid #cfcfcf;
-}
-
-.ps__rail-x:hover > .ps__thumb-x {
- background-color: rgb(207, 207, 207);
-}
-.ps__thumb-x:hover {
- background-position: center -7px;
-}
-.ps__rail-x:focus > .ps__thumb-x,
-.ps__rail-x.ps--clicking .ps__thumb-x {
- background-color: #adadad;
- border-color: #adadad;
- background-position: center -7px;
-}
-
-.ps__rail-y:hover > .ps__thumb-y {
- background-color: rgb(207, 207, 207);
-}
-.ps__thumb-y:hover {
- background-position: -7px center;
-}
-.ps__rail-y:focus > .ps__thumb-y,
-.ps__rail-y.ps--clicking .ps__thumb-y {
- background-color: #adadad;
- border-color: #adadad;
- background-position: -7px center;
-}
-
-/* MS supports */
-@supports (-ms-overflow-style: none) {
- .ps {
- overflow: auto !important;
- }
-}
-
-@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
- .ps {
- overflow: auto !important;
- }
-}
-
-/* loader */
-@keyframes rotation {
- from {
- transform: rotate(0deg);
- }
-
- to {
- transform: rotate(360deg);
- }
-}
-.asc-loader-container {
- position: relative;
-}
-.asc-plugin-loader {
- position: absolute;
- left: 50%;
- top: 50%;
- margin-top: -10px;
- z-index: 10000;
- line-height: 20px;
- background-image: none;
- background-color: transparent;
- color: #444444;
- transform: translate(-50%, 0);
-}
-.asc-plugin-loader .asc-loader-image {
- height: 20px;
- width: 20px;
- float: left;
- margin-top: -1px;
-
-
- animation-duration: .8s;
- animation-name: rotation;
- animation-iteration-count: infinite;
- animation-timing-function: linear;
-}
-.asc-loader-image-light {
- background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyOCAyOCI+PGNpcmNsZSBjeD0iMTQiIGN5PSIxNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEuNSIgcj0iMTAuMjUiIHN0cm9rZS1kYXNoYXJyYXk9IjE2MCUsIDQwJSIgLz48L3N2Zz4=);
-}
-.asc-loader-image-dark {
- background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+PGNpcmNsZSBjeD0iMTAiIGN5PSIxMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNDQ0IiBzdHJva2Utd2lkdGg9IjEuNSIgcj0iNy4yNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTYwJSwgNDAlIiAvPjwvc3ZnPg==);
-}
-
-.asc-plugin-loader .asc-loader-title {
- font-size: 13px;
- padding-left: 25px;
- min-width: 100px;
-}
-
-/* default scroll */
-
-/* * {
- scrollbar-width: thin;
-} */
-
-*::-webkit-scrollbar {
- width: 9px;
- height: 9px;
-}
-
-*::-webkit-scrollbar-thumb {
- border-radius: 2px;
- border: 1px solid;
- image-rendering: pixelated;
- background-repeat: no-repeat;
- cursor: default;
-}
-
-*::-webkit-scrollbar-track {
- cursor: default;
-}
-
-*::-webkit-scrollbar-thumb:vertical {
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAQAAAAz1Zf0AAAAIUlEQVR42mNgAILz/0GQAQo+/gdBBqLAqE5ydH5k+sgEANHgUH2JtDRHAAAAAElFTkSuQmCC);
- background-position: 0px center;
-}
-
-*::-webkit-scrollbar-thumb:horizontal {
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAOCAYAAAD0f5bSAAAACXBIWXMAAB2HAAAdhwGP5fFlAAAALElEQVQokWNgoBs4f/78f1JpJnIsIkvTsAQfP378TypNxyD/+PEjyRrJsgkAKS81km7nDNQAAAAASUVORK5CYII=);
- background-position: center 0px;
-}
-
-*::-webkit-scrollbar-thumb:vertical:hover {
- background-position: -7px center;
-}
-*::-webkit-scrollbar-thumb:horizontal:hover {
- background-position: center -7px;
-}
\ No newline at end of file
diff --git a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/v1/plugins.js b/public/packages/onlyoffice/9.3.0/sdkjs-plugins/v1/plugins.js
deleted file mode 100644
index 92f78186..00000000
--- a/public/packages/onlyoffice/9.3.0/sdkjs-plugins/v1/plugins.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- *
- * (c) Copyright Ascensio System SIA 2021
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-(function(a,u){function w(c){a.Asc.plugin.ie_channel?a.Asc.plugin.ie_channel.postMessage(c):a.parent.postMessage(c,"*")}function r(c,d){if(!c||"object"!=typeof c&&"array"!=typeof c)return c;d=d===u?{}:d;for(var h in c)c.hasOwnProperty(h)&&(d[h]=c[h]&&"object"===typeof c[h]?r(c[h]):c[h]);return d}function p(c){var d=a.location.search,h=c+"=";c=d.indexOf(h);return 0<=c?(c+=h.length,h=d.indexOf("&",c),0>h&&(h=d.length),d.substring(c,h)):u}function n(){var c=p("windowID");c&&(a.Asc.plugin.windowID=c,
-a.Asc.plugin.guid||(a.Asc.plugin.guid=decodeURIComponent(p("guid"))));return u!==c?!0:!1}function q(c){if(a.Asc&&a.Asc.plugin)if(a.plugin_onMessage)a.Asc.supportOrigins[c.origin]&&a.plugin_onMessage(c);else if(a.Asc.plugin._initInternal&&"string"==typeof c.data){var d={};try{d=JSON.parse(c.data)}catch(h){d={}}"plugin_init"==d.type&&(a.Asc.supportOrigins[c.origin]=!0,a.Asc.plugin.ie_channel_check(c),eval(d.data))}}a.Asc=a.Asc||{};a.Asc.plugin={};a.Asc.plugin.ie_channel=null;a.Asc.plugin.ie_channel_check=
-function(c){var d=navigator.userAgent.toLowerCase();(-1
+
+
+ );
}
diff --git a/src/app/docs/demos/page.tsx b/src/app/docs/demos/page.tsx
index cac582d2..568faa00 100644
--- a/src/app/docs/demos/page.tsx
+++ b/src/app/docs/demos/page.tsx
@@ -1,26 +1,32 @@
-import { Suspense } from "react";
import type { Metadata } from "next";
-import { DocsDemosTabsPage } from "@/features/docs/components/docs-demos-tabs-page";
-import { DEMO_TABS, type DemoTabId } from "@/features/docs/config/site-map";
-import { readCompDoc } from "@/features/docs/lib/server";
+import Link from "next/link";
+import { DEMO_TABS } from "@/features/docs/config/site-map";
export const metadata: Metadata = {
title: "在线示例 — OnlyOffice Web Comp",
};
-async function loadDemoContents(): Promise> {
- const entries = await Promise.all(
- DEMO_TABS.map(async (tab) => [tab.id, await readCompDoc(tab.file)] as const),
- );
- return Object.fromEntries(entries) as Record;
-}
-
-export default async function DocsDemosRoutePage() {
- const contents = await loadDemoContents();
-
+export default function DocsDemosRoutePage() {
return (
- 加载示例…}>
-
-
+
+
在线示例
+
选择一个示例路由打开对应的编辑器实例。
+
+ {DEMO_TABS.map((tab) => (
+
+ {tab.label}
+
+ {tab.id === "single"
+ ? "一个页面只挂载一个编辑器实例"
+ : "多个 Tab 按 containerId 隔离实例"}
+
+
+ ))}
+
+
);
}
diff --git a/src/app/docs/demos/single/page.tsx b/src/app/docs/demos/single/page.tsx
index 2527bcdd..aec57d1b 100644
--- a/src/app/docs/demos/single/page.tsx
+++ b/src/app/docs/demos/single/page.tsx
@@ -1,5 +1,25 @@
-import { redirect } from "next/navigation";
+import type { Metadata } from "next";
+import { FILE_TYPE } from "@/components/onlyoffice-web-comp";
+import { OfficePreviewPage } from "@/features/demo/office-preview-page";
+import { DocsDemoPage } from "@/features/docs/components/docs-demo-page";
+import { readCompDoc } from "@/features/docs/lib/server";
-export default function DocsDemosSingleRedirectPage() {
- redirect("/docs/demos?tab=single");
+export const metadata: Metadata = {
+ title: "单实例示例 — OnlyOffice Web Comp",
+};
+
+export default async function DocsDemosSinglePage() {
+ const content = await readCompDoc("单实例示例.md");
+
+ return (
+
+
+
+ );
}
diff --git a/src/app/examples/page.tsx b/src/app/examples/page.tsx
index 31f47c28..2681cd70 100644
--- a/src/app/examples/page.tsx
+++ b/src/app/examples/page.tsx
@@ -1,5 +1,5 @@
import { redirect } from "next/navigation";
export default function ExamplesRedirectPage() {
- redirect("/docs/demos?tab=multi");
+ redirect("/docs/demos/multi");
}
diff --git a/src/app/multi/page.tsx b/src/app/multi/page.tsx
index 1b51ba97..63a0a808 100644
--- a/src/app/multi/page.tsx
+++ b/src/app/multi/page.tsx
@@ -1,5 +1,5 @@
import { redirect } from "next/navigation";
export default function MultiRedirectPage() {
- redirect("/docs/demos?tab=multi");
+ redirect("/docs/demos/multi");
}
diff --git a/src/components/onlyoffice-web-comp/const/index.ts b/src/components/onlyoffice-web-comp/const/index.ts
index a36d4741..af0ff9b8 100644
--- a/src/components/onlyoffice-web-comp/const/index.ts
+++ b/src/components/onlyoffice-web-comp/const/index.ts
@@ -119,8 +119,7 @@ export type OnlyOfficeStaticResourceOptions = {
cdnOrigin?: string | null;
};
-/** @deprecated 使用 X2T_PDF_FONT_MANIFEST[0].file */
-export const X2T_PDF_DEFAULT_FONT_FILE = "Carlito-Regular.ttf";
+const X2T_PDF_DEFAULT_FONT_FILE = "Carlito-Regular.ttf";
/**
* x2t PDF 字体:每款 TTF 独立二进制 + 别名。
@@ -272,38 +271,6 @@ export const STATIC_RESOURCE = {
},
} as StaticResource;
-/** @deprecated 使用 STATIC_RESOURCE.onlyoffice */
-export const ONLYOFFICE_RESOURCE = {
- get APP_ROOT() {
- return getStaticResource().onlyoffice.root;
- },
- get API_JS() {
- return getStaticResource().onlyoffice.apiJs;
- },
- get PRELOAD_HTML() {
- return getStaticResource().onlyoffice.preloadHtml;
- },
- get API_URL() {
- return getStaticResource().onlyoffice.apiUrl;
- },
- get PRELOAD_URL() {
- return getStaticResource().onlyoffice.preloadUrl;
- },
-} as const;
-
-/** @deprecated 使用 STATIC_RESOURCE.x2t */
-export const X2T_RESOURCE = {
- get ROOT() {
- return getStaticResource().x2t.root;
- },
- get SCRIPT() {
- return getStaticResource().x2t.script;
- },
- get WASM() {
- return getStaticResource().x2t.wasm;
- },
-} as const;
-
/** 站点相对路径 → 绝对 URL(Worker 内 origin 用 self.location.origin) */
export function resolveSiteUrl(origin: string, path: string): string {
if (/^https?:\/\//i.test(path)) {
diff --git a/src/components/onlyoffice-web-comp/core/editor-manager.ts b/src/components/onlyoffice-web-comp/core/editor-manager.ts
index f78482f2..b6377d7e 100644
--- a/src/components/onlyoffice-web-comp/core/editor-manager.ts
+++ b/src/components/onlyoffice-web-comp/core/editor-manager.ts
@@ -6,7 +6,7 @@ import {
type OnlyOfficeProxyWindow,
type ReporterHookWindow,
type ScopedIoFactory,
-} from "../internal/editor/install-proxies";
+} from "../internal/editor/runtime-bridge";
import {
callCrossOriginEditor,
canAccessIframeWindow,
@@ -14,20 +14,20 @@ import {
subscribeCrossOriginEditorEvent,
unregisterCrossOriginBridge,
watchCrossOriginIframe,
-} from "../internal/editor/cross-origin-bridge";
+} from "../internal/editor/runtime-bridge";
import {
CROSS_ORIGIN_EDITOR_COMMAND,
CROSS_ORIGIN_EDITOR_EVENT,
-} from "../internal/editor/cross-origin-protocol";
+} from "../internal/editor/runtime-bridge";
import { EditorServer } from "../internal/editor/server";
-import io, { type MockSocketOptions } from "../internal/editor/socket";
+import io, { type MockSocketOptions } from "../internal/editor/runtime-bridge";
+import { EditorLogger } from "../internal/editor/logger";
import {
type DocEditor,
DocumentType,
isOfficeXmlSizeLimitExceededError,
type OfficeXmlSizeLimitExceededPayload,
type OfficeTheme,
- type PluginMode,
type User,
} from "../internal/editor/types";
import { getDocumentType, isOnlyOfficeCdnMode } from "../const";
@@ -83,7 +83,6 @@ export type CreateEditorViewOptions = {
editorManager?: EditorManager;
editing?: boolean;
theme?: OfficeTheme;
- plugins?: PluginMode;
/** 由 EditorManagerFactory.beginLoadSession 生成,用于丢弃过期的异步初始化 */
loadSession?: number;
/** 修订审阅页:开启 markup 显示与页边修订气泡 */
@@ -174,7 +173,7 @@ export class EditorManager {
private uiTheme: OfficeTheme = "theme-white";
private instanceId = getNewInstanceId();
private containerId: string;
- private plugins: PluginMode = "featured";
+ private logger: EditorLogger;
private fileName = "New Document.docx";
private fileType = "docx";
private media: Record = {};
@@ -196,8 +195,10 @@ export class EditorManager {
constructor(containerId = ONLYOFFICE_ID) {
this.containerId = containerId;
+ this.logger = new EditorLogger(containerId);
this.server = new EditorServer({
- getState: () => ({ plugins: "none", readOnly: this.readOnly }),
+ getState: () => ({ readOnly: this.readOnly }),
+ logger: this.logger,
onUserSave: (snapshot) => {
this.dirty = false;
this.notifyUserSave(snapshot);
@@ -274,7 +275,7 @@ export class EditorManager {
private createScopedIo() {
return (url?: string, options: MockSocketOptions = {}) => {
- const socket = io(url, options);
+ const socket = io(url, { ...options, logger: this.logger });
socket.on("connect", () => {
this.server.handleConnect({ socket });
@@ -289,7 +290,10 @@ export class EditorManager {
private createCrossOriginServerIo(): ScopedIoFactory {
return () => {
- const socket = io(undefined, { deferConnect: true });
+ const socket = io(undefined, {
+ deferConnect: true,
+ logger: this.logger,
+ });
socket.connected = true;
socket.disconnected = false;
return socket;
@@ -1104,7 +1108,6 @@ export class EditorManager {
viewport: true,
documentHolder: { clear: true, disable: true },
toolbar: true,
- plugins: true,
protect: true,
header: { search: false, startfill: false },
shortcuts: false,
@@ -1129,7 +1132,6 @@ export class EditorManager {
viewport: false,
documentHolder: { clear: false, disable: true },
toolbar: true,
- plugins: true,
protect: true,
});
}
@@ -1536,7 +1538,6 @@ export class EditorManager {
}
this.destroy();
- this.plugins = options.plugins || "featured";
this.readOnly = !!options.readOnly;
this.revisionReviewMode = !!options.revisionReview;
this.server.setOfficeXmlEventConfig(options.officeXmlEvent);
@@ -1730,6 +1731,14 @@ export class EditorManager {
return this.containerId;
}
+ getLogger() {
+ return this.logger;
+ }
+
+ printLogs() {
+ this.logger.print();
+ }
+
getFileName() {
return this.fileName;
}
diff --git a/src/components/onlyoffice-web-comp/core/onlyoffice-manager.ts b/src/components/onlyoffice-web-comp/core/onlyoffice-manager.ts
index 1088dd8e..2fa06536 100644
--- a/src/components/onlyoffice-web-comp/core/onlyoffice-manager.ts
+++ b/src/components/onlyoffice-web-comp/core/onlyoffice-manager.ts
@@ -18,9 +18,9 @@ import {
} from "../const";
import type { OfficeTheme } from "../internal/editor/types";
import {
- clearDocmentObj,
- getDocmentObj,
- setDocmentObj,
+ clearDocumentObj,
+ getDocumentObj,
+ setDocumentObj,
} from "../store/document";
import {
getCurrentLang,
@@ -168,7 +168,7 @@ export class OnlyOfficeManager {
async openDocument(input: OpenDocumentInput) {
const readOnly = input.readOnly ?? this.readOnly;
- setDocmentObj(
+ setDocumentObj(
{
fileName: input.fileName,
file: input.file,
@@ -177,7 +177,7 @@ export class OnlyOfficeManager {
this.containerId,
);
- const { fileName, file } = getDocmentObj(this.containerId);
+ const { fileName, file } = getDocumentObj(this.containerId);
await this.editor.create({
file,
@@ -276,6 +276,14 @@ export class OnlyOfficeManager {
return this.editor;
}
+ getLogger() {
+ return this.editor.getLogger();
+ }
+
+ printLogs() {
+ this.editor.printLogs();
+ }
+
async exportDocument() {
return this.editor.export();
}
@@ -283,7 +291,7 @@ export class OnlyOfficeManager {
/** 导出为 Office 文件 Blob:Editor.bin → x2t → doc.{fileType}。 */
async exportAsBlob(): Promise {
if (this.editor.isOfficeXmlSizeLimitExceeded()) {
- const { file, fileName } = getDocmentObj(this.containerId);
+ const { file, fileName } = getDocumentObj(this.containerId);
if (file) {
return {
blob: file,
@@ -302,6 +310,7 @@ export class OnlyOfficeManager {
exportFileType,
binData.media,
binData.themes,
+ this.editor.getLogger(),
);
return {
@@ -342,7 +351,7 @@ export class OnlyOfficeManager {
destroy() {
this.editor.destroy();
- clearDocmentObj(this.containerId);
+ clearDocumentObj(this.containerId);
this.ready = false;
}
}
diff --git "a/src/components/onlyoffice-web-comp/docs/00-\346\246\202\350\277\260.md" "b/src/components/onlyoffice-web-comp/docs/00-\346\246\202\350\277\260.md"
deleted file mode 100644
index 4180550c..00000000
--- "a/src/components/onlyoffice-web-comp/docs/00-\346\246\202\350\277\260.md"
+++ /dev/null
@@ -1,68 +0,0 @@
-# OnlyOffice Web Comp 文档
-
-OnlyOffice Web Comp 是基于 OnlyOffice 静态 SDK 的 Web 端文档编辑组件库,支持 Word、Excel、PowerPoint 的在线编辑、只读预览、导出与 x2t 格式转换。
-
-**包路径**:`src/components/onlyoffice-web-comp`
-**导入方式**:`import { ... } from "@/components/onlyoffice-web-comp"`
-
-## 文档目录
-
-| 编号 | 文档 | 说明 |
-|------|------|------|
-| 01 | [快速开始](./01-快速开始.md) | `OnlyOfficeManager`、容器挂载、多实例入门 |
-| 02 | [核心 API](./02-核心API.md) | `OnlyOfficeManager`、`onlyOfficeManagerFactory`、底层 API |
-| 03 | [事件系统](./03-事件系统.md) | EventBus、事件类型与监听 |
-| 04 | [完整示例](./04-完整示例.md) | React 组件完整示例 |
-| 05 | [API 参考](./05-API参考.md) | 常量、类型定义 |
-| 06 | [注意事项与支持格式](./06-注意事项与支持格式.md) | 前置条件、文件格式、常见注意点 |
-| 07 | [批注修订与 Word API](./07-批注修订与-Word-API.md) | 批注、修订、`subscribe` 与 SDK 回调 |
-| 10 | [字体配置](./10-字体配置.md) | `__custom_font_registry__`、catalog 转换与别名注册 |
-| 08 | [单实例示例](./08-单实例示例.md) | 单编辑器 Demo 与源码 |
-| 09 | [多实例示例](./09-多实例示例.md) | Tab 多实例完整源码与 Demo |
-
-## 推荐阅读路径
-
-| 场景 | 路径 |
-|------|------|
-| 首次接入 | [01 快速开始](./01-快速开始.md) → [02 核心 API](./02-核心API.md) |
-| 在线体验 | [08 单实例示例](./08-单实例示例.md) · [09 多实例示例](./09-多实例示例.md) |
-| React 页面集成 | [04 完整示例](./04-完整示例.md) |
-| 多实例 / 导出 | [02 核心 API](./02-核心API.md) · [03 事件系统](./03-事件系统.md) |
-| 公文审校(批注/修订) | [07 批注修订与 Word API](./07-批注修订与-Word-API.md) |
-| 查常量与类型 | [05 API 参考](./05-API参考.md) |
-| 自定义字体 / 公文字体 | [10 字体配置](./10-字体配置.md) |
-
-## 上层 API 速览
-
-业务页面优先使用 **`OnlyOfficeManager`**(门面),底层 **`EditorManager`** 供高级场景直接控制。
-
-```typescript
-import { OnlyOfficeManager, FILE_TYPE, ONLYOFFICE_ID } from "@/components/onlyoffice-web-comp";
-
-// 新建空白文档
-await OnlyOfficeManager.create({
- containerId: ONLYOFFICE_ID,
- fileType: FILE_TYPE.DOCX,
- defaultFileName: "New_Document.docx",
-});
-
-// 先取 File,再挂载(如 public 目录下的默认文件)
-const blob = await fetch("/test.xlsx").then((r) => r.blob());
-const file = new File([blob], "test.xlsx", { type: blob.type });
-await OnlyOfficeManager.createWithFile({
- containerId: ONLYOFFICE_ID,
- fileType: FILE_TYPE.XLSX,
- defaultFileName: "test.xlsx",
-}, file);
-```
-
-## 站点演示路由
-
-| 路由 | 说明 |
-|------|------|
-| `/` | 产品主页 |
-| `/docs` | 组件库文档(本目录 Markdown 渲染) |
-| `/docs/demos?tab=single` | 单实例在线示例 |
-| `/docs/demos?tab=multi` | 多实例 Tab 在线示例 |
-
-演示实现:`src/features/demo/`(`office-preview-page.tsx` · `tabs-multi-page.tsx`)
diff --git "a/src/components/onlyoffice-web-comp/docs/05-API\345\217\202\350\200\203.md" "b/src/components/onlyoffice-web-comp/docs/API\345\217\202\350\200\203.md"
similarity index 92%
rename from "src/components/onlyoffice-web-comp/docs/05-API\345\217\202\350\200\203.md"
rename to "src/components/onlyoffice-web-comp/docs/API\345\217\202\350\200\203.md"
index 8dc4c993..3ce745a0 100644
--- "a/src/components/onlyoffice-web-comp/docs/05-API\345\217\202\350\200\203.md"
+++ "b/src/components/onlyoffice-web-comp/docs/API\345\217\202\350\200\203.md"
@@ -1,6 +1,6 @@
-# 05 - API 参考
+# API 参考
-[← 完整示例](./04-完整示例.md) | [注意事项 →](./06-注意事项与支持格式.md)
+[← 完整示例](./完整示例.md) | [注意事项 →](./注意事项与支持格式.md)
## 常量
@@ -73,11 +73,9 @@ STATIC_RESOURCE.x2t.wasm // x2t.wasm 路径
可通过环境变量 `NEXT_PUBLIC_APP_ROOT` 覆盖 SDK 根路径。
-> `ONLYOFFICE_RESOURCE` / `X2T_RESOURCE` 已标记 `@deprecated`,请使用 `STATIC_RESOURCE`。
-
### `__custom_font_registry__`
-SDK 侧字体注册表,定义于 `public/packages/onlyoffice/9.3.0/sdkjs/common/AllFonts.js`。键为 catalog 文件 id(如 `"1001"`),值为文档内字体别名数组。完整配置流程见 [10 - 字体配置](./10-字体配置.md)。
+SDK 侧字体注册表,定义于 `public/packages/onlyoffice/9.3.0/sdkjs/common/AllFonts.js`。键为 catalog 文件 id(如 `"1001"`),值为文档内字体别名数组。完整配置流程见 [字体配置](./字体配置.md)。
## 类型定义
@@ -192,6 +190,7 @@ import {
onlyOfficeManagerFactory,
// 底层
EditorManager,
+ EditorLogger,
editorManager,
editorManagerFactory,
createEditorView,
@@ -210,9 +209,11 @@ import {
OFFICE_THEME_OPTIONS,
STATIC_RESOURCE,
// store
- setDocmentObj,
- getDocmentObj,
+ setDocumentObj,
+ getDocumentObj,
} from "@/components/onlyoffice-web-comp";
```
+`EditorLogger` 记录单个编辑器实例的 socket、downloadAs、x2t worker 与操作日志。通过 `manager.getLogger()` / `manager.printLogs()` 读取或打印当前实例日志。
+
类型通过 `export type *` 从 `type/word-api.ts`、`type/sdk-internal.ts` 导出。
diff --git "a/src/components/onlyoffice-web-comp/docs/03-\344\272\213\344\273\266\347\263\273\347\273\237.md" "b/src/components/onlyoffice-web-comp/docs/\344\272\213\344\273\266\347\263\273\347\273\237.md"
similarity index 96%
rename from "src/components/onlyoffice-web-comp/docs/03-\344\272\213\344\273\266\347\263\273\347\273\237.md"
rename to "src/components/onlyoffice-web-comp/docs/\344\272\213\344\273\266\347\263\273\347\273\237.md"
index d71c646a..104f819d 100644
--- "a/src/components/onlyoffice-web-comp/docs/03-\344\272\213\344\273\266\347\263\273\347\273\237.md"
+++ "b/src/components/onlyoffice-web-comp/docs/\344\272\213\344\273\266\347\263\273\347\273\237.md"
@@ -1,6 +1,6 @@
-# 03 - 事件系统
+# 事件系统
-[← 核心 API](./02-核心API.md) | [完整示例 →](./04-完整示例.md)
+[← 核心 API](./核心API.md) | [完整示例 →](./完整示例.md)
OnlyOffice Web Comp 使用 EventBus 进行跨模块事件通信。
@@ -148,7 +148,7 @@ function EditorPage() {
- `asc_onShowRevisionsChange` — 修订列表变化
- `asc_onDocumentModifiedChanged` — 文档修改状态
-详见 [07-批注修订与 Word API](./07-批注修订与-Word-API.md)。
+详见 [批注修订与 Word API](./批注修订与-Word-API.md)。
## 取消监听
diff --git "a/src/components/onlyoffice-web-comp/docs/08-\345\215\225\345\256\236\344\276\213\347\244\272\344\276\213.md" "b/src/components/onlyoffice-web-comp/docs/\345\215\225\345\256\236\344\276\213\347\244\272\344\276\213.md"
similarity index 92%
rename from "src/components/onlyoffice-web-comp/docs/08-\345\215\225\345\256\236\344\276\213\347\244\272\344\276\213.md"
rename to "src/components/onlyoffice-web-comp/docs/\345\215\225\345\256\236\344\276\213\347\244\272\344\276\213.md"
index ad7b45d2..8290b58a 100644
--- "a/src/components/onlyoffice-web-comp/docs/08-\345\215\225\345\256\236\344\276\213\347\244\272\344\276\213.md"
+++ "b/src/components/onlyoffice-web-comp/docs/\345\215\225\345\256\236\344\276\213\347\244\272\344\276\213.md"
@@ -1,10 +1,10 @@
-# 08 - 单实例示例
+# 单实例示例
-[← 快速开始](./01-快速开始.md) | [多实例示例 →](./09-多实例示例.md)
+[← 快速开始](./快速开始.md) | [多实例示例 →](./多实例示例.md)
单实例场景使用 **`OnlyOfficeManager`** 门面:一个页面、一个 `containerId`、一个编辑器实例。适合文档详情、预览、简单编辑页。
-**在线演示**:站点路由 [`/docs/demos?tab=single`](/docs/demos?tab=single)
+**在线演示**:站点路由 [`/docs/demos/single`](/docs/demos/single)
**完整源码**:`src/features/demo/office-preview-page.tsx`
## 在线演示
@@ -17,7 +17,7 @@
1. 挂载固定 `containerId` 的 DOM 容器
2. `OnlyOfficeManager.create` 初始化空白文档,或 `createWithFile` 打开本地文件
-3. 工具栏调用 `openDocument` / `downloadExport` / `toggleReadOnly` / `setTheme` / `toggleLanguage`
+3. 工具栏调用 `openDocument` / `downloadExport` / `toggleReadOnly` / `setTheme` / `toggleLanguage` / `printLogs`
4. 页面卸载时 `manager.destroy()`
## 容器组件
@@ -140,3 +140,4 @@ export function OfficePreviewPage({ embedded = false, ...props }) {
| 只读切换 | `manager.toggleReadOnly()` |
| 主题 | `manager.setTheme(theme)` |
| 语言 | `manager.toggleLanguage()` |
+| 打印日志 | `manager.printLogs()` |
diff --git "a/src/components/onlyoffice-web-comp/docs/09-\345\244\232\345\256\236\344\276\213\347\244\272\344\276\213.md" "b/src/components/onlyoffice-web-comp/docs/\345\244\232\345\256\236\344\276\213\347\244\272\344\276\213.md"
similarity index 98%
rename from "src/components/onlyoffice-web-comp/docs/09-\345\244\232\345\256\236\344\276\213\347\244\272\344\276\213.md"
rename to "src/components/onlyoffice-web-comp/docs/\345\244\232\345\256\236\344\276\213\347\244\272\344\276\213.md"
index 69c2634e..b27e08d8 100644
--- "a/src/components/onlyoffice-web-comp/docs/09-\345\244\232\345\256\236\344\276\213\347\244\272\344\276\213.md"
+++ "b/src/components/onlyoffice-web-comp/docs/\345\244\232\345\256\236\344\276\213\347\244\272\344\276\213.md"
@@ -1,10 +1,10 @@
-# 09 - 多实例示例
+# 多实例示例
-[← 单实例示例](./08-单实例示例.md) | [核心 API →](./02-核心API.md)
+[← 单实例示例](./单实例示例.md) | [核心 API →](./核心API.md)
多实例场景使用 **`onlyOfficeManagerFactory`**:每个 Tab 对应唯一 `containerId`,切换 Tab 时**隐藏 DOM 而非销毁实例**,从而保留各编辑器状态。
-**在线演示**:站点路由 [`/docs/demos?tab=multi`](/docs/demos?tab=multi)
+**在线演示**:站点路由 [`/docs/demos/multi`](/docs/demos/multi)
**完整源码**:`src/features/demo/tabs-multi-page.tsx`
## 在线演示
@@ -22,6 +22,7 @@
| 状态保留 | 非激活 Tab 用 `invisible` 隐藏,不 `destroy` |
| 关闭 Tab | `onlyOfficeManagerFactory.destroy(containerId)` 释放实例 |
| 页面卸载 | `onlyOfficeManagerFactory.destroyAll()` |
+| 打印日志 | 当前激活 Tab 调用 `manager.printLogs()`,只输出该实例日志 |
### Tab 数据模型
diff --git "a/src/components/onlyoffice-web-comp/docs/10-\345\255\227\344\275\223\351\205\215\347\275\256.md" "b/src/components/onlyoffice-web-comp/docs/\345\255\227\344\275\223\351\205\215\347\275\256.md"
similarity index 97%
rename from "src/components/onlyoffice-web-comp/docs/10-\345\255\227\344\275\223\351\205\215\347\275\256.md"
rename to "src/components/onlyoffice-web-comp/docs/\345\255\227\344\275\223\351\205\215\347\275\256.md"
index 3bd6df6b..53999beb 100644
--- "a/src/components/onlyoffice-web-comp/docs/10-\345\255\227\344\275\223\351\205\215\347\275\256.md"
+++ "b/src/components/onlyoffice-web-comp/docs/\345\255\227\344\275\223\351\205\215\347\275\256.md"
@@ -1,6 +1,6 @@
-# 10 - 字体配置
+# 字体配置
-[← 注意事项与格式](./06-注意事项与支持格式.md) | [批注修订 →](./07-批注修订与-Word-API.md)
+[← 注意事项与格式](./注意事项与支持格式.md) | [批注修订 →](./批注修订与-Word-API.md)
公文、合同等场景常依赖 **仿宋、楷体、方正小标宋** 等系统字体。OnlyOffice 静态 SDK 默认只内置部分字形;要让文档正确显示与导出,需要注册自定义字体。
diff --git "a/src/components/onlyoffice-web-comp/docs/04-\345\256\214\346\225\264\347\244\272\344\276\213.md" "b/src/components/onlyoffice-web-comp/docs/\345\256\214\346\225\264\347\244\272\344\276\213.md"
similarity index 95%
rename from "src/components/onlyoffice-web-comp/docs/04-\345\256\214\346\225\264\347\244\272\344\276\213.md"
rename to "src/components/onlyoffice-web-comp/docs/\345\256\214\346\225\264\347\244\272\344\276\213.md"
index 202b4727..cae19dac 100644
--- "a/src/components/onlyoffice-web-comp/docs/04-\345\256\214\346\225\264\347\244\272\344\276\213.md"
+++ "b/src/components/onlyoffice-web-comp/docs/\345\256\214\346\225\264\347\244\272\344\276\213.md"
@@ -1,6 +1,6 @@
-# 04 - 完整示例
+# 完整示例
-[← 事件系统](./03-事件系统.md) | [API 参考 →](./05-API参考.md)
+[← 事件系统](./事件系统.md) | [API 参考 →](./API参考.md)
以下示例对齐仓库演示页 `src/features/demo/office-preview-page.tsx`。
@@ -180,7 +180,6 @@ onlyOfficeManagerFactory.destroyAll();
以下场景可直接使用 `EditorManager` / `createEditorView`:
- 需要 `url` + 自定义 `loader` 打开远程文档
-- 需要 `plugins` 等更细粒度编辑器配置
- 批注修订、Word SDK 回调(`subscribe`)
-一般业务集成优先 `OnlyOfficeManager`,见 [02 - 核心 API](./02-核心API.md)。
+一般业务集成优先 `OnlyOfficeManager`,见 [核心 API](./核心API.md)。
diff --git "a/src/components/onlyoffice-web-comp/docs/01-\345\277\253\351\200\237\345\274\200\345\247\213.md" "b/src/components/onlyoffice-web-comp/docs/\345\277\253\351\200\237\345\274\200\345\247\213.md"
similarity index 92%
rename from "src/components/onlyoffice-web-comp/docs/01-\345\277\253\351\200\237\345\274\200\345\247\213.md"
rename to "src/components/onlyoffice-web-comp/docs/\345\277\253\351\200\237\345\274\200\345\247\213.md"
index 52545f9b..65421a83 100644
--- "a/src/components/onlyoffice-web-comp/docs/01-\345\277\253\351\200\237\345\274\200\345\247\213.md"
+++ "b/src/components/onlyoffice-web-comp/docs/\345\277\253\351\200\237\345\274\200\345\247\213.md"
@@ -1,6 +1,6 @@
-# 01 - 快速开始
+# 快速开始
-[← 概述](./00-概述.md) | [核心 API →](./02-核心API.md)
+[← 概述](./概述.md) | [核心 API →](./核心API.md)
## 推荐方式:`OnlyOfficeManager`
@@ -97,12 +97,12 @@ export function MultiEditorPage() {
}
```
-多实例打开文档使用 `onlyOfficeManagerFactory.open()`,见 [02 - 核心 API](./02-核心API.md#多实例管理)。
+多实例打开文档使用 `onlyOfficeManagerFactory.open()`,见 [核心 API](./核心API.md#多实例管理)。
**注意**:多实例下必须用 `data-onlyoffice-container-id` 精确定位容器,避免图片上传等操作路由到错误实例。
## 底层方式(高级)
-需要直接控制 `EditorManager` 时,可使用 `createEditorView` 或 `editorManagerFactory`。详见 [02 - 核心 API](./02-核心API.md#底层能力)。
+需要直接控制 `EditorManager` 时,可使用 `createEditorView` 或 `editorManagerFactory`。详见 [核心 API](./核心API.md#底层能力)。
`EditorManager.create()` 同样会在内部调用 `initializeOnlyOffice()`,一般不必提前手动初始化。仅在使用 `OnlyOfficeManager.fromEditor()` 自行绑定时,需确保 SDK 已加载。
diff --git "a/src/components/onlyoffice-web-comp/docs/07-\346\211\271\346\263\250\344\277\256\350\256\242\344\270\216-Word-API.md" "b/src/components/onlyoffice-web-comp/docs/\346\211\271\346\263\250\344\277\256\350\256\242\344\270\216-Word-API.md"
similarity index 97%
rename from "src/components/onlyoffice-web-comp/docs/07-\346\211\271\346\263\250\344\277\256\350\256\242\344\270\216-Word-API.md"
rename to "src/components/onlyoffice-web-comp/docs/\346\211\271\346\263\250\344\277\256\350\256\242\344\270\216-Word-API.md"
index 699477bc..96754a00 100644
--- "a/src/components/onlyoffice-web-comp/docs/07-\346\211\271\346\263\250\344\277\256\350\256\242\344\270\216-Word-API.md"
+++ "b/src/components/onlyoffice-web-comp/docs/\346\211\271\346\263\250\344\277\256\350\256\242\344\270\216-Word-API.md"
@@ -1,6 +1,6 @@
-# 07 - 批注修订与 Word API
+# 批注修订与 Word API
-[← 字体配置](./10-字体配置.md) | [概述](./00-概述.md)
+[← 字体配置](./字体配置.md) | [概述](./概述.md)
`EditorManager` 封装了 Word 文档的批注、修订能力,以及 OnlyOffice iframe 内 SDK 的回调订阅。通过 `OnlyOfficeManager.getEditor()` 获取底层实例。
diff --git "a/src/components/onlyoffice-web-comp/docs/02-\346\240\270\345\277\203API.md" "b/src/components/onlyoffice-web-comp/docs/\346\240\270\345\277\203API.md"
similarity index 92%
rename from "src/components/onlyoffice-web-comp/docs/02-\346\240\270\345\277\203API.md"
rename to "src/components/onlyoffice-web-comp/docs/\346\240\270\345\277\203API.md"
index d9171a42..d5356855 100644
--- "a/src/components/onlyoffice-web-comp/docs/02-\346\240\270\345\277\203API.md"
+++ "b/src/components/onlyoffice-web-comp/docs/\346\240\270\345\277\203API.md"
@@ -1,6 +1,6 @@
-# 02 - 核心 API
+# 核心 API
-[← 快速开始](./01-快速开始.md) | [事件系统 →](./03-事件系统.md)
+[← 快速开始](./快速开始.md) | [事件系统 →](./事件系统.md)
## 业务门面
@@ -101,8 +101,14 @@ type OnlyOfficeStaticResourceOptions = {
| `downloadExport()` | 导出并触发浏览器下载 |
| `onLoadingChange(handler)` | 监听 loading,返回取消函数 |
| `getEditor()` | 获取底层 `EditorManager` |
+| `getLogger()` | 获取当前实例的 `EditorLogger` |
+| `printLogs()` | 将当前实例日志历史打印到控制台 |
| `destroy()` | 销毁实例 |
+### 实例日志
+
+每个 `EditorManager` 都持有一个 `EditorLogger`,用于记录当前实例的 socket、downloadAs、x2t worker 与关键操作日志。日志仍会按原有 console 参数输出,便于 e2e/CDP 继续监听;同时可通过 `manager.printLogs()` 打印当前实例的历史记录。
+
### 主题切换
主题对应 OnlyOffice `customization.uiTheme`。切换时会短暂 remount iframe(先保存未提交编辑),可通过 `onLoadingChange` 监听 loading。
@@ -136,7 +142,7 @@ OFFICE_THEME_OPTIONS.map(({ id, label }) => (
));
```
-可用主题常量见 [05 - API 参考 · OFFICE_THEME](./05-API参考.md#office_theme)。
+可用主题常量见 [API 参考 · OFFICE_THEME](./API参考.md#office_theme)。
### 打开文档参数
@@ -212,7 +218,6 @@ await createEditorView({
containerId?: string;
editorManager?: EditorManager;
theme?: OfficeTheme;
- plugins?: PluginMode;
});
```
@@ -276,7 +281,7 @@ editorManagerFactory.destroyAll();
| `updateMedia(key, url)` | 更新媒体文件映射 |
| `getMedia()` | 获取媒体文件映射 |
| `destroy()` | 销毁编辑器实例 |
-| `subscribe({ type, fn })` | 订阅 Word SDK 回调,见 [07](./07-批注修订与-Word-API.md) |
+| `subscribe({ type, fn })` | 订阅 Word SDK 回调,见 [批注修订与 Word API](./批注修订与-Word-API.md) |
**`export()` 返回值:**
diff --git "a/src/components/onlyoffice-web-comp/docs/\346\246\202\350\277\260.md" "b/src/components/onlyoffice-web-comp/docs/\346\246\202\350\277\260.md"
new file mode 100644
index 00000000..b1ef27ca
--- /dev/null
+++ "b/src/components/onlyoffice-web-comp/docs/\346\246\202\350\277\260.md"
@@ -0,0 +1,68 @@
+# OnlyOffice Web Comp 文档
+
+OnlyOffice Web Comp 是基于 OnlyOffice 静态 SDK 的 Web 端文档编辑组件库,支持 Word、Excel、PowerPoint 的在线编辑、只读预览、导出与 x2t 格式转换。
+
+**包路径**:`src/components/onlyoffice-web-comp`
+**导入方式**:`import { ... } from "@/components/onlyoffice-web-comp"`
+
+## 文档目录
+
+| 编号 | 文档 | 说明 |
+|------|------|------|
+| - | [快速开始](./快速开始.md) | `OnlyOfficeManager`、容器挂载、多实例入门 |
+| - | [核心 API](./核心API.md) | `OnlyOfficeManager`、`onlyOfficeManagerFactory`、底层 API |
+| - | [事件系统](./事件系统.md) | EventBus、事件类型与监听 |
+| - | [完整示例](./完整示例.md) | React 组件完整示例 |
+| - | [API 参考](./API参考.md) | 常量、类型定义 |
+| - | [注意事项与支持格式](./注意事项与支持格式.md) | 前置条件、文件格式、常见注意点 |
+| - | [批注修订与 Word API](./批注修订与-Word-API.md) | 批注、修订、`subscribe` 与 SDK 回调 |
+| - | [字体配置](./字体配置.md) | `__custom_font_registry__`、catalog 转换与别名注册 |
+| - | [单实例示例](./单实例示例.md) | 单编辑器 Demo 与源码 |
+| - | [多实例示例](./多实例示例.md) | Tab 多实例完整源码与 Demo |
+
+## 推荐阅读路径
+
+| 场景 | 路径 |
+|------|------|
+| 首次接入 | [快速开始](./快速开始.md) → [核心 API](./核心API.md) |
+| 在线体验 | [单实例示例](./单实例示例.md) · [多实例示例](./多实例示例.md) |
+| React 页面集成 | [完整示例](./完整示例.md) |
+| 多实例 / 导出 | [核心 API](./核心API.md) · [事件系统](./事件系统.md) |
+| 公文审校(批注/修订) | [批注修订与 Word API](./批注修订与-Word-API.md) |
+| 查常量与类型 | [API 参考](./API参考.md) |
+| 自定义字体 / 公文字体 | [字体配置](./字体配置.md) |
+
+## 上层 API 速览
+
+业务页面优先使用 **`OnlyOfficeManager`**(门面),底层 **`EditorManager`** 供高级场景直接控制。
+
+```typescript
+import { OnlyOfficeManager, FILE_TYPE, ONLYOFFICE_ID } from "@/components/onlyoffice-web-comp";
+
+// 新建空白文档
+await OnlyOfficeManager.create({
+ containerId: ONLYOFFICE_ID,
+ fileType: FILE_TYPE.DOCX,
+ defaultFileName: "New_Document.docx",
+});
+
+// 先取 File,再挂载(如 public 目录下的默认文件)
+const blob = await fetch("/test.xlsx").then((r) => r.blob());
+const file = new File([blob], "test.xlsx", { type: blob.type });
+await OnlyOfficeManager.createWithFile({
+ containerId: ONLYOFFICE_ID,
+ fileType: FILE_TYPE.XLSX,
+ defaultFileName: "test.xlsx",
+}, file);
+```
+
+## 站点演示路由
+
+| 路由 | 说明 |
+|------|------|
+| `/` | 产品主页 |
+| `/docs` | 组件库文档(本目录 Markdown 渲染) |
+| `/docs/demos/single` | 单实例在线示例 |
+| `/docs/demos/multi` | 多实例 Tab 在线示例 |
+
+演示实现:`src/features/demo/`(`office-preview-page.tsx` · `tabs-multi-page.tsx`)
diff --git "a/src/components/onlyoffice-web-comp/docs/06-\346\263\250\346\204\217\344\272\213\351\241\271\344\270\216\346\224\257\346\214\201\346\240\274\345\274\217.md" "b/src/components/onlyoffice-web-comp/docs/\346\263\250\346\204\217\344\272\213\351\241\271\344\270\216\346\224\257\346\214\201\346\240\274\345\274\217.md"
similarity index 82%
rename from "src/components/onlyoffice-web-comp/docs/06-\346\263\250\346\204\217\344\272\213\351\241\271\344\270\216\346\224\257\346\214\201\346\240\274\345\274\217.md"
rename to "src/components/onlyoffice-web-comp/docs/\346\263\250\346\204\217\344\272\213\351\241\271\344\270\216\346\224\257\346\214\201\346\240\274\345\274\217.md"
index d04fc656..eefa5954 100644
--- "a/src/components/onlyoffice-web-comp/docs/06-\346\263\250\346\204\217\344\272\213\351\241\271\344\270\216\346\224\257\346\214\201\346\240\274\345\274\217.md"
+++ "b/src/components/onlyoffice-web-comp/docs/\346\263\250\346\204\217\344\272\213\351\241\271\344\270\216\346\224\257\346\214\201\346\240\274\345\274\217.md"
@@ -1,13 +1,13 @@
-# 06 - 注意事项与支持格式
+# 注意事项与支持格式
-[← API 参考](./05-API参考.md) | [字体配置 →](./10-字体配置.md) · [批注修订 →](./07-批注修订与-Word-API.md)
+[← API 参考](./API参考.md) | [字体配置 →](./字体配置.md) · [批注修订 →](./批注修订与-Word-API.md)
## 前置条件
-1. **静态资源**:将 OnlyOffice SDK(含 `web-apps/`、`sdkjs/`、`fonts/`、`x2t/`)放到站点可访问目录,默认 `public/packages/onlyoffice/9.3.0/`。自定义字体需在 `AllFonts.js` 的 `__custom_font_registry__` 中注册,详见 [10 - 字体配置](./10-字体配置.md)。
+1. **静态资源**:将 OnlyOffice SDK(含 `web-apps/`、`sdkjs/`、`fonts/`、`x2t/`)放到站点可访问目录,默认 `public/packages/onlyoffice/9.3.0/`。自定义字体需在 `AllFonts.js` 的 `__custom_font_registry__` 中注册,详见 [字体配置](./字体配置.md)。
2. **环境变量**(可选):`NEXT_PUBLIC_APP_ROOT=/packages/onlyoffice/9.3.0`,与 `STATIC_RESOURCE.onlyoffice.root` 一致。
-3. **x2t 资源**:`x2t/x2t.js` 为普通 JS 文本,`x2t.wasm` 为 Brotli 预压缩文件;**无需**为 `x2t.wasm` 配置 `Content-Encoding: br`,Worker 内 `fetch-brotli` 会自动解压。
-4. **DOM 容器**:页面需预留编辑器挂载点(见 [01-快速开始](./01-快速开始.md))。
+3. **x2t 资源**:`x2t/x2t.js` 为普通 JS 文本,`x2t.wasm` 为 Brotli 预压缩文件;**无需**为 `x2t.wasm` 配置 `Content-Encoding: br`,Worker 内 `x2t-assets` 会自动解压。
+4. **DOM 容器**:页面需预留编辑器挂载点(见 [快速开始](./快速开始.md))。
## 注意事项
diff --git a/src/components/onlyoffice-web-comp/index.ts b/src/components/onlyoffice-web-comp/index.ts
index 5a5fad70..40ef796a 100644
--- a/src/components/onlyoffice-web-comp/index.ts
+++ b/src/components/onlyoffice-web-comp/index.ts
@@ -16,8 +16,14 @@ export * from "./store";
export * from "./util";
export * from "./core";
export * from "./feature";
+export { EditorLogger } from "./internal/editor/logger";
+export type {
+ EditorLogCategory,
+ EditorLogEntry,
+ EditorLogLevel,
+} from "./internal/editor/logger";
export type * from "./type/word-api";
export type * from "./type/sdk-internal";
-export type { OfficeTheme, PluginMode, User } from "./internal/editor/types";
+export type { OfficeTheme, User } from "./internal/editor/types";
export type { OfficeThemeId } from "./const";
diff --git a/src/components/onlyoffice-web-comp/internal/editor/cross-origin-bridge.ts b/src/components/onlyoffice-web-comp/internal/editor/cross-origin-bridge.ts
deleted file mode 100644
index cdc37d0b..00000000
--- a/src/components/onlyoffice-web-comp/internal/editor/cross-origin-bridge.ts
+++ /dev/null
@@ -1,596 +0,0 @@
-import type { EditorServer } from "./server";
-import type { MockSocket } from "./socket";
-import type { ScopedIoFactory } from "./install-proxies";
-import { CROSS_ORIGIN_BRIDGE_MESSAGE } from "./cross-origin-protocol";
-
-const BRIDGE_SOURCE = "onlyoffice-bridge";
-
-type BridgeMessage = {
- source?: string;
- type?: string;
- frameEditorId?: string;
- requestId?: string;
- command?: string;
- payload?: Record;
- result?: unknown;
- error?: string;
- event?: string;
- args?: unknown[];
- method?: string;
- url?: string;
- headers?: Record;
- body?: string | null;
- bodyEncoding?: "base64";
- readOnly?: boolean;
- status?: number;
- responseHeaders?: Record;
- responseBody?: string | null;
- responseType?: XMLHttpRequestResponseType;
-};
-
-type BridgeSession = {
- frameEditorId: string;
- server: EditorServer;
- createIo: ScopedIoFactory;
- iframe: HTMLIFrameElement;
- targetOrigin: string;
- socket: MockSocket | null;
- bridgeReady: boolean;
- handshakeSent: boolean;
- pendingReadOnly: boolean | null;
-};
-
-const sessions = new Map();
-const pendingRequests = new Map<
- string,
- {
- resolve: (value: unknown) => void;
- reject: (reason?: unknown) => void;
- timer: number;
- }
->();
-const pendingReadyWaiters = new Map<
- string,
- Set<{
- resolve: (session: BridgeSession) => void;
- reject: (reason?: unknown) => void;
- timer: number;
- }>
->();
-const editorEventSubscribers = new Map<
- string,
- Map void>>
->();
-let listenerInstalled = false;
-
-function isBridgeMessage(data: unknown): data is BridgeMessage {
- return (
- typeof data === "object" &&
- data !== null &&
- (data as BridgeMessage).source === BRIDGE_SOURCE
- );
-}
-
-function getTargetOrigin(iframe: HTMLIFrameElement): string {
- try {
- return new URL(iframe.src, window.location.href).origin;
- } catch {
- return "*";
- }
-}
-
-function postToIframe(
- session: BridgeSession,
- message: BridgeMessage,
- targetWindow?: Window | null,
-) {
- const target = targetWindow ?? session.iframe.contentWindow;
- if (!target) {
- return;
- }
- target.postMessage(
- { ...message, source: BRIDGE_SOURCE },
- session.targetOrigin,
- );
-}
-
-function getIframeByWindow(source: MessageEventSource | null) {
- if (!source || !("postMessage" in source)) {
- return null;
- }
-
- const frames = document.querySelectorAll(
- 'iframe[name="frameEditor"]',
- );
- for (const frame of frames) {
- if (frame.contentWindow === source) {
- return frame;
- }
- }
-
- return null;
-}
-
-function updateSessionIframe(
- session: BridgeSession,
- iframe: HTMLIFrameElement,
-) {
- if (session.iframe === iframe) {
- return;
- }
-
- detachSocket(session);
- session.iframe = iframe;
- session.targetOrigin = getTargetOrigin(iframe);
- session.bridgeReady = false;
- session.handshakeSent = false;
-}
-
-function attachSocket(session: BridgeSession) {
- if (session.socket) {
- return;
- }
-
- const socket = session.createIo();
- session.socket = socket;
- session.server.registerSocketTransport(socket);
-
- const nativeServerEmit = socket.server.emit.bind(socket.server);
- socket.server.emit = (event: string, ...args: unknown[]) => {
- postToIframe(session, {
- type: "socket:event",
- frameEditorId: session.frameEditorId,
- event,
- args,
- });
- return nativeServerEmit(event, ...args);
- };
-}
-
-function detachSocket(session: BridgeSession) {
- if (!session.socket) {
- return;
- }
- session.server.handleDisconnect({ socket: session.socket });
- session.socket = null;
- session.handshakeSent = false;
-}
-
-async function handleHttpRequest(
- session: BridgeSession,
- message: BridgeMessage,
-) {
- const requestId = message.requestId;
- if (!requestId || !message.url || !message.method) {
- return;
- }
-
- try {
- const init: RequestInit = {
- method: message.method,
- headers: message.headers,
- };
- if (message.method !== "GET" && message.method !== "HEAD") {
- init.body = decodeRequestBody(message);
- }
-
- let response = await session.server.handleRequest(
- new Request(message.url, init),
- );
- if (!response) {
- response = await fetch(message.url, init);
- }
-
- const responseHeaders: Record = {};
- response.headers.forEach((value, key) => {
- responseHeaders[key] = value;
- });
-
- let responseBody: string | null = null;
- if (message.responseType === "arraybuffer") {
- const buffer = await response.arrayBuffer();
- responseBody = arrayBufferToBase64(buffer);
- } else {
- responseBody = await response.text();
- }
-
- postToIframe(session, {
- type: "http:response",
- frameEditorId: session.frameEditorId,
- requestId,
- status: response.status,
- responseHeaders,
- responseBody,
- responseType: message.responseType,
- });
- } catch (error) {
- postToIframe(session, {
- type: "http:response",
- frameEditorId: session.frameEditorId,
- requestId,
- status: 0,
- responseBody: null,
- });
- console.error("[OnlyOfficeBridge] HTTP proxy failed:", error);
- }
-}
-
-function arrayBufferToBase64(buffer: ArrayBuffer): string {
- const bytes = new Uint8Array(buffer);
- let binary = "";
- for (let i = 0; i < bytes.length; i++) {
- binary += String.fromCharCode(bytes[i]!);
- }
- return btoa(binary);
-}
-
-function base64ToArrayBuffer(base64: string): ArrayBuffer {
- const binary = atob(base64);
- const bytes = new Uint8Array(binary.length);
- for (let i = 0; i < binary.length; i++) {
- bytes[i] = binary.charCodeAt(i);
- }
- return bytes.buffer;
-}
-
-function decodeRequestBody(
- message: BridgeMessage,
-): BodyInit | null | undefined {
- if (message.body == null) {
- return null;
- }
- if (message.bodyEncoding === "base64") {
- return base64ToArrayBuffer(message.body);
- }
- return message.body;
-}
-
-function describeBridgeState(frameEditorId: string) {
- const session = sessions.get(frameEditorId);
- if (!session) {
- return `OnlyOffice cross-origin bridge is not ready for ${frameEditorId}: session is not registered`;
- }
-
- return `OnlyOffice cross-origin bridge is not ready for ${frameEditorId}: iframe=${session.iframe.src || "(empty)"}`;
-}
-
-function resolveReadyWaiters(frameEditorId: string, session: BridgeSession) {
- const waiters = pendingReadyWaiters.get(frameEditorId);
- if (!waiters) {
- return;
- }
-
- pendingReadyWaiters.delete(frameEditorId);
- waiters.forEach((waiter) => {
- window.clearTimeout(waiter.timer);
- waiter.resolve(session);
- });
-}
-
-function rejectReadyWaiters(frameEditorId: string, error: Error) {
- const waiters = pendingReadyWaiters.get(frameEditorId);
- if (!waiters) {
- return;
- }
-
- pendingReadyWaiters.delete(frameEditorId);
- waiters.forEach((waiter) => {
- window.clearTimeout(waiter.timer);
- waiter.reject(error);
- });
-}
-
-function waitForBridgeReady(frameEditorId: string, timeout: number) {
- const session = sessions.get(frameEditorId);
- if (session?.bridgeReady) {
- return Promise.resolve(session);
- }
-
- return new Promise((resolve, reject) => {
- const timer = window.setTimeout(() => {
- const waiters = pendingReadyWaiters.get(frameEditorId);
- waiters?.delete(waiter);
- if (waiters?.size === 0) {
- pendingReadyWaiters.delete(frameEditorId);
- }
- reject(new Error(describeBridgeState(frameEditorId)));
- }, timeout);
- const waiter = { resolve, reject, timer };
-
- let waiters = pendingReadyWaiters.get(frameEditorId);
- if (!waiters) {
- waiters = new Set();
- pendingReadyWaiters.set(frameEditorId, waiters);
- }
- waiters.add(waiter);
- });
-}
-
-function handleBridgeMessage(event: MessageEvent) {
- if (!isBridgeMessage(event.data)) {
- return;
- }
-
- const message = event.data;
- if (
- message.type === CROSS_ORIGIN_BRIDGE_MESSAGE.EDITOR_RESPONSE &&
- message.requestId
- ) {
- const pending = pendingRequests.get(message.requestId);
- if (pending) {
- window.clearTimeout(pending.timer);
- pendingRequests.delete(message.requestId);
- if (message.error) {
- pending.reject(new Error(message.error));
- } else {
- pending.resolve(message.result);
- }
- }
- return;
- }
-
- if (
- message.type === CROSS_ORIGIN_BRIDGE_MESSAGE.EDITOR_EVENT &&
- message.event
- ) {
- const frameEditorId = message.frameEditorId;
- const frameSubscribers = frameEditorId
- ? editorEventSubscribers.get(frameEditorId)
- : undefined;
- const subscribers = frameSubscribers?.get(message.event);
- subscribers?.forEach((handler) => {
- handler(message.args ?? []);
- });
- return;
- }
-
- const frameEditorId = message.frameEditorId;
- if (!frameEditorId) {
- return;
- }
-
- const session = sessions.get(frameEditorId);
- if (!session) {
- return;
- }
-
- const sourceIframe = getIframeByWindow(event.source);
- if (sourceIframe) {
- updateSessionIframe(session, sourceIframe);
- }
-
- switch (message.type) {
- case "hello": {
- session.bridgeReady = true;
- resolveReadyWaiters(frameEditorId, session);
- attachSocket(session);
- const targetWindow =
- event.source && "postMessage" in event.source
- ? (event.source as Window)
- : undefined;
- postToIframe(session, { type: "hello:ack", frameEditorId }, targetWindow);
- if (!session.handshakeSent) {
- session.handshakeSent = true;
- setTimeout(() => {
- if (session.socket) {
- session.server.sendCoAuthoringHandshake(session.socket);
- }
- }, 0);
- }
- if (session.pendingReadOnly !== null) {
- postToIframe(session, {
- type: CROSS_ORIGIN_BRIDGE_MESSAGE.EDITOR_SET_READONLY,
- frameEditorId,
- readOnly: session.pendingReadOnly,
- });
- session.pendingReadOnly = null;
- }
- break;
- }
- case "socket:emit": {
- if (!session.socket || !message.event) {
- break;
- }
- (
- session.socket as { emit: (event: string, ...args: unknown[]) => void }
- ).emit(message.event, ...(message.args ?? []));
- break;
- }
- case "http": {
- void handleHttpRequest(session, message);
- break;
- }
- default:
- break;
- }
-}
-
-export function registerCrossOriginBridge(
- frameEditorId: string,
- iframe: HTMLIFrameElement,
- server: EditorServer,
- createIo: ScopedIoFactory,
-) {
- const existing = sessions.get(frameEditorId);
- if (existing) {
- existing.server = server;
- existing.createIo = createIo;
- updateSessionIframe(existing, iframe);
- return;
- }
-
- sessions.set(frameEditorId, {
- frameEditorId,
- server,
- createIo,
- iframe,
- targetOrigin: getTargetOrigin(iframe),
- socket: null,
- bridgeReady: false,
- handshakeSent: false,
- pendingReadOnly: null,
- });
-
- if (!listenerInstalled) {
- window.addEventListener("message", handleBridgeMessage);
- listenerInstalled = true;
- }
-}
-
-export function unregisterCrossOriginBridge(frameEditorId: string) {
- const session = sessions.get(frameEditorId);
- if (session) {
- detachSocket(session);
- sessions.delete(frameEditorId);
- rejectReadyWaiters(
- frameEditorId,
- new Error(
- `OnlyOffice cross-origin bridge was unregistered: ${frameEditorId}`,
- ),
- );
- }
- editorEventSubscribers.delete(frameEditorId);
-}
-
-export function setCrossOriginReadOnly(
- frameEditorId: string,
- readOnly: boolean,
-) {
- const session = sessions.get(frameEditorId);
- if (!session) {
- return false;
- }
-
- session.pendingReadOnly = readOnly;
- if (!session.bridgeReady) {
- return true;
- }
-
- postToIframe(session, {
- type: CROSS_ORIGIN_BRIDGE_MESSAGE.EDITOR_SET_READONLY,
- frameEditorId,
- readOnly,
- });
- session.pendingReadOnly = null;
- return true;
-}
-
-export function callCrossOriginEditor(
- frameEditorId: string,
- command: string,
- payload: Record = {},
- timeout = 5000,
-) {
- const requestId = `editor-${Date.now()}-${Math.random()
- .toString(36)
- .slice(2)}`;
-
- return waitForBridgeReady(frameEditorId, timeout).then(
- (session) =>
- new Promise((resolve, reject) => {
- const timer = window.setTimeout(() => {
- pendingRequests.delete(requestId);
- reject(
- new Error(`OnlyOffice cross-origin command timed out: ${command}`),
- );
- }, timeout);
-
- pendingRequests.set(requestId, { resolve, reject, timer });
- postToIframe(session, {
- type: CROSS_ORIGIN_BRIDGE_MESSAGE.EDITOR_COMMAND,
- frameEditorId,
- requestId,
- command,
- payload,
- });
- }),
- );
-}
-
-export function subscribeCrossOriginEditorEvent(
- frameEditorId: string,
- event: string,
- handler: (args: unknown[]) => void,
-) {
- let frameSubscribers = editorEventSubscribers.get(frameEditorId);
- if (!frameSubscribers) {
- frameSubscribers = new Map();
- editorEventSubscribers.set(frameEditorId, frameSubscribers);
- }
-
- let subscribers = frameSubscribers.get(event);
- if (!subscribers) {
- subscribers = new Set();
- frameSubscribers.set(event, subscribers);
- }
-
- subscribers.add(handler);
-
- return () => {
- subscribers?.delete(handler);
- if (subscribers?.size === 0) {
- frameSubscribers?.delete(event);
- }
- if (frameSubscribers?.size === 0) {
- editorEventSubscribers.delete(frameEditorId);
- }
- };
-}
-
-export function canAccessIframeWindow(
- iframe: HTMLIFrameElement | null | undefined,
-) {
- if (!iframe) {
- return false;
- }
- try {
- void iframe.contentWindow?.location.href;
- return true;
- } catch {
- return false;
- }
-}
-
-export function watchCrossOriginIframe(
- frameEditorId: string,
- getIframe: () => HTMLIFrameElement | null | undefined,
- server: EditorServer,
- createIo: ScopedIoFactory,
-) {
- let registered = false;
-
- const tryRegister = () => {
- const iframe = getIframe();
- if (!iframe?.src || canAccessIframeWindow(iframe)) {
- return false;
- }
- if (registered) {
- registerCrossOriginBridge(frameEditorId, iframe, server, createIo);
- return true;
- }
- registered = true;
- registerCrossOriginBridge(frameEditorId, iframe, server, createIo);
- return true;
- };
-
- tryRegister();
-
- const timer = window.setInterval(() => {
- if (tryRegister()) {
- window.clearInterval(timer);
- }
- }, 10);
-
- const observer = new MutationObserver(() => {
- tryRegister();
- });
- observer.observe(document.body, { childList: true, subtree: true });
-
- return () => {
- window.clearInterval(timer);
- observer.disconnect();
- if (registered) {
- unregisterCrossOriginBridge(frameEditorId);
- }
- };
-}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/cross-origin-protocol.ts b/src/components/onlyoffice-web-comp/internal/editor/cross-origin-protocol.ts
deleted file mode 100644
index f31e00a7..00000000
--- a/src/components/onlyoffice-web-comp/internal/editor/cross-origin-protocol.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-export const CROSS_ORIGIN_BRIDGE_MESSAGE = {
- EDITOR_COMMAND: "editor:command",
- EDITOR_RESPONSE: "editor:response",
- EDITOR_EVENT: "editor:event",
- EDITOR_SET_READONLY: "editor:set-readonly",
-} as const;
-
-export const CROSS_ORIGIN_EDITOR_COMMAND = {
- EDITOR_SUBSCRIBE: "editor:subscribe",
- COMMENT_ADD: "comment:add",
- COMMENT_UPDATE: "comment:update",
- COMMENT_REMOVE: "comment:remove",
- COMMENT_GO_TO: "comment:go-to",
- COMMENT_LIST: "comment:list",
- COMMENT_SUBSCRIBE: "comment:subscribe",
- REVISION_ADD_DEMO: "revision:add-demo",
- REVISION_LIST: "revision:list",
- REVISION_SET_TRACK: "revision:set-track",
- REVISION_IS_TRACK: "revision:is-track",
- REVISION_HAVE_CHANGES: "revision:have-changes",
- REVISION_PREPARE_REVIEW: "revision:prepare-review",
- REVISION_NEXT: "revision:next",
- REVISION_PREV: "revision:prev",
- REVISION_GO_TO: "revision:go-to",
- REVISION_ACCEPT: "revision:accept",
- REVISION_REJECT: "revision:reject",
- REVISION_ACCEPT_ALL: "revision:accept-all",
- REVISION_REJECT_ALL: "revision:reject-all",
- REVISION_ACCEPT_SELECTION: "revision:accept-selection",
- REVISION_REJECT_SELECTION: "revision:reject-selection",
- REVISION_SUBSCRIBE: "revision:subscribe",
-} as const;
-
-export const CROSS_ORIGIN_EDITOR_EVENT = {
- ADD_COMMENT: "asc_onAddComment",
- CHANGE_COMMENT: "asc_onChangeCommentData",
- REMOVE_COMMENT: "asc_onRemoveComment",
- SHOW_REVISIONS_CHANGE: "asc_onShowRevisionsChange",
- TRACK_REVISIONS_CHANGE: "asc_onOnTrackRevisionsChange",
- DOCUMENT_MODIFIED_CHANGED: "asc_onDocumentModifiedChanged",
-} as const;
diff --git a/src/components/onlyoffice-web-comp/internal/editor/csv-to-xlsx.ts b/src/components/onlyoffice-web-comp/internal/editor/csv-to-xlsx.ts
deleted file mode 100644
index b16352da..00000000
--- a/src/components/onlyoffice-web-comp/internal/editor/csv-to-xlsx.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { parseCsvBuffer } from "./utils";
-
-async function loadExcelJS() {
- const mod = await import(
- "exceljs/dist/exceljs.min"
- );
- return mod.default ?? mod;
-}
-
-/** 复杂 CSV 先转 XLSX,再交给 x2t,避免 x2t CSV 解析崩溃。 */
-export async function convertCsvBufferToXlsxBuffer(buffer: ArrayBuffer) {
- const ExcelJS = await loadExcelJS();
- const rows = parseCsvBuffer(buffer);
- const workbook = new ExcelJS.Workbook();
- const worksheet = workbook.addWorksheet("Sheet1");
-
- for (const row of rows) {
- worksheet.addRow(row);
- }
-
- const output = await workbook.xlsx.writeBuffer();
- if (output instanceof ArrayBuffer) {
- return output;
- }
- const bytes = new Uint8Array(output as ArrayLike);
- return bytes.slice().buffer;
-}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/fetch.ts b/src/components/onlyoffice-web-comp/internal/editor/fetch.ts
deleted file mode 100644
index be7f26ff..00000000
--- a/src/components/onlyoffice-web-comp/internal/editor/fetch.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { XHRMiddleware } from "./xhr";
-
-export type FetchProxy = typeof fetch & {
- use(middleware: XHRMiddleware): void;
- clearMiddlewares(): void;
-};
-
-/**
- * Creates a fetch proxy function that supports middleware
- * @param BaseFetch The original fetch function
- * @returns The enhanced fetch function
- */
-export function createFetchProxy(
- target: (Window & { fetch: typeof fetch }) | typeof fetch = globalThis.fetch,
-): FetchProxy {
- const middlewares: XHRMiddleware[] = [];
- const BaseFetch =
- typeof target === "function" ? target : target.fetch.bind(target);
-
- const proxy = (async (input: RequestInfo | URL, init?: RequestInit) => {
- let request: Request;
- try {
- request = new Request(input, init);
- } catch (e) {
- // If request cannot be created, fallback to native fetch
- return BaseFetch(input, init);
- }
-
- try {
- for (const mw of middlewares) {
- const response = await mw(request.clone());
- if (response) {
- return response;
- }
- }
- } catch (err) {
- console.error("ProxyFetch middleware error:", err);
- return BaseFetch(request);
- }
-
- return BaseFetch(request);
- }) as FetchProxy;
-
- proxy.use = (middleware: XHRMiddleware) => {
- middlewares.push(middleware);
- };
-
- proxy.clearMiddlewares = () => {
- middlewares.length = 0;
- };
-
- return proxy;
-}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/install-proxies.ts b/src/components/onlyoffice-web-comp/internal/editor/install-proxies.ts
deleted file mode 100644
index 965c00a0..00000000
--- a/src/components/onlyoffice-web-comp/internal/editor/install-proxies.ts
+++ /dev/null
@@ -1,350 +0,0 @@
-import { createFetchProxy } from "./fetch";
-import { createXHRProxy } from "./xhr";
-import type { EditorServer } from "./server";
-import type { MockSocket, MockSocketOptions } from "./socket";
-
-export function shouldBypassOnlyOfficeProxy(url: string, baseUrl: string) {
- const pathname = new URL(url, baseUrl).pathname;
-
- return (
- pathname.includes("/sdkjs/common/AllFonts.js") ||
- pathname.includes("/sdkjs/common/libfont/") ||
- pathname.includes("/fonts/")
- );
-}
-
-export type ScopedIoFactory = (
- url?: string,
- options?: MockSocketOptions,
-) => MockSocket;
-
-export type OnlyOfficeParentWindow = Window & {
- __ONLYOFFICE_SCOPED_IO__?: Record;
-};
-
-export function getScopedIoRegistry(
- win: Window = window,
-): Record {
- const parent = win as OnlyOfficeParentWindow;
- if (!parent.__ONLYOFFICE_SCOPED_IO__) {
- parent.__ONLYOFFICE_SCOPED_IO__ = {};
- }
- return parent.__ONLYOFFICE_SCOPED_IO__;
-}
-
-export function registerScopedIo(
- containerId: string,
- factory: ScopedIoFactory,
- win: Window = window,
-) {
- getScopedIoRegistry(win)[containerId] = factory;
-}
-
-export function unregisterScopedIo(containerId: string, win: Window = window) {
- const registry = (win as OnlyOfficeParentWindow).__ONLYOFFICE_SCOPED_IO__;
- if (registry) {
- delete registry[containerId];
- }
-}
-
-export type OnlyOfficeProxyWindow = Window & {
- __ONLYOFFICE_PROXIES_INSTALLED__?: boolean;
- __ONLYOFFICE_GETFILE_PATCHED__?: boolean;
- __ONLYOFFICE_PROXY_SERVER__?: EditorServer;
- XMLHttpRequest: typeof XMLHttpRequest;
- Worker: typeof Worker;
- AscCommon?: {
- getFile?: (url: string) => void;
- };
-};
-
-export type InstallOnlyOfficeProxyOptions = {
- installIo?: boolean;
-};
-
-function extractDownloadFileName(url: string) {
- if (!url) {
- return "download";
- }
-
- try {
- const parsed = new URL(url, window.location.href);
- const fromQuery = parsed.searchParams.get("filename");
- if (fromQuery) {
- return decodeURIComponent(fromQuery);
- }
-
- const pathname = parsed.pathname;
- const name = decodeURIComponent(pathname.split("/").pop() || "");
- if (name.startsWith("output.")) {
- return name;
- }
- if (name) {
- return name;
- }
- } catch {
- const fallback = url.split("/").pop() || url.split("?")[0];
- if (fallback) {
- return decodeURIComponent(fallback);
- }
- }
-
- return "download";
-}
-
-function parseContentDispositionFileName(header: string | null) {
- if (!header) {
- return "";
- }
-
- const utf8Match = /filename\*=UTF-8''([^;]+)/i.exec(header);
- if (utf8Match?.[1]) {
- try {
- return decodeURIComponent(utf8Match[1]);
- } catch {
- return utf8Match[1];
- }
- }
-
- const asciiMatch = /filename="([^"]+)"/i.exec(header);
- if (asciiMatch?.[1]) {
- return asciiMatch[1];
- }
-
- return "";
-}
-
-function scheduleNamedDownloadPatch(
- win: OnlyOfficeProxyWindow,
- server: EditorServer,
- retries = 100,
-) {
- if (win.__ONLYOFFICE_GETFILE_PATCHED__) {
- return;
- }
-
- if (win.AscCommon?.getFile) {
- installNamedDownloadPatch(win, server);
- return;
- }
-
- if (retries > 0) {
- win.setTimeout(
- () => scheduleNamedDownloadPatch(win, server, retries - 1),
- 50,
- );
- }
-}
-
-function extractOutputNameFromCacheUrl(url: string) {
- const match = /\/cache\/files\/data\/[^/]+\/(output\.[^/?#]+)/i.exec(url);
- return match?.[1] ?? "";
-}
-
-function triggerBlobDownload(win: Window, blob: Blob, fileName: string) {
- const objectUrl = URL.createObjectURL(blob);
- const link = win.document.createElement("a");
- link.href = objectUrl;
- link.download = fileName;
- link.style.display = "none";
- win.document.body.appendChild(link);
- link.click();
- link.remove();
- URL.revokeObjectURL(objectUrl);
-}
-
-/** mock cache URL 无法通过 iframe 下载携带文件名,改为 fetch + 。 */
-function installNamedDownloadPatch(
- win: OnlyOfficeProxyWindow,
- server: EditorServer,
-) {
- win.__ONLYOFFICE_PROXY_SERVER__ = server;
- const ascCommon = win.AscCommon;
- if (!ascCommon?.getFile || win.__ONLYOFFICE_GETFILE_PATCHED__) {
- return;
- }
-
- const nativeGetFile = ascCommon.getFile.bind(ascCommon);
- const fetchFile = win.fetch.bind(win);
-
- ascCommon.getFile = (url: string) => {
- if (typeof url !== "string" || !url) {
- nativeGetFile(url);
- return;
- }
-
- const needsNamedDownload =
- url.includes("/cache/files/") || url.startsWith("blob:");
-
- if (!needsNamedDownload) {
- nativeGetFile(url);
- return;
- }
-
- const fallbackName = extractDownloadFileName(url);
- void fetchFile(url)
- .then((response) => {
- if (!response.ok) {
- throw new Error(`Download failed: ${response.status}`);
- }
- const fileName =
- parseContentDispositionFileName(
- response.headers.get("Content-Disposition"),
- ) || fallbackName;
- return response.blob().then((blob) => ({ blob, fileName }));
- })
- .then(({ blob, fileName }) => {
- triggerBlobDownload(win, blob, fileName);
- })
- .catch((err) => {
- console.warn("[OnlyOffice] named download fetch failed:", err);
- const outputName = extractOutputNameFromCacheUrl(url);
- const currentServer = win.__ONLYOFFICE_PROXY_SERVER__ ?? server;
- const blobUrl = outputName
- ? currentServer.getStoredOutputUrl(outputName)
- : null;
- const fileName =
- (outputName && currentServer.getStoredOutputFileName(outputName)) ||
- fallbackName;
- if (blobUrl) {
- void fetchFile(blobUrl)
- .then((response) => response.blob())
- .then((blob) => triggerBlobDownload(win, blob, fileName))
- .catch((fallbackErr) => {
- console.warn("[OnlyOffice] blob download fallback:", fallbackErr);
- nativeGetFile(url);
- });
- return;
- }
- nativeGetFile(url);
- });
- };
-
- win.__ONLYOFFICE_GETFILE_PATCHED__ = true;
-}
-
-export function installOnlyOfficeProxies(
- win: OnlyOfficeProxyWindow,
- server: EditorServer,
- createIo: ScopedIoFactory,
- options: InstallOnlyOfficeProxyOptions = {},
-) {
- win.__ONLYOFFICE_PROXY_SERVER__ = server;
-
- if (win.__ONLYOFFICE_PROXIES_INSTALLED__) {
- scheduleNamedDownloadPatch(win, server);
- return;
- }
-
- const xhr = createXHRProxy(win.XMLHttpRequest, {
- baseUrl: win.location.href,
- shouldBypass: (url) => shouldBypassOnlyOfficeProxy(url, win.location.href),
- });
- const fetchProxy = createFetchProxy(win);
- const WorkerCtor = win.Worker;
-
- xhr.use((request) => win.__ONLYOFFICE_PROXY_SERVER__?.handleRequest(request) ?? null);
- fetchProxy.use(
- (request) => win.__ONLYOFFICE_PROXY_SERVER__?.handleRequest(request) ?? null,
- );
-
- const patches: Partial & { io?: ScopedIoFactory } = {
- XMLHttpRequest: xhr,
- fetch: fetchProxy,
- Worker: function Worker(url: string, options?: WorkerOptions) {
- const u = new URL(url, win.location.origin);
- return new WorkerCtor(
- u.href.replace(u.origin, win.location.origin),
- options,
- );
- } as unknown as typeof Worker,
- };
- if (options.installIo !== false) {
- patches.io = createIo;
- }
-
- Object.assign(win, patches);
- win.__ONLYOFFICE_PROXIES_INSTALLED__ = true;
- scheduleNamedDownloadPatch(win, server);
-}
-
-export const REPORTER_HTML = "index.reporter.html";
-
-export type ReporterBridge = {
- install: (target: Window) => void;
-};
-
-export type ReporterHookWindow = Window & {
- open: typeof window.open;
- __ONLYOFFICE_REPORTER_HOOK__?: boolean;
- __ONLYOFFICE_REPORTER_BRIDGE__?: ReporterBridge;
-};
-
-export function installReporterWindowHook(
- win: ReporterHookWindow,
- installProxies: (target: Window) => void,
-) {
- if (win.__ONLYOFFICE_REPORTER_HOOK__) {
- return;
- }
-
- win.__ONLYOFFICE_REPORTER_BRIDGE__ = { install: installProxies };
- win.__ONLYOFFICE_REPORTER_HOOK__ = true;
-
- const nativeOpen = win.open.bind(win);
- win.open = function openReporter(
- url?: string | URL,
- target?: string,
- features?: string,
- ) {
- const popup = nativeOpen(url, target, features);
- const href = typeof url === "string" ? url : url?.toString() ?? "";
-
- if (popup && href.includes(REPORTER_HTML)) {
- watchReporterWindow(popup, installProxies);
- }
-
- return popup;
- };
-}
-
-function watchReporterWindow(
- popup: Window,
- installProxies: (target: Window) => void,
-) {
- const tryInstall = () => {
- if (popup.closed) {
- return true;
- }
-
- try {
- if (popup.location.href.includes(REPORTER_HTML)) {
- installProxies(popup);
- return true;
- }
- } catch {
- // Navigation in progress; keep polling.
- }
-
- return false;
- };
-
- if (tryInstall()) {
- return;
- }
-
- const interval = window.setInterval(() => {
- if (tryInstall()) {
- window.clearInterval(interval);
- }
- }, 1);
-
- popup.addEventListener(
- "load",
- () => {
- tryInstall();
- window.clearInterval(interval);
- },
- { once: true },
- );
-}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/logger.ts b/src/components/onlyoffice-web-comp/internal/editor/logger.ts
new file mode 100644
index 00000000..09e694b4
--- /dev/null
+++ b/src/components/onlyoffice-web-comp/internal/editor/logger.ts
@@ -0,0 +1,135 @@
+export type EditorLogLevel = "log" | "info" | "warn" | "error";
+
+export type EditorLogCategory = "socket" | "download" | "worker" | "operation";
+
+export type EditorLogEntry = {
+ id: number;
+ time: string;
+ level: EditorLogLevel;
+ category: EditorLogCategory;
+ message: string;
+ details: unknown[];
+};
+
+type ConsoleMethod = (...args: unknown[]) => void;
+
+export class EditorLogger {
+ private entries: EditorLogEntry[] = [];
+ private nextId = 1;
+
+ constructor(private readonly editorId: string) {}
+
+ private getConsoleMethod(level: EditorLogLevel): ConsoleMethod {
+ return console[level] ?? console.log;
+ }
+
+ private write(
+ level: EditorLogLevel,
+ category: EditorLogCategory,
+ message: string,
+ details: unknown[],
+ consoleArgs?: unknown[],
+ ) {
+ const entry: EditorLogEntry = {
+ id: this.nextId++,
+ time: new Date().toISOString(),
+ level,
+ category,
+ message,
+ details,
+ };
+ this.entries.push(entry);
+
+ this.getConsoleMethod(level)(
+ ...(consoleArgs ?? [
+ `[OnlyOffice:${this.editorId}] [${category}] ${message}`,
+ ...details,
+ ]),
+ );
+ }
+
+ log(category: EditorLogCategory, message: string, ...details: unknown[]) {
+ this.write("log", category, message, details);
+ }
+
+ info(category: EditorLogCategory, message: string, ...details: unknown[]) {
+ this.write("info", category, message, details);
+ }
+
+ warn(category: EditorLogCategory, message: string, ...details: unknown[]) {
+ this.write("warn", category, message, details);
+ }
+
+ error(category: EditorLogCategory, message: string, ...details: unknown[]) {
+ this.write("error", category, message, details);
+ }
+
+ raw(
+ level: EditorLogLevel,
+ category: EditorLogCategory,
+ message: string,
+ consoleArgs: unknown[],
+ ...details: unknown[]
+ ) {
+ this.write(
+ level,
+ category,
+ message,
+ details.length > 0 ? details : consoleArgs,
+ consoleArgs,
+ );
+ }
+
+ socket(message: string, ...details: unknown[]) {
+ this.log("socket", message, ...details);
+ }
+
+ download(message: string, ...details: unknown[]) {
+ this.log("download", message, ...details);
+ }
+
+ worker(message: string, ...details: unknown[]) {
+ this.log("worker", message, ...details);
+ }
+
+ operation(message: string, ...details: unknown[]) {
+ this.log("operation", message, ...details);
+ }
+
+ getEntries() {
+ return [...this.entries];
+ }
+
+ clear() {
+ this.entries = [];
+ }
+
+ print() {
+ const title = `[OnlyOffice:${this.editorId}] editor logs (${this.entries.length})`;
+ if (typeof console.groupCollapsed === "function") {
+ console.groupCollapsed(title);
+ const rows = this.entries.map(({ id, time, level, category, message }) => ({
+ id,
+ time,
+ level,
+ category,
+ message,
+ }));
+ if (typeof console.table === "function") {
+ console.table(rows);
+ } else {
+ console.log(rows);
+ }
+ this.entries.forEach((entry) => {
+ this.getConsoleMethod(entry.level)(
+ `#${entry.id} ${entry.time} [${entry.category}] ${entry.message}`,
+ ...entry.details,
+ );
+ });
+ console.groupEnd();
+ return;
+ }
+
+ console.log(title, this.getEntries());
+ }
+}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/office-format.ts b/src/components/onlyoffice-web-comp/internal/editor/office-format.ts
new file mode 100644
index 00000000..734810a4
--- /dev/null
+++ b/src/components/onlyoffice-web-comp/internal/editor/office-format.ts
@@ -0,0 +1,602 @@
+import { parseCsvBuffer } from "./utils";
+
+
+
+const crcTable = (() => {
+ const table = new Uint32Array(256);
+ for (let i = 0; i < 256; i++) {
+ let c = i;
+ for (let k = 0; k < 8; k++) {
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
+ }
+ table[i] = c >>> 0;
+ }
+ return table;
+})();
+
+export function crc32(data: Uint8Array) {
+ let crc = 0xffffffff;
+ for (const byte of data) {
+ crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8);
+ }
+ return (crc ^ 0xffffffff) >>> 0;
+}
+
+export function readU16(data: Uint8Array, offset: number) {
+ return data[offset] | (data[offset + 1] << 8);
+}
+
+export function readU32(data: Uint8Array, offset: number) {
+ return (
+ data[offset] |
+ (data[offset + 1] << 8) |
+ (data[offset + 2] << 16) |
+ (data[offset + 3] << 24)
+ ) >>> 0;
+}
+
+export function writeU16(data: Uint8Array, offset: number, value: number) {
+ data[offset] = value & 0xff;
+ data[offset + 1] = (value >>> 8) & 0xff;
+}
+
+export function writeU32(data: Uint8Array, offset: number, value: number) {
+ data[offset] = value & 0xff;
+ data[offset + 1] = (value >>> 8) & 0xff;
+ data[offset + 2] = (value >>> 16) & 0xff;
+ data[offset + 3] = (value >>> 24) & 0xff;
+}
+
+export function concatBytes(parts: Uint8Array[]) {
+ const size = parts.reduce((total, part) => total + part.length, 0);
+ const output = new Uint8Array(size);
+ let offset = 0;
+ for (const part of parts) {
+ output.set(part, offset);
+ offset += part.length;
+ }
+ return output;
+}
+
+async function decompressDeflateRaw(data: Uint8Array) {
+ if (!("DecompressionStream" in globalThis)) {
+ return null;
+ }
+
+ const stream = new Blob([data as Uint8Array])
+ .stream()
+ .pipeThrough(new DecompressionStream("deflate-raw"));
+ return new Uint8Array(await new Response(stream).arrayBuffer());
+}
+
+export type ZipEntry = {
+ name: string;
+ nameBytes: Uint8Array;
+ method: number;
+ modTime: number;
+ modDate: number;
+ crc: number;
+ compressedSize: number;
+ uncompressedSize: number;
+ compressedData: Uint8Array;
+ internalAttrs: number;
+ externalAttrs: number;
+};
+
+export function readZipEntries(data: ArrayBuffer) {
+ const input = new Uint8Array(data);
+ let eocd = -1;
+ for (let i = input.length - 22; i >= Math.max(0, input.length - 65558); i--) {
+ if (readU32(input, i) === 0x06054b50) {
+ eocd = i;
+ break;
+ }
+ }
+ if (eocd < 0) return null;
+
+ const entryCount = readU16(input, eocd + 10);
+ const centralOffset = readU32(input, eocd + 16);
+ const decoder = new TextDecoder();
+ const entries: ZipEntry[] = [];
+ let readOffset = centralOffset;
+
+ for (let i = 0; i < entryCount; i++) {
+ if (readU32(input, readOffset) !== 0x02014b50) return null;
+
+ const method = readU16(input, readOffset + 10);
+ const modTime = readU16(input, readOffset + 12);
+ const modDate = readU16(input, readOffset + 14);
+ const crc = readU32(input, readOffset + 16);
+ const compressedSize = readU32(input, readOffset + 20);
+ const uncompressedSize = readU32(input, readOffset + 24);
+ const nameLength = readU16(input, readOffset + 28);
+ const extraLength = readU16(input, readOffset + 30);
+ const commentLength = readU16(input, readOffset + 32);
+ const internalAttrs = readU16(input, readOffset + 36);
+ const externalAttrs = readU32(input, readOffset + 38);
+ const localOffset = readU32(input, readOffset + 42);
+ const nameBytes = input.slice(readOffset + 46, readOffset + 46 + nameLength);
+ const name = decoder.decode(nameBytes);
+
+ if (readU32(input, localOffset) !== 0x04034b50) return null;
+ const localNameLength = readU16(input, localOffset + 26);
+ const localExtraLength = readU16(input, localOffset + 28);
+ const dataStart = localOffset + 30 + localNameLength + localExtraLength;
+ const compressedData = input.slice(dataStart, dataStart + compressedSize);
+
+ entries.push({
+ name,
+ nameBytes,
+ method,
+ modTime,
+ modDate,
+ crc,
+ compressedSize,
+ uncompressedSize,
+ compressedData,
+ internalAttrs,
+ externalAttrs,
+ });
+
+ readOffset += 46 + nameLength + extraLength + commentLength;
+ }
+
+ return entries;
+}
+
+export async function readZipEntryData(entry: ZipEntry) {
+ if (entry.method === 0) {
+ return entry.compressedData;
+ }
+ if (entry.method === 8) {
+ return decompressDeflateRaw(entry.compressedData);
+ }
+ return null;
+}
+
+function buildLocalHeader(
+ entry: Pick,
+ crc: number,
+ compressedSize: number,
+ uncompressedSize: number,
+) {
+ const localHeader = new Uint8Array(30 + entry.nameBytes.length);
+ writeU32(localHeader, 0, 0x04034b50);
+ writeU16(localHeader, 4, 20);
+ writeU16(localHeader, 6, 0);
+ writeU16(localHeader, 8, entry.method);
+ writeU16(localHeader, 10, entry.modTime);
+ writeU16(localHeader, 12, entry.modDate);
+ writeU32(localHeader, 14, crc);
+ writeU32(localHeader, 18, compressedSize);
+ writeU32(localHeader, 22, uncompressedSize);
+ writeU16(localHeader, 26, entry.nameBytes.length);
+ writeU16(localHeader, 28, 0);
+ localHeader.set(entry.nameBytes, 30);
+ return localHeader;
+}
+
+function buildCentralHeader(
+ entry: Pick<
+ ZipEntry,
+ | "nameBytes"
+ | "method"
+ | "modTime"
+ | "modDate"
+ | "internalAttrs"
+ | "externalAttrs"
+ >,
+ crc: number,
+ compressedSize: number,
+ uncompressedSize: number,
+ localOffset: number,
+) {
+ const centralHeader = new Uint8Array(46 + entry.nameBytes.length);
+ writeU32(centralHeader, 0, 0x02014b50);
+ writeU16(centralHeader, 4, 20);
+ writeU16(centralHeader, 6, 20);
+ writeU16(centralHeader, 8, 0);
+ writeU16(centralHeader, 10, entry.method);
+ writeU16(centralHeader, 12, entry.modTime);
+ writeU16(centralHeader, 14, entry.modDate);
+ writeU32(centralHeader, 16, crc);
+ writeU32(centralHeader, 20, compressedSize);
+ writeU32(centralHeader, 24, uncompressedSize);
+ writeU16(centralHeader, 28, entry.nameBytes.length);
+ writeU16(centralHeader, 30, 0);
+ writeU16(centralHeader, 32, 0);
+ writeU16(centralHeader, 34, 0);
+ writeU16(centralHeader, 36, entry.internalAttrs);
+ writeU32(centralHeader, 38, entry.externalAttrs);
+ writeU32(centralHeader, 42, localOffset);
+ centralHeader.set(entry.nameBytes, 46);
+ return centralHeader;
+}
+
+export type ZipReplacement = {
+ data: Uint8Array;
+ modTime?: number;
+ modDate?: number;
+};
+
+export type ZipXmlSizeStats = {
+ totalSize: number;
+ entryCount: number;
+};
+
+export function isOfficeXmlEntryName(name: string) {
+ return /\.(xml|rels)$/i.test(name);
+}
+
+export function getZipXmlUncompressedSize(
+ entries: readonly ZipEntry[],
+): ZipXmlSizeStats {
+ let totalSize = 0;
+ let entryCount = 0;
+
+ for (const entry of entries) {
+ if (entry.name.endsWith("/") || !isOfficeXmlEntryName(entry.name)) {
+ continue;
+ }
+
+ totalSize += entry.uncompressedSize;
+ entryCount += 1;
+ }
+
+ return { totalSize, entryCount };
+}
+
+export function writeZipEntries(
+ entries: ZipEntry[],
+ replacements: Map,
+) {
+ const encoder = new TextEncoder();
+ const localParts: Uint8Array[] = [];
+ const centralParts: Uint8Array[] = [];
+ let outputOffset = 0;
+ const emitted = new Set();
+ let emittedCount = 0;
+
+ const emit = (
+ entry: Pick<
+ ZipEntry,
+ | "name"
+ | "nameBytes"
+ | "method"
+ | "modTime"
+ | "modDate"
+ | "crc"
+ | "compressedData"
+ | "uncompressedSize"
+ | "internalAttrs"
+ | "externalAttrs"
+ >,
+ replacement?: ZipReplacement,
+ ) => {
+ const outputData = replacement?.data ?? entry.compressedData;
+ const method = replacement ? 0 : entry.method;
+ const crc = replacement ? crc32(outputData) : entry.crc;
+ const uncompressedSize = replacement
+ ? outputData.length
+ : entry.uncompressedSize;
+ const modTime = replacement?.modTime ?? entry.modTime;
+ const modDate = replacement?.modDate ?? entry.modDate;
+ const nextEntry = { ...entry, method, modTime, modDate };
+
+ const localHeader = buildLocalHeader(
+ nextEntry,
+ crc,
+ outputData.length,
+ uncompressedSize,
+ );
+ localParts.push(localHeader, outputData);
+ centralParts.push(
+ buildCentralHeader(
+ nextEntry,
+ crc,
+ outputData.length,
+ uncompressedSize,
+ outputOffset,
+ ),
+ );
+ outputOffset += localHeader.length + outputData.length;
+ emitted.add(entry.name);
+ emittedCount += 1;
+ };
+
+ for (const entry of entries) {
+ emit(entry, replacements.get(entry.name));
+ }
+
+ for (const [name, replacement] of replacements) {
+ if (emitted.has(name)) continue;
+ const nameBytes = encoder.encode(name);
+ emit({
+ name,
+ nameBytes,
+ method: 0,
+ modTime: replacement.modTime ?? 0,
+ modDate: replacement.modDate ?? 0,
+ crc: 0,
+ compressedData: replacement.data,
+ uncompressedSize: replacement.data.length,
+ internalAttrs: 0,
+ externalAttrs: 0,
+ });
+ }
+
+ const centralDirectory = concatBytes(centralParts);
+ const end = new Uint8Array(22);
+ writeU32(end, 0, 0x06054b50);
+ writeU16(end, 8, emittedCount);
+ writeU16(end, 10, emittedCount);
+ writeU32(end, 12, centralDirectory.length);
+ writeU32(end, 16, outputOffset);
+
+ return concatBytes([...localParts, centralDirectory, end]).buffer;
+}
+
+
+async function loadExcelJS() {
+ const mod = await import(
+ "exceljs/dist/exceljs.min"
+ );
+ return mod.default ?? mod;
+}
+
+/**
+ * @description 复杂 CSV 先转 XLSX,再交给 x2t,避免 x2t CSV 解析崩溃。
+ */
+export async function convertCsvBufferToXlsxBuffer(buffer: ArrayBuffer) {
+ const ExcelJS = await loadExcelJS();
+ const rows = parseCsvBuffer(buffer);
+ const workbook = new ExcelJS.Workbook();
+ const worksheet = workbook.addWorksheet("Sheet1");
+
+ for (const row of rows) {
+ worksheet.addRow(row);
+ }
+
+ const output = await workbook.xlsx.writeBuffer();
+ if (output instanceof ArrayBuffer) {
+ return output;
+ }
+ const bytes = new Uint8Array(output as ArrayLike);
+ return bytes.slice().buffer;
+}
+
+
+const encoder = new TextEncoder();
+
+function utf8(value: string) {
+ return encoder.encode(value);
+}
+
+function xml(strings: TemplateStringsArray, ...values: unknown[]) {
+ return strings
+ .reduce((result, value, index) => `${result}${value}${values[index] ?? ""}`, "")
+ .trim();
+}
+
+function escapeXml(value: string) {
+ return value
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+}
+
+function decodeText(buffer: ArrayBuffer) {
+ return new TextDecoder("utf-8").decode(buffer);
+}
+
+function zip(entries: Array<[string, string | Uint8Array]>) {
+ const localParts: Uint8Array[] = [];
+ const centralParts: Uint8Array[] = [];
+ let localOffset = 0;
+
+ for (const [name, content] of entries) {
+ const nameBytes = utf8(name);
+ const data = typeof content === "string" ? utf8(content) : content;
+ const crc = crc32(data);
+
+ const localHeader = new Uint8Array(30 + nameBytes.length);
+ writeU32(localHeader, 0, 0x04034b50);
+ writeU16(localHeader, 4, 20);
+ writeU16(localHeader, 6, 0);
+ writeU16(localHeader, 8, 0);
+ writeU16(localHeader, 10, 0);
+ writeU16(localHeader, 12, 0);
+ writeU32(localHeader, 14, crc);
+ writeU32(localHeader, 18, data.length);
+ writeU32(localHeader, 22, data.length);
+ writeU16(localHeader, 26, nameBytes.length);
+ writeU16(localHeader, 28, 0);
+ localHeader.set(nameBytes, 30);
+ localParts.push(localHeader, data);
+
+ const centralHeader = new Uint8Array(46 + nameBytes.length);
+ writeU32(centralHeader, 0, 0x02014b50);
+ writeU16(centralHeader, 4, 20);
+ writeU16(centralHeader, 6, 20);
+ writeU16(centralHeader, 8, 0);
+ writeU16(centralHeader, 10, 0);
+ writeU16(centralHeader, 12, 0);
+ writeU16(centralHeader, 14, 0);
+ writeU32(centralHeader, 16, crc);
+ writeU32(centralHeader, 20, data.length);
+ writeU32(centralHeader, 24, data.length);
+ writeU16(centralHeader, 28, nameBytes.length);
+ writeU16(centralHeader, 30, 0);
+ writeU16(centralHeader, 32, 0);
+ writeU16(centralHeader, 34, 0);
+ writeU16(centralHeader, 36, 0);
+ writeU32(centralHeader, 38, 0);
+ writeU32(centralHeader, 42, localOffset);
+ centralHeader.set(nameBytes, 46);
+ centralParts.push(centralHeader);
+
+ localOffset += localHeader.length + data.length;
+ }
+
+ const centralDirectory = concatBytes(centralParts);
+ const end = new Uint8Array(22);
+ writeU32(end, 0, 0x06054b50);
+ writeU16(end, 8, entries.length);
+ writeU16(end, 10, entries.length);
+ writeU32(end, 12, centralDirectory.length);
+ writeU32(end, 16, localOffset);
+
+ return concatBytes([...localParts, centralDirectory, end]).buffer;
+}
+
+const coreProps = xml`
+
+
+ Plain text fallback
+
+`;
+
+const appProps = xml`
+
+
+ OnlyOffice Web Comp
+
+`;
+
+export function createDocxFromText(buffer: ArrayBuffer) {
+ const paragraphs = decodeText(buffer)
+ .split(/\r\n|\r|\n/)
+ .map((line) => `${escapeXml(line)} `)
+ .join("");
+
+ return zip([
+ [
+ "[Content_Types].xml",
+ xml`
+
+
+
+
+
+
+
+
+ `,
+ ],
+ [
+ "_rels/.rels",
+ xml`
+
+
+
+
+
+
+ `,
+ ],
+ ["docProps/core.xml", coreProps],
+ ["docProps/app.xml", appProps],
+ [
+ "word/document.xml",
+ xml`
+
+
+
+ ${paragraphs || ' '}
+
+
+
+
+
+
+ `,
+ ],
+ [
+ "word/_rels/document.xml.rels",
+ xml`
+
+
+ `,
+ ],
+ ]);
+}
+
+export function createXlsxFromText(buffer: ArrayBuffer) {
+ const text = escapeXml(decodeText(buffer));
+
+ return zip([
+ [
+ "[Content_Types].xml",
+ xml`
+
+
+
+
+
+
+
+
+
+
+ `,
+ ],
+ [
+ "_rels/.rels",
+ xml`
+
+
+
+
+
+
+ `,
+ ],
+ ["docProps/core.xml", coreProps],
+ ["docProps/app.xml", appProps],
+ [
+ "xl/workbook.xml",
+ xml`
+
+
+
+
+ `,
+ ],
+ [
+ "xl/_rels/workbook.xml.rels",
+ xml`
+
+
+
+
+
+ `,
+ ],
+ [
+ "xl/styles.xml",
+ xml`
+
+
+
+
+
+
+
+
+ `,
+ ],
+ [
+ "xl/worksheets/sheet1.xml",
+ xml`
+
+
+
+ ${text}
+
+
+ `,
+ ],
+ ]);
+}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/plain-text-office.ts b/src/components/onlyoffice-web-comp/internal/editor/plain-text-office.ts
deleted file mode 100644
index 43fa0a95..00000000
--- a/src/components/onlyoffice-web-comp/internal/editor/plain-text-office.ts
+++ /dev/null
@@ -1,236 +0,0 @@
-import { concatBytes, crc32, writeU16, writeU32 } from "./zip";
-
-const encoder = new TextEncoder();
-
-function utf8(value: string) {
- return encoder.encode(value);
-}
-
-function xml(strings: TemplateStringsArray, ...values: unknown[]) {
- return strings
- .reduce((result, value, index) => `${result}${value}${values[index] ?? ""}`, "")
- .trim();
-}
-
-function escapeXml(value: string) {
- return value
- .replace(/&/g, "&")
- .replace(//g, ">")
- .replace(/"/g, """)
- .replace(/'/g, "'");
-}
-
-function decodeText(buffer: ArrayBuffer) {
- return new TextDecoder("utf-8").decode(buffer);
-}
-
-function zip(entries: Array<[string, string | Uint8Array]>) {
- const localParts: Uint8Array[] = [];
- const centralParts: Uint8Array[] = [];
- let localOffset = 0;
-
- for (const [name, content] of entries) {
- const nameBytes = utf8(name);
- const data = typeof content === "string" ? utf8(content) : content;
- const crc = crc32(data);
-
- const localHeader = new Uint8Array(30 + nameBytes.length);
- writeU32(localHeader, 0, 0x04034b50);
- writeU16(localHeader, 4, 20);
- writeU16(localHeader, 6, 0);
- writeU16(localHeader, 8, 0);
- writeU16(localHeader, 10, 0);
- writeU16(localHeader, 12, 0);
- writeU32(localHeader, 14, crc);
- writeU32(localHeader, 18, data.length);
- writeU32(localHeader, 22, data.length);
- writeU16(localHeader, 26, nameBytes.length);
- writeU16(localHeader, 28, 0);
- localHeader.set(nameBytes, 30);
- localParts.push(localHeader, data);
-
- const centralHeader = new Uint8Array(46 + nameBytes.length);
- writeU32(centralHeader, 0, 0x02014b50);
- writeU16(centralHeader, 4, 20);
- writeU16(centralHeader, 6, 20);
- writeU16(centralHeader, 8, 0);
- writeU16(centralHeader, 10, 0);
- writeU16(centralHeader, 12, 0);
- writeU16(centralHeader, 14, 0);
- writeU32(centralHeader, 16, crc);
- writeU32(centralHeader, 20, data.length);
- writeU32(centralHeader, 24, data.length);
- writeU16(centralHeader, 28, nameBytes.length);
- writeU16(centralHeader, 30, 0);
- writeU16(centralHeader, 32, 0);
- writeU16(centralHeader, 34, 0);
- writeU16(centralHeader, 36, 0);
- writeU32(centralHeader, 38, 0);
- writeU32(centralHeader, 42, localOffset);
- centralHeader.set(nameBytes, 46);
- centralParts.push(centralHeader);
-
- localOffset += localHeader.length + data.length;
- }
-
- const centralDirectory = concatBytes(centralParts);
- const end = new Uint8Array(22);
- writeU32(end, 0, 0x06054b50);
- writeU16(end, 8, entries.length);
- writeU16(end, 10, entries.length);
- writeU32(end, 12, centralDirectory.length);
- writeU32(end, 16, localOffset);
-
- return concatBytes([...localParts, centralDirectory, end]).buffer;
-}
-
-const coreProps = xml`
-
-
- Plain text fallback
-
-`;
-
-const appProps = xml`
-
-
- OnlyOffice Web Comp
-
-`;
-
-export function createDocxFromText(buffer: ArrayBuffer) {
- const paragraphs = decodeText(buffer)
- .split(/\r\n|\r|\n/)
- .map((line) => `${escapeXml(line)} `)
- .join("");
-
- return zip([
- [
- "[Content_Types].xml",
- xml`
-
-
-
-
-
-
-
-
- `,
- ],
- [
- "_rels/.rels",
- xml`
-
-
-
-
-
-
- `,
- ],
- ["docProps/core.xml", coreProps],
- ["docProps/app.xml", appProps],
- [
- "word/document.xml",
- xml`
-
-
-
- ${paragraphs || ' '}
-
-
-
-
-
-
- `,
- ],
- [
- "word/_rels/document.xml.rels",
- xml`
-
-
- `,
- ],
- ]);
-}
-
-export function createXlsxFromText(buffer: ArrayBuffer) {
- const text = escapeXml(decodeText(buffer));
-
- return zip([
- [
- "[Content_Types].xml",
- xml`
-
-
-
-
-
-
-
-
-
-
- `,
- ],
- [
- "_rels/.rels",
- xml`
-
-
-
-
-
-
- `,
- ],
- ["docProps/core.xml", coreProps],
- ["docProps/app.xml", appProps],
- [
- "xl/workbook.xml",
- xml`
-
-
-
-
- `,
- ],
- [
- "xl/_rels/workbook.xml.rels",
- xml`
-
-
-
-
-
- `,
- ],
- [
- "xl/styles.xml",
- xml`
-
-
-
-
-
-
-
-
- `,
- ],
- [
- "xl/worksheets/sheet1.xml",
- xml`
-
-
-
- ${text}
-
-
- `,
- ],
- ]);
-}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/plugins.ts b/src/components/onlyoffice-web-comp/internal/editor/plugins.ts
deleted file mode 100644
index f40989ab..00000000
--- a/src/components/onlyoffice-web-comp/internal/editor/plugins.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-export const pluginsBase = "https://office-plugins.ziziyi.com/v9/sdkjs-plugins";
-
-export const allPlugins = [
- "ai",
- "apertium",
- "autocomplete",
- "bergamot",
- "chess",
- "cvbuilder",
- "datepicker",
- "deepl",
- "doc2md",
- "drawio",
- "easybib",
- "glavred",
- "grammalecte",
- "highlightcode",
- "html",
- "icons",
- "idphoto",
- "insertQR",
- "jitsi",
- "languagetool",
- "marketplace",
- "mathpix",
- "mendeley",
- "news",
- "ocr",
- "onlydraw",
- "photoeditor",
- "pixabay",
- "pomodoro",
- "rainbow",
- "speech",
- "speechrecognition",
- "telegram",
- "termef",
- "textcleaner",
- "texthighlighter",
- "thesaurus",
- "translator",
- "typograf",
- "videoembedder",
- "wordpress",
- "wordscounter",
- "youtube",
- "zhipu",
- "zoom",
- "zotero",
-];
-
-export const featuredPlugins = [
- "marketplace",
- "ai",
- "youtube",
- "jitsi",
- "photoeditor",
- "typograf",
- "languagetool",
- "thesaurus",
- "deepl",
- "zhipu",
-];
-
-export function getPluginConfigUrl(name: string) {
- return `${pluginsBase}/${name}/config.json`;
-}
-
-export function getPluginsData(list: string[]) {
- return {
- url: "",
- pluginsData: list.map(getPluginConfigUrl),
- autostart: [],
- };
-}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/runtime-bridge.ts b/src/components/onlyoffice-web-comp/internal/editor/runtime-bridge.ts
new file mode 100644
index 00000000..f5a70644
--- /dev/null
+++ b/src/components/onlyoffice-web-comp/internal/editor/runtime-bridge.ts
@@ -0,0 +1,1581 @@
+import { EventEmitter } from "../../util/event-emitter";
+import type { EditorLogger } from "./logger";
+import type { EditorServer } from "./server";
+
+type Callback = (...args: any[]) => void;
+
+export interface MockSocketOptions {
+ /**
+ * @description 是否开启调试日志。
+ */
+ debug?: boolean;
+ /**
+ * @description 跨域 bridge 父页侧仅路由消息,不自动 connect
+ */
+ deferConnect?: boolean;
+ logger?: EditorLogger;
+}
+
+/**
+ * @description 基于内部 EventEmitter 模拟 socket.io-client 的最小运行时。
+ */
+export class MockSocket<
+ ListenEvents extends Record = any,
+ EmitEvents extends Record = any
+> {
+ private static _staticEmitter = new EventEmitter();
+ static on(event: E, listener: Callback) {
+ MockSocket._staticEmitter.on(event, listener);
+ }
+ static off(event: E, listener?: Callback) {
+ MockSocket._staticEmitter.off(event, listener);
+ }
+
+ public active = true;
+ public connected: boolean = false;
+ public disconnected: boolean = true;
+ public recovered = false;
+ public id: string = "";
+ public io = {
+ setOpenToken: () => {
+
+ },
+ setSessionToken: () => {
+
+ },
+ on: function () {
+ return this;
+ },
+ reconnectionAttempts: function () {
+ return this;
+ },
+ reconnectionDelay: function () {
+ return this;
+ },
+ reconnectionDelayMax: function () {
+ return this;
+ },
+ timeout: function () {
+ return this;
+ },
+ transports: function () {
+ return this;
+ },
+ upgrade: function () {
+ return this;
+ },
+ upgradeTransport: function () {
+ return this;
+ },
+ upgradeTimeout: function () {
+ return this;
+ },
+ };
+
+ private _clientEmitter = new EventEmitter();
+ private _serverEmitter = new EventEmitter();
+
+ private _debug: boolean;
+ private _logger?: EditorLogger;
+
+ constructor(options: MockSocketOptions = {}) {
+ this._debug = options.debug;
+ this._logger = options.logger;
+ if (!options.deferConnect) {
+ this.connect();
+ }
+ }
+
+ private _log(...args: any[]): void {
+ if (this._logger) {
+ this._logger.raw("log", "socket", "mock socket", ["[MockSocket]", ...args]);
+ return;
+ }
+ if (this._debug) {
+ console.log("[MockSocket]", ...args);
+ }
+ }
+
+ open() {
+ return this.connect();
+ }
+
+ compress() {}
+
+ /**
+ * @description 模拟连接建立并生成新的 session id。
+ */
+ connect() {
+ this.connected = true;
+ this.disconnected = false;
+ this.id = Math.random().toString(36).substring(2, 15);
+ setTimeout(() => {
+ this._trigger("connect");
+ MockSocket._staticEmitter.emit("connect", { socket: this });
+ }, 0);
+ return this;
+ }
+
+ disconnect() {
+ this.connected = false;
+ this.disconnected = true;
+ this._trigger("disconnect");
+ MockSocket._staticEmitter.emit("disconnect", { socket: this });
+ return this;
+ }
+
+ close(): this {
+ return this.disconnect();
+ }
+
+ /**
+ * @description 触发本地监听器,用于模拟服务端下行事件。
+ */
+ private _trigger(event: string, ...args: any[]): this {
+ this._log(`trigger event: ${event}`, ...args);
+ this._clientEmitter.emit(event, ...args);
+ return this;
+ }
+
+ /**
+ * @description 注册服务端下行事件监听器。
+ */
+ on(
+ event: E,
+ listener: ListenEvents[E]
+ ): this {
+ this._clientEmitter.on(event, listener);
+ return this;
+ }
+
+ /**
+ * @description 注册一次性的服务端下行事件监听器。
+ */
+ once(
+ event: E,
+ listener: ListenEvents[E]
+ ): this {
+ this._clientEmitter.once(event, listener);
+ return this;
+ }
+
+ /**
+ * @description 移除事件监听器。
+ */
+ off(
+ event: E,
+ listener?: ListenEvents[E]
+ ): this {
+ this._clientEmitter.off(event, listener);
+ return this;
+ }
+
+ /**
+ * @description 移除全部监听器,或移除指定事件的监听器。
+ */
+ removeAllListeners(event?: string): this {
+ this._clientEmitter.removeAllListeners(event);
+ return this;
+ }
+
+ /**
+ * @description 使用 message 事件向服务端发送消息。
+ */
+ send(...args: Parameters): this {
+ if (!this.connected) return this;
+ this.emit("message", ...args);
+ return this;
+ }
+
+ /**
+ * @description 向服务端发送事件消息。
+ */
+ emit(
+ event: E,
+ ...args: Parameters
+ ): this {
+ this._log(`emit: ${event}`, ...args);
+
+ if (!this.connected) return this;
+
+ const processEmit = async () => {
+ this._serverEmitter.emit(event, ...args);
+ };
+
+ setTimeout(() => processEmit(), 0);
+ return this;
+ }
+
+ public server = {
+ on: (event: string, listener: Callback) => {
+ this._serverEmitter.on(event, listener);
+ },
+ off: (event: string, listener?: Callback) => {
+ this._serverEmitter.off(event, listener);
+ },
+ emit: (event: string, ...args: any[]) => {
+ this._clientEmitter.emit(event, ...args);
+ },
+ };
+}
+
+/**
+ * @description 兼容 socket.io-client 调用方式的工厂函数。
+ */
+export function io(_url?: string, options?: MockSocketOptions): MockSocket {
+ return new MockSocket(options);
+}
+
+/**
+ * @description 为 socket.io 兼容层保留函数命名空间类型。
+ */
+export interface SocketIOStatic {
+ (url?: string, options?: MockSocketOptions): MockSocket;
+}
+
+const ioWithStatics = io as SocketIOStatic;
+
+/**
+ * @description 默认导出保持 socket.io-client 兼容。
+ */
+export default ioWithStatics;
+
+
+export interface XHRMiddleware {
+ (request: Request): Response | null | Promise;
+}
+
+export interface XHRProxyOptions {
+ baseUrl?: string;
+ shouldBypass?: (url: string, method: string) => boolean;
+}
+
+export interface FetchProxyOptions {
+ baseUrl?: string;
+}
+
+function isForbiddenRequestHeader(name: string) {
+ const lowerName = name.toLowerCase();
+ return (
+ lowerName === "accept-charset" ||
+ lowerName === "accept-encoding" ||
+ lowerName === "access-control-request-headers" ||
+ lowerName === "access-control-request-method" ||
+ lowerName === "connection" ||
+ lowerName === "content-length" ||
+ lowerName === "cookie" ||
+ lowerName === "cookie2" ||
+ lowerName === "date" ||
+ lowerName === "dnt" ||
+ lowerName === "expect" ||
+ lowerName === "host" ||
+ lowerName === "keep-alive" ||
+ lowerName === "origin" ||
+ lowerName === "referer" ||
+ lowerName === "te" ||
+ lowerName === "trailer" ||
+ lowerName === "transfer-encoding" ||
+ lowerName === "upgrade" ||
+ lowerName === "via" ||
+ lowerName.startsWith("proxy-") ||
+ lowerName.startsWith("sec-")
+ );
+}
+
+/**
+ * @description 创建支持中间件拦截的 XMLHttpRequest 代理类。
+ * @param BaseXHR 原始 XMLHttpRequest 构造器。
+ */
+export function createXHRProxy(
+ BaseXHR = globalThis.XMLHttpRequest,
+ options: XHRProxyOptions = {},
+) {
+ return class ProxyXMLHttpRequest extends BaseXHR {
+ private static _middlewares: XHRMiddleware[] = [];
+
+ private _isMocked: boolean = false;
+ private _requestMethod: string = "GET";
+ private _requestUrl: string = "";
+ private _requestHeaders: Headers = new Headers();
+ private _requestBody: any = null;
+ private _responseHeaders: Headers = new Headers();
+
+ /**
+ * @description 注册全局 XHR 中间件。
+ */
+ static use(middleware: XHRMiddleware) {
+ this._middlewares.push(middleware);
+ }
+
+ /**
+ * @description 清空全部 XHR 中间件。
+ */
+ static clearMiddlewares() {
+ this._middlewares = [];
+ }
+
+ open(
+ method: string,
+ url: string | URL,
+ async: boolean = true,
+ username?: string | null,
+ password?: string | null,
+ ): void {
+ const normalizedUrl = (() => {
+ try {
+ return options.baseUrl
+ ? new URL(url.toString(), options.baseUrl).href
+ : url.toString();
+ } catch {
+ return url.toString();
+ }
+ })();
+
+ this._requestMethod = method;
+ this._requestUrl = normalizedUrl;
+ this._requestHeaders = new Headers();
+ this._responseHeaders = new Headers();
+ this._isMocked = false;
+
+ super.open(
+ method,
+ normalizedUrl,
+ async,
+ username ?? undefined,
+ password ?? undefined,
+ );
+ }
+
+ setRequestHeader(name: string, value: string): void {
+ if (isForbiddenRequestHeader(name)) {
+ return;
+ }
+
+ this._requestHeaders.append(name, value);
+
+ if (!this._isMocked) {
+ super.setRequestHeader(name, value);
+ }
+ }
+
+ getResponseHeader(name: string): string | null {
+ if (this._isMocked) {
+ return this._responseHeaders.get(name);
+ }
+ return super.getResponseHeader(name);
+ }
+
+ getAllResponseHeaders(): string {
+ if (!this._isMocked) {
+ return super.getAllResponseHeaders();
+ }
+ return Array.from(this._responseHeaders.entries())
+ .map(([key, value]) => `${key}: ${value}`)
+ .join("\r\n");
+ }
+
+ send(body?: Document | XMLHttpRequestBodyInit | null): void {
+ this._requestBody = body;
+
+ if (options.shouldBypass?.(this._requestUrl, this._requestMethod)) {
+ super.send(body);
+ return;
+ }
+
+ this._tryMiddlewares()
+ .then((handled) => {
+ if (!handled) {
+ super.send(body);
+ }
+ })
+ .catch((err) => {
+ console.error("ProxyXMLHttpRequest middleware error:", err);
+ super.send(body);
+ });
+ }
+
+ private async _tryMiddlewares(): Promise {
+ let request: Request;
+ try {
+ const reqInit: RequestInit = {
+ method: this._requestMethod,
+ headers: this._requestHeaders,
+ body: this._requestBody as BodyInit,
+ mode: "cors",
+ };
+
+ if (this.withCredentials) {
+ reqInit.credentials = "include";
+ }
+
+ request = new Request(this._requestUrl, reqInit);
+ console.log("ProxyXHR created request:", {
+ url: this._requestUrl,
+ method: request.method,
+ hasBody: !!request.body,
+ originalBody: this._requestBody,
+ });
+ } catch (e) {
+ return false;
+ }
+
+ for (const mw of ProxyXMLHttpRequest._middlewares) {
+ const response = await mw(request.clone());
+ if (response) {
+ this._isMocked = true;
+ await this._handleMockResponse(response);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private async _handleMockResponse(response: Response) {
+ this._responseHeaders = new Headers(response.headers);
+
+ const emit = (event: Event) => {
+ this.dispatchEvent(event);
+ };
+
+ emit(new ProgressEvent("loadstart"));
+
+ Object.defineProperty(this, "readyState", {
+ value: 2,
+ writable: false,
+ configurable: true,
+ });
+ emit(new Event("readystatechange"));
+
+ Object.defineProperty(this, "readyState", {
+ value: 3,
+ writable: false,
+ configurable: true,
+ });
+ emit(new Event("readystatechange"));
+
+ try {
+ let responseData: any;
+
+ if (this.responseType === "json") {
+ responseData = await response.json();
+ } else if (this.responseType === "arraybuffer") {
+ responseData = await response.arrayBuffer();
+ } else if (this.responseType === "blob") {
+ responseData = await response.blob();
+ } else if (this.responseType === "document") {
+ const text = await response.text();
+ responseData = new DOMParser().parseFromString(text, "text/xml");
+ } else {
+ responseData = await response.text();
+ }
+
+ Object.defineProperty(this, "status", {
+ value: response.status,
+ writable: false,
+ configurable: true,
+ });
+
+ Object.defineProperty(this, "statusText", {
+ value: response.statusText,
+ writable: false,
+ configurable: true,
+ });
+
+ Object.defineProperty(this, "response", {
+ value: responseData,
+ writable: false,
+ configurable: true,
+ });
+
+ Object.defineProperty(this, "responseText", {
+ value:
+ typeof responseData === "string"
+ ? responseData
+ : JSON.stringify(responseData),
+ writable: false,
+ configurable: true,
+ });
+
+ Object.defineProperty(this, "responseURL", {
+ value: response.url,
+ writable: false,
+ configurable: true,
+ });
+
+ emit(
+ new ProgressEvent("progress", {
+ lengthComputable: true,
+ loaded: 100,
+ total: 100,
+ }),
+ );
+
+ Object.defineProperty(this, "readyState", {
+ value: 4,
+ writable: false,
+ configurable: true,
+ });
+ emit(new Event("readystatechange"));
+
+ emit(new ProgressEvent("load"));
+
+ emit(new ProgressEvent("loadend"));
+ } catch (e) {
+ console.error("ProxyXHR: error handling response", e);
+
+ Object.defineProperty(this, "readyState", {
+ value: 4,
+ writable: false,
+ configurable: true,
+ });
+ emit(new Event("readystatechange"));
+
+ emit(new ProgressEvent("error"));
+ emit(new ProgressEvent("loadend"));
+ }
+ }
+ };
+}
+
+
+export type FetchProxy = typeof fetch & {
+ use(middleware: XHRMiddleware): void;
+ clearMiddlewares(): void;
+};
+
+/**
+ * @description 创建支持中间件拦截的 fetch 代理函数。
+ */
+export function createFetchProxy(
+ target: (Window & { fetch: typeof fetch }) | typeof fetch = globalThis.fetch,
+ options: FetchProxyOptions = {},
+): FetchProxy {
+ const middlewares: XHRMiddleware[] = [];
+ const BaseFetch =
+ typeof target === "function" ? target : target.fetch.bind(target);
+
+ const proxy = (async (input: RequestInfo | URL, init?: RequestInit) => {
+ let request: Request;
+ try {
+ const normalizedInput =
+ options.baseUrl && !(input instanceof Request)
+ ? new URL(input.toString(), options.baseUrl).href
+ : input;
+ request = new Request(normalizedInput, init);
+ } catch (e) {
+ return BaseFetch(input, init);
+ }
+
+ try {
+ for (const mw of middlewares) {
+ const response = await mw(request.clone());
+ if (response) {
+ return response;
+ }
+ }
+ } catch (err) {
+ console.error("ProxyFetch middleware error:", err);
+ return BaseFetch(request);
+ }
+
+ return BaseFetch(request);
+ }) as FetchProxy;
+
+ proxy.use = (middleware: XHRMiddleware) => {
+ middlewares.push(middleware);
+ };
+
+ proxy.clearMiddlewares = () => {
+ middlewares.length = 0;
+ };
+
+ return proxy;
+}
+
+
+export const CROSS_ORIGIN_BRIDGE_MESSAGE = {
+ EDITOR_COMMAND: "editor:command",
+ EDITOR_RESPONSE: "editor:response",
+ EDITOR_EVENT: "editor:event",
+ EDITOR_SET_READONLY: "editor:set-readonly",
+} as const;
+
+export const CROSS_ORIGIN_EDITOR_COMMAND = {
+ EDITOR_SUBSCRIBE: "editor:subscribe",
+ COMMENT_ADD: "comment:add",
+ COMMENT_UPDATE: "comment:update",
+ COMMENT_REMOVE: "comment:remove",
+ COMMENT_GO_TO: "comment:go-to",
+ COMMENT_LIST: "comment:list",
+ COMMENT_SUBSCRIBE: "comment:subscribe",
+ REVISION_ADD_DEMO: "revision:add-demo",
+ REVISION_LIST: "revision:list",
+ REVISION_SET_TRACK: "revision:set-track",
+ REVISION_IS_TRACK: "revision:is-track",
+ REVISION_HAVE_CHANGES: "revision:have-changes",
+ REVISION_PREPARE_REVIEW: "revision:prepare-review",
+ REVISION_NEXT: "revision:next",
+ REVISION_PREV: "revision:prev",
+ REVISION_GO_TO: "revision:go-to",
+ REVISION_ACCEPT: "revision:accept",
+ REVISION_REJECT: "revision:reject",
+ REVISION_ACCEPT_ALL: "revision:accept-all",
+ REVISION_REJECT_ALL: "revision:reject-all",
+ REVISION_ACCEPT_SELECTION: "revision:accept-selection",
+ REVISION_REJECT_SELECTION: "revision:reject-selection",
+ REVISION_SUBSCRIBE: "revision:subscribe",
+} as const;
+
+export const CROSS_ORIGIN_EDITOR_EVENT = {
+ ADD_COMMENT: "asc_onAddComment",
+ CHANGE_COMMENT: "asc_onChangeCommentData",
+ REMOVE_COMMENT: "asc_onRemoveComment",
+ SHOW_REVISIONS_CHANGE: "asc_onShowRevisionsChange",
+ TRACK_REVISIONS_CHANGE: "asc_onOnTrackRevisionsChange",
+ DOCUMENT_MODIFIED_CHANGED: "asc_onDocumentModifiedChanged",
+} as const;
+
+
+export function shouldBypassOnlyOfficeProxy(url: string, baseUrl: string) {
+ const pathname = new URL(url, baseUrl).pathname;
+
+ return (
+ pathname.includes("/sdkjs/common/AllFonts.js") ||
+ pathname.includes("/sdkjs/common/libfont/") ||
+ pathname.includes("/fonts/")
+ );
+}
+
+export type ScopedIoFactory = (
+ url?: string,
+ options?: MockSocketOptions,
+) => MockSocket;
+
+export type OnlyOfficeParentWindow = Window & {
+ __ONLYOFFICE_SCOPED_IO__?: Record;
+};
+
+export function getScopedIoRegistry(
+ win: Window = window,
+): Record {
+ const parent = win as OnlyOfficeParentWindow;
+ if (!parent.__ONLYOFFICE_SCOPED_IO__) {
+ parent.__ONLYOFFICE_SCOPED_IO__ = {};
+ }
+ return parent.__ONLYOFFICE_SCOPED_IO__;
+}
+
+export function registerScopedIo(
+ containerId: string,
+ factory: ScopedIoFactory,
+ win: Window = window,
+) {
+ getScopedIoRegistry(win)[containerId] = factory;
+}
+
+export function unregisterScopedIo(containerId: string, win: Window = window) {
+ const registry = (win as OnlyOfficeParentWindow).__ONLYOFFICE_SCOPED_IO__;
+ if (registry) {
+ delete registry[containerId];
+ }
+}
+
+export type OnlyOfficeProxyWindow = Window & {
+ __ONLYOFFICE_PROXIES_INSTALLED__?: boolean;
+ __ONLYOFFICE_GETFILE_PATCHED__?: boolean;
+ __ONLYOFFICE_PROXY_SERVER__?: EditorServer;
+ XMLHttpRequest: typeof XMLHttpRequest;
+ Worker: typeof Worker;
+ AscCommon?: {
+ getFile?: (url: string) => void;
+ };
+};
+
+export type InstallOnlyOfficeProxyOptions = {
+ installIo?: boolean;
+};
+
+function extractDownloadFileName(url: string) {
+ if (!url) {
+ return "download";
+ }
+
+ try {
+ const parsed = new URL(url, window.location.href);
+ const fromQuery = parsed.searchParams.get("filename");
+ if (fromQuery) {
+ return decodeURIComponent(fromQuery);
+ }
+
+ const pathname = parsed.pathname;
+ const name = decodeURIComponent(pathname.split("/").pop() || "");
+ if (name.startsWith("output.")) {
+ return name;
+ }
+ if (name) {
+ return name;
+ }
+ } catch {
+ const fallback = url.split("/").pop() || url.split("?")[0];
+ if (fallback) {
+ return decodeURIComponent(fallback);
+ }
+ }
+
+ return "download";
+}
+
+function parseContentDispositionFileName(header: string | null) {
+ if (!header) {
+ return "";
+ }
+
+ const utf8Match = /filename\*=UTF-8''([^;]+)/i.exec(header);
+ if (utf8Match?.[1]) {
+ try {
+ return decodeURIComponent(utf8Match[1]);
+ } catch {
+ return utf8Match[1];
+ }
+ }
+
+ const asciiMatch = /filename="([^"]+)"/i.exec(header);
+ if (asciiMatch?.[1]) {
+ return asciiMatch[1];
+ }
+
+ return "";
+}
+
+function scheduleNamedDownloadPatch(
+ win: OnlyOfficeProxyWindow,
+ server: EditorServer,
+ retries = 100,
+) {
+ if (win.__ONLYOFFICE_GETFILE_PATCHED__) {
+ return;
+ }
+
+ if (win.AscCommon?.getFile) {
+ installNamedDownloadPatch(win, server);
+ return;
+ }
+
+ if (retries > 0) {
+ win.setTimeout(
+ () => scheduleNamedDownloadPatch(win, server, retries - 1),
+ 50,
+ );
+ }
+}
+
+function extractOutputNameFromCacheUrl(url: string) {
+ const match = /\/cache\/files\/data\/[^/]+\/(output\.[^/?#]+)/i.exec(url);
+ return match?.[1] ?? "";
+}
+
+function triggerBlobDownload(win: Window, blob: Blob, fileName: string) {
+ const objectUrl = URL.createObjectURL(blob);
+ const link = win.document.createElement("a");
+ link.href = objectUrl;
+ link.download = fileName;
+ link.style.display = "none";
+ win.document.body.appendChild(link);
+ link.click();
+ link.remove();
+ URL.revokeObjectURL(objectUrl);
+}
+
+/**
+ * @description mock cache URL 无法通过 iframe 下载携带文件名,改为 fetch + 。
+ */
+function installNamedDownloadPatch(
+ win: OnlyOfficeProxyWindow,
+ server: EditorServer,
+) {
+ win.__ONLYOFFICE_PROXY_SERVER__ = server;
+ const ascCommon = win.AscCommon;
+ if (!ascCommon?.getFile || win.__ONLYOFFICE_GETFILE_PATCHED__) {
+ return;
+ }
+
+ const nativeGetFile = ascCommon.getFile.bind(ascCommon);
+ const fetchFile = win.fetch.bind(win);
+
+ ascCommon.getFile = (url: string) => {
+ if (typeof url !== "string" || !url) {
+ nativeGetFile(url);
+ return;
+ }
+
+ const needsNamedDownload =
+ url.includes("/cache/files/") || url.startsWith("blob:");
+
+ if (!needsNamedDownload) {
+ nativeGetFile(url);
+ return;
+ }
+
+ const fallbackName = extractDownloadFileName(url);
+ void fetchFile(url)
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error(`Download failed: ${response.status}`);
+ }
+ const fileName =
+ parseContentDispositionFileName(
+ response.headers.get("Content-Disposition"),
+ ) || fallbackName;
+ return response.blob().then((blob) => ({ blob, fileName }));
+ })
+ .then(({ blob, fileName }) => {
+ triggerBlobDownload(win, blob, fileName);
+ })
+ .catch((err) => {
+ console.warn("[OnlyOffice] named download fetch failed:", err);
+ const outputName = extractOutputNameFromCacheUrl(url);
+ const currentServer = win.__ONLYOFFICE_PROXY_SERVER__ ?? server;
+ const blobUrl = outputName
+ ? currentServer.getStoredOutputUrl(outputName)
+ : null;
+ const fileName =
+ (outputName && currentServer.getStoredOutputFileName(outputName)) ||
+ fallbackName;
+ if (blobUrl) {
+ void fetchFile(blobUrl)
+ .then((response) => response.blob())
+ .then((blob) => triggerBlobDownload(win, blob, fileName))
+ .catch((fallbackErr) => {
+ console.warn("[OnlyOffice] blob download fallback:", fallbackErr);
+ nativeGetFile(url);
+ });
+ return;
+ }
+ nativeGetFile(url);
+ });
+ };
+
+ win.__ONLYOFFICE_GETFILE_PATCHED__ = true;
+}
+
+export function installOnlyOfficeProxies(
+ win: OnlyOfficeProxyWindow,
+ server: EditorServer,
+ createIo: ScopedIoFactory,
+ options: InstallOnlyOfficeProxyOptions = {},
+) {
+ win.__ONLYOFFICE_PROXY_SERVER__ = server;
+
+ if (win.__ONLYOFFICE_PROXIES_INSTALLED__) {
+ scheduleNamedDownloadPatch(win, server);
+ return;
+ }
+
+ const xhr = createXHRProxy(win.XMLHttpRequest, {
+ baseUrl: win.location.href,
+ shouldBypass: (url) => shouldBypassOnlyOfficeProxy(url, win.location.href),
+ });
+ const fetchProxy = createFetchProxy(win, { baseUrl: win.location.href });
+ const WorkerCtor = win.Worker;
+
+ xhr.use((request) => win.__ONLYOFFICE_PROXY_SERVER__?.handleRequest(request) ?? null);
+ fetchProxy.use(
+ (request) => win.__ONLYOFFICE_PROXY_SERVER__?.handleRequest(request) ?? null,
+ );
+
+ const patches: Partial & { io?: ScopedIoFactory } = {
+ XMLHttpRequest: xhr,
+ fetch: fetchProxy,
+ Worker: function Worker(url: string, options?: WorkerOptions) {
+ const u = new URL(url, win.location.origin);
+ return new WorkerCtor(
+ u.href.replace(u.origin, win.location.origin),
+ options,
+ );
+ } as unknown as typeof Worker,
+ };
+ if (options.installIo !== false) {
+ patches.io = createIo;
+ }
+
+ Object.assign(win, patches);
+ win.__ONLYOFFICE_PROXIES_INSTALLED__ = true;
+ scheduleNamedDownloadPatch(win, server);
+}
+
+export const REPORTER_HTML = "index.reporter.html";
+
+export type ReporterBridge = {
+ install: (target: Window) => void;
+};
+
+export type ReporterHookWindow = Window & {
+ open: typeof window.open;
+ __ONLYOFFICE_REPORTER_HOOK__?: boolean;
+ __ONLYOFFICE_REPORTER_BRIDGE__?: ReporterBridge;
+};
+
+export function installReporterWindowHook(
+ win: ReporterHookWindow,
+ installProxies: (target: Window) => void,
+) {
+ if (win.__ONLYOFFICE_REPORTER_HOOK__) {
+ return;
+ }
+
+ win.__ONLYOFFICE_REPORTER_BRIDGE__ = { install: installProxies };
+ win.__ONLYOFFICE_REPORTER_HOOK__ = true;
+
+ const nativeOpen = win.open.bind(win);
+ win.open = function openReporter(
+ url?: string | URL,
+ target?: string,
+ features?: string,
+ ) {
+ const popup = nativeOpen(url, target, features);
+ const href = typeof url === "string" ? url : url?.toString() ?? "";
+
+ if (popup && href.includes(REPORTER_HTML)) {
+ watchReporterWindow(popup, installProxies);
+ }
+
+ return popup;
+ };
+}
+
+function watchReporterWindow(
+ popup: Window,
+ installProxies: (target: Window) => void,
+) {
+ const tryInstall = () => {
+ if (popup.closed) {
+ return true;
+ }
+
+ try {
+ if (popup.location.href.includes(REPORTER_HTML)) {
+ installProxies(popup);
+ return true;
+ }
+ } catch {
+ /**
+ * @description 弹窗仍在导航过程中,继续轮询等待 reporter 页面可访问。
+ */
+ }
+
+ return false;
+ };
+
+ if (tryInstall()) {
+ return;
+ }
+
+ const interval = window.setInterval(() => {
+ if (tryInstall()) {
+ window.clearInterval(interval);
+ }
+ }, 1);
+
+ popup.addEventListener(
+ "load",
+ () => {
+ tryInstall();
+ window.clearInterval(interval);
+ },
+ { once: true },
+ );
+}
+
+
+const BRIDGE_SOURCE = "onlyoffice-bridge";
+
+type BridgeMessage = {
+ source?: string;
+ type?: string;
+ frameEditorId?: string;
+ requestId?: string;
+ command?: string;
+ payload?: Record;
+ result?: unknown;
+ error?: string;
+ event?: string;
+ args?: unknown[];
+ method?: string;
+ url?: string;
+ headers?: Record;
+ body?: string | null;
+ bodyEncoding?: "base64";
+ readOnly?: boolean;
+ status?: number;
+ responseHeaders?: Record;
+ responseBody?: string | null;
+ responseType?: XMLHttpRequestResponseType;
+};
+
+type BridgeSession = {
+ frameEditorId: string;
+ server: EditorServer;
+ createIo: ScopedIoFactory;
+ iframe: HTMLIFrameElement;
+ targetOrigin: string;
+ socket: MockSocket | null;
+ bridgeReady: boolean;
+ handshakeSent: boolean;
+ pendingReadOnly: boolean | null;
+};
+
+const sessions = new Map();
+const pendingRequests = new Map<
+ string,
+ {
+ resolve: (value: unknown) => void;
+ reject: (reason?: unknown) => void;
+ timer: number;
+ }
+>();
+const pendingReadyWaiters = new Map<
+ string,
+ Set<{
+ resolve: (session: BridgeSession) => void;
+ reject: (reason?: unknown) => void;
+ timer: number;
+ }>
+>();
+const editorEventSubscribers = new Map<
+ string,
+ Map void>>
+>();
+let listenerInstalled = false;
+
+function isBridgeMessage(data: unknown): data is BridgeMessage {
+ return (
+ typeof data === "object" &&
+ data !== null &&
+ (data as BridgeMessage).source === BRIDGE_SOURCE
+ );
+}
+
+function getTargetOrigin(iframe: HTMLIFrameElement): string {
+ try {
+ return new URL(iframe.src, window.location.href).origin;
+ } catch {
+ return "*";
+ }
+}
+
+function postToIframe(
+ session: BridgeSession,
+ message: BridgeMessage,
+ targetWindow?: Window | null,
+) {
+ const target = targetWindow ?? session.iframe.contentWindow;
+ if (!target) {
+ return;
+ }
+ target.postMessage(
+ { ...message, source: BRIDGE_SOURCE },
+ session.targetOrigin,
+ );
+}
+
+function getIframeByWindow(source: MessageEventSource | null) {
+ if (!source || !("postMessage" in source)) {
+ return null;
+ }
+
+ const frames = document.querySelectorAll(
+ 'iframe[name="frameEditor"]',
+ );
+ for (const frame of frames) {
+ if (frame.contentWindow === source) {
+ return frame;
+ }
+ }
+
+ return null;
+}
+
+function updateSessionIframe(
+ session: BridgeSession,
+ iframe: HTMLIFrameElement,
+) {
+ if (session.iframe === iframe) {
+ return;
+ }
+
+ detachSocket(session);
+ session.iframe = iframe;
+ session.targetOrigin = getTargetOrigin(iframe);
+ session.bridgeReady = false;
+ session.handshakeSent = false;
+}
+
+function attachSocket(session: BridgeSession) {
+ if (session.socket) {
+ return;
+ }
+
+ const socket = session.createIo();
+ session.socket = socket;
+ session.server.registerSocketTransport(socket);
+
+ const nativeServerEmit = socket.server.emit.bind(socket.server);
+ socket.server.emit = (event: string, ...args: unknown[]) => {
+ postToIframe(session, {
+ type: "socket:event",
+ frameEditorId: session.frameEditorId,
+ event,
+ args,
+ });
+ return nativeServerEmit(event, ...args);
+ };
+}
+
+function detachSocket(session: BridgeSession) {
+ if (!session.socket) {
+ return;
+ }
+ session.server.handleDisconnect({ socket: session.socket });
+ session.socket = null;
+ session.handshakeSent = false;
+}
+
+async function handleHttpRequest(
+ session: BridgeSession,
+ message: BridgeMessage,
+) {
+ const requestId = message.requestId;
+ if (!requestId || !message.url || !message.method) {
+ return;
+ }
+
+ try {
+ const init: RequestInit = {
+ method: message.method,
+ headers: message.headers,
+ };
+ if (message.method !== "GET" && message.method !== "HEAD") {
+ init.body = decodeRequestBody(message);
+ }
+
+ let response = await session.server.handleRequest(
+ new Request(message.url, init),
+ );
+ if (!response) {
+ response = await fetch(message.url, init);
+ }
+
+ const responseHeaders: Record = {};
+ response.headers.forEach((value, key) => {
+ responseHeaders[key] = value;
+ });
+
+ let responseBody: string | null = null;
+ if (message.responseType === "arraybuffer") {
+ const buffer = await response.arrayBuffer();
+ responseBody = arrayBufferToBase64(buffer);
+ } else {
+ responseBody = await response.text();
+ }
+
+ postToIframe(session, {
+ type: "http:response",
+ frameEditorId: session.frameEditorId,
+ requestId,
+ status: response.status,
+ responseHeaders,
+ responseBody,
+ responseType: message.responseType,
+ });
+ } catch (error) {
+ postToIframe(session, {
+ type: "http:response",
+ frameEditorId: session.frameEditorId,
+ requestId,
+ status: 0,
+ responseBody: null,
+ });
+ console.error("[OnlyOfficeBridge] HTTP proxy failed:", error);
+ }
+}
+
+function arrayBufferToBase64(buffer: ArrayBuffer): string {
+ const bytes = new Uint8Array(buffer);
+ let binary = "";
+ for (let i = 0; i < bytes.length; i++) {
+ binary += String.fromCharCode(bytes[i]!);
+ }
+ return btoa(binary);
+}
+
+function base64ToArrayBuffer(base64: string): ArrayBuffer {
+ const binary = atob(base64);
+ const bytes = new Uint8Array(binary.length);
+ for (let i = 0; i < binary.length; i++) {
+ bytes[i] = binary.charCodeAt(i);
+ }
+ return bytes.buffer;
+}
+
+function decodeRequestBody(
+ message: BridgeMessage,
+): BodyInit | null | undefined {
+ if (message.body == null) {
+ return null;
+ }
+ if (message.bodyEncoding === "base64") {
+ return base64ToArrayBuffer(message.body);
+ }
+ return message.body;
+}
+
+function describeBridgeState(frameEditorId: string) {
+ const session = sessions.get(frameEditorId);
+ if (!session) {
+ return `OnlyOffice cross-origin bridge is not ready for ${frameEditorId}: session is not registered`;
+ }
+
+ return `OnlyOffice cross-origin bridge is not ready for ${frameEditorId}: iframe=${session.iframe.src || "(empty)"}`;
+}
+
+function resolveReadyWaiters(frameEditorId: string, session: BridgeSession) {
+ const waiters = pendingReadyWaiters.get(frameEditorId);
+ if (!waiters) {
+ return;
+ }
+
+ pendingReadyWaiters.delete(frameEditorId);
+ waiters.forEach((waiter) => {
+ window.clearTimeout(waiter.timer);
+ waiter.resolve(session);
+ });
+}
+
+function rejectReadyWaiters(frameEditorId: string, error: Error) {
+ const waiters = pendingReadyWaiters.get(frameEditorId);
+ if (!waiters) {
+ return;
+ }
+
+ pendingReadyWaiters.delete(frameEditorId);
+ waiters.forEach((waiter) => {
+ window.clearTimeout(waiter.timer);
+ waiter.reject(error);
+ });
+}
+
+function waitForBridgeReady(frameEditorId: string, timeout: number) {
+ const session = sessions.get(frameEditorId);
+ if (session?.bridgeReady) {
+ return Promise.resolve(session);
+ }
+
+ return new Promise((resolve, reject) => {
+ const timer = window.setTimeout(() => {
+ const waiters = pendingReadyWaiters.get(frameEditorId);
+ waiters?.delete(waiter);
+ if (waiters?.size === 0) {
+ pendingReadyWaiters.delete(frameEditorId);
+ }
+ reject(new Error(describeBridgeState(frameEditorId)));
+ }, timeout);
+ const waiter = { resolve, reject, timer };
+
+ let waiters = pendingReadyWaiters.get(frameEditorId);
+ if (!waiters) {
+ waiters = new Set();
+ pendingReadyWaiters.set(frameEditorId, waiters);
+ }
+ waiters.add(waiter);
+ });
+}
+
+function handleBridgeMessage(event: MessageEvent) {
+ if (!isBridgeMessage(event.data)) {
+ return;
+ }
+
+ const message = event.data;
+ if (
+ message.type === CROSS_ORIGIN_BRIDGE_MESSAGE.EDITOR_RESPONSE &&
+ message.requestId
+ ) {
+ const pending = pendingRequests.get(message.requestId);
+ if (pending) {
+ window.clearTimeout(pending.timer);
+ pendingRequests.delete(message.requestId);
+ if (message.error) {
+ pending.reject(new Error(message.error));
+ } else {
+ pending.resolve(message.result);
+ }
+ }
+ return;
+ }
+
+ if (
+ message.type === CROSS_ORIGIN_BRIDGE_MESSAGE.EDITOR_EVENT &&
+ message.event
+ ) {
+ const frameEditorId = message.frameEditorId;
+ const frameSubscribers = frameEditorId
+ ? editorEventSubscribers.get(frameEditorId)
+ : undefined;
+ const subscribers = frameSubscribers?.get(message.event);
+ subscribers?.forEach((handler) => {
+ handler(message.args ?? []);
+ });
+ return;
+ }
+
+ const frameEditorId = message.frameEditorId;
+ if (!frameEditorId) {
+ return;
+ }
+
+ const session = sessions.get(frameEditorId);
+ if (!session) {
+ return;
+ }
+
+ const sourceIframe = getIframeByWindow(event.source);
+ if (sourceIframe) {
+ updateSessionIframe(session, sourceIframe);
+ }
+
+ switch (message.type) {
+ case "hello": {
+ session.bridgeReady = true;
+ resolveReadyWaiters(frameEditorId, session);
+ attachSocket(session);
+ const targetWindow =
+ event.source && "postMessage" in event.source
+ ? (event.source as Window)
+ : undefined;
+ postToIframe(session, { type: "hello:ack", frameEditorId }, targetWindow);
+ if (!session.handshakeSent) {
+ session.handshakeSent = true;
+ setTimeout(() => {
+ if (session.socket) {
+ session.server.sendCoAuthoringHandshake(session.socket);
+ }
+ }, 0);
+ }
+ if (session.pendingReadOnly !== null) {
+ postToIframe(session, {
+ type: CROSS_ORIGIN_BRIDGE_MESSAGE.EDITOR_SET_READONLY,
+ frameEditorId,
+ readOnly: session.pendingReadOnly,
+ });
+ session.pendingReadOnly = null;
+ }
+ break;
+ }
+ case "socket:emit": {
+ if (!session.socket || !message.event) {
+ break;
+ }
+ (
+ session.socket as { emit: (event: string, ...args: unknown[]) => void }
+ ).emit(message.event, ...(message.args ?? []));
+ break;
+ }
+ case "http": {
+ void handleHttpRequest(session, message);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+export function registerCrossOriginBridge(
+ frameEditorId: string,
+ iframe: HTMLIFrameElement,
+ server: EditorServer,
+ createIo: ScopedIoFactory,
+) {
+ const existing = sessions.get(frameEditorId);
+ if (existing) {
+ existing.server = server;
+ existing.createIo = createIo;
+ updateSessionIframe(existing, iframe);
+ return;
+ }
+
+ sessions.set(frameEditorId, {
+ frameEditorId,
+ server,
+ createIo,
+ iframe,
+ targetOrigin: getTargetOrigin(iframe),
+ socket: null,
+ bridgeReady: false,
+ handshakeSent: false,
+ pendingReadOnly: null,
+ });
+
+ if (!listenerInstalled) {
+ window.addEventListener("message", handleBridgeMessage);
+ listenerInstalled = true;
+ }
+}
+
+export function unregisterCrossOriginBridge(frameEditorId: string) {
+ const session = sessions.get(frameEditorId);
+ if (session) {
+ detachSocket(session);
+ sessions.delete(frameEditorId);
+ rejectReadyWaiters(
+ frameEditorId,
+ new Error(
+ `OnlyOffice cross-origin bridge was unregistered: ${frameEditorId}`,
+ ),
+ );
+ }
+ editorEventSubscribers.delete(frameEditorId);
+}
+
+export function setCrossOriginReadOnly(
+ frameEditorId: string,
+ readOnly: boolean,
+) {
+ const session = sessions.get(frameEditorId);
+ if (!session) {
+ return false;
+ }
+
+ session.pendingReadOnly = readOnly;
+ if (!session.bridgeReady) {
+ return true;
+ }
+
+ postToIframe(session, {
+ type: CROSS_ORIGIN_BRIDGE_MESSAGE.EDITOR_SET_READONLY,
+ frameEditorId,
+ readOnly,
+ });
+ session.pendingReadOnly = null;
+ return true;
+}
+
+export function callCrossOriginEditor(
+ frameEditorId: string,
+ command: string,
+ payload: Record = {},
+ timeout = 5000,
+) {
+ const requestId = `editor-${Date.now()}-${Math.random()
+ .toString(36)
+ .slice(2)}`;
+
+ return waitForBridgeReady(frameEditorId, timeout).then(
+ (session) =>
+ new Promise((resolve, reject) => {
+ const timer = window.setTimeout(() => {
+ pendingRequests.delete(requestId);
+ reject(
+ new Error(`OnlyOffice cross-origin command timed out: ${command}`),
+ );
+ }, timeout);
+
+ pendingRequests.set(requestId, { resolve, reject, timer });
+ postToIframe(session, {
+ type: CROSS_ORIGIN_BRIDGE_MESSAGE.EDITOR_COMMAND,
+ frameEditorId,
+ requestId,
+ command,
+ payload,
+ });
+ }),
+ );
+}
+
+export function subscribeCrossOriginEditorEvent(
+ frameEditorId: string,
+ event: string,
+ handler: (args: unknown[]) => void,
+) {
+ let frameSubscribers = editorEventSubscribers.get(frameEditorId);
+ if (!frameSubscribers) {
+ frameSubscribers = new Map();
+ editorEventSubscribers.set(frameEditorId, frameSubscribers);
+ }
+
+ let subscribers = frameSubscribers.get(event);
+ if (!subscribers) {
+ subscribers = new Set();
+ frameSubscribers.set(event, subscribers);
+ }
+
+ subscribers.add(handler);
+
+ return () => {
+ subscribers?.delete(handler);
+ if (subscribers?.size === 0) {
+ frameSubscribers?.delete(event);
+ }
+ if (frameSubscribers?.size === 0) {
+ editorEventSubscribers.delete(frameEditorId);
+ }
+ };
+}
+
+export function canAccessIframeWindow(
+ iframe: HTMLIFrameElement | null | undefined,
+) {
+ if (!iframe) {
+ return false;
+ }
+ try {
+ void iframe.contentWindow?.location.href;
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+export function watchCrossOriginIframe(
+ frameEditorId: string,
+ getIframe: () => HTMLIFrameElement | null | undefined,
+ server: EditorServer,
+ createIo: ScopedIoFactory,
+) {
+ let registered = false;
+
+ const tryRegister = () => {
+ const iframe = getIframe();
+ if (!iframe?.src || canAccessIframeWindow(iframe)) {
+ return false;
+ }
+ if (registered) {
+ registerCrossOriginBridge(frameEditorId, iframe, server, createIo);
+ return true;
+ }
+ registered = true;
+ registerCrossOriginBridge(frameEditorId, iframe, server, createIo);
+ return true;
+ };
+
+ tryRegister();
+
+ const timer = window.setInterval(() => {
+ if (tryRegister()) {
+ window.clearInterval(timer);
+ }
+ }, 10);
+
+ const observer = new MutationObserver(() => {
+ tryRegister();
+ });
+ observer.observe(document.body, { childList: true, subtree: true });
+
+ return () => {
+ window.clearInterval(timer);
+ observer.disconnect();
+ if (registered) {
+ unregisterCrossOriginBridge(frameEditorId);
+ }
+ };
+}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/server.ts b/src/components/onlyoffice-web-comp/internal/editor/server.ts
index 963b2afe..98c95abd 100644
--- a/src/components/onlyoffice-web-comp/internal/editor/server.ts
+++ b/src/components/onlyoffice-web-comp/internal/editor/server.ts
@@ -1,5 +1,5 @@
import { converter } from "./x2t";
-import { MockSocket } from "./socket";
+import { MockSocket } from "./runtime-bridge";
import {
User,
Participant,
@@ -9,8 +9,8 @@ import {
isOfficeXmlSizeLimitExceededError,
} from "./types";
import { emptyDocx, emptyPdf, emptyPptx, emptyXlsx } from "./empty";
-import { convertCsvBufferToXlsxBuffer } from "./csv-to-xlsx";
-import { createDocxFromText, createXlsxFromText } from "./plain-text-office";
+import { convertCsvBufferToXlsxBuffer } from "./office-format";
+import { createDocxFromText, createXlsxFromText } from "./office-format";
import {
getDocumentType,
getFileExt,
@@ -30,28 +30,21 @@ import {
isOnlyOfficeCdnMode,
type OfficeXmlEventConfig,
} from "../../const";
-import { allPlugins, featuredPlugins, getPluginsData } from "./plugins";
import {
getZipXmlUncompressedSize,
readZipEntries,
readZipEntryData,
writeZipEntries,
type ZipReplacement,
-} from "./zip";
+} from "./office-format";
+import type { EditorLogCategory, EditorLogLevel } from "./logger";
/**
- * Mock OnlyOffice 协作服务:维护 fsMap(Editor.bin + media),处理 WebSocket 与 /downloadas/ HTTP。
- *
- * 关键链路:
- * 打开 — loadDocument:x2t doc.* → Editor.bin
- * 导出 — captureCurrentDocument + downloadAs → /downloadas/ → resolvePendingExport
- * 保存 — 同 URL 无 pendingExport 时 commitUserSave(UI 已禁用,兜底保留)
+ * @description Mock OnlyOffice 协作服务,维护 Editor.bin、媒体资源和 downloadAs HTTP 管道。
*/
/**
- * programmatic export(downloadAs "bin")完成时 WebSocket save 的占位 URL。
- * SDK 要求 data 为 truthy 才会标记成功;空字符串会误触发 asc_onError(DirectUrl)。
- * fVg=true 时走 asc_onDownloadUrl,父页 onDownloadAs 为空实现,不会触发浏览器下载。
+ * @description 程序化导出完成时返回给 WebSocket save 的占位 URL,避免触发真实浏览器下载。
*/
const PROGRAMMATIC_EXPORT_ACK_URL = "onlyoffice://export/ack";
function mergeBuffers(buffers: Uint8Array[]) {
@@ -65,7 +58,9 @@ function mergeBuffers(buffers: Uint8Array[]) {
return mergedBuffer;
}
-/** OnlyOffice 画布 bin 魔数;x2t 只接受 XLSY/DOCY/PPTY,非法数据会导致导出失败。 */
+/**
+ * @description OnlyOffice 画布 bin 魔数;x2t 只接受 XLSY/DOCY/PPTY,非法数据会导致导出失败。
+ */
function isValidEditorBin(data: Uint8Array) {
if (data.length < 4) {
return false;
@@ -117,7 +112,6 @@ function createPlainTextOfficeFallback(
}
}
-const PDF_MAX_OUTPUT_BYTES = 50 * 1024 * 1024;
const PDF_EOF_MARKER = new TextEncoder().encode("%%EOF");
function indexOfSubarray(haystack: Uint8Array, needle: Uint8Array) {
@@ -139,11 +133,6 @@ function assertValidPdfOutput(data: Uint8Array, label = "PDF") {
if (!isPdfBytes(data)) {
throw new Error(`${label} output is not a valid PDF`);
}
- if (data.byteLength > PDF_MAX_OUTPUT_BYTES) {
- throw new Error(
- `${label} output too large (${data.byteLength} bytes); x2t conversion likely corrupt`,
- );
- }
const scanLen = Math.min(data.byteLength, 1024 * 1024);
const tail = data.subarray(data.byteLength - scanLen);
if (indexOfSubarray(tail, PDF_EOF_MARKER) < 0) {
@@ -389,7 +378,9 @@ function rewriteEditorBinUnsupportedMediaRefs(data: Uint8Array) {
return output;
}
-/** Response 头值须为 ISO-8859-1;中文文件名放在 filename*=UTF-8 段。 */
+/**
+ * @description Response 头值须为 ISO-8859-1,中文文件名放在 filename*=UTF-8 段。
+ */
function buildContentDisposition(fileName: string): string {
const encoded = encodeURIComponent(fileName).replace(/['()]/g, escape);
const ascii = fileName.replace(/[^\x20-\x7E]/g, "_").replace(/"/g, '\\"');
@@ -536,6 +527,366 @@ function getImageExt(mime: string) {
return subtype === "jpeg" ? "jpg" : subtype;
}
+function asRecord(value: unknown): Record | null {
+ return typeof value === "object" && value !== null
+ ? (value as Record)
+ : null;
+}
+
+function getStringField(record: Record | null, key: string) {
+ const value = record?.[key];
+ return typeof value === "string" ? value : undefined;
+}
+
+function getNumberField(record: Record | null, key: string) {
+ const value = record?.[key];
+ return typeof value === "number" ? value : undefined;
+}
+
+function getBooleanField(record: Record | null, key: string) {
+ const value = record?.[key];
+ return typeof value === "boolean" ? value : undefined;
+}
+
+function truncateText(value: string, maxLength = 80) {
+ return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
+}
+
+function tryParseJson(value: string): unknown {
+ const trimmed = value.trim();
+ if (!trimmed || !/^[{[]/.test(trimmed)) {
+ return value;
+ }
+ try {
+ return JSON.parse(trimmed);
+ } catch {
+ return value;
+ }
+}
+
+function getArrayField(record: Record | null, key: string) {
+ const value = record?.[key];
+ return Array.isArray(value) ? value : undefined;
+}
+
+function getJsonArrayField(record: Record | null, key: string) {
+ const value = record?.[key];
+ if (Array.isArray(value)) {
+ return value;
+ }
+ if (typeof value !== "string") {
+ return undefined;
+ }
+ const parsed = tryParseJson(value);
+ return Array.isArray(parsed) ? parsed : undefined;
+}
+
+function getPayloadByteLength(value: unknown): number | undefined {
+ if (value instanceof ArrayBuffer) {
+ return value.byteLength;
+ }
+ if (ArrayBuffer.isView(value)) {
+ return value.byteLength;
+ }
+ if (typeof value === "string") {
+ return value.length;
+ }
+ return undefined;
+}
+
+function describeValue(value: unknown, maxLength = 120): string | undefined {
+ if (value == null) {
+ return undefined;
+ }
+ if (typeof value === "string") {
+ return truncateText(value, maxLength);
+ }
+ if (typeof value === "number" || typeof value === "boolean") {
+ return String(value);
+ }
+ if (Array.isArray(value)) {
+ return `array(${value.length})`;
+ }
+ if (typeof value === "object") {
+ const record = value as Record;
+ const keys = Object.keys(record).slice(0, 6);
+ return `{${keys.join(", ")}}`;
+ }
+ return typeof value;
+}
+
+function collectTextFragments(
+ value: unknown,
+ fragments: string[] = [],
+ depth = 0,
+) {
+ if (fragments.length >= 3 || depth > 4 || value == null) {
+ return fragments;
+ }
+
+ if (typeof value === "string") {
+ const parsed = tryParseJson(value);
+ if (parsed !== value) {
+ return collectTextFragments(parsed, fragments, depth + 1);
+ }
+ const text = value.trim();
+ if (
+ text.length > 0 &&
+ text.length <= 200 &&
+ /[\p{L}\p{N}\u4e00-\u9fff]/u.test(text)
+ ) {
+ fragments.push(truncateText(text));
+ }
+ return fragments;
+ }
+
+ if (Array.isArray(value)) {
+ for (const item of value.slice(0, 8)) {
+ collectTextFragments(item, fragments, depth + 1);
+ if (fragments.length >= 3) break;
+ }
+ return fragments;
+ }
+
+ if (typeof value === "object") {
+ const record = value as Record;
+ const preferredKeys = [
+ "text",
+ "Text",
+ "value",
+ "insert",
+ "m_sText",
+ "data",
+ "items",
+ ];
+ const keys = [
+ ...preferredKeys.filter((key) => key in record),
+ ...Object.keys(record).filter((key) => !preferredKeys.includes(key)),
+ ];
+ for (const key of keys.slice(0, 12)) {
+ collectTextFragments(record[key], fragments, depth + 1);
+ if (fragments.length >= 3) break;
+ }
+ }
+
+ return fragments;
+}
+
+function summarizeLockBlock(block: unknown) {
+ const blocks = normalizeCoAuthoringLockBlocks(block);
+ return {
+ lockCount: blocks.length,
+ locks: blocks.slice(0, 3).map((item) => {
+ if (typeof item === "object" && item !== null) {
+ const record = item as Record;
+ return {
+ guid: getStringField(record, "guid"),
+ sheetId: record.sheetId ?? record.sheet ?? record.tabId,
+ range: record.range ?? record.bbox ?? record.bounds,
+ raw: describeValue(record),
+ };
+ }
+ return { raw: describeValue(item) };
+ }),
+ };
+}
+
+function getBase64ByteLength(value: string) {
+ const normalized = value.replace(/\s/g, "");
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(normalized)) {
+ return undefined;
+ }
+ const padding = normalized.endsWith("==") ? 2 : normalized.endsWith("=") ? 1 : 0;
+ return Math.floor((normalized.length * 3) / 4) - padding;
+}
+
+function summarizeChangePacket(value: unknown) {
+ if (typeof value !== "string") {
+ return {
+ kind: typeof value,
+ byteLength: getPayloadByteLength(value),
+ preview: describeValue(value),
+ };
+ }
+
+ const separatorIndex = value.indexOf(";");
+ if (separatorIndex < 0) {
+ return {
+ kind: "string",
+ charLength: value.length,
+ preview: truncateText(value),
+ };
+ }
+
+ const declaredLength = Number(value.slice(0, separatorIndex));
+ const base64 = value.slice(separatorIndex + 1);
+ const byteLength = getBase64ByteLength(base64);
+
+ return {
+ kind: "binary-change",
+ declaredLength: Number.isFinite(declaredLength) ? declaredLength : undefined,
+ byteLength,
+ byteLengthMatches:
+ Number.isFinite(declaredLength) && byteLength != null
+ ? declaredLength === byteLength
+ : undefined,
+ base64Preview: truncateText(base64, 32),
+ };
+}
+
+function summarizeSaveChanges(record: Record) {
+ const changes =
+ getJsonArrayField(record, "changes") ??
+ getJsonArrayField(record, "changesData") ??
+ getJsonArrayField(record, "data");
+ const firstChange = changes?.[0] ?? record.change ?? record.data;
+ const parsedFirstChange =
+ typeof firstChange === "string" ? tryParseJson(firstChange) : firstChange;
+ const textPreview = collectTextFragments(changes ?? firstChange);
+ const packets = changes?.map(summarizeChangePacket);
+ const changeBytes =
+ packets?.reduce((total, packet) => total + (packet.byteLength ?? 0), 0) ??
+ getPayloadByteLength(firstChange);
+
+ return {
+ start: getBooleanField(record, "startSaveChanges"),
+ end: getBooleanField(record, "endSaveChanges"),
+ isCoAuthoring: getBooleanField(record, "isCoAuthoring"),
+ isExcel: getBooleanField(record, "isExcel"),
+ deleteIndex: getNumberField(record, "deleteIndex"),
+ unlock: getBooleanField(record, "unlock"),
+ releaseLocks: getBooleanField(record, "releaseLocks"),
+ changesCount: changes?.length,
+ changeBytes,
+ packets: packets?.slice(0, 4),
+ firstChange: describeValue(parsedFirstChange),
+ textPreview,
+ };
+}
+
+function getSaveTypeLabel(value: unknown) {
+ switch (value) {
+ case AscSaveTypes.PartStart:
+ return "PartStart";
+ case AscSaveTypes.Part:
+ return "Part";
+ case AscSaveTypes.Complete:
+ return "Complete";
+ case AscSaveTypes.CompleteAll:
+ return "CompleteAll";
+ default:
+ return value == null ? undefined : String(value);
+ }
+}
+
+function summarizeWsPayload(payloads: unknown[]) {
+ const first = payloads[0];
+ const record = asRecord(first);
+ const data = asRecord(record?.data);
+ const type = getStringField(record, "type");
+ const dataType = getStringField(data, "type");
+ const extraParts: string[] = [];
+ let extra: Record = {};
+
+ if (type === "saveChanges" && record) {
+ const saveSummary = summarizeSaveChanges(record);
+ extra = saveSummary;
+ if (saveSummary.changesCount != null) {
+ extraParts.push(`changes=${saveSummary.changesCount}`);
+ }
+ if (saveSummary.changeBytes != null) {
+ extraParts.push(`bytes=${saveSummary.changeBytes}`);
+ }
+ if (saveSummary.start || saveSummary.end) {
+ extraParts.push(
+ `${saveSummary.start ? "start" : ""}${saveSummary.end ? "+end" : ""}`,
+ );
+ }
+ if (saveSummary.isExcel) {
+ extraParts.push("excel");
+ }
+ if (saveSummary.deleteIndex != null) {
+ extraParts.push(`deleteIndex=${saveSummary.deleteIndex}`);
+ }
+ if (saveSummary.textPreview.length > 0) {
+ extraParts.push(`text="${saveSummary.textPreview.join(" | ")}"`);
+ }
+ } else if (type === "getLock" && record) {
+ const lockSummary = summarizeLockBlock(record.block);
+ extra = lockSummary;
+ extraParts.push(`locks=${lockSummary.lockCount}`);
+ const firstLock = lockSummary.locks[0];
+ if (firstLock?.guid) {
+ extraParts.push(`guid=${firstLock.guid}`);
+ } else if (firstLock?.raw) {
+ extraParts.push(`block=${firstLock.raw}`);
+ }
+ } else if (
+ (type === "getLock" || type === "releaseLock") &&
+ record?.locks &&
+ typeof record.locks === "object"
+ ) {
+ const lockKeys = Object.keys(record.locks as Record);
+ extra = { lockCount: lockKeys.length, lockKeys: lockKeys.slice(0, 3) };
+ extraParts.push(`locks=${lockKeys.length}`);
+ if (lockKeys[0]) extraParts.push(`key=${truncateText(lockKeys[0], 32)}`);
+ } else if (type === "unSaveLock" && record) {
+ const index = getNumberField(record, "index");
+ const syncChangesIndex = getNumberField(record, "syncChangesIndex");
+ extra = { index, syncChangesIndex };
+ if (index != null) extraParts.push(`index=${index}`);
+ if (syncChangesIndex != null) {
+ extraParts.push(`sync=${syncChangesIndex}`);
+ }
+ }
+
+ const labelParts = [
+ type ?? typeof first,
+ dataType,
+ getStringField(data, "status"),
+ ].filter(Boolean);
+ const label = [
+ labelParts.join(" / "),
+ extraParts.length > 0 ? `(${extraParts.join(", ")})` : "",
+ ]
+ .filter(Boolean)
+ .join(" ");
+
+ return {
+ label,
+ type,
+ dataType,
+ status: getStringField(data, "status"),
+ payloadCount: payloads.length,
+ ...extra,
+ };
+}
+
+function summarizeDownloadCommand(
+ cmd: Record,
+ buffer: ArrayBuffer,
+) {
+ const outputFormat = getNumberField(cmd, "outputformat");
+ const filetype =
+ outputFormat == null ? undefined : extensionFromOutputFormat(outputFormat);
+ const saveType = getSaveTypeLabel(cmd.savetype);
+
+ return {
+ label: [
+ saveType,
+ filetype ? `to ${filetype}` : undefined,
+ cmd.isSaveAs ? "saveAs" : undefined,
+ ]
+ .filter(Boolean)
+ .join(" "),
+ saveType,
+ outputFormat,
+ filetype,
+ title: getStringField(cmd, "title"),
+ byteLength: buffer.byteLength,
+ };
+}
+
export class EditorServer {
private id = "";
private sockets = new Set();
@@ -557,23 +908,32 @@ export class EditorServer {
private loadPromise: Promise | null = null;
private loadBlocked = false;
- private file: File | null = null;
private fileType: string = "docx";
private title: string = "";
private fsMap: Map = new Map();
private urlsMap: Map = new Map();
private downloadId: string = "";
- /** downloadAs multipart 分片缓冲;保存与导出共用 HTTP 管道,需与 pendingExport 配合区分意图。 */
+ /**
+ * @description downloadAs multipart 分片缓冲;保存与导出共用 HTTP 管道,需与 pendingExport 配合区分意图。
+ */
private downloadParts: Uint8Array[] = [];
- /** 当前 downloadAs 请求的 cmd(含 outputformat / isSaveAs / title)。 */
+ /**
+ * @description 当前 downloadAs 请求的 cmd(含 outputformat / isSaveAs / title)。
+ */
private downloadCmd: Record | null = null;
- /** 另存为 GET 响应 Content-Disposition 使用的文件名。 */
+ /**
+ * @description 另存为 GET 响应 Content-Disposition 使用的文件名。
+ */
private downloadFileNames = new Map();
- /** 用户保存 downloadAs 进行中时阻塞 export,避免分片交错污染 Editor.bin。 */
+ /**
+ * @description 用户保存 downloadAs 进行中时阻塞 export,避免分片交错污染 Editor.bin。
+ */
private savingDone: Promise = Promise.resolve();
private finishSaving: (() => void) | null = null;
- /** export() 调用 downloadAs("bin") 后等待 resolvePendingExport 完成。 */
+ /**
+ * @description export() 调用 downloadAs("bin") 后等待 resolvePendingExport 完成。
+ */
private pendingExport:
| {
resolve: (snapshot: ReturnType) => void;
@@ -593,6 +953,21 @@ export class EditorServer {
this.handleMessage = this.handleMessage.bind(this);
}
+ private logRaw(
+ level: EditorLogLevel,
+ category: EditorLogCategory,
+ message: string,
+ consoleArgs: unknown[],
+ ...details: unknown[]
+ ) {
+ const logger = this.options.logger;
+ if (logger) {
+ logger.raw(level, category, message, consoleArgs, ...details);
+ return;
+ }
+ console[level](...consoleArgs);
+ }
+
setOfficeXmlEventConfig(config?: OfficeXmlEventConfig) {
this.officeXmlEventConfig = {
...OFFICE_XML_EVENT_CONFIG.default,
@@ -623,7 +998,9 @@ export class EditorServer {
}
}
- /** CDN iframe 与主站跨域:Editor.bin 必须用相对 /cache/files/,由 bridge 代拉。 */
+ /**
+ * @description CDN iframe 与主站跨域:Editor.bin 必须用相对 /cache/files/,由 bridge 代拉。
+ */
private getCacheFileUrl(name: string) {
return `/cache/files/data/${this.id}/${name}`;
}
@@ -658,7 +1035,6 @@ export class EditorServer {
}
this.id = "";
- this.file = null;
this.fileType = "docx";
this.title = "";
this.fsMap.clear();
@@ -682,7 +1058,6 @@ export class EditorServer {
const title = ensureTitleWithExtension(fileName || file.name, this.fileType);
const documentType = getDocumentType(this.fileType);
this.id = randomId();
- this.file = file;
this.title = title;
const buffer = await file.arrayBuffer();
const sizeLimitError = this.getOfficeXmlSizeLimitError(
@@ -710,7 +1085,6 @@ export class EditorServer {
openNew(fileType?: string) {
this.fileType = fileType || "docx";
this.id = randomId();
- this.file = null;
this.loadPromise = null;
this.title = ensureTitleWithExtension("New Document", this.fileType);
const documentType = getDocumentType(this.fileType);
@@ -786,7 +1160,9 @@ export class EditorServer {
};
}
- /** 另存为产物 blob URL,cache GET 失败时供 getFile 回退下载。 */
+ /**
+ * @description 另存为产物 blob URL,cache GET 失败时供 getFile 回退下载。
+ */
getStoredOutputUrl(outputName: string) {
return this.urlsMap.get(outputName) ?? null;
}
@@ -862,7 +1238,9 @@ export class EditorServer {
return rewriteEditorBinUnsupportedMediaRefs(binData);
}
- /** 用户保存:更新 Editor.bin 并通知接入层,不触发浏览器下载。 */
+ /**
+ * @description 用户保存:更新 Editor.bin 并通知接入层,不触发浏览器下载。
+ */
commitUserSave(data: Uint8Array) {
if (!isValidEditorBin(data)) {
console.warn(
@@ -891,9 +1269,7 @@ export class EditorServer {
}
/**
- * 导出链路:register pendingExport → trigger downloadAs("bin")
- * → iframe XHR/fetch 命中 /downloadas/ → resolvePendingExport 写入 Editor.bin。
- * 开始前 await savingDone 并清空 downloadParts,避免与保存分片冲突。
+ * @description 捕获当前文档快照;通过 downloadAs("bin") 让 iframe 把最新 Editor.bin 回传到 mock 服务。
*/
async captureCurrentDocument(
trigger: () => void,
@@ -931,7 +1307,9 @@ export class EditorServer {
});
}
- /** 打开文档:x2t 将 doc.{fileType} 转为 Editor.bin 写入 fsMap,供 iframe 加载。 */
+ /**
+ * @description 打开文档:x2t 将 doc.{fileType} 转为 Editor.bin 写入 fsMap,供 iframe 加载。
+ */
private async loadDocument(
buffer: ArrayBuffer | (() => Promise),
fileType: string,
@@ -986,13 +1364,16 @@ export class EditorServer {
this.assertOfficeXmlSizeWithinLimit(buffer, fileType);
buffer = await this.rewriteUnsupportedPptxImages(buffer, fileType);
const { formatFrom, formatTo } = getX2tConvertFormats(fileType);
- const result = await converter.convert({
- data: buffer,
- fileFrom: "doc." + fileType,
- fileTo: "Editor.bin",
- formatFrom,
- formatTo,
- });
+ const result = await converter.convert(
+ {
+ data: buffer,
+ fileFrom: "doc." + fileType,
+ fileTo: "Editor.bin",
+ formatFrom,
+ formatTo,
+ },
+ this.options.logger,
+ );
return {
output: result.output,
media: result.media,
@@ -1022,7 +1403,10 @@ export class EditorServer {
}
const { totalSize, entryCount } = getZipXmlUncompressedSize(entries);
- console.log("onlyoffice-totalSize:", `${(totalSize / 1024 / 1024).toFixed(2)} MB`);
+ this.logRaw("log", "operation", "office xml total size", [
+ "onlyoffice-totalSize:",
+ `${(totalSize / 1024 / 1024).toFixed(2)} MB`,
+ ]);
if (totalSize <= this.officeXmlEventConfig.limitBytes) {
return null;
}
@@ -1150,14 +1534,17 @@ export class EditorServer {
const { formatFrom, formatTo } = getX2tConvertFormats("csv");
try {
- const result = await converter.convert({
- data: convertBuffer,
- fileFrom: "doc.csv",
- fileTo: "Editor.bin",
- formatFrom,
- formatTo,
- ...getX2tCsvConvertOptions(convertBuffer),
- });
+ const result = await converter.convert(
+ {
+ data: convertBuffer,
+ fileFrom: "doc.csv",
+ fileTo: "Editor.bin",
+ formatFrom,
+ formatTo,
+ ...getX2tCsvConvertOptions(convertBuffer),
+ },
+ this.options.logger,
+ );
if (result.output?.byteLength) {
return { output: result.output, media: result.media };
}
@@ -1185,13 +1572,17 @@ export class EditorServer {
this.setEditorBinUrl(data);
}
- /** 与 Document Server 一致:/cache/files/data/{id}/output.{ext}(站点根绝对路径,便于 iframe 代理命中) */
+ /**
+ * @description 与 Document Server 一致:/cache/files/data/{id}/output.{ext}(站点根绝对路径,便于 iframe 代理命中)
+ */
private buildDownloadFileUrl(outputName: string, downloadFileName: string) {
const params = new URLSearchParams({ filename: downloadFileName });
return `/cache/files/data/${this.id}/${outputName}?${params}`;
}
- /** 另存为:写入 fsMap,返回带文件名的 HTTP 路径(非 blob URL)。 */
+ /**
+ * @description 另存为:写入 fsMap,返回带文件名的 HTTP 路径(非 blob URL)。
+ */
private storeDownloadOutput(
outputName: string,
data: Uint8Array,
@@ -1207,8 +1598,7 @@ export class EditorServer {
}
/**
- * 「文件 → 另存为」常 POST Editor.bin + cmd.outputformat,由服务端 x2t 转换。
- * 仅 isSaveAs 或非法 bin 魔数不足以覆盖该路径。
+ * @description 判断 downloadAs 请求是否属于「另存为」输出;该路径通常需要服务端 x2t 转换。
*/
private isDownloadAsOutput(
cmd: Record | null,
@@ -1236,15 +1626,18 @@ export class EditorServer {
const media = this.getExportSafeMedia();
const themes = this.getStoredThemes();
const { formatFrom, formatTo } = getX2tExportFormats(filetype, this.fileType);
- const result = await converter.convert({
- data: binData.slice().buffer,
- fileFrom: "Editor.bin",
- fileTo: `doc.${filetype}`,
- formatFrom,
- formatTo,
- media,
- themes,
- });
+ const result = await converter.convert(
+ {
+ data: binData.slice().buffer,
+ fileFrom: "Editor.bin",
+ fileTo: `doc.${filetype}`,
+ formatFrom,
+ formatTo,
+ media,
+ themes,
+ },
+ this.options.logger,
+ );
if (!result.output?.byteLength) {
throw new Error(`Failed to convert Editor.bin to ${filetype}`);
@@ -1253,13 +1646,17 @@ export class EditorServer {
return result.output;
}
- /** Web 表格 PDF 另存为 POST 的是渲染器 Memory 流,需回退 fsMap 中的 Editor.bin。 */
+ /**
+ * @description Web 表格 PDF 另存为 POST 的是渲染器 Memory 流,需回退 fsMap 中的 Editor.bin。
+ */
private resolveEditorBinSource(input: Uint8Array): Uint8Array | null {
if (isValidEditorBin(input)) {
return input;
}
- // 禁止在 downloadAs 中调用 asc_nativeGetFile(getFreshEditorBin):SDK 等 HTTP 响应时会死锁。
+ /**
+ * @description downloadAs 等 HTTP 响应时不能调用 asc_nativeGetFile,否则 SDK 会互相等待。
+ */
const cached = this.fsMap.get("Editor.bin");
if (cached && isValidEditorBin(cached)) {
return cached;
@@ -1268,8 +1665,7 @@ export class EditorServer {
}
/**
- * Editor.bin + pdf.bin(Web SDK Memory 流)→ PDF。
- * WASM x2t 无 JS 引擎,表格/文档 PDF 另存为必须走此路径(CryptPad 同款)。
+ * @description 将 Editor.bin 和 Web SDK 渲染器 Memory 流组合导出为 PDF。
*/
private async convertEditorBinToPdf(
binData: Uint8Array,
@@ -1281,14 +1677,17 @@ export class EditorServer {
const { formatFrom, formatTo } = getX2tExportFormats("pdf", this.fileType);
if (pdfRendererStream?.byteLength) {
- const result = await converter.convert({
- data: binData.slice().buffer,
- fileFrom: "output.bin",
- fileTo: "output.pdf",
- media,
- themes,
- pdfBin: pdfRendererStream,
- });
+ const result = await converter.convert(
+ {
+ data: binData.slice().buffer,
+ fileFrom: "output.bin",
+ fileTo: "output.pdf",
+ media,
+ themes,
+ pdfBin: pdfRendererStream,
+ },
+ this.options.logger,
+ );
if (!result.output?.byteLength) {
throw new Error("Failed to convert Editor.bin + pdf.bin to PDF");
@@ -1298,15 +1697,18 @@ export class EditorServer {
return result.output;
}
- const result = await converter.convert({
- data: binData.slice().buffer,
- fileFrom: "Editor.bin",
- fileTo: "doc.pdf",
- formatFrom,
- formatTo,
- media,
- themes,
- });
+ const result = await converter.convert(
+ {
+ data: binData.slice().buffer,
+ fileFrom: "Editor.bin",
+ fileTo: "doc.pdf",
+ formatFrom,
+ formatTo,
+ media,
+ themes,
+ },
+ this.options.logger,
+ );
if (!result.output?.byteLength) {
throw new Error("Failed to convert Editor.bin to PDF");
@@ -1385,7 +1787,9 @@ export class EditorServer {
window.clearTimeout(pendingExport.timer);
this.pendingExport = null;
- // 校验魔数后再写入 fsMap,避免脏分片进入 x2t 导出链路。
+ /**
+ * @description 校验魔数后再写入 fsMap,避免脏分片进入 x2t 导出链路。
+ */
if (!isValidEditorBin(data)) {
pendingExport.reject(
new Error("OnlyOffice export returned invalid document data"),
@@ -1465,13 +1869,13 @@ export class EditorServer {
}
handleConnect({ socket }: { socket: MockSocket }) {
- console.log("connect: ", socket);
+ this.logRaw("log", "socket", "connect", ["connect: ", socket]);
this.registerSocketTransport(socket);
this.sendCoAuthoringHandshake(socket);
}
handleDisconnect({ socket }: { socket: MockSocket }) {
- console.log("disconnect: ", socket);
+ this.logRaw("log", "socket", "disconnect", ["disconnect: ", socket]);
const handler = this.messageHandlers.get(socket);
if (handler) {
@@ -1518,14 +1922,23 @@ export class EditorServer {
data: {
type: "imgurls",
status: "ok",
- // SDK 用 $dc(error) 判断是否弹错;缺省 undefined → qD(未知错误)
+ /**
+ * @description SDK 用 $dc(error) 判断是否弹错;缺省 undefined 会变成未知错误。
+ */
data: { urls, error: 0 },
},
});
}
private sendTo(socket: MockSocket, ...msg: unknown[]) {
- console.log("[ws] >> ", ...msg);
+ const summary = summarizeWsPayload(msg);
+ this.logRaw(
+ "log",
+ "socket",
+ `ws outgoing: ${summary.label}`,
+ ["[ws] >> ", ...msg],
+ summary,
+ );
socket.server.emit("message", ...msg);
}
@@ -1540,7 +1953,14 @@ export class EditorServer {
msg: Record,
...args: unknown[]
) {
- console.log("[ws] << ", msg, args);
+ const incomingSummary = summarizeWsPayload([msg, ...args]);
+ this.logRaw(
+ "log",
+ "socket",
+ `ws incoming: ${incomingSummary.label}`,
+ ["[ws] << ", msg, args],
+ incomingSummary,
+ );
const send = (...payload: unknown[]) => this.sendTo(socket, ...payload);
const { sessionId, participants, user, client } = this;
@@ -1560,8 +1980,6 @@ export class EditorServer {
sessionId: sessionId,
participants: participants,
locks: [],
- // changes: changes,
- // changesIndex: 0,
indexUser: 1,
buildVersion: client.buildVersion || "9.3.0",
buildNumber: client.buildNumber || 9,
@@ -1664,18 +2082,12 @@ export class EditorServer {
}
/**
- * Mock 协作 HTTP 入口。OnlyOffice iframe 内 XHR/fetch 被代理到此。
- *
- * downloadAs 双路径(同一 URL,靠 pendingExport 区分):
- * - 有 pendingExport → 导出:resolvePendingExport,写入 Editor.bin 供 x2t
- * - 无 pendingExport → 保存:commitUserSave(UI 保存已禁用,保留兜底)
+ * @description Mock 协作 HTTP 入口,OnlyOffice iframe 内 XHR/fetch 会被代理到这里。
*/
async handleRequest(req: Request) {
const u = new URL(req.url);
const { id: key } = this;
- // console.log("[msg] server: ", u, key);
-
const cacheMatch = u.pathname.match(/\/cache\/files\/data\/([^/]+)\/(.+)$/);
if (cacheMatch && req.method === "GET") {
const [, , rawName] = cacheMatch;
@@ -1715,7 +2127,14 @@ export class EditorServer {
>;
const buffer = await req.arrayBuffer();
- console.log("downloadAs -> ", cmd, buffer);
+ const downloadSummary = summarizeDownloadCommand(cmd, buffer);
+ this.logRaw(
+ "log",
+ "download",
+ `downloadAs chunk: ${downloadSummary.label || "unknown"}`,
+ ["downloadAs -> ", cmd, buffer],
+ downloadSummary,
+ );
if (
cmd.savetype === AscSaveTypes.PartStart ||
@@ -1776,7 +2195,9 @@ export class EditorServer {
};
}
- // 用户保存(Ctrl+S / 工具栏保存):保留 bin,走 EventBus,不触发浏览器下载。
+ /**
+ * @description 用户保存时保留 Editor.bin 并走 EventBus,不触发浏览器下载。
+ */
this.commitUserSave(input);
this.endSaving();
return {
@@ -1801,7 +2222,10 @@ export class EditorServer {
try {
result = await download();
} catch (err) {
- console.error("[EditorServer] downloadAs failed:", err);
+ this.logRaw("error", "download", "downloadAs failed", [
+ "[EditorServer] downloadAs failed:",
+ err,
+ ]);
this.endSaving();
result = {
status: "err",
@@ -1816,7 +2240,9 @@ export class EditorServer {
isFinalChunk = true;
};
- // OnlyOffice downloadAs 按 PartStart → Part* → Complete(All) 分片 POST。
+ /**
+ * @description OnlyOffice downloadAs 按 PartStart、Part、Complete/CompleteAll 分片 POST。
+ */
switch (cmd.savetype) {
case AscSaveTypes.PartStart:
if (!this.pendingExport) {
@@ -1842,7 +2268,9 @@ export class EditorServer {
break;
}
- // programmatic export 广播占位 URL,让 SDK 标记成功但不触发 getFile 下载。
+ /**
+ * @description 程序化导出广播占位 URL,让 SDK 标记成功但不触发 getFile 下载。
+ */
if (isFinalChunk) {
const { downloadUrl, filetype, status, error } = result;
@@ -1881,17 +2309,6 @@ export class EditorServer {
return Response.json({ [pathname]: url });
}
- if (u.pathname == "/plugins.json") {
- const state = this.options.getState?.();
- if (state?.plugins == "none") {
- return Response.json({ url: "", pluginsData: [], autostart: [] });
- }
- if (state?.plugins == "all") {
- return Response.json(getPluginsData(allPlugins));
- }
- return Response.json(getPluginsData(featuredPlugins));
- }
-
return null;
}
}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/socket.ts b/src/components/onlyoffice-web-comp/internal/editor/socket.ts
deleted file mode 100644
index c15555c9..00000000
--- a/src/components/onlyoffice-web-comp/internal/editor/socket.ts
+++ /dev/null
@@ -1,236 +0,0 @@
-import { EventEmitter } from "../../util/event-emitter";
-
-type Callback = (...args: any[]) => void;
-
-export interface MockSocketOptions {
- /** Enable debug logging. Defaults to true in development mode. */
- debug?: boolean;
- /** 跨域 bridge 父页侧仅路由消息,不自动 connect */
- deferConnect?: boolean;
-}
-
-/**
- * Mock implementation of socket.io-client using internal EventEmitter.
- *
- * @template ListenEvents - Events Server -> Client
- * @template EmitEvents - Events Client -> Server
- */
-export class MockSocket<
- ListenEvents extends Record = any,
- EmitEvents extends Record = any
-> {
- private static _staticEmitter = new EventEmitter();
- static on(event: E, listener: Callback) {
- MockSocket._staticEmitter.on(event, listener);
- }
- static off(event: E, listener?: Callback) {
- MockSocket._staticEmitter.off(event, listener);
- }
-
- public active = true;
- public connected: boolean = false;
- public disconnected: boolean = true;
- public recovered = false;
- public id: string = "";
- public io = {
- setOpenToken: () => {
- /* no-op */
- },
- setSessionToken: () => {
- /* no-op */
- },
- on: function () {
- return this;
- },
- reconnectionAttempts: function () {
- return this;
- },
- reconnectionDelay: function () {
- return this;
- },
- reconnectionDelayMax: function () {
- return this;
- },
- timeout: function () {
- return this;
- },
- transports: function () {
- return this;
- },
- upgrade: function () {
- return this;
- },
- upgradeTransport: function () {
- return this;
- },
- upgradeTimeout: function () {
- return this;
- },
- };
-
- private _clientEmitter = new EventEmitter();
- private _serverEmitter = new EventEmitter();
-
- // Instance debug flag (can override global)
- private _debug: boolean;
-
- constructor(options: MockSocketOptions = {}) {
- this._debug = options.debug;
- if (!options.deferConnect) {
- this.connect();
- }
- }
-
- private _log(...args: any[]): void {
- if (this._debug) {
- console.log("[MockSocket]", ...args);
- }
- }
-
- open() {
- return this.connect();
- }
-
- compress() {}
-
- /**
- * Simulates connection establishment and generates a new Session ID.
- */
- connect() {
- this.connected = true;
- this.disconnected = false;
- this.id = Math.random().toString(36).substring(2, 15);
- setTimeout(() => {
- this._trigger("connect");
- MockSocket._staticEmitter.emit("connect", { socket: this });
- }, 0);
- return this;
- }
-
- disconnect() {
- this.connected = false;
- this.disconnected = true;
- this._trigger("disconnect");
- MockSocket._staticEmitter.emit("disconnect", { socket: this });
- return this;
- }
-
- close(): this {
- return this.disconnect();
- }
-
- /**
- * Triggers local listeners (internal helper).
- * Used to simulate incoming server events.
- */
- private _trigger(event: string, ...args: any[]): this {
- this._log(`trigger event: ${event}`, ...args);
- this._clientEmitter.emit(event, ...args);
- return this;
- }
-
- // --- Client API ---
-
- /**
- * Registers a listener for an event from the server.
- */
- on(
- event: E,
- listener: ListenEvents[E]
- ): this {
- this._clientEmitter.on(event, listener);
- return this;
- }
-
- /**
- * Registers a one-time listener for an event from the server.
- */
- once(
- event: E,
- listener: ListenEvents[E]
- ): this {
- this._clientEmitter.once(event, listener);
- return this;
- }
-
- /**
- * Removes a listener for an event.
- */
- off(
- event: E,
- listener?: ListenEvents[E]
- ): this {
- this._clientEmitter.off(event, listener);
- return this;
- }
-
- /**
- * Removes all listeners, or those of the specified event.
- */
- removeAllListeners(event?: string): this {
- this._clientEmitter.removeAllListeners(event);
- return this;
- }
-
- /**
- * Sends a message to the server using the 'message' event.
- * This is a shorthand for `emit('message', ...args)`.
- */
- send(...args: Parameters): this {
- if (!this.connected) return this;
- this.emit("message", ...args);
- return this;
- }
-
- /**
- * Sends a message to the server.
- * First tries global middlewares, then instance handler defined by `serverSideOn`.
- */
- emit(
- event: E,
- ...args: Parameters
- ): this {
- this._log(`emit: ${event}`, ...args);
-
- if (!this.connected) return this;
-
- const processEmit = async () => {
- this._serverEmitter.emit(event, ...args);
- };
-
- // Execute asynchronously
- setTimeout(() => processEmit(), 0);
- return this;
- }
-
- public server = {
- on: (event: string, listener: Callback) => {
- this._serverEmitter.on(event, listener);
- },
- off: (event: string, listener?: Callback) => {
- this._serverEmitter.off(event, listener);
- },
- emit: (event: string, ...args: any[]) => {
- this._clientEmitter.emit(event, ...args);
- },
- };
-}
-
-/**
- * Factory function compatible with socket.io-client API.
- * Usage: const socket = io() or io(url, options)
- */
-export function io(url?: string, options?: MockSocketOptions): MockSocket {
- return new MockSocket(options);
-}
-
-// Add namespace support for socket.io compatibility
-export interface SocketIOStatic {
- (url?: string, options?: MockSocketOptions): MockSocket;
-}
-
-// Create the io function with static methods attached
-const ioWithStatics = io as SocketIOStatic;
-
-// Export as default for compatibility
-export default ioWithStatics;
diff --git a/src/components/onlyoffice-web-comp/internal/editor/types.ts b/src/components/onlyoffice-web-comp/internal/editor/types.ts
index 9bf25cd6..7b4f5335 100644
--- a/src/components/onlyoffice-web-comp/internal/editor/types.ts
+++ b/src/components/onlyoffice-web-comp/internal/editor/types.ts
@@ -1,3 +1,5 @@
+import type { EditorLogger } from "./logger";
+
export interface DocEditor {
attachMouseEvents: () => void;
blurFocus: (data: unknown) => void;
@@ -66,7 +68,9 @@ export const enum DocumentType {
export const enum AvsFileType {
AVS_FILE_UNKNOWN = 0x0000,
- // Document
+ /**
+ * @description 文档格式。
+ */
AVS_FILE_DOCUMENT = 0x0040,
AVS_FILE_DOCUMENT_DOCX = AVS_FILE_DOCUMENT + 0x0001,
AVS_FILE_DOCUMENT_DOC = AVS_FILE_DOCUMENT + 0x0002,
@@ -91,7 +95,9 @@ export const enum AvsFileType {
AVS_FILE_DOCUMENT_DOCXF = AVS_FILE_DOCUMENT + 0x0016,
AVS_FILE_DOCUMENT_OFORM_PDF = AVS_FILE_DOCUMENT + 0x0017,
- // Presentation
+ /**
+ * @description 演示文稿格式。
+ */
AVS_FILE_PRESENTATION = 0x0080,
AVS_FILE_PRESENTATION_PPTX = AVS_FILE_PRESENTATION + 0x0001,
AVS_FILE_PRESENTATION_PPT = AVS_FILE_PRESENTATION + 0x0002,
@@ -106,7 +112,9 @@ export const enum AvsFileType {
AVS_FILE_PRESENTATION_PPTX_PACKAGE = AVS_FILE_PRESENTATION + 0x000b,
AVS_FILE_PRESENTATION_ODG = AVS_FILE_PRESENTATION + 0x000c,
- // Spreadsheet
+ /**
+ * @description 电子表格格式。
+ */
AVS_FILE_SPREADSHEET = 0x0100,
AVS_FILE_SPREADSHEET_XLSX = AVS_FILE_SPREADSHEET + 0x0001,
AVS_FILE_SPREADSHEET_XLS = AVS_FILE_SPREADSHEET + 0x0002,
@@ -121,7 +129,9 @@ export const enum AvsFileType {
AVS_FILE_SPREADSHEET_XLSX_FLAT = AVS_FILE_SPREADSHEET + 0x000b,
AVS_FILE_SPREADSHEET_XLSX_PACKAGE = AVS_FILE_SPREADSHEET + 0x000c,
- // Crossplatform
+ /**
+ * @description 跨平台输出格式。
+ */
AVS_FILE_CROSSPLATFORM = 0x0200,
AVS_FILE_CROSSPLATFORM_PDF = AVS_FILE_CROSSPLATFORM + 0x0001,
AVS_FILE_CROSSPLATFORM_SWF = AVS_FILE_CROSSPLATFORM + 0x0002,
@@ -133,7 +143,9 @@ export const enum AvsFileType {
AVS_FILE_CROSSPLATFORM_HTMLR_CANVAS = AVS_FILE_CROSSPLATFORM + 0x0008,
AVS_FILE_CROSSPLATFORM_PDFA = AVS_FILE_CROSSPLATFORM + 0x0009,
- // Image
+ /**
+ * @description 图片格式。
+ */
AVS_FILE_IMAGE = 0x0400,
AVS_FILE_IMAGE_JPG = AVS_FILE_IMAGE + 0x0001,
AVS_FILE_IMAGE_TIFF = AVS_FILE_IMAGE + 0x0002,
@@ -149,7 +161,9 @@ export const enum AvsFileType {
AVS_FILE_IMAGE_PSD = AVS_FILE_IMAGE + 0x000c,
AVS_FILE_IMAGE_ICO = AVS_FILE_IMAGE + 0x000d,
- // Other
+ /**
+ * @description 其他格式。
+ */
AVS_FILE_OTHER = 0x0800,
AVS_FILE_OTHER_EXTRACT_IMAGE = AVS_FILE_OTHER + 0x0001,
AVS_FILE_OTHER_MS_OFFCRYPTO = AVS_FILE_OTHER + 0x0002,
@@ -158,26 +172,35 @@ export const enum AvsFileType {
AVS_FILE_OTHER_OLD_PRESENTATION = AVS_FILE_OTHER + 0x0005,
AVS_FILE_OTHER_OLD_DRAWING = AVS_FILE_OTHER + 0x0006,
AVS_FILE_OTHER_OOXML = AVS_FILE_OTHER + 0x0007,
- AVS_FILE_OTHER_JSON = AVS_FILE_OTHER + 0x0008, // 对于 mail-merge
+ /**
+ * @description mail-merge 使用的 JSON 数据格式。
+ */
+ AVS_FILE_OTHER_JSON = AVS_FILE_OTHER + 0x0008,
AVS_FILE_OTHER_ODF = AVS_FILE_OTHER + 0x000a,
AVS_FILE_OTHER_MS_MITCRYPTO = AVS_FILE_OTHER + 0x000b,
AVS_FILE_OTHER_MS_VBAPROJECT = AVS_FILE_OTHER + 0x000c,
AVS_FILE_OTHER_PACKAGE_IN_OLE = AVS_FILE_OTHER + 0x000d,
- // Teamlab
+ /**
+ * @description Teamlab 内部格式。
+ */
AVS_FILE_TEAMLAB = 0x1000,
AVS_FILE_TEAMLAB_DOCY = AVS_FILE_TEAMLAB + 0x0001,
AVS_FILE_TEAMLAB_XLSY = AVS_FILE_TEAMLAB + 0x0002,
AVS_FILE_TEAMLAB_PPTY = AVS_FILE_TEAMLAB + 0x0003,
- // Canvas
+ /**
+ * @description OnlyOffice 画布格式。
+ */
AVS_FILE_CANVAS = 0x2000,
AVS_FILE_CANVAS_WORD = AVS_FILE_CANVAS + 0x0001,
AVS_FILE_CANVAS_SPREADSHEET = AVS_FILE_CANVAS + 0x0002,
AVS_FILE_CANVAS_PRESENTATION = AVS_FILE_CANVAS + 0x0003,
AVS_FILE_CANVAS_PDF = AVS_FILE_CANVAS + 0x0004,
- // Draw
+ /**
+ * @description 绘图格式。
+ */
AVS_FILE_DRAW = 0x4000,
AVS_FILE_DRAW_VSDX = AVS_FILE_DRAW + 0x0001,
AVS_FILE_DRAW_VSSX = AVS_FILE_DRAW + 0x0002,
@@ -187,13 +210,19 @@ export const enum AvsFileType {
AVS_FILE_DRAW_VSTM = AVS_FILE_DRAW + 0x0006,
}
-/** OnlyOffice x2t CSV encoding index (46 = UTF-8 / codepage 65001). */
+/**
+ * @description OnlyOffice x2t CSV 编码索引,46 表示 UTF-8 / codepage 65001。
+ */
export const X2T_CSV_ENCODING_UTF8 = 46;
-/** OnlyOffice x2t CSV encoding index (18 = GB2312 / codepage 936). */
+/**
+ * @description OnlyOffice x2t CSV 编码索引,18 表示 GB2312 / codepage 936。
+ */
export const X2T_CSV_ENCODING_GBK = 18;
-/** OnlyOffice x2t CSV delimiter enum values. */
+/**
+ * @description OnlyOffice x2t CSV 分隔符枚举值。
+ */
export const X2T_CSV_DELIMITER_TAB = 1;
export const X2T_CSV_DELIMITER_SEMICOLON = 2;
export const X2T_CSV_DELIMITER_COMMA = 4;
@@ -208,7 +237,9 @@ export interface X2tConvertParams {
csvDelimiter?: number;
csvDelimiterChar?: string;
media?: { [key: string]: Uint8Array };
- /** Web SDK PDF 另存为 POST 的渲染器 Memory 流,x2t 需写入 /working/pdf.bin。 */
+ /**
+ * @description Web SDK PDF 另存为 POST 的渲染器 Memory 流,x2t 需写入 /working/pdf.bin。
+ */
pdfBin?: Uint8Array;
fonts?: { [key: string]: Uint8Array };
fontAliases?: { [key: string]: string };
@@ -241,8 +272,6 @@ export type OfficeTheme =
| "theme-night"
| "theme-contrast-dark";
-export type PluginMode = "featured" | "all" | "none";
-
export type EditorDocumentSnapshot = {
fileName: string;
fileType: string;
@@ -284,9 +313,14 @@ export function isOfficeXmlSizeLimitExceededError(
}
export interface ServerOptions {
- getState?: () => { plugins: PluginMode; readOnly?: boolean };
- /** 用户触发保存(非 export/downloadAs 导出)时回调,携带最新文档快照。 */
+ getState?: () => { readOnly?: boolean };
+ logger?: EditorLogger;
+ /**
+ * @description 用户触发保存(非 export/downloadAs 导出)时回调,携带最新文档快照。
+ */
onUserSave?: (snapshot: EditorDocumentSnapshot) => void;
- /** 文档异步加载失败时回调;open() 返回后 x2t 转换仍可能在 loadPromise 中失败。 */
+ /**
+ * @description 文档异步加载失败时回调;open() 返回后 x2t 转换仍可能在 loadPromise 中失败。
+ */
onLoadError?: (error: Error) => void;
}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/utils.ts b/src/components/onlyoffice-web-comp/internal/editor/utils.ts
index 5ed7d212..70446ac4 100644
--- a/src/components/onlyoffice-web-comp/internal/editor/utils.ts
+++ b/src/components/onlyoffice-web-comp/internal/editor/utils.ts
@@ -165,11 +165,9 @@ function encodeCsvBuffer(text: string, withUtf8Bom: boolean) {
return withBom.slice().buffer;
}
-function parseCsvLine(line: string, delimiter: string) {
- return parseCsvText(line, delimiter)[0] ?? [];
-}
-
-/** RFC 4180 风格解析,支持引号内换行与逗号。 */
+/**
+ * @description RFC 4180 风格解析,支持引号内换行与逗号。
+ */
function parseCsvText(text: string, delimiter: string): string[][] {
const rows: string[][] = [];
let row: string[] = [];
@@ -257,7 +255,9 @@ function toCsvFormulaCell(value: string) {
return `="${value.replace(/"/g, '""')}"`;
}
-/** 仅包裹「以数字结尾且不像日期/时间」的单元格,避免误包 2018/9/6 9:32 引发 x2t 异常。 */
+/**
+ * @description 仅包裹「以数字结尾且不像日期/时间」的单元格,避免误包 2018/9/6 9:32 引发 x2t 异常。
+ */
function shouldApplyCsvFormulaWrap(value: string) {
if (!/[0-9]$/.test(value)) {
return false;
@@ -277,7 +277,9 @@ function getCsvDelimiterChar(delimiter: number) {
return ",";
}
-/** 引号内换行会导致按行 split 破坏结构;物理行数明显多于逻辑行数即视为复杂 CSV。 */
+/**
+ * @description 引号内换行会导致按行 split 破坏结构;物理行数明显多于逻辑行数即视为复杂 CSV。
+ */
export function isMultilineCsv(buffer: ArrayBuffer) {
const csvEncoding = detectX2tCsvEncoding(buffer);
const delimiter = getCsvDelimiterChar(
@@ -289,7 +291,9 @@ export function isMultilineCsv(buffer: ArrayBuffer) {
return physicalLines > logicalRows + 2;
}
-/** 解析 CSV 为二维数组,供 xlsx 兜底转换等场景复用。 */
+/**
+ * @description 解析 CSV 为二维数组,供 xlsx 兜底转换等场景复用。
+ */
export function parseCsvBuffer(buffer: ArrayBuffer) {
const csvEncoding = detectX2tCsvEncoding(buffer);
const delimiter = getCsvDelimiterChar(
@@ -299,8 +303,8 @@ export function parseCsvBuffer(buffer: ArrayBuffer) {
}
/**
- * x2t CSV 解析 bug:某列单元格以 ASCII 数字结尾时,下一列会误走 DateReader 并崩溃
- *(如 login1.csv 的「用户3」+「2018/9/6 9:32」)。用 ="value" 包裹前一格可绕过。
+ * @description x2t CSV 解析 bug:某列单元格以 ASCII 数字结尾时,下一列会误走 DateReader 并崩溃
+ * (如 login1.csv 的「用户3」+「2018/9/6 9:32」)。用 ="value" 包裹前一格可绕过。
*/
export function sanitizeCsvBufferForX2t(buffer: ArrayBuffer) {
const csvEncoding = detectX2tCsvEncoding(buffer);
@@ -334,7 +338,9 @@ function getFirstCsvLine(text: string) {
return newline === -1 ? text : text.slice(0, newline);
}
-/** Detect delimiter from the first CSV row. */
+/**
+ * @description 根据 CSV 第一行推断分隔符。
+ */
export function detectX2tCsvDelimiter(buffer: ArrayBuffer, encoding: number) {
const line = getFirstCsvLine(decodeCsvSample(buffer, encoding));
const counts = { comma: 0, semicolon: 0, tab: 0 };
@@ -365,7 +371,9 @@ export function detectX2tCsvDelimiter(buffer: ArrayBuffer, encoding: number) {
return X2T_CSV_DELIMITER_COMMA;
}
-/** Detect OnlyOffice x2t CSV encoding index from BOM / byte patterns. */
+/**
+ * @description 根据 BOM 和字节特征推断 OnlyOffice x2t CSV 编码索引。
+ */
export function detectX2tCsvEncoding(buffer: ArrayBuffer) {
const bytes = new Uint8Array(buffer);
if (
@@ -387,7 +395,10 @@ export function getX2tCsvConvertOptions(buffer: ArrayBuffer) {
};
}
-/** @param sourceFileType 源文档类型(如 xlsx);导出 pdf 等目标格式时必须传入,否则会误用 CANVAS_WORD。 */
+/**
+ * @description 获取 x2t 导出格式;导出 pdf 等目标格式时需要源文档类型,否则会误用 CANVAS_WORD。
+ * @param sourceFileType 源文档类型,例如 xlsx。
+ */
export function getX2tExportFormats(
fileType: string,
sourceFileType?: string,
@@ -403,7 +414,9 @@ export function getX2tExportFormats(
};
}
-/** x2t wasm 无法稳定输出旧二进制 .doc;导出时降级为 docx。 */
+/**
+ * @description x2t wasm 无法稳定输出旧二进制 .doc;导出时降级为 docx。
+ */
export function normalizeX2tExportFileType(fileType: string) {
const ext = getFileExt(fileType);
return ext === "doc" ? "docx" : ext;
@@ -467,12 +480,16 @@ const canvasBinOutputFormats = new Set([
AvsFileType.AVS_FILE_TEAMLAB_PPTY,
]);
-/** EditorManager export() / downloadAs("bin") 使用的 canvas bin 格式,不是 UI 另存为。 */
+/**
+ * @description EditorManager export() / downloadAs("bin") 使用的 canvas bin 格式,不是 UI 另存为。
+ */
export function isCanvasBinOutputFormat(outputFormat?: number) {
return outputFormat != null && canvasBinOutputFormats.has(outputFormat);
}
-/** OnlyOffice downloadAs cmd.outputformat(AvsFileType 数值)→ 文件扩展名。 */
+/**
+ * @description OnlyOffice downloadAs cmd.outputformat(AvsFileType 数值)→ 文件扩展名。
+ */
export function extensionFromOutputFormat(outputFormat?: number): string {
if (outputFormat == null) {
return "";
@@ -480,7 +497,9 @@ export function extensionFromOutputFormat(outputFormat?: number): string {
return outputFormatToExt[outputFormat] || "";
}
-/** 保证文档标题带正确后缀,供 OnlyOffice 内置「另存为」推导下载文件名。 */
+/**
+ * @description 保证文档标题带正确后缀,供 OnlyOffice 内置「另存为」推导下载文件名。
+ */
export function ensureTitleWithExtension(title: string, fileType: string): string {
const ext = fileType.toLowerCase();
if (!ext) {
diff --git a/src/components/onlyoffice-web-comp/internal/editor/fetch-brotli.ts b/src/components/onlyoffice-web-comp/internal/editor/x2t-assets.ts
similarity index 53%
rename from src/components/onlyoffice-web-comp/internal/editor/fetch-brotli.ts
rename to src/components/onlyoffice-web-comp/internal/editor/x2t-assets.ts
index 8e237674..ece301da 100644
--- a/src/components/onlyoffice-web-comp/internal/editor/fetch-brotli.ts
+++ b/src/components/onlyoffice-web-comp/internal/editor/x2t-assets.ts
@@ -1,3 +1,8 @@
+import {
+ resolveSiteUrl,
+ STATIC_RESOURCE,
+ X2T_PDF_FONT_MANIFEST,
+} from "../../const";
const WASM_MAGIC = [0x00, 0x61, 0x73, 0x6d] as const;
@@ -67,7 +72,9 @@ async function decompressBrotli(buffer: ArrayBuffer) {
try {
return await decompressWithStream(buffer);
} catch {
- // Safari 等环境可能构造成功但运行失败,继续走内置 JS 解码。
+ /**
+ * @description Safari 等环境可能构造成功但运行失败,此时继续走内置 JS 解码。
+ */
}
}
@@ -75,8 +82,7 @@ async function decompressBrotli(buffer: ArrayBuffer) {
}
/**
- * 拉取 Brotli 预压缩的 x2t 静态资源并解压。
- * 不依赖 npm 包或服务端 Content-Encoding: br。
+ * @description 拉取 Brotli 预压缩的 x2t 静态资源并解压,不依赖服务端 Content-Encoding: br。
*/
export async function fetchMaybeBrotliAsset(
url: string,
@@ -97,7 +103,9 @@ export async function fetchMaybeBrotliAsset(
return decompressBrotli(buffer);
}
-/** 拉取并解码为 JS 源码(供 Worker 内 executeEmscriptenScript 执行)。 */
+/**
+ * @description 拉取并解码为 JS 源码(供 Worker 内 executeEmscriptenScript 执行)。
+ */
export async function fetchMaybeBrotliScript(
url: string,
fetchImpl: typeof fetch = fetch,
@@ -105,3 +113,71 @@ export async function fetchMaybeBrotliScript(
const buffer = await fetchMaybeBrotliAsset(url, fetchImpl);
return new TextDecoder().decode(buffer);
}
+
+
+let cachedFonts: Record | null = null;
+let cachedFontsRoot = "";
+let loadingPromise: Promise> | null = null;
+let loadingFontsRoot = "";
+
+async function fetchPdfFontFile(origin: string, root: string, file: string) {
+ const url = resolveSiteUrl(
+ origin,
+ `${root}/${file}`,
+ );
+ const response = await fetch(url);
+ if (!response.ok) {
+ console.warn("[x2t-assets] font fetch failed:", url, response.status);
+ return null;
+ }
+ const bytes = new Uint8Array(await response.arrayBuffer());
+ return bytes.byteLength ? bytes : null;
+}
+
+/**
+ * @description 加载 PDF 导出字体,结果缓存在内存中。
+ */
+export async function loadX2tPdfFonts(
+ origin: string,
+ root = STATIC_RESOURCE.x2t.pdfFonts.root,
+): Promise> {
+ if (cachedFonts && cachedFontsRoot === root) {
+ return cachedFonts;
+ }
+ if (loadingPromise && loadingFontsRoot === root) {
+ return loadingPromise;
+ }
+ loadingFontsRoot = root;
+
+ loadingPromise = (async () => {
+ const fonts: Record = {};
+
+ for (const entry of X2T_PDF_FONT_MANIFEST) {
+ const bytes = await fetchPdfFontFile(origin, root, entry.file);
+ if (!bytes) {
+ continue;
+ }
+ for (const alias of entry.aliases) {
+ fonts[alias] = bytes;
+ }
+ }
+
+ if (!fonts["Carlito.ttf"]?.byteLength) {
+ console.warn(
+ "[x2t-assets] Carlito regular missing under",
+ root,
+ );
+ return {};
+ }
+
+ cachedFonts = fonts;
+ cachedFontsRoot = root;
+ return cachedFonts;
+ })();
+
+ try {
+ return await loadingPromise;
+ } finally {
+ loadingPromise = null;
+ }
+}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/x2t-pdf-fonts.ts b/src/components/onlyoffice-web-comp/internal/editor/x2t-pdf-fonts.ts
deleted file mode 100644
index 7c75e5ef..00000000
--- a/src/components/onlyoffice-web-comp/internal/editor/x2t-pdf-fonts.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import {
- STATIC_RESOURCE,
- resolveSiteUrl,
- X2T_PDF_FONT_MANIFEST,
-} from "../../const";
-
-let cachedFonts: Record | null = null;
-let cachedFontsRoot = "";
-let loadingPromise: Promise> | null = null;
-let loadingFontsRoot = "";
-
-async function fetchPdfFontFile(origin: string, root: string, file: string) {
- const url = resolveSiteUrl(
- origin,
- `${root}/${file}`,
- );
- const response = await fetch(url);
- if (!response.ok) {
- console.warn("[x2t-pdf-fonts] font fetch failed:", url, response.status);
- return null;
- }
- const bytes = new Uint8Array(await response.arrayBuffer());
- return bytes.byteLength ? bytes : null;
-}
-
-/** 加载 PDF 导出字体,结果缓存在内存中。 */
-export async function loadX2tPdfFonts(
- origin: string,
- root = STATIC_RESOURCE.x2t.pdfFonts.root,
-): Promise> {
- if (cachedFonts && cachedFontsRoot === root) {
- return cachedFonts;
- }
- if (loadingPromise && loadingFontsRoot === root) {
- return loadingPromise;
- }
- loadingFontsRoot = root;
-
- loadingPromise = (async () => {
- const fonts: Record = {};
-
- for (const entry of X2T_PDF_FONT_MANIFEST) {
- const bytes = await fetchPdfFontFile(origin, root, entry.file);
- if (!bytes) {
- continue;
- }
- for (const alias of entry.aliases) {
- fonts[alias] = bytes;
- }
- }
-
- if (!fonts["Carlito.ttf"]?.byteLength) {
- console.warn(
- "[x2t-pdf-fonts] Carlito regular missing under",
- root,
- );
- return {};
- }
-
- cachedFonts = fonts;
- cachedFontsRoot = root;
- return cachedFonts;
- })();
-
- try {
- return await loadingPromise;
- } finally {
- loadingPromise = null;
- }
-}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/x2t.ts b/src/components/onlyoffice-web-comp/internal/editor/x2t.ts
index 03a3cd3e..c8fe2c94 100644
--- a/src/components/onlyoffice-web-comp/internal/editor/x2t.ts
+++ b/src/components/onlyoffice-web-comp/internal/editor/x2t.ts
@@ -1,12 +1,10 @@
/**
- * X2T Converter with Web Worker Support
- *
- * This module provides a main-thread proxy that delegates heavy conversion
- * operations to a Web Worker, preventing UI blocking.
+ * @description x2t 主线程代理,将重型文档转换转交给 Web Worker,避免阻塞界面线程。
*/
import { X2tConvertParams, X2tConvertResult } from "./types";
import { getStaticResource, resolveSiteUrl, type StaticResource } from "../../const";
+import type { EditorLogger } from "./logger";
interface PendingMessage {
resolve: (value: any) => void;
@@ -26,6 +24,7 @@ export class X2tConverter {
private messageId = 0;
private pendingMessages = new Map();
private resourceKey = "";
+ private logger?: EditorLogger;
constructor() {}
@@ -54,14 +53,26 @@ export class X2tConverter {
}
/**
- * Get next unique message ID
+ * @description 生成递增的 worker 消息 ID。
*/
private getNextId(): number {
return ++this.messageId;
}
+ private logRaw(
+ level: "log" | "error",
+ message: string,
+ consoleArgs: unknown[],
+ ) {
+ if (this.logger) {
+ this.logger.raw(level, "worker", message, consoleArgs);
+ return;
+ }
+ console[level](...consoleArgs);
+ }
+
/**
- * Send message to worker and wait for response
+ * @description 向 worker 发送请求并等待对应响应。
*/
private sendMessage(type: string, payload?: any): Promise {
return new Promise((resolve, reject) => {
@@ -73,7 +84,9 @@ export class X2tConverter {
const id = this.getNextId();
this.pendingMessages.set(id, { resolve, reject });
- // For convert messages, use Transferable if payload contains ArrayBuffer
+ /**
+ * @description 转换请求携带 ArrayBuffer 时使用 Transferable,减少主线程复制开销。
+ */
if (type === "convert" && payload?.data instanceof ArrayBuffer) {
this.worker.postMessage({ id, type, payload }, [payload.data]);
} else {
@@ -83,14 +96,13 @@ export class X2tConverter {
}
/**
- * Handle worker response messages
+ * @description 处理 worker 返回的响应消息。
*/
private handleWorkerMessage = (event: MessageEvent) => {
const { id, type, payload, error } = event.data;
- // Skip ready message
if (type === "ready") {
- console.log("[X2tConverter] Worker ready");
+ this.logRaw("log", "worker ready", ["[X2tConverter] Worker ready"]);
return;
}
@@ -107,12 +119,14 @@ export class X2tConverter {
};
/**
- * Handle worker errors
+ * @description 处理 worker 运行错误,并让所有等待中的请求失败。
*/
private handleWorkerError = (error: ErrorEvent) => {
- console.error("[X2tConverter] Worker error:", error);
+ this.logRaw("error", "worker error", [
+ "[X2tConverter] Worker error:",
+ error,
+ ]);
- // Reject all pending messages
for (const [id, pending] of this.pendingMessages) {
pending.reject(new Error(`Worker error: ${error.message}`));
this.pendingMessages.delete(id);
@@ -120,26 +134,29 @@ export class X2tConverter {
};
/**
- * Initialize the worker (automatically called on construction)
+ * @description 初始化 x2t worker;重复调用会复用同一个初始化 Promise。
*/
- public init(): Promise {
+ public init(logger?: EditorLogger): Promise {
+ if (logger) {
+ this.logger = logger;
+ }
if (this.initPromise) {
return this.initPromise;
}
this.initPromise = new Promise((resolve, reject) => {
try {
- // Create worker using Next.js compatible syntax
- // Worker auto-initializes x2t internally
+ /**
+ * @description 使用 Next.js 可识别的 URL 语法创建 module worker。
+ */
this.worker = new Worker(new URL("./x2t.worker.ts", import.meta.url), {
type: "module",
});
- // Set up message handlers
this.worker.onmessage = this.handleWorkerMessage;
this.worker.onerror = this.handleWorkerError;
- console.log("[X2tConverter] Worker created");
+ this.logRaw("log", "worker created", ["[X2tConverter] Worker created"]);
resolve();
} catch (err) {
this.initPromise = null;
@@ -151,7 +168,7 @@ export class X2tConverter {
}
/**
- * Convert document from one format to another
+ * @description 将文档从一种格式转换为另一种格式。
*/
public async convert({
data,
@@ -168,15 +185,18 @@ export class X2tConverter {
csvEncoding,
csvDelimiter,
csvDelimiterChar,
- }: X2tConvertParams): Promise {
+ }: X2tConvertParams, logger?: EditorLogger): Promise {
+ if (logger) {
+ this.logger = logger;
+ }
const staticResource = this.getWorkerStaticResource();
const resourceKey = JSON.stringify(staticResource.x2t);
if (this.worker && this.resourceKey && this.resourceKey !== resourceKey) {
- this.terminate();
+ this.terminate(logger);
}
this.resourceKey = resourceKey;
- await this.init();
+ await this.init(logger);
const cloneMap = (map?: { [key: string]: Uint8Array }) => {
if (!map) return undefined;
@@ -185,7 +205,9 @@ export class X2tConverter {
);
};
- // Clone ArrayBuffer since it will be transferred
+ /**
+ * @description 发送给 worker 前复制数据,避免转移原始调用方持有的 ArrayBuffer。
+ */
const dataClone = data.slice(0);
const payload = {
@@ -205,15 +227,23 @@ export class X2tConverter {
csvDelimiterChar,
staticResource,
};
+ this.logger?.worker("convert", {
+ fileFrom,
+ fileTo,
+ formatFrom,
+ formatTo,
+ });
return this.sendMessage("convert", payload);
}
/**
- * Terminate the worker and release resources
+ * @description 终止 worker 并释放关联资源。
*/
- public terminate(): void {
+ public terminate(logger?: EditorLogger): void {
+ if (logger) {
+ this.logger = logger;
+ }
if (this.worker) {
- // Reject all pending messages
for (const [id, pending] of this.pendingMessages) {
pending.reject(new Error("Worker terminated"));
this.pendingMessages.delete(id);
@@ -222,17 +252,21 @@ export class X2tConverter {
this.worker.terminate();
this.worker = null;
this.initPromise = null;
- console.log("[X2tConverter] Worker terminated");
+ this.logRaw("log", "worker terminated", [
+ "[X2tConverter] Worker terminated",
+ ]);
}
}
/**
- * Check if worker is initialized
+ * @description 判断 worker 是否已经初始化。
*/
public get isInitialized(): boolean {
return this.worker !== null && this.initPromise !== null;
}
}
-// Default converter instance
+/**
+ * @description 默认 x2t 转换器实例。
+ */
export const converter = new X2tConverter();
diff --git a/src/components/onlyoffice-web-comp/internal/editor/x2t.worker.ts b/src/components/onlyoffice-web-comp/internal/editor/x2t.worker.ts
index 49bcaea0..c9f3e3d4 100644
--- a/src/components/onlyoffice-web-comp/internal/editor/x2t.worker.ts
+++ b/src/components/onlyoffice-web-comp/internal/editor/x2t.worker.ts
@@ -1,23 +1,22 @@
///
import { AvsFileType, X2tConvertParams, X2tConvertResult } from "./types";
-import { loadX2tPdfFonts } from "./x2t-pdf-fonts";
+import { loadX2tPdfFonts } from "./x2t-assets";
import {
fetchMaybeBrotliAsset,
fetchMaybeBrotliScript,
-} from "./fetch-brotli";
+} from "./x2t-assets";
import { getStaticResource, resolveSiteUrl } from "../../const";
/**
- * X2T Converter Web Worker
- *
- * This worker handles CPU-intensive document conversion operations
- * off the main thread to prevent UI blocking.
+ * @description x2t 转换 worker,在后台线程执行文档转换,避免阻塞主界面。
*/
/* eslint-disable no-restricted-globals */
-// Worker 内用 origin 拼绝对 URL(见 STATIC_RESOURCE.x2t)
+/**
+ * @description Worker 内使用当前 origin 拼接 x2t 静态资源绝对地址。
+ */
const X2T_ORIGIN = self.location.origin;
let x2t: any = null;
@@ -120,7 +119,9 @@ function rewriteFontText(text: string, aliases?: Record) {
let output = text;
for (const [from, to] of entries) {
const escaped = from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- // 避免「仿宋」替换命中已是「仿宋_GB2312」的前缀,产生 _GB2312_GB2312
+ /**
+ * @description 避免「仿宋」替换命中已是「仿宋_GB2312」的前缀,产生 _GB2312_GB2312。
+ */
const suffix =
to.startsWith(from) && to.length > from.length
? to.slice(from.length).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
@@ -414,8 +415,7 @@ async function sanitizeDocxForX2t(data: ArrayBuffer, fileFrom: string) {
}
/**
- * 在 Worker 全局作用域执行 Emscripten 脚本。
- * classic worker 用 importScripts;module worker 用间接 eval(全局作用域)。
+ * @description 在 Worker 全局作用域执行 Emscripten 脚本;classic worker 用 importScripts,module worker 用间接 eval。
*/
function executeEmscriptenScript(scriptSource: string): void {
if (typeof importScripts === "function") {
@@ -438,12 +438,14 @@ function executeEmscriptenScript(scriptSource: string): void {
}
}
- // Module worker 不支持 importScripts;间接 eval 在 Worker 全局作用域执行。
+ /**
+ * @description Module worker 不支持 importScripts,因此使用间接 eval 在 Worker 全局作用域执行。
+ */
(0, eval)(scriptSource);
}
/**
- * Initialize x2t module in Worker context
+ * @description 在 Worker 上下文初始化 x2t 模块。
*/
function getWorkerStaticResource(params?: X2tConvertParams) {
return params?.staticResource ?? getStaticResource();
@@ -471,19 +473,16 @@ async function initX2t(params?: X2tConvertParams): Promise {
x2t = (self as any).Module;
- // Wait for WASM runtime initialization
await new Promise((resolve) => {
x2t.onRuntimeInitialized = () => resolve();
});
- // Create working directories
try {
x2t.FS.mkdir("/working");
x2t.FS.mkdir("/working/media");
x2t.FS.mkdir("/working/fonts");
x2t.FS.mkdir("/working/themes");
} catch (err) {
- // Directories may already exist
console.error("[x2t.worker] mkdir error:", err);
}
@@ -491,7 +490,7 @@ async function initX2t(params?: X2tConvertParams): Promise {
}
/**
- * Ensure x2t is initialized before conversion
+ * @description 转换前确保 x2t 已完成初始化。
*/
async function ensureInit(params?: X2tConvertParams): Promise {
const staticResource = getWorkerStaticResource(params);
@@ -507,7 +506,7 @@ async function ensureInit(params?: X2tConvertParams): Promise {
}
/**
- * Clean up temporary files after conversion
+ * @description 转换完成后清理临时文件。
*/
function cleanupFiles(files: string[]): void {
for (const file of files) {
@@ -633,7 +632,7 @@ function writePdfBin(pdfBin?: Uint8Array) {
}
/**
- * Read media files from the working directory
+ * @description 从工作目录读取转换生成的媒体文件。
*/
function readMedia(): { [key: string]: Uint8Array } {
const media: { [key: string]: Uint8Array } = {};
@@ -762,7 +761,7 @@ ${content}
}
/**
- * Convert document from one format to another
+ * @description 将文档从一种格式转换为另一种格式。
*/
async function convert({
data,
@@ -869,7 +868,6 @@ async function convert({
console.error("ccall", e);
}
- // Read output file
let output: Uint8Array | null = null;
try {
output = x2t.FS.readFile(toPath);
@@ -892,11 +890,9 @@ async function convert({
throw new Error(`x2t conversion produced no output (${fileFrom} -> ${fileTo})`);
}
- // Read media files
const outputMedia = readMedia();
const outputThemes = readDirectoryFiles("/working/themes", "themes");
- // Cleanup temporary files
setTimeout(() => {
cleanupFiles(files);
});
@@ -904,7 +900,9 @@ async function convert({
return { output, media: outputMedia, themes: outputThemes };
}
-// Message types
+/**
+ * @description 主线程发送给 x2t worker 的消息结构。
+ */
interface WorkerMessage {
id?: number;
type: "convert";
@@ -912,7 +910,7 @@ interface WorkerMessage {
}
/**
- * Handle incoming messages from main thread
+ * @description 处理主线程发来的 worker 消息。
*/
self.onmessage = async (event: MessageEvent) => {
const { id, type, payload } = event.data;
@@ -920,11 +918,12 @@ self.onmessage = async (event: MessageEvent) => {
try {
switch (type) {
case "convert": {
- // Ensure x2t is initialized before conversion
await ensureInit(payload);
const result = await convert(payload);
- // Use Transferable objects for zero-copy transfer
+ /**
+ * @description 使用 Transferable 返回大块二进制结果,降低复制成本。
+ */
const transferables: Transferable[] = [];
if (result.output) {
transferables.push(result.output.buffer);
@@ -956,5 +955,7 @@ self.onmessage = async (event: MessageEvent) => {
}
};
-// Signal that worker is ready
+/**
+ * @description 通知主线程 worker 已加载。
+ */
self.postMessage({ type: "ready" });
diff --git a/src/components/onlyoffice-web-comp/internal/editor/xhr.ts b/src/components/onlyoffice-web-comp/internal/editor/xhr.ts
deleted file mode 100644
index aa1e1e7d..00000000
--- a/src/components/onlyoffice-web-comp/internal/editor/xhr.ts
+++ /dev/null
@@ -1,312 +0,0 @@
-export interface XHRMiddleware {
- (request: Request): Response | null | Promise;
-}
-
-export interface XHRProxyOptions {
- baseUrl?: string;
- shouldBypass?: (url: string, method: string) => boolean;
-}
-
-function isForbiddenRequestHeader(name: string) {
- const lowerName = name.toLowerCase();
- return (
- lowerName === "accept-charset" ||
- lowerName === "accept-encoding" ||
- lowerName === "access-control-request-headers" ||
- lowerName === "access-control-request-method" ||
- lowerName === "connection" ||
- lowerName === "content-length" ||
- lowerName === "cookie" ||
- lowerName === "cookie2" ||
- lowerName === "date" ||
- lowerName === "dnt" ||
- lowerName === "expect" ||
- lowerName === "host" ||
- lowerName === "keep-alive" ||
- lowerName === "origin" ||
- lowerName === "referer" ||
- lowerName === "te" ||
- lowerName === "trailer" ||
- lowerName === "transfer-encoding" ||
- lowerName === "upgrade" ||
- lowerName === "via" ||
- lowerName.startsWith("proxy-") ||
- lowerName.startsWith("sec-")
- );
-}
-
-/**
- * Creates an XMLHttpRequest proxy class that supports middleware
- * @param BaseXHR The original XMLHttpRequest class
- * @returns The enhanced XMLHttpRequest class
- */
-export function createXHRProxy(
- BaseXHR = globalThis.XMLHttpRequest,
- options: XHRProxyOptions = {},
-) {
- return class ProxyXMLHttpRequest extends BaseXHR {
- private static _middlewares: XHRMiddleware[] = [];
-
- private _isMocked: boolean = false;
- private _requestMethod: string = "GET";
- private _requestUrl: string = "";
- private _requestHeaders: Headers = new Headers();
- private _requestBody: any = null;
- private _responseHeaders: Headers = new Headers();
-
- /**
- * Register global middleware
- */
- static use(middleware: XHRMiddleware) {
- this._middlewares.push(middleware);
- }
-
- /**
- * Clear all middleware
- */
- static clearMiddlewares() {
- this._middlewares = [];
- }
-
- open(
- method: string,
- url: string | URL,
- async: boolean = true,
- username?: string | null,
- password?: string | null,
- ): void {
- const normalizedUrl = (() => {
- try {
- return options.baseUrl
- ? new URL(url.toString(), options.baseUrl).href
- : url.toString();
- } catch {
- return url.toString();
- }
- })();
-
- this._requestMethod = method;
- this._requestUrl = normalizedUrl;
- this._requestHeaders = new Headers();
- this._responseHeaders = new Headers();
- this._isMocked = false;
-
- // Call native open
- super.open(
- method,
- normalizedUrl,
- async,
- username ?? undefined,
- password ?? undefined,
- );
- }
-
- setRequestHeader(name: string, value: string): void {
- if (isForbiddenRequestHeader(name)) {
- return;
- }
-
- this._requestHeaders.append(name, value);
-
- // If it is not a mock request, also set it on the native XHR
- if (!this._isMocked) {
- super.setRequestHeader(name, value);
- }
- }
-
- getResponseHeader(name: string): string | null {
- if (this._isMocked) {
- return this._responseHeaders.get(name);
- }
- return super.getResponseHeader(name);
- }
-
- getAllResponseHeaders(): string {
- if (!this._isMocked) {
- return super.getAllResponseHeaders();
- }
- return Array.from(this._responseHeaders.entries())
- .map(([key, value]) => `${key}: ${value}`)
- .join("\r\n");
- }
-
- send(body?: Document | XMLHttpRequestBodyInit | null): void {
- this._requestBody = body;
-
- if (options.shouldBypass?.(this._requestUrl, this._requestMethod)) {
- super.send(body);
- return;
- }
-
- // Try to run middleware
- this._tryMiddlewares()
- .then((handled) => {
- if (!handled) {
- // No middleware handled it, use native send
- super.send(body);
- }
- })
- .catch((err) => {
- console.error("ProxyXMLHttpRequest middleware error:", err);
- // Fallback to native implementation on error
- super.send(body);
- });
- }
-
- private async _tryMiddlewares(): Promise {
- // Create Request object
- let request: Request;
- try {
- const reqInit: RequestInit = {
- method: this._requestMethod,
- headers: this._requestHeaders,
- body: this._requestBody as BodyInit,
- mode: "cors",
- };
-
- if (this.withCredentials) {
- reqInit.credentials = "include";
- }
-
- request = new Request(this._requestUrl, reqInit);
- console.log("ProxyXHR created request:", {
- url: this._requestUrl,
- method: request.method,
- hasBody: !!request.body,
- originalBody: this._requestBody,
- });
- } catch (e) {
- // Unable to create Request, do not use middleware
- return false;
- }
-
- // Run middleware
- for (const mw of ProxyXMLHttpRequest._middlewares) {
- const response = await mw(request.clone());
- if (response) {
- this._isMocked = true;
- await this._handleMockResponse(response);
- return true;
- }
- }
-
- return false;
- }
-
- private async _handleMockResponse(response: Response) {
- this._responseHeaders = new Headers(response.headers);
-
- const emit = (event: Event) => {
- this.dispatchEvent(event);
- };
-
- // 1. Trigger loadstart
- emit(new ProgressEvent("loadstart"));
-
- // 2. HEADERS_RECEIVED (readyState = 2)
- Object.defineProperty(this, "readyState", {
- value: 2,
- writable: false,
- configurable: true,
- });
- emit(new Event("readystatechange"));
-
- // 3. LOADING (readyState = 3)
- Object.defineProperty(this, "readyState", {
- value: 3,
- writable: false,
- configurable: true,
- });
- emit(new Event("readystatechange"));
-
- try {
- // Read response body
- let responseData: any;
-
- if (this.responseType === "json") {
- responseData = await response.json();
- } else if (this.responseType === "arraybuffer") {
- responseData = await response.arrayBuffer();
- } else if (this.responseType === "blob") {
- responseData = await response.blob();
- } else if (this.responseType === "document") {
- const text = await response.text();
- responseData = new DOMParser().parseFromString(text, "text/xml");
- } else {
- responseData = await response.text();
- }
-
- // Set response properties
- Object.defineProperty(this, "status", {
- value: response.status,
- writable: false,
- configurable: true,
- });
-
- Object.defineProperty(this, "statusText", {
- value: response.statusText,
- writable: false,
- configurable: true,
- });
-
- Object.defineProperty(this, "response", {
- value: responseData,
- writable: false,
- configurable: true,
- });
-
- Object.defineProperty(this, "responseText", {
- value:
- typeof responseData === "string"
- ? responseData
- : JSON.stringify(responseData),
- writable: false,
- configurable: true,
- });
-
- Object.defineProperty(this, "responseURL", {
- value: response.url,
- writable: false,
- configurable: true,
- });
-
- // 4. Trigger progress event
- emit(
- new ProgressEvent("progress", {
- lengthComputable: true,
- loaded: 100,
- total: 100,
- }),
- );
-
- // 5. DONE (readyState = 4)
- Object.defineProperty(this, "readyState", {
- value: 4,
- writable: false,
- configurable: true,
- });
- emit(new Event("readystatechange"));
-
- // 6. Trigger load event
- emit(new ProgressEvent("load"));
-
- // 7. Trigger loadend event
- emit(new ProgressEvent("loadend"));
- } catch (e) {
- console.error("ProxyXHR: error handling response", e);
-
- // Set readyState to DONE
- Object.defineProperty(this, "readyState", {
- value: 4,
- writable: false,
- configurable: true,
- });
- emit(new Event("readystatechange"));
-
- // Trigger error event
- emit(new ProgressEvent("error"));
- emit(new ProgressEvent("loadend"));
- }
- }
- };
-}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/zip.ts b/src/components/onlyoffice-web-comp/internal/editor/zip.ts
deleted file mode 100644
index ecf15b26..00000000
--- a/src/components/onlyoffice-web-comp/internal/editor/zip.ts
+++ /dev/null
@@ -1,333 +0,0 @@
-const crcTable = (() => {
- const table = new Uint32Array(256);
- for (let i = 0; i < 256; i++) {
- let c = i;
- for (let k = 0; k < 8; k++) {
- c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
- }
- table[i] = c >>> 0;
- }
- return table;
-})();
-
-export function crc32(data: Uint8Array) {
- let crc = 0xffffffff;
- for (const byte of data) {
- crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8);
- }
- return (crc ^ 0xffffffff) >>> 0;
-}
-
-export function readU16(data: Uint8Array, offset: number) {
- return data[offset] | (data[offset + 1] << 8);
-}
-
-export function readU32(data: Uint8Array, offset: number) {
- return (
- data[offset] |
- (data[offset + 1] << 8) |
- (data[offset + 2] << 16) |
- (data[offset + 3] << 24)
- ) >>> 0;
-}
-
-export function writeU16(data: Uint8Array, offset: number, value: number) {
- data[offset] = value & 0xff;
- data[offset + 1] = (value >>> 8) & 0xff;
-}
-
-export function writeU32(data: Uint8Array, offset: number, value: number) {
- data[offset] = value & 0xff;
- data[offset + 1] = (value >>> 8) & 0xff;
- data[offset + 2] = (value >>> 16) & 0xff;
- data[offset + 3] = (value >>> 24) & 0xff;
-}
-
-export function concatBytes(parts: Uint8Array[]) {
- const size = parts.reduce((total, part) => total + part.length, 0);
- const output = new Uint8Array(size);
- let offset = 0;
- for (const part of parts) {
- output.set(part, offset);
- offset += part.length;
- }
- return output;
-}
-
-async function decompressDeflateRaw(data: Uint8Array) {
- if (!("DecompressionStream" in globalThis)) {
- return null;
- }
-
- const stream = new Blob([data as Uint8Array])
- .stream()
- .pipeThrough(new DecompressionStream("deflate-raw"));
- return new Uint8Array(await new Response(stream).arrayBuffer());
-}
-
-export type ZipEntry = {
- name: string;
- nameBytes: Uint8Array;
- method: number;
- modTime: number;
- modDate: number;
- crc: number;
- compressedSize: number;
- uncompressedSize: number;
- compressedData: Uint8Array;
- internalAttrs: number;
- externalAttrs: number;
-};
-
-export function readZipEntries(data: ArrayBuffer) {
- const input = new Uint8Array(data);
- let eocd = -1;
- for (let i = input.length - 22; i >= Math.max(0, input.length - 65558); i--) {
- if (readU32(input, i) === 0x06054b50) {
- eocd = i;
- break;
- }
- }
- if (eocd < 0) return null;
-
- const entryCount = readU16(input, eocd + 10);
- const centralOffset = readU32(input, eocd + 16);
- const decoder = new TextDecoder();
- const entries: ZipEntry[] = [];
- let readOffset = centralOffset;
-
- for (let i = 0; i < entryCount; i++) {
- if (readU32(input, readOffset) !== 0x02014b50) return null;
-
- const method = readU16(input, readOffset + 10);
- const modTime = readU16(input, readOffset + 12);
- const modDate = readU16(input, readOffset + 14);
- const crc = readU32(input, readOffset + 16);
- const compressedSize = readU32(input, readOffset + 20);
- const uncompressedSize = readU32(input, readOffset + 24);
- const nameLength = readU16(input, readOffset + 28);
- const extraLength = readU16(input, readOffset + 30);
- const commentLength = readU16(input, readOffset + 32);
- const internalAttrs = readU16(input, readOffset + 36);
- const externalAttrs = readU32(input, readOffset + 38);
- const localOffset = readU32(input, readOffset + 42);
- const nameBytes = input.slice(readOffset + 46, readOffset + 46 + nameLength);
- const name = decoder.decode(nameBytes);
-
- if (readU32(input, localOffset) !== 0x04034b50) return null;
- const localNameLength = readU16(input, localOffset + 26);
- const localExtraLength = readU16(input, localOffset + 28);
- const dataStart = localOffset + 30 + localNameLength + localExtraLength;
- const compressedData = input.slice(dataStart, dataStart + compressedSize);
-
- entries.push({
- name,
- nameBytes,
- method,
- modTime,
- modDate,
- crc,
- compressedSize,
- uncompressedSize,
- compressedData,
- internalAttrs,
- externalAttrs,
- });
-
- readOffset += 46 + nameLength + extraLength + commentLength;
- }
-
- return entries;
-}
-
-export async function readZipEntryData(entry: ZipEntry) {
- if (entry.method === 0) {
- return entry.compressedData;
- }
- if (entry.method === 8) {
- return decompressDeflateRaw(entry.compressedData);
- }
- return null;
-}
-
-function buildLocalHeader(
- entry: Pick,
- crc: number,
- compressedSize: number,
- uncompressedSize: number,
-) {
- const localHeader = new Uint8Array(30 + entry.nameBytes.length);
- writeU32(localHeader, 0, 0x04034b50);
- writeU16(localHeader, 4, 20);
- writeU16(localHeader, 6, 0);
- writeU16(localHeader, 8, entry.method);
- writeU16(localHeader, 10, entry.modTime);
- writeU16(localHeader, 12, entry.modDate);
- writeU32(localHeader, 14, crc);
- writeU32(localHeader, 18, compressedSize);
- writeU32(localHeader, 22, uncompressedSize);
- writeU16(localHeader, 26, entry.nameBytes.length);
- writeU16(localHeader, 28, 0);
- localHeader.set(entry.nameBytes, 30);
- return localHeader;
-}
-
-function buildCentralHeader(
- entry: Pick<
- ZipEntry,
- | "nameBytes"
- | "method"
- | "modTime"
- | "modDate"
- | "internalAttrs"
- | "externalAttrs"
- >,
- crc: number,
- compressedSize: number,
- uncompressedSize: number,
- localOffset: number,
-) {
- const centralHeader = new Uint8Array(46 + entry.nameBytes.length);
- writeU32(centralHeader, 0, 0x02014b50);
- writeU16(centralHeader, 4, 20);
- writeU16(centralHeader, 6, 20);
- writeU16(centralHeader, 8, 0);
- writeU16(centralHeader, 10, entry.method);
- writeU16(centralHeader, 12, entry.modTime);
- writeU16(centralHeader, 14, entry.modDate);
- writeU32(centralHeader, 16, crc);
- writeU32(centralHeader, 20, compressedSize);
- writeU32(centralHeader, 24, uncompressedSize);
- writeU16(centralHeader, 28, entry.nameBytes.length);
- writeU16(centralHeader, 30, 0);
- writeU16(centralHeader, 32, 0);
- writeU16(centralHeader, 34, 0);
- writeU16(centralHeader, 36, entry.internalAttrs);
- writeU32(centralHeader, 38, entry.externalAttrs);
- writeU32(centralHeader, 42, localOffset);
- centralHeader.set(entry.nameBytes, 46);
- return centralHeader;
-}
-
-export type ZipReplacement = {
- data: Uint8Array;
- modTime?: number;
- modDate?: number;
-};
-
-export type ZipXmlSizeStats = {
- totalSize: number;
- entryCount: number;
-};
-
-export function isOfficeXmlEntryName(name: string) {
- return /\.(xml|rels)$/i.test(name);
-}
-
-export function getZipXmlUncompressedSize(
- entries: readonly ZipEntry[],
-): ZipXmlSizeStats {
- let totalSize = 0;
- let entryCount = 0;
-
- for (const entry of entries) {
- if (entry.name.endsWith("/") || !isOfficeXmlEntryName(entry.name)) {
- continue;
- }
-
- totalSize += entry.uncompressedSize;
- entryCount += 1;
- }
-
- return { totalSize, entryCount };
-}
-
-export function writeZipEntries(
- entries: ZipEntry[],
- replacements: Map,
-) {
- const encoder = new TextEncoder();
- const localParts: Uint8Array[] = [];
- const centralParts: Uint8Array[] = [];
- let outputOffset = 0;
- const emitted = new Set();
- let emittedCount = 0;
-
- const emit = (
- entry: Pick<
- ZipEntry,
- | "name"
- | "nameBytes"
- | "method"
- | "modTime"
- | "modDate"
- | "crc"
- | "compressedData"
- | "uncompressedSize"
- | "internalAttrs"
- | "externalAttrs"
- >,
- replacement?: ZipReplacement,
- ) => {
- const outputData = replacement?.data ?? entry.compressedData;
- const method = replacement ? 0 : entry.method;
- const crc = replacement ? crc32(outputData) : entry.crc;
- const uncompressedSize = replacement
- ? outputData.length
- : entry.uncompressedSize;
- const modTime = replacement?.modTime ?? entry.modTime;
- const modDate = replacement?.modDate ?? entry.modDate;
- const nextEntry = { ...entry, method, modTime, modDate };
-
- const localHeader = buildLocalHeader(
- nextEntry,
- crc,
- outputData.length,
- uncompressedSize,
- );
- localParts.push(localHeader, outputData);
- centralParts.push(
- buildCentralHeader(
- nextEntry,
- crc,
- outputData.length,
- uncompressedSize,
- outputOffset,
- ),
- );
- outputOffset += localHeader.length + outputData.length;
- emitted.add(entry.name);
- emittedCount += 1;
- };
-
- for (const entry of entries) {
- emit(entry, replacements.get(entry.name));
- }
-
- for (const [name, replacement] of replacements) {
- if (emitted.has(name)) continue;
- const nameBytes = encoder.encode(name);
- emit({
- name,
- nameBytes,
- method: 0,
- modTime: replacement.modTime ?? 0,
- modDate: replacement.modDate ?? 0,
- crc: 0,
- compressedData: replacement.data,
- uncompressedSize: replacement.data.length,
- internalAttrs: 0,
- externalAttrs: 0,
- });
- }
-
- const centralDirectory = concatBytes(centralParts);
- const end = new Uint8Array(22);
- writeU32(end, 0, 0x06054b50);
- writeU16(end, 8, emittedCount);
- writeU16(end, 10, emittedCount);
- writeU32(end, 12, centralDirectory.length);
- writeU32(end, 16, outputOffset);
-
- return concatBytes([...localParts, centralDirectory, end]).buffer;
-}
diff --git a/src/components/onlyoffice-web-comp/readme.md b/src/components/onlyoffice-web-comp/readme.md
index dbc71850..e2060b44 100644
--- a/src/components/onlyoffice-web-comp/readme.md
+++ b/src/components/onlyoffice-web-comp/readme.md
@@ -4,32 +4,32 @@
A **browser-side document editor component library** built on the OnlyOffice static SDK. Supports online editing, read-only preview, export, and x2t conversion for Word, Excel, and PowerPoint. **No self-hosted Document Server required**—only static SDK assets on your site.
-> This file is an **entry point**. Full documentation lives in [`docs/`](./docs/00-概述.md). On the demo site, these Markdown files are rendered at `/docs`.
+> This file is an **entry point**. Full documentation lives in [`docs/`](./docs/概述.md). On the demo site, these Markdown files are rendered at `/docs`.
## Documentation
| # | Doc | Description |
|---|-----|-------------|
-| 00 | [Overview](./docs/00-概述.md) | Index and reading path |
-| 01 | [Quick Start](./docs/01-快速开始.md) | Init, container mount, create editor |
-| 02 | [Core API](./docs/02-核心API.md) | `OnlyOfficeManager`, `EditorManager`, multi-instance |
-| 03 | [Event System](./docs/03-事件系统.md) | EventBus, event types, listeners |
-| 04 | [Full Examples](./docs/04-完整示例.md) | React integration patterns |
-| 05 | [API Reference](./docs/05-API参考.md) | Constants and types |
-| 06 | [Notes & Formats](./docs/06-注意事项与支持格式.md) | Prerequisites, formats, pitfalls |
-| 07 | [Comments, Revisions & Word API](./docs/07-批注修订与-Word-API.md) | Comments, revisions, SDK callbacks |
-| 10 | [Fonts](./docs/10-字体配置.md) | `__custom_font_registry__`, catalog conversion |
-| 08 | [Single-instance Demo](./docs/08-单实例示例.md) | Single editor demo + source walkthrough |
-| 09 | [Multi-instance Demo](./docs/09-多实例示例.md) | Full Tab demo source |
+| - | [Overview](./docs/概述.md) | Index and reading path |
+| - | [Quick Start](./docs/快速开始.md) | Init, container mount, create editor |
+| - | [Core API](./docs/核心API.md) | `OnlyOfficeManager`, `EditorManager`, multi-instance |
+| - | [Event System](./docs/事件系统.md) | EventBus, event types, listeners |
+| - | [Full Examples](./docs/完整示例.md) | React integration patterns |
+| - | [API Reference](./docs/API参考.md) | Constants and types |
+| - | [Notes & Formats](./docs/注意事项与支持格式.md) | Prerequisites, formats, pitfalls |
+| - | [Comments, Revisions & Word API](./docs/批注修订与-Word-API.md) | Comments, revisions, SDK callbacks |
+| - | [Fonts](./docs/字体配置.md) | `__custom_font_registry__`, catalog conversion |
+| - | [Single-instance Demo](./docs/单实例示例.md) | Single editor demo + source walkthrough |
+| - | [Multi-instance Demo](./docs/多实例示例.md) | Full Tab demo source |
**Suggested paths**
| Scenario | Path |
|----------|------|
-| First integration | [01](./docs/01-快速开始.md) → [02](./docs/02-核心API.md) |
-| Try live demos | [08](./docs/08-单实例示例.md) · [09](./docs/09-多实例示例.md) |
-| React page integration | [04](./docs/04-完整示例.md) |
-| Multi-instance / export | [02](./docs/02-核心API.md) · [03](./docs/03-事件系统.md) |
+| First integration | [快速开始](./docs/快速开始.md) → [核心 API](./docs/核心API.md) |
+| Try live demos | [单实例示例](./docs/单实例示例.md) · [多实例示例](./docs/多实例示例.md) |
+| React page integration | [完整示例](./docs/完整示例.md) |
+| Multi-instance / export | [核心 API](./docs/核心API.md) · [事件系统](./docs/事件系统.md) |
## Package Layout
@@ -70,7 +70,7 @@ await OnlyOfficeManager.createWithFile({
}, file);
```
-See [docs/02-核心API.md](./docs/02-核心API.md) for events, export, multi-instance, theme, language, and read-only toggling.
+See [docs/核心API.md](./docs/核心API.md) for events, export, multi-instance, theme, language, and read-only toggling.
## Demos in This Repo
@@ -79,8 +79,8 @@ Documentation Markdown under `docs/` is rendered by the demo site. Live editors
| Route | Description |
|-------|-------------|
| `/docs` | All docs from this `docs/` folder |
-| `/docs/demos?tab=single` | Single-instance demo ([08](./docs/08-单实例示例.md)) |
-| `/docs/demos?tab=multi` | Multi-instance Tab demo ([09](./docs/09-多实例示例.md)) |
+| `/docs/demos?tab=single` | Single-instance demo ([单实例示例](./docs/单实例示例.md)) |
+| `/docs/demos?tab=multi` | Multi-instance Tab demo ([多实例示例](./docs/多实例示例.md)) |
Demo components: `src/features/demo/` (`office-preview-page.tsx`, `tabs-multi-page.tsx`)
diff --git a/src/components/onlyoffice-web-comp/readme.zh.md b/src/components/onlyoffice-web-comp/readme.zh.md
index ada215ae..73a2b7c6 100644
--- a/src/components/onlyoffice-web-comp/readme.zh.md
+++ b/src/components/onlyoffice-web-comp/readme.zh.md
@@ -4,32 +4,32 @@
基于 OnlyOffice 静态 SDK 的 **Web 端文档编辑组件库**,支持 Word / Excel / PowerPoint 的在线编辑、只读预览、导出与 x2t 格式转换。**无需自建 Document Server**,只需托管 SDK 静态资源。
-> 本文档为**入口页**。完整说明见 [`docs/`](./docs/00-概述.md)。演示站点会直接渲染本目录下的 Markdown,对应路由 `/docs`。
+> 本文档为**入口页**。完整说明见 [`docs/`](./docs/概述.md)。演示站点会直接渲染本目录下的 Markdown,对应路由 `/docs`。
## 文档导航
| 编号 | 文档 | 说明 |
|------|------|------|
-| 00 | [概述](./docs/00-概述.md) | 文档索引与阅读路径 |
-| 01 | [快速开始](./docs/01-快速开始.md) | 初始化、容器挂载、创建编辑器 |
-| 02 | [核心 API](./docs/02-核心API.md) | `OnlyOfficeManager`、`EditorManager`、多实例 |
-| 03 | [事件系统](./docs/03-事件系统.md) | EventBus、事件类型与监听 |
-| 04 | [完整示例](./docs/04-完整示例.md) | React 集成模式 |
-| 05 | [API 参考](./docs/05-API参考.md) | 常量、类型定义 |
-| 06 | [注意事项与支持格式](./docs/06-注意事项与支持格式.md) | 前置条件、文件格式、常见坑 |
-| 07 | [批注修订与 Word API](./docs/07-批注修订与-Word-API.md) | 批注、修订、SDK 回调 |
-| 10 | [字体配置](./docs/10-字体配置.md) | `__custom_font_registry__`、catalog 转换 |
-| 08 | [单实例示例](./docs/08-单实例示例.md) | 单编辑器 Demo 与源码说明 |
-| 09 | [多实例示例](./docs/09-多实例示例.md) | Tab 多实例完整源码 |
+| - | [概述](./docs/概述.md) | 文档索引与阅读路径 |
+| - | [快速开始](./docs/快速开始.md) | 初始化、容器挂载、创建编辑器 |
+| - | [核心 API](./docs/核心API.md) | `OnlyOfficeManager`、`EditorManager`、多实例 |
+| - | [事件系统](./docs/事件系统.md) | EventBus、事件类型与监听 |
+| - | [完整示例](./docs/完整示例.md) | React 集成模式 |
+| - | [API 参考](./docs/API参考.md) | 常量、类型定义 |
+| - | [注意事项与支持格式](./docs/注意事项与支持格式.md) | 前置条件、文件格式、常见坑 |
+| - | [批注修订与 Word API](./docs/批注修订与-Word-API.md) | 批注、修订、SDK 回调 |
+| - | [字体配置](./docs/字体配置.md) | `__custom_font_registry__`、catalog 转换 |
+| - | [单实例示例](./docs/单实例示例.md) | 单编辑器 Demo 与源码说明 |
+| - | [多实例示例](./docs/多实例示例.md) | Tab 多实例完整源码 |
**推荐阅读路径**
| 场景 | 路径 |
|------|------|
-| 首次接入 | [01](./docs/01-快速开始.md) → [02](./docs/02-核心API.md) |
-| 在线体验 | [08](./docs/08-单实例示例.md) · [09](./docs/09-多实例示例.md) |
-| React 页面集成 | [04](./docs/04-完整示例.md) |
-| 多实例 / 导出 | [02](./docs/02-核心API.md) · [03](./docs/03-事件系统.md) |
+| 首次接入 | [快速开始](./docs/快速开始.md) → [核心 API](./docs/核心API.md) |
+| 在线体验 | [单实例示例](./docs/单实例示例.md) · [多实例示例](./docs/多实例示例.md) |
+| React 页面集成 | [完整示例](./docs/完整示例.md) |
+| 多实例 / 导出 | [核心 API](./docs/核心API.md) · [事件系统](./docs/事件系统.md) |
## 目录结构
@@ -70,7 +70,7 @@ await OnlyOfficeManager.createWithFile({
}, file);
```
-更多用法(事件、导出、多实例、主题、语言、只读切换)见 [docs/02-核心API.md](./docs/02-核心API.md)。
+更多用法(事件、导出、多实例、主题、语言、只读切换)见 [docs/核心API.md](./docs/核心API.md)。
## 本仓库中的演示
@@ -79,8 +79,8 @@ await OnlyOfficeManager.createWithFile({
| 路由 | 说明 |
|------|------|
| `/docs` | 渲染本目录全部文档 |
-| `/docs/demos?tab=single` | 单实例在线示例([08](./docs/08-单实例示例.md)) |
-| `/docs/demos?tab=multi` | 多实例 Tab 示例([09](./docs/09-多实例示例.md)) |
+| `/docs/demos?tab=single` | 单实例在线示例([单实例示例](./docs/单实例示例.md)) |
+| `/docs/demos?tab=multi` | 多实例 Tab 示例([多实例示例](./docs/多实例示例.md)) |
演示组件:`src/features/demo/`(`office-preview-page.tsx`、`tabs-multi-page.tsx`)
diff --git a/src/components/onlyoffice-web-comp/store/document.ts b/src/components/onlyoffice-web-comp/store/document.ts
index fda5f050..c0ed3ab6 100644
--- a/src/components/onlyoffice-web-comp/store/document.ts
+++ b/src/components/onlyoffice-web-comp/store/document.ts
@@ -45,14 +45,14 @@ function getScopeId(scopeId?: string) {
return scopeId || DEFAULT_DOCUMENT_SCOPE;
}
-export function setDocmentObj(
+export function setDocumentObj(
state: SetOnlyOfficeDocumentStateInput,
scopeId?: string,
) {
documentStates.set(getScopeId(scopeId), normalizeDocumentState(state));
}
-export function getDocmentObj(scopeId?: string) {
+export function getDocumentObj(scopeId?: string) {
const key = getScopeId(scopeId);
let state = documentStates.get(key);
if (!state) {
@@ -61,7 +61,7 @@ export function getDocmentObj(scopeId?: string) {
}
return state;
}
-export function clearDocmentObj(scopeId?: string) {
+export function clearDocumentObj(scopeId?: string) {
if (scopeId) {
documentStates.delete(scopeId);
return;
@@ -71,7 +71,7 @@ export function clearDocmentObj(scopeId?: string) {
}
export function setNewDocument(fileType = "docx", scopeId?: string) {
- setDocmentObj(
+ setDocumentObj(
{
isNew: true,
fileName: `New Document.${fileType}`,
@@ -86,7 +86,7 @@ export function setDocumentFile(
fileName = file.name,
scopeId?: string,
) {
- setDocmentObj(
+ setDocumentObj(
{
isNew: false,
file,
@@ -112,7 +112,7 @@ export function setDocumentUrl(
) {
const name = fileName || decodeURIComponent(url.split("/").pop() || "Document");
- setDocmentObj(
+ setDocumentObj(
{
isNew: false,
url,
@@ -124,11 +124,11 @@ export function setDocumentUrl(
);
}
-export function clearAllDocmentObjs() {
+export function clearAllDocumentObjs() {
documentStates.clear();
documentStates.set(DEFAULT_DOCUMENT_SCOPE, createDefaultDocumentState());
}
-export function getAllDocmentObjs() {
+export function getAllDocumentObjs() {
return new Map(documentStates);
}
diff --git a/src/components/onlyoffice-web-comp/type/sdk-internal.ts b/src/components/onlyoffice-web-comp/type/sdk-internal.ts
index 9206f287..227d512a 100644
--- a/src/components/onlyoffice-web-comp/type/sdk-internal.ts
+++ b/src/components/onlyoffice-web-comp/type/sdk-internal.ts
@@ -8,7 +8,7 @@
* - 主编辑器 iframe:web-apps/apps/{documenteditor|spreadsheeteditor|presentationeditor}/main/index.html
* - PPT 演示者视图 popup:web-apps/apps/presentationeditor/main/index.reporter.html
* - 协作 Mock 注入:core/editor-manager.ts → installIframeProxies() /
- * internal/editor/install-proxies.ts
+ * internal/editor/runtime-bridge.ts
*/
/** Word 逻辑文档运行时(SDK 混淆名,如 ra.Ea);仅声明接入层用到的字段。 */
@@ -39,7 +39,7 @@ export type OnlyOfficeIframeWindow = typeof window & {
/**
* XHR / fetch / socket.io 已代理到内存 EditorServer。
- * 写入:install-proxies.ts → installOnlyOfficeProxies()
+ * 写入:runtime-bridge.ts → installOnlyOfficeProxies()
* 读取:EditorManager.installIframeProxies() 防重复安装。
*/
__ONLYOFFICE_PROXIES_INSTALLED__?: boolean;
@@ -53,7 +53,7 @@ export type OnlyOfficeIframeWindow = typeof window & {
/**
* 演示者视图(Reporter)跨窗口注入入口。
- * 写入:install-proxies.ts → installReporterWindowHook()(挂在 iframe window 上)
+ * 写入:runtime-bridge.ts → installReporterWindowHook()(挂在 iframe window 上)
* 读取:index.reporter.html 启动脚本通过 window.opener 链调用 install(target),
* 在 RequireJS 加载 SDK 前完成 Mock 注入。
*/
@@ -63,7 +63,7 @@ export type OnlyOfficeIframeWindow = typeof window & {
/**
* iframe 的 window.open 已劫持,用于在打开 index.reporter.html 时尽早注入 Mock。
- * 写入:install-proxies.ts → installReporterWindowHook()
+ * 写入:runtime-bridge.ts → installReporterWindowHook()
*/
__ONLYOFFICE_REPORTER_HOOK__?: boolean;
};
diff --git a/src/components/onlyoffice-web-comp/util/x2t.ts b/src/components/onlyoffice-web-comp/util/x2t.ts
index 6c750d21..d816eeb4 100644
--- a/src/components/onlyoffice-web-comp/util/x2t.ts
+++ b/src/components/onlyoffice-web-comp/util/x2t.ts
@@ -9,6 +9,7 @@ import {
editorManagerFactory,
} from "../core/editor-manager";
import type { FileType } from "../const";
+import type { EditorLogger } from "../internal/editor/logger";
export async function createEditorView(options: CreateEditorViewOptions) {
const manager =
@@ -27,19 +28,23 @@ export async function convertBinToDocument(
fileType: FileType | string,
media?: Record,
themes?: Record,
+ logger?: EditorLogger,
) {
const targetExt = normalizeX2tExportFileType(fileType);
const data = new Uint8Array(binData).buffer;
const { formatFrom, formatTo } = getX2tExportFormats(targetExt);
- const result = await converter.convert({
- data,
- fileFrom: "Editor.bin",
- fileTo: `doc.${targetExt}`,
- formatFrom,
- formatTo,
- media,
- themes,
- });
+ const result = await converter.convert(
+ {
+ data,
+ fileFrom: "Editor.bin",
+ fileTo: `doc.${targetExt}`,
+ formatFrom,
+ formatTo,
+ media,
+ themes,
+ },
+ logger,
+ );
if (!result.output) {
throw new Error("Failed to convert OnlyOffice bin document");
diff --git a/src/features/demo/office-preview-page.tsx b/src/features/demo/office-preview-page.tsx
index e3fa5149..797d074c 100644
--- a/src/features/demo/office-preview-page.tsx
+++ b/src/features/demo/office-preview-page.tsx
@@ -2,7 +2,7 @@
/**
* 单实例演示页:OnlyOfficeManager 门面 + 工具栏(上传/导出/主题/语言/只读)。
- * 文档说明见 `onlyoffice-web-comp/docs/08-单实例示例.md`。
+ * 文档说明见 `onlyoffice-web-comp/docs/单实例示例.md`。
*/
import { memo, useEffect, useRef, useState } from "react";
import {
@@ -275,6 +275,10 @@ export function OfficePreviewPage({
await manager.downloadExport();
}, "导出失败");
+ const handlePrintLogs = () => {
+ managerRef.current?.printLogs();
+ };
+
const handleToggleReadOnly = () =>
runAction(async () => {
const manager = managerRef.current;
@@ -314,6 +318,7 @@ export function OfficePreviewPage({
{editorReady && (
<>
导出
+ 打印日志
{readOnly ? "只读" : "编辑"}
diff --git a/src/features/demo/tabs-multi-page.tsx b/src/features/demo/tabs-multi-page.tsx
index 168e151f..064c88e1 100644
--- a/src/features/demo/tabs-multi-page.tsx
+++ b/src/features/demo/tabs-multi-page.tsx
@@ -2,7 +2,7 @@
/**
* 多实例 Tab 演示:onlyOfficeManagerFactory 按 containerId 隔离,切换 Tab 时隐藏不销毁。
- * 文档与完整源码说明见 `onlyoffice-web-comp/docs/09-多实例示例.md`。
+ * 文档与完整源码说明见 `onlyoffice-web-comp/docs/多实例示例.md`。
*/
import { memo, useEffect, useRef, useState } from "react";
import { nanoid } from "nanoid";
@@ -347,6 +347,12 @@ export function TabsMultiPage({ embedded = false }: { embedded?: boolean }) {
await manager.downloadExport();
}, "导出失败");
+ const printActiveLogs = () =>
+ runAction(async () => {
+ const manager = await ensureActiveManager();
+ manager.printLogs();
+ }, "打印日志失败");
+
const toggleReadOnly = () =>
runAction(async () => {
if (!activeTab) return;
@@ -428,6 +434,7 @@ export function TabsMultiPage({ embedded = false }: { embedded?: boolean }) {
新建{activePreset?.label ?? "文档"}
导出
+ 打印日志
{activeTab?.readOnly ? "只读" : "编辑"}
diff --git a/src/features/docs/components/docs-demo-page.tsx b/src/features/docs/components/docs-demo-page.tsx
new file mode 100644
index 00000000..2f8b0586
--- /dev/null
+++ b/src/features/docs/components/docs-demo-page.tsx
@@ -0,0 +1,50 @@
+"use client";
+
+import type { ReactNode } from "react";
+import { useMemo } from "react";
+import { getTocHeadings } from "../lib/extract-headings";
+import { DocToc } from "./doc-toc";
+import { MarkdownContent } from "./markdown-content";
+
+type DocsDemoPageProps = {
+ content: string;
+ marker: "single" | "multi";
+ children: ReactNode;
+};
+
+function splitDemoMarkdown(content: string, marker: string) {
+ const slot = ``;
+ const index = content.indexOf(slot);
+ if (index < 0) {
+ return { before: content.trim(), after: "" };
+ }
+
+ return {
+ before: content.slice(0, index).trim(),
+ after: content.slice(index + slot.length).trim(),
+ };
+}
+
+export function DocsDemoPage({ content, marker, children }: DocsDemoPageProps) {
+ const { before, after } = useMemo(
+ () => splitDemoMarkdown(content, marker),
+ [content, marker],
+ );
+ const tocHeadings = useMemo(() => getTocHeadings(content), [content]);
+
+ return (
+
+
+
+
+ {after ? : null}
+
+
+
+ );
+}
diff --git a/src/features/docs/components/docs-demos-tabs-page.tsx b/src/features/docs/components/docs-demos-tabs-page.tsx
deleted file mode 100644
index f76f845a..00000000
--- a/src/features/docs/components/docs-demos-tabs-page.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-"use client";
-
-/**
- * 示例页:按侧栏 `?tab=` 渲染对应 Markdown,并在简介后嵌入 live demo。
- * Markdown 源文件:`onlyoffice-web-comp/docs/08|09-*.md`
- */
-import { useMemo } from "react";
-import { useSearchParams } from "next/navigation";
-import { OfficePreviewPage } from "@/features/demo/office-preview-page";
-import { TabsMultiPage } from "@/features/demo/tabs-multi-page";
-import { FILE_TYPE } from "@/components/onlyoffice-web-comp";
-import type { DemoTabId } from "../config/site-map";
-import { getTocHeadings } from "../lib/extract-headings";
-import { DocToc } from "./doc-toc";
-import { MarkdownContent } from "./markdown-content";
-
-type DocsDemosTabsPageProps = {
- contents: Record;
-};
-
-function splitDemoMarkdown(content: string) {
- const [intro, rest = ""] = content.split(/\n## 在线演示\n/);
- const body = rest
- .replace(/\s*/g, "")
- .replace(/^[^\n]+\n?/, "")
- .trim();
-
- return { intro: intro.trim(), body };
-}
-
-function isDemoTab(value: string | null): DemoTabId {
- return value === "multi" ? "multi" : "single";
-}
-
-export function DocsDemosTabsPage({ contents }: DocsDemosTabsPageProps) {
- const searchParams = useSearchParams();
- const activeTab = isDemoTab(searchParams.get("tab"));
- const fullContent = contents[activeTab];
- const { intro, body } = splitDemoMarkdown(fullContent);
- const tocHeadings = useMemo(() => getTocHeadings(fullContent), [fullContent]);
-
- return (
-
-
-
-
-
-
- {body ?
: null}
-
-
-
- );
-}
diff --git a/src/features/docs/components/docs-shell.tsx b/src/features/docs/components/docs-shell.tsx
index 18a45144..18e15b56 100644
--- a/src/features/docs/components/docs-shell.tsx
+++ b/src/features/docs/components/docs-shell.tsx
@@ -2,15 +2,14 @@
/**
* 文档站布局:左侧目录 + 右侧 Markdown 内容区。
- * 目录数据来自 {@link ../config/site-map.ts};示例项通过 `?tab=` 区分激活态。
+ * 目录数据来自 {@link ../config/site-map.ts};示例项使用独立路由区分激活态。
*/
import Link from "next/link";
-import { usePathname, useSearchParams } from "next/navigation";
+import { usePathname } from "next/navigation";
import { getDocsNav, isDocsNavActive } from "../config/site-map";
export function DocsShell({ children }: { children: React.ReactNode }) {
const pathname = usePathname() ?? "/docs";
- const searchParams = useSearchParams();
const navGroups = getDocsNav();
return (
@@ -29,11 +28,7 @@ export function DocsShell({ children }: { children: React.ReactNode }) {
{group.items.map((item) => {
- const active = isDocsNavActive(
- pathname,
- item.href,
- searchParams,
- );
+ const active = isDocsNavActive(pathname, item.href);
return (
- 开始使用
+ 开始使用
阅读文档
diff --git a/src/features/shell/site-header.tsx b/src/features/shell/site-header.tsx
index 20a0eb68..bb315cdd 100644
--- a/src/features/shell/site-header.tsx
+++ b/src/features/shell/site-header.tsx
@@ -133,7 +133,7 @@ function MobileMenuLayer({
GitHub
@@ -192,7 +192,7 @@ export function SiteHeader() {
>
GitHub
-
+
开始使用
From 9fd05960279eeaadbb3bf650785ea5e6f2f6956d Mon Sep 17 00:00:00 2001
From: electroluxcode <3451613934@qq.com>
Date: Sat, 11 Jul 2026 18:37:37 +0800
Subject: [PATCH 2/4] fix(editor): report office XML size diagnostics as errors
---
src/components/onlyoffice-web-comp/internal/editor/server.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/onlyoffice-web-comp/internal/editor/server.ts b/src/components/onlyoffice-web-comp/internal/editor/server.ts
index 98c95abd..d1d0ee87 100644
--- a/src/components/onlyoffice-web-comp/internal/editor/server.ts
+++ b/src/components/onlyoffice-web-comp/internal/editor/server.ts
@@ -1403,7 +1403,7 @@ export class EditorServer {
}
const { totalSize, entryCount } = getZipXmlUncompressedSize(entries);
- this.logRaw("log", "operation", "office xml total size", [
+ this.logRaw("error", "operation", "office xml total size", [
"onlyoffice-totalSize:",
`${(totalSize / 1024 / 1024).toFixed(2)} MB`,
]);
From d2c1b5ad94b6d27c2617258612c88bc4d91c1fa5 Mon Sep 17 00:00:00 2001
From: electroluxcode <3451613934@qq.com>
Date: Sat, 11 Jul 2026 18:46:18 +0800
Subject: [PATCH 3/4] fix
---
src/app/docs/demos/page.tsx | 26 ++------------------------
src/features/shell/site-nav.ts | 2 +-
2 files changed, 3 insertions(+), 25 deletions(-)
diff --git a/src/app/docs/demos/page.tsx b/src/app/docs/demos/page.tsx
index 568faa00..a5368ec1 100644
--- a/src/app/docs/demos/page.tsx
+++ b/src/app/docs/demos/page.tsx
@@ -1,32 +1,10 @@
import type { Metadata } from "next";
-import Link from "next/link";
-import { DEMO_TABS } from "@/features/docs/config/site-map";
+import { redirect } from "next/navigation";
export const metadata: Metadata = {
title: "在线示例 — OnlyOffice Web Comp",
};
export default function DocsDemosRoutePage() {
- return (
-
-
在线示例
-
选择一个示例路由打开对应的编辑器实例。
-
- {DEMO_TABS.map((tab) => (
-
- {tab.label}
-
- {tab.id === "single"
- ? "一个页面只挂载一个编辑器实例"
- : "多个 Tab 按 containerId 隔离实例"}
-
-
- ))}
-
-
- );
+ redirect("/docs/demos/single");
}
diff --git a/src/features/shell/site-nav.ts b/src/features/shell/site-nav.ts
index fdda2907..ddd7efa9 100644
--- a/src/features/shell/site-nav.ts
+++ b/src/features/shell/site-nav.ts
@@ -21,7 +21,7 @@ export const SITE_NAV: SiteNavItem[] = [
(pathname.startsWith("/docs/") && !pathname.startsWith("/docs/demos")),
},
{
- href: "/docs/demos",
+ href: "/docs/demos/single",
label: "示例",
isActive: (pathname) => pathname.startsWith("/docs/demos"),
},
From 378aa21e126c2eccb5d113a0e8c83b4f874b7752 Mon Sep 17 00:00:00 2001
From: electroluxcode <3451613934@qq.com>
Date: Sat, 11 Jul 2026 19:02:30 +0800
Subject: [PATCH 4/4] fix(editor): report x2t worker as errors
---
src/app/examples/page.tsx | 2 +-
.../internal/editor/x2t.ts | 15 ++-
.../internal/editor/x2t.worker.ts | 101 +++++++++++++++++-
3 files changed, 114 insertions(+), 4 deletions(-)
diff --git a/src/app/examples/page.tsx b/src/app/examples/page.tsx
index 2681cd70..a6c22dd7 100644
--- a/src/app/examples/page.tsx
+++ b/src/app/examples/page.tsx
@@ -1,5 +1,5 @@
import { redirect } from "next/navigation";
export default function ExamplesRedirectPage() {
- redirect("/docs/demos/multi");
+ redirect("/docs/demos/single");
}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/x2t.ts b/src/components/onlyoffice-web-comp/internal/editor/x2t.ts
index c8fe2c94..d79049bd 100644
--- a/src/components/onlyoffice-web-comp/internal/editor/x2t.ts
+++ b/src/components/onlyoffice-web-comp/internal/editor/x2t.ts
@@ -16,6 +16,7 @@ interface WorkerResponse {
type: string;
payload?: any;
error?: string;
+ errorDetails?: unknown;
}
export class X2tConverter {
@@ -99,7 +100,7 @@ export class X2tConverter {
* @description 处理 worker 返回的响应消息。
*/
private handleWorkerMessage = (event: MessageEvent) => {
- const { id, type, payload, error } = event.data;
+ const { id, type, payload, error, errorDetails } = event.data;
if (type === "ready") {
this.logRaw("log", "worker ready", ["[X2tConverter] Worker ready"]);
@@ -112,7 +113,17 @@ export class X2tConverter {
this.pendingMessages.delete(id);
if (type === "error") {
- pending.reject(new Error(error || "Unknown worker error"));
+ const errorMessage = error || "Unknown worker error";
+ const details =
+ errorDetails && typeof errorDetails === "object"
+ ? { message: errorMessage, ...errorDetails }
+ : { message: errorMessage };
+ if (this.logger) {
+ this.logger.error("worker", "worker request failed", details);
+ } else {
+ console.error("[X2tConverter] Worker request failed:", details);
+ }
+ pending.reject(new Error(errorMessage));
} else {
pending.resolve(payload);
}
diff --git a/src/components/onlyoffice-web-comp/internal/editor/x2t.worker.ts b/src/components/onlyoffice-web-comp/internal/editor/x2t.worker.ts
index c9f3e3d4..35ae5221 100644
--- a/src/components/onlyoffice-web-comp/internal/editor/x2t.worker.ts
+++ b/src/components/onlyoffice-web-comp/internal/editor/x2t.worker.ts
@@ -521,6 +521,96 @@ function cleanupFiles(files: string[]): void {
cleanThemes();
}
+type SerializedWorkerError = {
+ name: string;
+ message?: string;
+ errno?: unknown;
+ code?: unknown;
+ path?: unknown;
+ text: string;
+};
+
+type X2tWorkerErrorDetails = {
+ stage: string;
+ fileFrom?: string;
+ fileTo?: string;
+ readOutputError?: SerializedWorkerError;
+};
+
+class X2tWorkerError extends Error {
+ constructor(
+ message: string,
+ public readonly details: X2tWorkerErrorDetails,
+ ) {
+ super(message);
+ this.name = "X2tWorkerError";
+ }
+}
+
+function formatSerializedWorkerError(error: Omit) {
+ const message = error.message ? `: ${error.message}` : "";
+ const details = [
+ ["errno", error.errno],
+ ["code", error.code],
+ ["path", error.path],
+ ]
+ .filter(([, value]) => value != null)
+ .map(([key, value]) => `${key}=${String(value)}`)
+ .join(", ");
+ return details
+ ? `${error.name}${message} (${details})`
+ : `${error.name}${message}`;
+}
+
+function serializeWorkerError(error: unknown): SerializedWorkerError {
+ if (error instanceof Error) {
+ const serialized = {
+ name: error.name || "Error",
+ message: error.message,
+ };
+ return {
+ ...serialized,
+ text: formatSerializedWorkerError(serialized),
+ };
+ }
+
+ if (error && typeof error === "object") {
+ const record = error as Record;
+ const constructorName =
+ "constructor" in record &&
+ record.constructor &&
+ typeof record.constructor === "function"
+ ? record.constructor.name
+ : "";
+ const name =
+ typeof record.name === "string" && record.name
+ ? record.name
+ : constructorName || Object.prototype.toString.call(error);
+ const message =
+ typeof record.message === "string" && record.message
+ ? record.message
+ : undefined;
+ const serialized = {
+ name,
+ message,
+ errno: record.errno,
+ code: record.code,
+ path: record.path,
+ };
+ return {
+ ...serialized,
+ text: formatSerializedWorkerError(serialized),
+ };
+ }
+
+ const text = String(error);
+ return {
+ name: "Error",
+ message: text,
+ text,
+ };
+}
+
function cleanMedia() {
try {
const mediaFiles = x2t.FS.readdir("/working/media/");
@@ -869,6 +959,7 @@ async function convert({
}
let output: Uint8Array | null = null;
+ let readOutputError: SerializedWorkerError | undefined;
try {
output = x2t.FS.readFile(toPath);
if (output && fileTo === "Editor.bin" && fontAliases) {
@@ -884,10 +975,16 @@ async function convert({
}
} catch (e) {
console.error("[x2t.worker] read output failed:", e);
+ readOutputError = serializeWorkerError(e);
}
if (!output) {
- throw new Error(`x2t conversion produced no output (${fileFrom} -> ${fileTo})`);
+ throw new X2tWorkerError("x2t conversion produced no output", {
+ stage: "read-output",
+ fileFrom,
+ fileTo,
+ readOutputError,
+ });
}
const outputMedia = readMedia();
@@ -951,6 +1048,8 @@ self.onmessage = async (event: MessageEvent) => {
id,
type: "error",
error: error instanceof Error ? error.message : String(error),
+ errorDetails:
+ error instanceof X2tWorkerError ? error.details : undefined,
});
}
};