# 共同編集

- Human documentation: [https://docs.univer.ai/ja-JP/guides/pdfs/features/collaboration](https://docs.univer.ai/ja-JP/guides/pdfs/features/collaboration)

- Agent Markdown: [https://docs.univer.ai/ja-JP/guides/pdfs/features/collaboration.md](https://docs.univer.ai/ja-JP/guides/pdfs/features/collaboration.md)

- Requested language: `ja-JP`

- Content language: `ja-JP`

- Documentation version: `1.2.0`

- Source: [pdfs/features/collaboration.ja-JP.mdx](https://github.com/dream-num/documentation/blob/dev/content/guides/pdfs/features/collaboration.ja-JP.mdx)

---

[共同編集の統合](https://docs.univer.ai/ja-JP/server/collaboration/browser-integration.md)に沿って、サンプルの実行、アプリへの組み込み、永続化と権限の検証を進めてください。実装と Web/Server サンプルは [共同編集クイックスタート](https://docs.univer.ai/ja-JP/server/collaboration/quick-start.md) を参照してください。

認証済みの業務 API から Collaboration Service を呼び出して Unit を作成し、返された ID をブラウザーで読み込みます。[Quick Start](https://docs.univer.ai/ja-JP/server/collaboration/quick-start.md) で HTTP、Session Ticket、WebSocket を設定し、Database Adapter で永続化します。読み取り、JOIN、編集は[サーバー側の権限](https://docs.univer.ai/ja-JP/server/collaboration/identity-and-authorization.md)で個別に保護してください。

> [!NOTE]
> 使用前に、対象のバックエンドが PDF Unit をサポートしていることを確認してください。Sheets の共同編集サンプルだけでは PDF の対応を確認できません。

## 共同編集 PDF を読み込む

サーバー API または `importPdfToUnitIdAsync` を通じて PDF ユニットを作成した後、共同編集 Facade で読み込みます。

文書を読み込む前に現在のユーザーを設定してください。詳細は[共同編集の統合](https://docs.univer.ai/ja-JP/server/collaboration/browser-integration.md)を参照してください。

```ts
import { UserManagerService } from '@univerjs/core'
import '@univerjs-pro/collaboration-client/facade'

univer.__getInjector().get(UserManagerService).setCurrentUser({ userID, name, avatar })

const pdf = await univerAPI.getCollaboration().loadPdfAsync(unitId)

if (!pdf) {
  throw new Error(`PDF unit not found: ${unitId}`)
}

console.log(pdf.getId(), pdf.getName())
```

`loadPdfAsync` は、サーバースナップショットを読み込み、共同編集セッションを準備して `FPdf` を返します。ユニットを PDF に関連付けられない場合は `null` を返します。

[同期状態、再送、競合の復旧](https://docs.univer.ai/ja-JP/server/collaboration/browser-integration.md#sync-status)を使い、保存状況の表示と再読み込み前の未確認編集の保護を実装できます。

## 共同編集ドキュメントの組み込み URL 読み込みの削除

> [!WARNING: 注意]
> **1.2.0** 以降、Univer は共同編集ドキュメントの組み込み URL 読み込み処理を削除しました。URL の `unit` と `type` パラメーターを自動的に読み取って、共同編集ドキュメントを読み込むことはなくなりました。

アプリケーションがこの動作に依存している場合は、カスタムプラグインで復元できます。

### カスタムプラグインの組み込み

以下の 4 つのファイルを、次のディレクトリ構成でプロジェクトにコピーしてください。

```text
custom-url-load/
├── plugin.ts
├── controllers/
│   └── data-loader.controller.ts
└── services/
    ├── url.service.ts
    └── web-url.service.ts
```

エディターと共同編集プラグインの初期化後、現在のユーザーを設定してから `CustomUrlLoadPlugin` を登録します。

```ts
import { UserManagerService } from '@univerjs/core'
import { CustomUrlLoadPlugin } from './custom-url-load/plugin'

univer.__getInjector().get(UserManagerService).setCurrentUser({ userID, name, avatar })

univer.registerPlugin(CustomUrlLoadPlugin)
```

登録後はプラグインがドキュメントを読み込むため、同じドキュメントに対して `loadPdfAsync()` を呼び出す必要はありません。すでに Facade で読み込んでいる場合は、前のセクションの方法を引き続き使用し、アプリケーションの要件に合わせて URL を処理できます。

### プラグインの全コード

#### plugin.ts

```ts
import { UniverCollaborationClientPlugin } from '@univerjs-pro/collaboration-client'
import { DependentOn, Inject, Injector, Plugin } from '@univerjs/core'
import { CollaborationDataLoaderController } from './controllers/data-loader.controller'
import { IURLService } from './services/url.service'
import { WebURLService } from './services/web-url.service'

@DependentOn(UniverCollaborationClientPlugin)
export class CustomUrlLoadPlugin extends Plugin {
  static override pluginName = 'CUSTOM_URL_LOAD_PLUGIN'

  constructor(
    _config: undefined,
    @Inject(Injector) protected override readonly _injector: Injector
  ) {
    super()
  }

  override onStarting(): void {
    this._injector.add([IURLService, { useClass: WebURLService }])
    this._injector.add([CollaborationDataLoaderController])

    this.disposeWithMe(this._injector.get(IURLService))
    this.disposeWithMe(this._injector.get(CollaborationDataLoaderController))
  }
}
```

#### controllers/data-loader.controller.ts

```ts
import { DataLoaderService } from '@univerjs-pro/collaboration-client'
import { ILogService, Inject, RxDisposable } from '@univerjs/core'
import { IURLService } from '../services/url.service'

/**
 * URL パラメーターから共同編集ドキュメントを読み込む。
 */
export class CollaborationDataLoaderController extends RxDisposable {
  constructor(
    @IURLService private readonly _urlService: IURLService,
    @ILogService private readonly _logService: ILogService,
    @Inject(DataLoaderService) private readonly _dataLoaderService: DataLoaderService
  ) {
    super()

    const unitId = this._urlService.getParam('unit')
    const type = this._urlService.getParam('type')
    if (!unitId || !type) {
      return
    }

    this._dataLoaderService.loadUnit(unitId, Number(type))
      .catch((error: unknown) => {
        this._logService.error('[CollaborationDataLoaderController]', error)
      })
  }
}
```

#### services/url.service.ts

```ts
import type { IDisposable } from '@univerjs/core'
import type { Observable } from 'rxjs'
import { createIdentifier } from '@univerjs/core'

export interface IURLService extends IDisposable {
  getParam: (key: string) => string | undefined
  setParam: (key: string, value: string, useReplace?: boolean) => void
  removeParam: (key: string, useReplace?: boolean) => void
  readonly urlChange$: Observable<void>
}

export const IURLService = createIdentifier<IURLService>('demo.custom-url-load.url-service')
```

#### services/web-url.service.ts

```ts
import type { IURLService } from './url.service'
import { RxDisposable } from '@univerjs/core'
import { fromEvent, map, shareReplay, takeUntil } from 'rxjs'

export class WebURLService extends RxDisposable implements IURLService {
  readonly urlChange$ = fromEvent(window, 'popstate').pipe(
    map(() => undefined),
    takeUntil(this.dispose$),
    shareReplay({ bufferSize: 1, refCount: true })
  )

  getParam(key: string): string | undefined {
    return new URL(window.location.href).searchParams.get(key) ?? undefined
  }

  setParam(key: string, value: string, useReplace = false): void {
    const url = new URL(window.location.href)
    url.searchParams.set(key, value)
    this._updateUrl(url, useReplace)
  }

  removeParam(key: string, useReplace = false): void {
    const url = new URL(window.location.href)
    url.searchParams.delete(key)
    this._updateUrl(url, useReplace)
  }

  private _updateUrl(url: URL, useReplace: boolean): void {
    if (useReplace) {
      window.history.replaceState(window.history.state, '', url.toString())
    } else {
      window.history.pushState(window.history.state, '', url.toString())
    }
  }
}
```
