0.3.2.1 UI修复3

This commit is contained in:
liaibo
2026-01-20 07:24:12 +08:00
parent fa7235b1a7
commit f13de35321
16 changed files with 741 additions and 108 deletions
@@ -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 });
+14 -2
View File
@@ -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);
+22 -2
View File
@@ -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: {