0.3.2.1 UI修复3
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { HttpError } from "@/lib/auth/authContext";
|
||||
import { getAuthedConvexClient } from "@/lib/convex/route";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const { auth } = await getAuthedConvexClient();
|
||||
return NextResponse.json({
|
||||
userId: auth.userId,
|
||||
email: auth.email ?? null,
|
||||
name: auth.name ?? null,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof HttpError) {
|
||||
return NextResponse.json({ error: "未登录" }, { status: err.status });
|
||||
}
|
||||
return NextResponse.json({ error: "未登录" }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import type { MediaAsset } from "@/types/media";
|
||||
import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { HttpError, requireAuthContext } from "@/lib/auth/authContext";
|
||||
import { api } from "@/lib/convex/api";
|
||||
import { getConvexAuthedHttpClient } from "@/lib/convex/server";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
if (isConvexEnabled()) {
|
||||
let auth;
|
||||
try {
|
||||
auth = await requireAuthContext();
|
||||
} catch (err) {
|
||||
if (err instanceof HttpError) {
|
||||
return NextResponse.json({ error: "未登录" }, { status: err.status });
|
||||
}
|
||||
return NextResponse.json({ error: "未登录" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const documentId = searchParams.get("documentId") ?? "";
|
||||
const limit = Number.parseInt(searchParams.get("limit") ?? "200", 10);
|
||||
|
||||
if (!documentId) {
|
||||
return NextResponse.json({ error: "缺少 documentId" }, { status: 400 });
|
||||
}
|
||||
|
||||
const client = await getConvexAuthedHttpClient();
|
||||
const items = await client.query(api.mediaAssets.listByDocument, {
|
||||
userId: auth.userId,
|
||||
documentId,
|
||||
limit: Number.isNaN(limit) ? 200 : limit,
|
||||
});
|
||||
|
||||
return NextResponse.json({ items: (items ?? []) as MediaAsset[] });
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
@@ -82,7 +82,11 @@ export async function POST(request: Request) {
|
||||
if (isConvexEnabled()) {
|
||||
try {
|
||||
// 说明:Convex 模式下,保存写回 Convex Files,并更新 media_assets.storage_id/file_url。
|
||||
const userId = String(process.env.DEV_USER_ID || "dev-user").trim() || "dev-user";
|
||||
// 优先使用前端在 /onlyoffice 页面透传的 userId;避免固定 dev-user 导致 workspace membership 校验失败。
|
||||
const userId =
|
||||
String(searchParams.get("userId") || "").trim() ||
|
||||
String(process.env.DEV_USER_ID || "").trim() ||
|
||||
"dev-user";
|
||||
const client = getConvexHttpClient();
|
||||
|
||||
const asset = await client.query(api.mediaAssets.getById, { userId, id: assetId });
|
||||
|
||||
@@ -97,6 +97,18 @@ export async function GET(request: Request) {
|
||||
includeDeleted: true,
|
||||
});
|
||||
|
||||
const mediaAssets = await client.query(api.mediaAssets.listByWorkspace, {
|
||||
userId: auth.userId,
|
||||
workspaceId: targetWorkspaceId,
|
||||
limit: 200,
|
||||
});
|
||||
|
||||
const trashedMediaAssets = await client.query(api.mediaAssets.listDeletedByWorkspace, {
|
||||
userId: auth.userId,
|
||||
workspaceId: targetWorkspaceId,
|
||||
limit: 2000,
|
||||
});
|
||||
|
||||
const activeMindmaps = (mindmapRows ?? []).filter((r) => !r.deleted_at);
|
||||
const trashedMindmaps = (mindmapRows ?? []).filter((r) => !!r.deleted_at);
|
||||
|
||||
@@ -166,13 +178,13 @@ export async function GET(request: Request) {
|
||||
workspaces,
|
||||
documents,
|
||||
trashedDocuments,
|
||||
trashedMediaAssets: [],
|
||||
trashedMediaAssets: (trashedMediaAssets ?? []) as MediaAsset[],
|
||||
trashedMindmapAssets,
|
||||
mindmapDocs,
|
||||
mindmapAssets,
|
||||
mindmapAssetChildren,
|
||||
tableAssets: [],
|
||||
mediaAssets: [],
|
||||
mediaAssets: (mediaAssets ?? []) as MediaAsset[],
|
||||
};
|
||||
|
||||
return NextResponse.json(payload);
|
||||
|
||||
@@ -343,6 +343,27 @@ body {
|
||||
position: relative;
|
||||
margin: 24px 0;
|
||||
border-radius: 18px;
|
||||
/* 说明:Wolai 的附件/媒体块在选中时不显示蓝色外框(ProseMirror-selectednode 默认会加 outline)。 */
|
||||
outline: none !important;
|
||||
}
|
||||
/* 说明:文件附件需要表现为“普通行”,不应该出现外层大卡片/大外框 */
|
||||
.wolai-media--file {
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.wolai-media--file::after {
|
||||
content: none;
|
||||
}
|
||||
.wolai-media--file .wolai-media__canvas {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
.wolai-media--file .wolai-media__figure {
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
.wolai-media::after {
|
||||
content: "";
|
||||
@@ -353,8 +374,7 @@ body {
|
||||
transition: border-color 0.2s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
.wolai-media:hover::after,
|
||||
.wolai-media:focus-within::after {
|
||||
.wolai-media:hover::after {
|
||||
border-color: rgba(37, 99, 235, 0.15);
|
||||
}
|
||||
.wolai-media--empty {
|
||||
|
||||
@@ -279,8 +279,10 @@ export default function OnlyOfficePage() {
|
||||
const fileType = (params.get("fileType") ?? "docx").toLowerCase();
|
||||
const mode = (params.get("mode") ?? "edit") as EditorMode;
|
||||
const assetId = params.get("assetId") ?? "";
|
||||
const initialUserId = params.get("userId") ?? "";
|
||||
const channel = (params.get("channel") ?? "").trim().toLowerCase();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [authedUserId, setAuthedUserId] = useState<string>(initialUserId);
|
||||
const runtimeConfig = useMemo(() => getMnoteRuntimeConfig(), []);
|
||||
const baseUrlCandidates = useMemo(() => {
|
||||
const uniq: string[] = [];
|
||||
@@ -330,6 +332,31 @@ export default function OnlyOfficePage() {
|
||||
const proxyOrigin = runtimeConfig.onlyofficeProxyOrigin;
|
||||
const callbackOrigin = runtimeConfig.onlyofficeCallbackOrigin;
|
||||
|
||||
useEffect(() => {
|
||||
// 说明:ONLYOFFICE 回调由文档服务器触发,不携带用户 Cookie。
|
||||
// 为了让 /api/onlyoffice/callback 能以真实用户身份写回存储,
|
||||
// 这里尽量从当前会话获取 userId,并透传到 callbackUrl。
|
||||
if (authedUserId) return undefined;
|
||||
let canceled = false;
|
||||
fetch("/api/auth/whoami", { method: "GET" })
|
||||
.then(async (r) => {
|
||||
if (!r.ok) return null;
|
||||
return (await r.json().catch(() => null)) as { userId?: string } | null;
|
||||
})
|
||||
.then((payload) => {
|
||||
const uid = String(payload?.userId || "").trim();
|
||||
if (!uid) return;
|
||||
if (canceled) return;
|
||||
setAuthedUserId(uid);
|
||||
})
|
||||
.catch(() => {
|
||||
// ignore
|
||||
});
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [authedUserId]);
|
||||
|
||||
useEffect(() => {
|
||||
// 说明:在部分 ONLYOFFICE 版本/环境下,编辑器内部会触发 DOMException(NotFoundError: removeChild),
|
||||
// 该异常会被 Next.js 捕获并显示“客户端异常”大红屏,但实际文档仍可继续使用。
|
||||
@@ -540,7 +567,7 @@ export default function OnlyOfficePage() {
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, [assetId, baseUrl, callbackOrigin, fileName, fileType, fileUrl, mode, proxyOrigin, resolvedFileUrl]);
|
||||
}, [assetId, authedUserId, baseUrl, callbackOrigin, fileName, fileType, fileUrl, mode, proxyOrigin, resolvedFileUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!baseUrl) {
|
||||
@@ -623,6 +650,7 @@ export default function OnlyOfficePage() {
|
||||
const base = callbackOrigin || proxyOrigin || window.location.origin;
|
||||
const cb = new URL("/api/onlyoffice/callback", base);
|
||||
if (assetId) cb.searchParams.set("assetId", assetId);
|
||||
if (authedUserId) cb.searchParams.set("userId", authedUserId);
|
||||
return cb.toString();
|
||||
})(),
|
||||
customization: {
|
||||
|
||||
Reference in New Issue
Block a user