0.3.5 共享功能修复
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent, type KeyboardEvent } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent, type KeyboardEvent as ReactKeyboardEvent } from "react";
|
||||
import type { DocumentStats, PageOptionsState } from "@/types/page-options";
|
||||
import { PageOptionsSidebar } from "@/components/editor/page-options-sidebar";
|
||||
import { PageBacklinksPanel } from "@/components/editor/page-backlinks-panel";
|
||||
import { useEditorBridgeStore } from "@/store/editor-bridge";
|
||||
import { usePageLayoutStore } from "@/store/page-layout";
|
||||
import { useCurrentDocumentStore } from "@/store/current-document";
|
||||
import type { Json } from "@/types/supabase";
|
||||
import { DocumentHistoryDrawer } from "@/components/editor/document-history-drawer";
|
||||
import type { DocumentSnapshot } from "@/types/document";
|
||||
@@ -36,6 +37,8 @@ export interface DocumentContentProps {
|
||||
initialStats: DocumentStats | null;
|
||||
openTableId?: string | null;
|
||||
readOnly?: boolean;
|
||||
disableDownload?: boolean;
|
||||
disableCopy?: boolean;
|
||||
}
|
||||
|
||||
const defaultOptions: PageOptionsState = {
|
||||
@@ -59,7 +62,11 @@ export function DocumentContent({
|
||||
initialStats,
|
||||
openTableId,
|
||||
readOnly = false,
|
||||
disableDownload = false,
|
||||
disableCopy = false,
|
||||
}: DocumentContentProps) {
|
||||
const setCurrentDocument = useCurrentDocumentStore((state) => state.setCurrent);
|
||||
const clearIfMatch = useCurrentDocumentStore((state) => state.clearIfMatch);
|
||||
const [options, setOptions] = useState<PageOptionsState>(initialOptions ?? defaultOptions);
|
||||
const [stats, setStats] = useState<DocumentStats>(initialStats ?? defaultStats);
|
||||
const [history, setHistory] = useState<DocumentSnapshot[]>([]);
|
||||
@@ -76,6 +83,78 @@ export function DocumentContent({
|
||||
const contentLoadingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const pendingOpenTableRef = useRef<string | null>(null);
|
||||
const latestBlocksRef = useRef<Json | null>(null);
|
||||
const pageRootRef = useRef<HTMLDivElement>(null);
|
||||
const lastCopyBlockedAtRef = useRef<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentDocument(documentId, readOnly, disableDownload, disableCopy);
|
||||
return () => {
|
||||
clearIfMatch(documentId);
|
||||
};
|
||||
}, [clearIfMatch, disableCopy, disableDownload, documentId, readOnly, setCurrentDocument]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!disableCopy) return;
|
||||
|
||||
const isEventInsidePage = () => {
|
||||
const root = pageRootRef.current;
|
||||
if (!root) return false;
|
||||
const selection = typeof window !== "undefined" ? window.getSelection() : null;
|
||||
const anchor = selection?.anchorNode ?? null;
|
||||
const focus = selection?.focusNode ?? null;
|
||||
const anchorEl =
|
||||
anchor && "nodeType" in anchor && anchor.nodeType === Node.TEXT_NODE
|
||||
? anchor.parentElement
|
||||
: (anchor as any as Element | null);
|
||||
const focusEl =
|
||||
focus && "nodeType" in focus && focus.nodeType === Node.TEXT_NODE
|
||||
? focus.parentElement
|
||||
: (focus as any as Element | null);
|
||||
return Boolean((anchorEl && root.contains(anchorEl)) || (focusEl && root.contains(focusEl)));
|
||||
};
|
||||
|
||||
const notifyBlocked = () => {
|
||||
const now = Date.now();
|
||||
if (now - lastCopyBlockedAtRef.current < 1200) return;
|
||||
lastCopyBlockedAtRef.current = now;
|
||||
window.alert("该页面已禁止复制");
|
||||
};
|
||||
|
||||
const onCopy = (event: ClipboardEvent) => {
|
||||
if (!isEventInsidePage()) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
notifyBlocked();
|
||||
};
|
||||
|
||||
const onCut = (event: ClipboardEvent) => {
|
||||
if (!isEventInsidePage()) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
notifyBlocked();
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (!isEventInsidePage()) return;
|
||||
const key = String(event.key ?? "").toLowerCase();
|
||||
const ctrlOrMeta = event.ctrlKey || event.metaKey;
|
||||
if (!ctrlOrMeta) return;
|
||||
if (key === "c" || key === "x" || key === "insert") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
notifyBlocked();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("copy", onCopy, true);
|
||||
document.addEventListener("cut", onCut, true);
|
||||
document.addEventListener("keydown", onKeyDown, true);
|
||||
return () => {
|
||||
document.removeEventListener("copy", onCopy, true);
|
||||
document.removeEventListener("cut", onCut, true);
|
||||
document.removeEventListener("keydown", onKeyDown, true);
|
||||
};
|
||||
}, [disableCopy]);
|
||||
|
||||
useEffect(() => {
|
||||
const tableId = (openTableId ?? "").trim();
|
||||
@@ -208,7 +287,7 @@ export function DocumentContent({
|
||||
void persistTitle(pageTitle);
|
||||
};
|
||||
|
||||
const handleTitleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||
const handleTitleKeyDown = (event: ReactKeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
event.currentTarget.blur();
|
||||
@@ -251,6 +330,10 @@ export function DocumentContent({
|
||||
}, [updatedAt]);
|
||||
|
||||
const handleExport = useCallback(() => {
|
||||
if (disableDownload) {
|
||||
window.alert("该页面已禁止下载");
|
||||
return;
|
||||
}
|
||||
const latest = history[0];
|
||||
if (!latest) {
|
||||
window.alert("暂无可导出的内容");
|
||||
@@ -264,7 +347,7 @@ export function DocumentContent({
|
||||
anchor.download = `${title ?? "未命名页面"}-${new Date(latest.timestamp).toISOString()}.json`;
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}, [history, title]);
|
||||
}, [disableDownload, history, title]);
|
||||
|
||||
const handleSnapshot = useCallback((payload: { blocks: Json; stats: DocumentStats }) => {
|
||||
latestBlocksRef.current = payload.blocks;
|
||||
@@ -315,7 +398,7 @@ export function DocumentContent({
|
||||
|
||||
return (
|
||||
<ImagePickerProvider documentId={documentId} workspaceId={workspaceId}>
|
||||
<div className="flex h-full overflow-hidden bg-wolai-bg">
|
||||
<div className="flex h-full overflow-hidden bg-wolai-bg" ref={pageRootRef}>
|
||||
<div className="flex h-full flex-1 flex-col overflow-hidden">
|
||||
<div className="border-b border-wolai-border px-12 pb-6 pt-8">
|
||||
<div className="relative">
|
||||
|
||||
Reference in New Issue
Block a user