Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ notes. Entries describe what users experience, not internal refactors.

### Fixed

- Read-only embeds can export again: `document:save` returns the file while
editing stays locked, and the File tab's download control inside an iframe
starts a real download instead of silently dropping the bytes ([#307](https://github.com/ranuts/document/issues/307)).
- Read-only documents now look read-only: the toolbar keeps only the File and View tabs, editing buttons such as paste and macros are hidden, and the side panels are locked, while search, zoom and navigation stay available. Switching read-only off restores the full toolbar.
- Exporting a PDF or another format keeps unsaved changes in the editable original visible until the original is saved.
- Failed file opens restore usable file controls, including keyboard access.
Expand Down
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,9 @@ test/setup/vitest.ts # 全局 mock:matchMedia、URL.createObjectURL
- 多 sheet 工作簿 open-buffer 打开 + 保存往返数据完整(#113、#31)
- xlsx → PDF 导出(canvas 渲染管线,#28 / 错误码 80 的场景)
- CSV 打开 + 存回 CSV 内容一致(#13、#33)
- 只读打开:状态正确且保存被拒(#25、#87)
- 只读打开:状态正确且仍可导出(#25、#87、#307;只读禁编辑不禁下载)
- 运行时只读切换:restriction 真实生效(iframe 内 `restrictions` 属性)、
锁定期保存被拒、解锁后保存往返完整
锁定期仍可导出、解锁后保存往返完整(#307)
- docx open-buffer 打开 + 存回 docx(页内零依赖手拼最小 OOXML zip,#113 直接钉死)
- PDF 打开:真实挂载 pdfeditor(断言 iframe URL 路由),页内手拼合法最小 PDF
- URL 插图后保存:产物 zip 含 media 条目且字节完整(守护 serverless image
Expand Down Expand Up @@ -1152,7 +1152,7 @@ E2E 回归套件覆盖**(`pnpm run test:e2e` 的 embed-regression.spec:打

- `getSavedFileMimeType` / `getNormalizedFile` / `toUint8Array` — 纯计算逻辑
- `setReadonlyMode` / `getReadonlyMode` — 状态读写
- `requestSaveDocument` — 所有拒绝路径(无编辑器、只读、并发、超时、不支持 downloadAs)
- `requestSaveDocument` — 拒绝路径(无编辑器、并发、超时、不支持 downloadAs;只读允许导出,见 #307)

### AI assistant product entry

Expand Down
4 changes: 2 additions & 2 deletions content/de/embed-document-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ iframe.contentWindow.postMessage({ id, type: 'document:save', payload: { targetE

## Schreibgeschützt und Vorschau

Öffnen Sie ein Dokument schreibgeschützt (als Betrachter, als Prüfschritt, als gesperrter Datensatz), indem Sie `readonly: true` an den Öffnen-Befehl übergeben, und wechseln Sie jederzeit mit `document:set-readonly` — ohne Neuladen, das Dokument bleibt an der Stelle des Nutzers. Im schreibgeschützten Modus ist Bearbeiten deaktiviert und `document:save` antwortet mit `document:error`; `document:get-state` meldet das aktuelle `readonly`-Flag.
Öffnen Sie ein Dokument schreibgeschützt (als Betrachter, als Prüfschritt, als gesperrter Datensatz), indem Sie `readonly: true` an den Öffnen-Befehl übergeben, und wechseln Sie jederzeit mit `document:set-readonly` — ohne Neuladen, das Dokument bleibt an der Stelle des Nutzers. Im schreibgeschützten Modus ist Bearbeiten deaktiviert, aber `document:save` liefert die aktuellen Bytes weiterhin über `document:saved` (Export ist erlaubt; gesperrt ist nur das Bearbeiten); `document:get-state` meldet das aktuelle `readonly`-Flag.

```js
// gesperrt öffnen, später entsperren
Expand Down Expand Up @@ -88,4 +88,4 @@ Ergänzen Sie die iframe-URL um `embedOrigin`, um den Nachrichtenaustausch auf e

### Kann ich ein Dokument schreibgeschützt zeigen oder später sperren?

Ja. Übergeben Sie beim Öffnen `readonly: true` oder senden Sie jederzeit `document:set-readonly` — es wechselt im laufenden Editor ohne Neuladen, und Speichern wird währenddessen abgelehnt.
Ja. Übergeben Sie beim Öffnen `readonly: true` oder senden Sie jederzeit `document:set-readonly` — es wechselt im laufenden Editor ohne Neuladen. Bearbeiten bleibt gesperrt, der Export über `document:save` funktioniert weiterhin.
4 changes: 2 additions & 2 deletions content/en/embed-document-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ iframe.contentWindow.postMessage({ id, type: 'document:save', payload: { targetE

## Read-only and preview mode

Open a document read-only (a viewer, a review step, a locked record) by passing `readonly: true` with the open command, and switch at any time with `document:set-readonly` — no reload, the document stays where the user was. In read-only mode editing is disabled and `document:save` answers with `document:error`; `document:get-state` reports the current `readonly` flag.
Open a document read-only (a viewer, a review step, a locked record) by passing `readonly: true` with the open command, and switch at any time with `document:set-readonly` — no reload, the document stays where the user was. In read-only mode editing is disabled, but `document:save` still returns the current bytes via `document:saved` (export is allowed; only edits are locked); `document:get-state` reports the current `readonly` flag.

```js
// open locked, unlock later
Expand Down Expand Up @@ -88,4 +88,4 @@ Add `embedOrigin` to the iframe URL to lock messaging to a specific origin, and

### Can I show a document read-only, or lock it after a while?

Yes. Pass `readonly: true` when opening, or send `document:set-readonly` at any time — it switches the live editor without reloading, and saves are refused while locked.
Yes. Pass `readonly: true` when opening, or send `document:set-readonly` at any time — it switches the live editor without reloading. Editing stays locked, but export via `document:save` still works.
4 changes: 2 additions & 2 deletions content/es/embed-document-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ iframe.contentWindow.postMessage({ id, type: 'document:save', payload: { targetE

## Solo lectura y modo vista previa

Abre un documento en solo lectura (un visor, un paso de revisión, un registro bloqueado) pasando `readonly: true` con el comando de apertura, y cambia cuando quieras con `document:set-readonly`: sin recargar, y el documento se queda donde estaba la persona. En modo de solo lectura la edición se desactiva y `document:save` responde con `document:error`; `document:get-state` informa del valor actual de `readonly`.
Abre un documento en solo lectura (un visor, un paso de revisión, un registro bloqueado) pasando `readonly: true` con el comando de apertura, y cambia cuando quieras con `document:set-readonly`: sin recargar, y el documento se queda donde estaba la persona. En modo de solo lectura la edición se desactiva, pero `document:save` sigue devolviendo los bytes actuales mediante `document:saved` (se permite exportar; solo se bloquea la edición); `document:get-state` informa del valor actual de `readonly`.

```js
// ábrelo bloqueado, desbloquéalo después
Expand Down Expand Up @@ -88,4 +88,4 @@ Añade `embedOrigin` a la URL del iframe para limitar los mensajes a un origen c

### ¿Puedo mostrar un documento en solo lectura, o bloquearlo más tarde?

Sí. Pasa `readonly: true` al abrirlo, o envía `document:set-readonly` cuando quieras: cambia el editor en vivo sin recargar, y los guardados se rechazan mientras está bloqueado.
Sí. Pasa `readonly: true` al abrirlo, o envía `document:set-readonly` cuando quieras: cambia el editor en vivo sin recargar. La edición permanece bloqueada, pero la exportación con `document:save` sigue funcionando.
8 changes: 4 additions & 4 deletions content/ja/embed-document-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ howTo: 自分のサイトにドキュメントエディタを埋め込む方法
appDescription: 'iframe と postMessage でエディタを埋め込み、ファイルのアクセスやアップロードは埋め込み先が管理します。'
---

基本のローカル編集では:エディタは OnlyOffice の WebAssembly エンジンでブラウザ内だけで動作するため、ドキュメントはクライアント側で描画・編集されます——ドキュメントサーバーを立てる必要はありません。推奨する構成は境界をきれいに保ちます: **親アプリが認証・取得・保存を担当し、iframe は編集だけを担当する。** トークンも Cookie も業務 API も、あなたのアプリの中に留まります。
基本のローカル編集では:エディタは OnlyOffice の WebAssembly エンジンでブラウザ内だけで動作するため、ドキュメントはクライアント側で描画・編集されます——ドキュメントサーバーを立てる必要はありません。推奨する構成は境界をきれいに保ちます:**親アプリが認証・取得・保存を担当し、iframe は編集だけを担当する。** トークンも Cookie も業務 API も、あなたのアプリの中に留まります。

## 一つの iframe で追加する

Expand All @@ -24,7 +24,7 @@ appDescription: 'iframe と postMessage でエディタを埋め込み、ファ
></iframe>
```

そのあとは `postMessage` で通信します。各コマンドは返信と対応づけるための `id` を持ち、エディタのイベントはすべて `document:*` メッセージです:
そのあとは `postMessage` で通信します。各コマンドは返信と対応づけるための `id` を持ち、エディタのイベントはすべて `document:*` メッセージです:

```js
// open a file your app already fetched (auth stays with you)
Expand Down Expand Up @@ -56,7 +56,7 @@ iframe.contentWindow.postMessage({ id, type: 'document:save', payload: { targetE

## 読み取り専用・プレビューモード

ビューア、レビュー工程、ロックされたレコードなど、読み取り専用で開きたいときは open コマンドに `readonly: true` を渡します。`document:set-readonly` でいつでも切り替えられます——再読み込みは不要で、ユーザーが見ていた位置も保たれます。読み取り専用のあいだは編集が無効になり、`document:save` は `document:error` を返します。`document:get-state` は現在の `readonly` フラグを報告します。
ビューア、レビュー工程、ロックされたレコードなど、読み取り専用で開きたいときは open コマンドに `readonly: true` を渡します。`document:set-readonly` でいつでも切り替えられます——再読み込みは不要で、ユーザーが見ていた位置も保たれます。読み取り専用のあいだは編集が無効ですが、`document:save` は `document:saved` 経由で現在のバイト列を返します(エクスポートは可能、ロックされるのは編集だけ)。`document:get-state` は現在の `readonly` フラグを報告します。

```js
// open locked, unlock later
Expand Down Expand Up @@ -88,4 +88,4 @@ iframe の URL に `embedOrigin` を付けてメッセージのやり取りを

### ドキュメントを読み取り専用で表示したり、途中からロックしたりできますか?

はい。開くときに `readonly: true` を渡すか、いつでも `document:set-readonly` を送ってください——再読み込みなしにその場で切り替わり、ロック中の保存は拒否されます。
はい。開くときに `readonly: true` を渡すか、いつでも `document:set-readonly` を送ってください——再読み込みなしにその場で切り替わります。ロック中も編集はできませんが、`document:save` によるエクスポートは可能です。
32 changes: 16 additions & 16 deletions content/ko/embed-document-editor.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
---
title: '내 사이트에 문서 편집기 임베드하기 — iframe + postMessage API'
description: 'iframe과 postMessage로 편집기를 임베드하고 호스트가 파일 접근과 업로드를 관리합니다.'
description: 'iframe 과 postMessage 로 편집기를 임베드하고 호스트가 파일 접근과 업로드를 관리합니다.'
eyebrow: 개발자 · 임베드
h1: 내 웹 앱에 문서 편집기 임베드하기
lead: 'iframe 하나와 **postMessage** API만으로 **DOCX·XLSX·PPTX·CSV** 편집기를 제품에 추가하세요. 인증, 파일 접근, 업로드는 내 앱이 계속 담당하고 편집기는 편집만 합니다 — 사용자의 토큰을 보지 않습니다.'
lead: 'iframe 하나와 **postMessage** API 만으로 **DOCX·XLSX·PPTX·CSV** 편집기를 제품에 추가하세요. 인증, 파일 접근, 업로드는 내 앱이 계속 담당하고 편집기는 편집만 합니다 — 사용자의 토큰을 보지 않습니다.'
cta: 라이브 데모 열기 →
ctaHref: /embed-demo.html
ogDescription: 'iframe과 postMessage로 편집기를 임베드하고 호스트가 파일 접근과 업로드를 관리합니다.'
ogDescription: 'iframe 과 postMessage 로 편집기를 임베드하고 호스트가 파일 접근과 업로드를 관리합니다.'
breadcrumb: Embed Document Editor
howTo: 내 사이트에 문서 편집기를 임베드하는 방법
appDescription: 'iframe과 postMessage로 편집기를 임베드하고 호스트가 파일 접근과 업로드를 관리합니다.'
appDescription: 'iframe 과 postMessage 로 편집기를 임베드하고 호스트가 파일 접근과 업로드를 관리합니다.'
---

기본 로컬 편집의 경우: 편집기는 OnlyOffice의 WebAssembly 엔진으로 브라우저 안에서만 동작하므로 문서는 클라이언트에서 렌더링되고 편집됩니다 — 문서 서버를 세울 필요가 없습니다. 권장 구성은 경계를 깔끔하게 유지합니다: **부모 앱이 인증·가져오기·저장을 담당하고, iframe은 편집만 담당합니다.** 토큰, 쿠키, 업무 API는 내 앱 안에 남습니다.
기본 로컬 편집의 경우: 편집기는 OnlyOffice 의 WebAssembly 엔진으로 브라우저 안에서만 동작하므로 문서는 클라이언트에서 렌더링되고 편집됩니다 — 문서 서버를 세울 필요가 없습니다. 권장 구성은 경계를 깔끔하게 유지합니다: **부모 앱이 인증·가져오기·저장을 담당하고, iframe 은 편집만 담당합니다.** 토큰, 쿠키, 업무 API 는 내 앱 안에 남습니다.

## iframe 하나로 추가하기

Expand All @@ -27,7 +27,7 @@ appDescription: 'iframe과 postMessage로 편집기를 임베드하고 호스트
그다음에는 `postMessage`로 통신합니다. 각 명령은 응답과 짝지을 `id`를 가지며, 편집기의 모든 이벤트는 `document:*` 메시지입니다:

```js
// 내 앱이 이미 가져온 파일 열기(인증은 내 앱에 남습니다)
// 내 앱이 이미 가져온 파일 열기 (인증은 내 앱에 남습니다)
iframe.contentWindow.postMessage(
{ id, type: 'document:open-buffer', payload: { fileName: 'report.xlsx', buffer } },
'https://edit.chaxus.com',
Expand All @@ -40,23 +40,23 @@ iframe.contentWindow.postMessage({ id, type: 'document:save', payload: { targetE

## 무엇을 얻나요

- iframe 하나와 작은 **postMessage** 명령/응답 API — 설치할 SDK가 없습니다
- iframe 하나와 작은 **postMessage** 명령/응답 API — 설치할 SDK 가 없습니다
- 내 앱이 자체 자격 증명으로 가져온 **URL·File·ArrayBuffer**에서 열기
- **XLSX·DOCX·PPTX·CSV**로 저장하고 `File`로 돌려받아 내 앱이 업로드
- 읽기 전용 모드, 메시지 단위 오리진 제한(`embedOrigin`), 상태 조회
- 읽기 전용 모드, 메시지 단위 오리진 제한 (`embedOrigin`), 상태 조회
- 기본 로컬 편집의 경우: 운영할 문서 서버 없음 — 편집은 100% 클라이언트의 WebAssembly
- 오픈 소스(AGPL-3.0)이며 자체 호스팅 가능 — 내 도메인 아래에 임베드
- 오픈 소스 (AGPL-3.0) 이며 자체 호스팅 가능 — 내 도메인 아래에 임베드

## 동작 방식

1. 레이아웃에 맞춘 크기로 `/editor?embed=1`을 가리키는 iframe을 추가합니다.
1. 레이아웃에 맞춘 크기로 `/editor?embed=1`을 가리키는 iframe 을 추가합니다.
2. `document:ready` 이벤트를 기다린 뒤 `document:open-url`, `open-file`, `open-buffer`를 보냅니다.
3. 사용자가 그 자리에서 편집합니다. 내 앱이 어딘가로 보내지 않는 한 파일은 브라우저를 벗어나지 않습니다.
4. `document:save`를 보내면 편집기가 `document:saved`로 편집된 파일을 돌려주고, 내 앱이 자체 인증으로 업로드합니다.

## 읽기 전용과 미리보기 모드

뷰어, 검토 단계, 잠긴 레코드처럼 읽기 전용으로 열려면 open 명령에 `readonly: true`를 전달하고, 언제든 `document:set-readonly`로 전환하세요 — 다시 불러오지 않으며 사용자가 보던 위치도 유지됩니다. 읽기 전용 동안에는 편집이 비활성화되고 `document:save`는 `document:error`로 응답합니다. `document:get-state`는 현재 `readonly` 값을 알려 줍니다.
뷰어, 검토 단계, 잠긴 레코드처럼 읽기 전용으로 열려면 open 명령에 `readonly: true`를 전달하고, 언제든 `document:set-readonly`로 전환하세요 — 다시 불러오지 않으며 사용자가 보던 위치도 유지됩니다. 읽기 전용 동안에는 편집이 비활성화되지만, `document:save`는 `document:saved`로 현재 바이트를 반환합니다 (내보내기는 가능하고 잠기는 것은 편집뿐입니다). `document:get-state`는 현재 `readonly` 값을 알려 줍니다.

```js
// 잠긴 상태로 열고 나중에 해제
Expand All @@ -68,24 +68,24 @@ send('document:set-readonly', { readonly: false });

### 문서 편집기를 어떻게 임베드하나요?

`/editor?embed=1`을 가리키는 iframe을 하나 추가하고 postMessage API로 열기와 저장을 제어하세요. 동작하는 데모는 [/embed-demo.html](/embed-demo.html)에 있습니다.
`/editor?embed=1`을 가리키는 iframe 을 하나 추가하고 postMessage API 로 열기와 저장을 제어하세요. 동작하는 데모는 [/embed-demo.html](/embed-demo.html)에 있습니다.

### 편집기가 사용자의 인증 토큰을 보나요?

기본 로컬 편집의 경우: 아니요. 인증, 파일 가져오기, 업로드는 내 앱에 남습니다 — 내 앱이 자체 자격 증명으로 파일을 가져와 바이트를 편집기에 넘기므로 토큰과 쿠키는 iframe에 들어가지 않습니다.
기본 로컬 편집의 경우: 아니요. 인증, 파일 가져오기, 업로드는 내 앱에 남습니다 — 내 앱이 자체 자격 증명으로 파일을 가져와 바이트를 편집기에 넘기므로 토큰과 쿠키는 iframe 에 들어가지 않습니다.

### 임베드된 편집기는 어떤 형식을 다루나요?

DOCX, XLSX, PPTX, CSV입니다. OnlyOffice의 WebAssembly 엔진으로 클라이언트에서 편집합니다. save 명령은 XLSX, DOCX, PPTX, CSV로 내보냅니다.
DOCX, XLSX, PPTX, CSV 입니다. OnlyOffice 의 WebAssembly 엔진으로 클라이언트에서 편집합니다. save 명령은 XLSX, DOCX, PPTX, CSV 로 내보냅니다.

### 자체 호스팅이나 화이트라벨이 가능한가요?

네. AGPL-3.0 오픈 소스이며 정적 파일로 배포되므로 자신의 사본을 호스팅해 자기 도메인 아래에 임베드할 수 있습니다.

### 어떤 사이트가 편집기와 통신할 수 있는지 제한하려면?

iframe URL에 `embedOrigin`을 추가해 메시지를 특정 오리진으로 제한하고, 내 메시지 핸들러에서도 `event.origin`을 검증하세요.
iframe URL 에 `embedOrigin`을 추가해 메시지를 특정 오리진으로 제한하고, 내 메시지 핸들러에서도 `event.origin`을 검증하세요.

### 문서를 읽기 전용으로 보여 주거나 도중에 잠글 수 있나요?

네. 열 때 `readonly: true`를 전달하거나 언제든 `document:set-readonly`를 보내세요 — 다시 불러오지 않고 즉시 전환되며, 잠긴 동안에는 저장이 거부됩니다.
네. 열 때 `readonly: true`를 전달하거나 언제든 `document:set-readonly`를 보내세요 — 다시 불러오지 않고 즉시 전환됩니다. 잠긴 동안에도 편집은 막히지만 `document:save`로 내보내기는 가능합니다.
Loading
Loading