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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
};
var EDITOR_COMMAND = {
EDITOR_SUBSCRIBE: "editor:subscribe",
DOCUMENT_RENAME: "document:rename",
COMMENT_ADD: "comment:add",
COMMENT_UPDATE: "comment:update",
COMMENT_REMOVE: "comment:remove",
Expand Down Expand Up @@ -1598,6 +1599,15 @@
switch (command) {
case EDITOR_COMMAND.EDITOR_SUBSCRIBE:
return registerEditorCallback(api, payload && payload.event);
case EDITOR_COMMAND.DOCUMENT_RENAME:
if (!payload || !payload.fileName) {
throw new Error("Document file name is required");
}
if (typeof api.asc_wopi_renameFile !== "function") {
throw new Error("OnlyOffice WOPI rename API is not available");
}
api.asc_wopi_renameFile(String(payload.fileName));
return true;
case EDITOR_COMMAND.COMMENT_ADD: {
var data = payload && payload.data ? payload.data : {};
return (
Expand Down
2 changes: 1 addition & 1 deletion src/components/onlyoffice-web-comp/const/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export type StaticResource = {
};

export type OnlyOfficeStaticResourceOptions = {
/** CDN packages 根地址,例如 https://a6006408.onlyoffice-packages.pages.dev。 */
/** CDN packages 根地址,例如 https://8057ac0e.onlyoffice-packages.pages.dev。 */
cdnOrigin?: string | null;
/** CDN 上的 OnlyOffice 目录版本;未传时使用当前 SDK 版本。 */
onlyofficeVersion?: string | null;
Expand Down
128 changes: 127 additions & 1 deletion src/components/onlyoffice-web-comp/core/editor-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import {
prepareRevisionReviewDisplay,
} from "../feature/revisions";
import { getOnlyOfficeLang, type OnlyOfficeLang } from "../store/lang";
import { getDocumentObj, setDocumentObj } from "../store/document";
import { initializeOnlyOffice } from "../util/initialize";
import {
removeOfficeXmlSizeLimitOverlay,
Expand Down Expand Up @@ -139,6 +140,8 @@ type OnlyOfficeSdkApi = {
pluginMethod_InputText?: (text: string) => void;
pluginMethod_PasteText?: (text: string) => void;
asc_AddText?: (text: string) => void;
/** OnlyOffice 内部 WOPI 重命名通道;通过 socket rpc 请求宿主重命名。 */
asc_wopi_renameFile?: (fileName: string) => void;
};

/** iframe 内运行时;混淆字段见 type/sdk-internal.ts,asc_* 为公开 API */
Expand Down Expand Up @@ -194,6 +197,13 @@ export class EditorManager {
private officeXmlSizeLimitOverlayTeardown: (() => void) | null = null;
private officeXmlSizeLimitPayload: OfficeXmlSizeLimitExceededPayload | null =
null;
private pendingRename:
| {
resolve: (fileName: string) => void;
reject: (error: Error) => void;
timer: number;
}
| null = null;

constructor(containerId = ONLYOFFICE_ID) {
this.containerId = containerId;
Expand All @@ -209,6 +219,9 @@ export class EditorManager {
onLoadError: (error) => {
this.handleServerLoadError(error);
},
onDocumentRename: (fileName) => {
this.syncRenamedDocument(fileName);
},
});
}

Expand Down Expand Up @@ -1460,6 +1473,21 @@ export class EditorManager {
this.dirty = true;
}
},
// DocsAPI 仅在注册此回调时暴露「文件 → 重命名」入口。
// 回调由 OnlyOffice 的内部 Gateway 发出;标题显示已由 iframe 更新,
// 使用 iframe 内 asc_wopi_renameFile 发起 RPC,由内存服务按 WOPI 协议回包。
// onRequestRename: (event: { data?: unknown }) => {
// const fileName =
// typeof event.data === "string" ? event.data : "";
// void this.renameDocument(fileName).catch((error) => {
// this.logger.error("operation", "OnlyOffice document rename failed", {
// fileName,
// instanceId: this.instanceId,
// containerId: this.containerId,
// error: error instanceof Error ? error.message : String(error),
// });
// });
// },
// 不注册 onSave/onSaveDocument:内部保存已禁用,导出统一走 export() → downloadAs。
onDownloadAs: () => {
// Required so DocsAPI.downloadAs can request the current editor binary.
Expand Down Expand Up @@ -1623,7 +1651,7 @@ export class EditorManager {
this.teardownWordContentSync();

if (options.isNew) {
this.server.openNew(fileType);
this.server.openNew(fileType, options.fileName);
} else if (options.file) {
await this.server.open(options.file, {
fileName: options.fileName,
Expand Down Expand Up @@ -1810,6 +1838,103 @@ export class EditorManager {
return this.fileName;
}

/**
* @description 通过 iframe 内 asc_wopi_renameFile 重命名当前实例。
* SDK 经 socket rpc 收到内存 WOPI 回包后才更新本地标题,因此返回 Promise。
*/
renameDocument(fileName: string): Promise<string> {
const requestedName =
typeof fileName === "string" ? fileName.trim() : "";
if (!requestedName) {
return Promise.reject(
new Error("OnlyOffice document name cannot be empty"),
);
}

if (this.pendingRename) {
return Promise.reject(new Error("OnlyOffice document rename is pending"));
}

return new Promise<string>((resolve, reject) => {
const timer = window.setTimeout(() => {
if (this.pendingRename?.timer !== timer) {
return;
}
this.pendingRename = null;
reject(new Error("OnlyOffice document rename timed out"));
}, 5000);

this.pendingRename = { resolve, reject, timer };

try {
if (isOnlyOfficeCdnMode()) {
void callCrossOriginEditor(
this.containerId,
CROSS_ORIGIN_EDITOR_COMMAND.DOCUMENT_RENAME,
{ fileName: requestedName },
)
.then(() => {
// 跨域 bridge 只能确认 iframe 已调用 SDK API;原生「文件 → 重命名」
// 路径不一定把 WOPI RPC 回包转回父页。由宿主提交标题,确保导出
// 快照与当前文件名同步;若 RPC 已先到达,pendingRename 已被清空。
if (this.pendingRename) {
this.server.rename(requestedName);
}
})
.catch((error) => this.rejectPendingRename(error));
return;
}

const api = this.requireSdkApi();
if (!api.asc_wopi_renameFile) {
throw new Error("OnlyOffice WOPI rename API is not available");
}
api.asc_wopi_renameFile(requestedName);
} catch (error) {
this.rejectPendingRename(error);
}
});
}

private syncRenamedDocument(renamedFileName: string) {
this.fileName = renamedFileName;

const document = getDocumentObj(this.containerId);
setDocumentObj(
{
...document,
fileName: renamedFileName,
},
this.containerId,
);

const pendingRename = this.pendingRename;
if (pendingRename) {
window.clearTimeout(pendingRename.timer);
this.pendingRename = null;
pendingRename.resolve(renamedFileName);
}

this.logger.operation("OnlyOffice document renamed through WOPI RPC", {
fileName: renamedFileName,
instanceId: this.instanceId,
containerId: this.containerId,
});
}

private rejectPendingRename(error: unknown) {
const pendingRename = this.pendingRename;
if (!pendingRename) {
return;
}

window.clearTimeout(pendingRename.timer);
this.pendingRename = null;
pendingRename.reject(
error instanceof Error ? error : new Error(String(error)),
);
}

getContainerParentSelector() {
return `${ONLYOFFICE_CONTAINER_CONFIG.PARENT_SELECTOR}[data-onlyoffice-container-id="${this.containerId}"], ${ONLYOFFICE_CONTAINER_CONFIG.PARENT_SELECTOR}`;
}
Expand Down Expand Up @@ -2469,6 +2594,7 @@ export class EditorManager {
}

destroy() {
this.rejectPendingRename(new Error("OnlyOffice editor was destroyed"));
if (this.userSaveTimer !== null) {
window.clearTimeout(this.userSaveTimer);
this.userSaveTimer = null;
Expand Down
4 changes: 2 additions & 2 deletions src/components/onlyoffice-web-comp/docs/核心API.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import {

// packages 根地址,目录下应包含 onlyoffice/{version}/...
OnlyOfficeManager.registerStaticResource({
cdnOrigin: "https://a6006408.onlyoffice-packages.pages.dev",
cdnOrigin: "https://8057ac0e.onlyoffice-packages.pages.dev",
});

const resource = getStaticResource();
Expand All @@ -78,7 +78,7 @@ OnlyOfficeManager.resetStaticResource();

```typescript
type OnlyOfficeStaticResourceOptions = {
/** CDN packages 根地址,例如 https://a6006408.onlyoffice-packages.pages.dev */
/** CDN packages 根地址,例如 https://8057ac0e.onlyoffice-packages.pages.dev */
cdnOrigin?: string | null;
/** CDN 的 SDK 版本;默认当前 9.4 SDK */
onlyofficeVersion?: string | null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ export const CROSS_ORIGIN_BRIDGE_MESSAGE = {

export const CROSS_ORIGIN_EDITOR_COMMAND = {
EDITOR_SUBSCRIBE: "editor:subscribe",
DOCUMENT_RENAME: "document:rename",
COMMENT_ADD: "comment:add",
COMMENT_UPDATE: "comment:update",
COMMENT_REMOVE: "comment:remove",
Expand Down
50 changes: 48 additions & 2 deletions src/components/onlyoffice-web-comp/internal/editor/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1082,11 +1082,14 @@ export class EditorServer {
};
}

openNew(fileType?: string) {
openNew(fileType?: string, fileName?: string) {
this.fileType = fileType || "docx";
this.id = randomId();
this.loadPromise = null;
this.title = ensureTitleWithExtension("New Document", this.fileType);
this.title = ensureTitleWithExtension(
fileName || "New Document",
this.fileType,
);
const documentType = getDocumentType(this.fileType);

let binData: Uint8Array | null = null;
Expand Down Expand Up @@ -1160,6 +1163,20 @@ export class EditorServer {
};
}

/**
* @description 更新当前内存文档标题。文件内容与 document key 保持不变。
*/
rename(fileName: string) {
const requestedName = fileName.trim();
if (!requestedName) {
throw new Error("OnlyOffice document name cannot be empty");
}

this.title = ensureTitleWithExtension(requestedName, this.fileType);
this.options.onDocumentRename?.(this.title);
return this.title;
}

/**
* @description 另存为产物 blob URL,cache GET 失败时供 getFile 回退下载。
*/
Expand Down Expand Up @@ -2076,6 +2093,35 @@ export class EditorServer {
}
break;
}
case "rpc": {
const rpc = msg.data as Record<string, unknown> | undefined;
if (rpc?.type !== "wopi_RenameFile") {
break;
}

const responseKey = msg.responseKey;
if (typeof responseKey !== "number") {
break;
}

try {
const title = this.rename(String(rpc.name ?? ""));
const extension = getFileExt(title);
const name = extension
? title.slice(0, -(extension.length + 1))
: title;

// asc_wopi_renameFile 期望 { Name },SDK 会自行补回当前扩展名并广播 asc_onMeta。
send({ type: "rpc", responseKey, data: { Name: name } });
} catch (error) {
this.logRaw("warn", "socket", "wopi rename failed", [
"[EditorServer] wopi rename failed:",
error,
]);
send({ type: "rpc", responseKey, data: null });
}
break;
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/components/onlyoffice-web-comp/internal/editor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,8 @@ export function isOfficeXmlSizeLimitExceededError(
export interface ServerOptions {
getState?: () => { readOnly?: boolean };
logger?: EditorLogger;
/** WOPI 重命名 RPC 成功后的回调,携带 SDK 最终采用的完整文件名。 */
onDocumentRename?: (fileName: string) => void;
/**
* @description 用户触发保存(非 export/downloadAs 导出)时回调,携带最新文档快照。
*/
Expand Down
2 changes: 1 addition & 1 deletion src/features/demo/resource-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DemoButton, DemoField } from "./demo-toolbar";
import { OnlyOfficeManager } from "@/components/onlyoffice-web-comp";

export const DEFAULT_DEMO_CDN_ORIGIN =
"https://a6006408.onlyoffice-packages.pages.dev";
"https://8057ac0e.onlyoffice-packages.pages.dev";

export type DemoResourceMode = "local" | "cdn";

Expand Down
Loading