chore: release 0.0.1
This commit is contained in:
@@ -170,6 +170,7 @@ export function BlockNoteEditor({
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [tocEntries, setTocEntries] = useState<TocEntry[]>([]);
|
||||
const [fullScreenTableId, setFullScreenTableId] = useState<string | null>(null);
|
||||
const isFullScreenTableOpen = fullScreenTableId !== null;
|
||||
|
||||
const openReferencePalette = useSearchPaletteStore((state) => state.openReference);
|
||||
const registerEditorBridge = useEditorBridgeStore((state) => state.registerBridge);
|
||||
@@ -280,6 +281,7 @@ export function BlockNoteEditor({
|
||||
const blocknoteClass = cn(
|
||||
"wolai-editor min-h-full",
|
||||
pageOptions.showStructure && "wolai-editor-show-structure",
|
||||
isFullScreenTableOpen && "pointer-events-none select-none",
|
||||
);
|
||||
|
||||
const buildDocumentPath = (documentId: string): string => {
|
||||
@@ -575,11 +577,13 @@ const computeDocumentStats = (blocks: Block<CustomBlockSchema>[]): DocumentStats
|
||||
editable={!pageOptions.protectEditing}
|
||||
className={blocknoteClass}
|
||||
>
|
||||
<SideMenuController
|
||||
sideMenu={(props: SideMenuProps<CustomBlockSchema>) => (
|
||||
<CustomSideMenu {...props} currentDocumentId={documentId} />
|
||||
)}
|
||||
/>
|
||||
{!isFullScreenTableOpen && (
|
||||
<SideMenuController
|
||||
sideMenu={(props: SideMenuProps<CustomBlockSchema>) => (
|
||||
<CustomSideMenu {...props} currentDocumentId={documentId} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<CustomSlashMenu editor={editor} currentDocumentId={documentId} />
|
||||
</BlockNoteView>
|
||||
<div className="pointer-events-none absolute right-4 top-3 text-xs text-gray-400">
|
||||
|
||||
@@ -2,13 +2,42 @@
|
||||
|
||||
import { BlockNoteEditor, Block } from "@blocknote/core";
|
||||
import { createReactBlockSpec } from "@blocknote/react";
|
||||
import { OnlineTableBlockProps } from "@/types/online-table";
|
||||
import { Table } from "lucide-react";
|
||||
import React, { useCallback } from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import type { CustomBlockSchema } from "../schema";
|
||||
import CompactTablePreview from "@/components/online-table/CompactTablePreview";
|
||||
import { useEditorBridgeStore } from "@/store/editor-bridge";
|
||||
|
||||
const DEFAULT_WIDTH = 960;
|
||||
const DEFAULT_HEIGHT = 520;
|
||||
const MIN_WIDTH = 420;
|
||||
const MAX_WIDTH = 1400;
|
||||
const MIN_HEIGHT = 320;
|
||||
const MAX_HEIGHT = 900;
|
||||
|
||||
type ResizeHandle =
|
||||
| "left"
|
||||
| "right"
|
||||
| "top"
|
||||
| "bottom"
|
||||
| "top-left"
|
||||
| "top-right"
|
||||
| "bottom-left"
|
||||
| "bottom-right";
|
||||
|
||||
const handleMapping: Record<
|
||||
ResizeHandle,
|
||||
{ horizontal?: "left" | "right"; vertical?: "top" | "bottom" }
|
||||
> = {
|
||||
left: { horizontal: "left" },
|
||||
right: { horizontal: "right" },
|
||||
top: { vertical: "top" },
|
||||
bottom: { vertical: "bottom" },
|
||||
"top-left": { horizontal: "left", vertical: "top" },
|
||||
"top-right": { horizontal: "right", vertical: "top" },
|
||||
"bottom-left": { horizontal: "left", vertical: "bottom" },
|
||||
"bottom-right": { horizontal: "right", vertical: "bottom" },
|
||||
};
|
||||
|
||||
// 占位符组件:在紧凑模式下渲染表格块
|
||||
const OnlineTableBlockComponent = ({
|
||||
block,
|
||||
@@ -19,6 +48,34 @@ const OnlineTableBlockComponent = ({
|
||||
}) => {
|
||||
const { tableId } = block.props;
|
||||
const openTableFullScreen = useEditorBridgeStore((state) => state.bridge?.openTableFullScreen);
|
||||
const storedWidth = typeof block.props.width === "number" ? block.props.width : undefined;
|
||||
const storedHeight = typeof block.props.height === "number" ? block.props.height : undefined;
|
||||
const [activeHandle, setActiveHandle] = useState<ResizeHandle | null>(null);
|
||||
const [draftSize, setDraftSize] = useState({
|
||||
width: storedWidth ?? DEFAULT_WIDTH,
|
||||
height: storedHeight ?? DEFAULT_HEIGHT,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setDraftSize({
|
||||
width: storedWidth ?? DEFAULT_WIDTH,
|
||||
height: storedHeight ?? DEFAULT_HEIGHT,
|
||||
});
|
||||
}, [storedWidth, storedHeight]);
|
||||
|
||||
const commitSize = useCallback(
|
||||
(next: { width: number; height: number }) => {
|
||||
setDraftSize(next);
|
||||
editor.updateBlock(block, {
|
||||
props: {
|
||||
...block.props,
|
||||
width: next.width,
|
||||
height: next.height,
|
||||
},
|
||||
});
|
||||
},
|
||||
[block, block.props, editor],
|
||||
);
|
||||
|
||||
// 阶段三:实现双击/按钮进入全屏编辑
|
||||
const handleFullScreen = () => {
|
||||
@@ -33,9 +90,151 @@ const OnlineTableBlockComponent = ({
|
||||
editor.removeBlocks([block.id]);
|
||||
}, [block.id, editor]);
|
||||
|
||||
const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));
|
||||
|
||||
const startResize = useCallback(
|
||||
(handle: ResizeHandle) => (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const startX = event.clientX;
|
||||
const startY = event.clientY;
|
||||
const startWidth = draftSize.width;
|
||||
const startHeight = draftSize.height;
|
||||
let nextWidth = startWidth;
|
||||
let nextHeight = startHeight;
|
||||
const axes = handleMapping[handle];
|
||||
setActiveHandle(handle);
|
||||
document.body.style.userSelect = "none";
|
||||
const cursor =
|
||||
axes.horizontal && axes.vertical
|
||||
? axes.horizontal === "left"
|
||||
? axes.vertical === "top"
|
||||
? "nwse-resize"
|
||||
: "nesw-resize"
|
||||
: axes.vertical === "top"
|
||||
? "nesw-resize"
|
||||
: "nwse-resize"
|
||||
: axes.horizontal
|
||||
? "ew-resize"
|
||||
: "ns-resize";
|
||||
document.body.style.cursor = cursor;
|
||||
|
||||
const handleMove = (moveEvent: MouseEvent) => {
|
||||
const deltaX = moveEvent.clientX - startX;
|
||||
const deltaY = moveEvent.clientY - startY;
|
||||
if (axes.horizontal === "left") {
|
||||
nextWidth = clamp(startWidth - deltaX, MIN_WIDTH, MAX_WIDTH);
|
||||
} else if (axes.horizontal === "right") {
|
||||
nextWidth = clamp(startWidth + deltaX, MIN_WIDTH, MAX_WIDTH);
|
||||
} else {
|
||||
nextWidth = startWidth;
|
||||
}
|
||||
|
||||
if (axes.vertical === "top") {
|
||||
nextHeight = clamp(startHeight - deltaY, MIN_HEIGHT, MAX_HEIGHT);
|
||||
} else if (axes.vertical === "bottom") {
|
||||
nextHeight = clamp(startHeight + deltaY, MIN_HEIGHT, MAX_HEIGHT);
|
||||
} else {
|
||||
nextHeight = startHeight;
|
||||
}
|
||||
setDraftSize({
|
||||
width: nextWidth,
|
||||
height: nextHeight,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUp = () => {
|
||||
window.removeEventListener("mousemove", handleMove);
|
||||
window.removeEventListener("mouseup", handleUp);
|
||||
document.body.style.userSelect = "";
|
||||
document.body.style.cursor = "";
|
||||
setActiveHandle(null);
|
||||
commitSize({
|
||||
width: Math.round(nextWidth),
|
||||
height: Math.round(nextHeight),
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("mousemove", handleMove);
|
||||
window.addEventListener("mouseup", handleUp);
|
||||
},
|
||||
[commitSize, draftSize.height, draftSize.width],
|
||||
);
|
||||
|
||||
const size = useMemo(
|
||||
() => ({
|
||||
width: clamp(draftSize.width, MIN_WIDTH, MAX_WIDTH),
|
||||
height: clamp(draftSize.height, MIN_HEIGHT, MAX_HEIGHT),
|
||||
}),
|
||||
[draftSize.height, draftSize.width],
|
||||
);
|
||||
|
||||
const handleClass = (handle: ResizeHandle) =>
|
||||
`wolai-table-resize-handle wolai-table-resize-handle--${handle} ${
|
||||
activeHandle === handle ? "is-dragging" : ""
|
||||
}`;
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<CompactTablePreview tableId={tableId} onFullScreen={handleFullScreen} onDelete={handleDelete} />
|
||||
<div className="w-full overflow-auto" contentEditable={false}>
|
||||
<div
|
||||
className="online-table-block group relative mx-auto"
|
||||
style={{ width: size.width, minWidth: MIN_WIDTH }}
|
||||
>
|
||||
<CompactTablePreview
|
||||
tableId={tableId}
|
||||
onFullScreen={handleFullScreen}
|
||||
onDelete={handleDelete}
|
||||
height={size.height}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="向左拖拽以调整宽度"
|
||||
className={handleClass("left")}
|
||||
onMouseDown={startResize("left")}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="向右拖拽以调整宽度"
|
||||
className={handleClass("right")}
|
||||
onMouseDown={startResize("right")}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="向上拖拽以调整高度"
|
||||
className={handleClass("top")}
|
||||
onMouseDown={startResize("top")}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="向下拖拽以调整高度"
|
||||
className={handleClass("bottom")}
|
||||
onMouseDown={startResize("bottom")}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="拖拽以调整宽高"
|
||||
className={handleClass("top-left")}
|
||||
onMouseDown={startResize("top-left")}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="拖拽以调整宽高"
|
||||
className={handleClass("top-right")}
|
||||
onMouseDown={startResize("top-right")}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="拖拽以调整宽高"
|
||||
className={handleClass("bottom-left")}
|
||||
onMouseDown={startResize("bottom-left")}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="拖拽以调整宽高"
|
||||
className={handleClass("bottom-right")}
|
||||
onMouseDown={startResize("bottom-right")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -47,6 +246,8 @@ export const onlineTableBlock = createReactBlockSpec(
|
||||
propSchema: {
|
||||
tableId: { default: "new-table-id" }, // 应该在创建时被覆盖
|
||||
title: { default: "未命名表格" },
|
||||
width: { default: DEFAULT_WIDTH },
|
||||
height: { default: DEFAULT_HEIGHT },
|
||||
},
|
||||
content: "inline", // 允许内联内容,但通常表格块不会有太多内联内容
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user