HIGH fixes (7): - H1: VNCdirectory admin i18n — 30+ translation keys added - H2: handleSave try/catch with error toast - H3: Free/busy accountId scoping - H4: cancelEventBookings filter by eventId - H5: Resource picker static apiFetch import - H6: Sharing-store toast messages via lastMessage state - H7: roleLabel for all resource types MEDIUM fixes (11): - M1: identitySignatureMap cleanup on delete - M2: Now-line relative positioning - M3: Radial menu disabled item keyboard nav - M4: Radial menu stable event listener via refs - M5: cancelBooking error on missing booking - M6: PasswordRow isMasked state flag - M7: Extract shared rights into lib/sharing-rights.ts - M8: VNCtalk client server-side guard - M9: Collabora configManager instead of process.env - M10: CONFIG_ENV_MAP VNCdirectory fields - M11: SENSITIVE_CONFIG_KEYS field name unification LOW fixes (7): - L1-L3: Unused imports removed - L4: aria-labels on close, clear, search, spinner - L5-L7: Comments for intentional patterns, null guard
90 lines
2.6 KiB
TypeScript
90 lines
2.6 KiB
TypeScript
import { configManager } from "@/lib/admin/config-manager";
|
|
|
|
export async function getCollaboraEditUrl(
|
|
fileId: string,
|
|
fileName: string
|
|
): Promise<string> {
|
|
const serverUrl =
|
|
configManager.get<string>("collaboraServerUrl") ||
|
|
process.env.COLLABORA_SERVER_URL ||
|
|
"";
|
|
|
|
if (!serverUrl) {
|
|
throw new Error("COLLABORA_SERVER_URL is not configured");
|
|
}
|
|
|
|
const base = serverUrl.replace(/\/+$/, "");
|
|
const fileExt = fileName.split(".").pop()?.toLowerCase() || "";
|
|
|
|
// Collabora WOPI host discovery endpoint
|
|
const response = await fetch(`${base}/hosting/discovery`, {
|
|
headers: { Accept: "application/json" },
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Collabora discovery failed: ${response.status}`);
|
|
}
|
|
|
|
const discovery = await response.json();
|
|
|
|
// Find the WOPI action URL for the file extension
|
|
let actionUrl: string | null = null;
|
|
const mimeMap: Record<string, string> = {
|
|
docx: "text",
|
|
doc: "text",
|
|
odt: "text",
|
|
xlsx: "spreadsheet",
|
|
xls: "spreadsheet",
|
|
ods: "spreadsheet",
|
|
pptx: "presentation",
|
|
ppt: "presentation",
|
|
odp: "presentation",
|
|
};
|
|
const docType = mimeMap[fileExt] || "text";
|
|
|
|
if (discovery.net?.zone) {
|
|
const zones = Array.isArray(discovery.net.zone)
|
|
? discovery.net.zone
|
|
: [discovery.net.zone];
|
|
for (const zone of zones) {
|
|
const apps = Array.isArray(zone.app) ? zone.app : zone.app ? [zone.app] : [];
|
|
for (const app of apps) {
|
|
if (
|
|
app.name &&
|
|
docType &&
|
|
app.name.toLowerCase().includes(docType.toLowerCase())
|
|
) {
|
|
const actions = Array.isArray(app.action)
|
|
? app.action
|
|
: app.action
|
|
? [app.action]
|
|
: [];
|
|
for (const action of actions) {
|
|
if (action.name === "edit" && action.urlsrc) {
|
|
actionUrl = action.urlsrc;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (actionUrl) break;
|
|
}
|
|
if (actionUrl) break;
|
|
}
|
|
}
|
|
|
|
if (!actionUrl) {
|
|
// Fallback: construct URL manually
|
|
actionUrl = `${base}/loleaflet/dist/loleaflet.html`;
|
|
}
|
|
|
|
// For now, return the base edit URL. A full WOPI implementation would
|
|
// generate a WOPI src URL with an access token pointing back to this server.
|
|
const appUrl = configManager.get<string>("appUrl") || process.env.NEXT_PUBLIC_APP_URL;
|
|
const port = configManager.get<string>("port") || process.env.PORT || "3000";
|
|
const wopiSrcUrl = `${actionUrl}?WOPISrc=${encodeURIComponent(
|
|
`${appUrl || `http://localhost:${port}`}/api/collabora/wopi/files/${encodeURIComponent(fileId)}`
|
|
)}`;
|
|
|
|
return wopiSrcUrl;
|
|
}
|