diff --git a/src/index.css b/src/index.css
index 4de0fb8..834fff3 100644
--- a/src/index.css
+++ b/src/index.css
@@ -34,7 +34,8 @@ body {
}
}
-.markdown-body,.tiptap {
+.markdown-body,
+.tiptap {
ul,
li {
list-style: unset;
@@ -47,4 +48,10 @@ iframe {
border: unset;
width: 100%;
height: 100%;
+ /* will-change: transform; */
+ /* pointer-events: none; */
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 99999;
}
diff --git a/src/modules/panels/components/WindowManager.tsx b/src/modules/panels/components/WindowManager.tsx
index 08454f7..aa8813d 100644
--- a/src/modules/panels/components/WindowManager.tsx
+++ b/src/modules/panels/components/WindowManager.tsx
@@ -1,5 +1,5 @@
-import React, { useState, useCallback, useRef, useEffect, RefObject } from 'react';
-import { Maximize2, Minimize2, Minimize, Expand, X, SquareMinus, Maximize, ChevronDown, CommandIcon } from 'lucide-react';
+import React, { useState, useCallback, useRef, useEffect, RefObject, useMemo } from 'react';
+import { Maximize2, Minimize2, Minimize, Expand, X, SquareMinus, Maximize, ChevronDown, CommandIcon, LogOut } from 'lucide-react';
import { WindowData, WindowPosition } from '../types';
import classNames from 'clsx';
import Draggable from 'react-draggable';
@@ -273,8 +273,23 @@ const WindowManager = React.forwardRef(({ windows: initialWindows, showTaskbar =
// window.removeEventListener('resize', handleResize);
// };
// }, []);
+ const showLogout = useMemo(() => {
+ return localStorage.getItem('token');
+ }, []);
return (
+ {showLogout && (
+
{
+ context?.app?.call?.({
+ path: 'user',
+ key: 'logout',
+ });
+ }}>
+
+
+ )}
{
diff --git a/src/modules/panels/render/manager/manager.ts b/src/modules/panels/render/manager/manager.ts
index 3843292..57b8954 100644
--- a/src/modules/panels/render/manager/manager.ts
+++ b/src/modules/panels/render/manager/manager.ts
@@ -38,6 +38,8 @@ export class BaseRender {
// @ts-ignore
const app = (await useContextKey('app')) as QueryRouterServer;
const render = windowData.render;
+ console.log('base render', render, render?.command);
+
if (render?.command) {
const res = await app.call({
path: render.command.path,
@@ -65,6 +67,8 @@ export class BaseRender {
data: windowData,
});
}
+ } else {
+ console.log('render error', res);
}
}
}
diff --git a/src/modules/panels/store/create/create-editor-window.ts b/src/modules/panels/store/create/create-editor-window.ts
index a479c88..b2aaf35 100644
--- a/src/modules/panels/store/create/create-editor-window.ts
+++ b/src/modules/panels/store/create/create-editor-window.ts
@@ -43,7 +43,7 @@ export const createEditorWindow = (pageId: string, nodeData: any, windowData?: W
render: {
command: {
path: 'editor',
- key: 'render',
+ key: 'nodeRender',
payload: {
pageId: pageId,
id: nodeData.id,
diff --git a/src/modules/panels/store/index.ts b/src/modules/panels/store/index.ts
index e03cd99..66e07df 100644
--- a/src/modules/panels/store/index.ts
+++ b/src/modules/panels/store/index.ts
@@ -90,7 +90,8 @@ export const usePanelStore = create
((set, get) => ({
set({
data: {
- windows: [e.windowData],
+ // windows: [e.windowData],
+ windows: [],
showTaskbar: true,
},
});
@@ -136,8 +137,9 @@ export const usePanelStore = create((set, get) => ({
const { width, height } = getDocumentWidthAndHeight();
data.windows.push({
id: '__ai__',
- title: 'AI Command',
+ title: '🤖 AI Command',
type: 'command',
+ showTitle: true,
position: {
x: 100,
y: height - 200 - 40,
@@ -147,6 +149,15 @@ export const usePanelStore = create((set, get) => ({
},
resizeHandles: ['se', 'sw', 'ne', 'nw', 's', 'w', 'n', 'e'],
show: true,
+ render: {
+ command: {
+ path: 'editor',
+ key: 'render',
+ payload: {
+ id: '__ai__',
+ },
+ },
+ },
});
}
// set({ data: { ...data, windows: data.windows } });
@@ -166,20 +177,20 @@ export const usePanelStore = create((set, get) => ({
},
}));
-const e = createEditorWindow(
- '123',
- {
- id: '123',
- title: '123',
- type: 'editor',
- position: { x: 0, y: 0, width: 100, height: 100, zIndex: 1000 },
- },
- createDemoEditorWindow({
- id: '123',
- title: '123',
- type: 'editor',
- position: { x: 0, y: 0, width: 100, height: 100, zIndex: 1000 },
- }),
-);
+// const e = createEditorWindow(
+// '123',
+// {
+// id: '123',
+// title: '123',
+// type: 'editor',
+// position: { x: 0, y: 0, width: 100, height: 100, zIndex: 1000 },
+// },
+// createDemoEditorWindow({
+// id: '123',
+// title: '123',
+// type: 'editor',
+// position: { x: 0, y: 0, width: 100, height: 100, zIndex: 1000 },
+// }),
+// );
-console.log('e', e);
+// console.log('e', e);
diff --git a/src/pages/demo-login/index.css b/src/pages/demo-login/index.css
new file mode 100644
index 0000000..dbc7738
--- /dev/null
+++ b/src/pages/demo-login/index.css
@@ -0,0 +1,40 @@
+
+.demo-login-prompt {
+ background-color: white;
+ padding: 2rem;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ text-align: center;
+ max-width: 400px;
+ width: 90%;
+}
+
+.demo-login-link {
+ margin-bottom: 1rem;
+ font-size: 1.2rem;
+}
+
+.demo-login-link a {
+ color: #4a90e2;
+ text-decoration: none;
+ transition: color 0.3s ease;
+}
+
+.demo-login-link a:hover {
+ color: #357abd;
+ text-decoration: underline;
+}
+
+.demo-accounts {
+ color: #666;
+ font-size: 0.95rem;
+}
+
+.demo-account {
+ background-color: #f0f0f0;
+ padding: 0.2rem 0.5rem;
+ border-radius: 4px;
+ margin: 0 0.3rem;
+ color: #333;
+ font-family: monospace;
+}
\ No newline at end of file
diff --git a/src/pages/demo-login/index.tsx b/src/pages/demo-login/index.tsx
new file mode 100644
index 0000000..f96ae7c
--- /dev/null
+++ b/src/pages/demo-login/index.tsx
@@ -0,0 +1,27 @@
+import { Button } from '@mui/material';
+import './index.css';
+export const AppendDemo = () => {
+ return (
+
+ );
+};
+
+export const DemoLogin = () => {
+ return (
+
+
+
+
+ );
+};
diff --git a/src/pages/editor/NodeTextEditor.tsx b/src/pages/editor/NodeTextEditor.tsx
index 4025a7c..4666591 100644
--- a/src/pages/editor/NodeTextEditor.tsx
+++ b/src/pages/editor/NodeTextEditor.tsx
@@ -30,6 +30,11 @@ export const useListenCtrlS = (saveContent: () => void, exitEdit: () => void) =>
type EditorProps = {
id?: string;
};
+/**
+ * Node Edit Editor
+ * @param param0
+ * @returns
+ */
export const NodeTextEditor = ({ id }: EditorProps) => {
const textEditorRef = useRef(null);
const editorRef = useRef(null);
@@ -61,7 +66,7 @@ export const NodeTextEditor = ({ id }: EditorProps) => {
};
const exitEdit = () => {
// 退出编辑
- saveContent()
+ saveContent();
setTimeout(() => {
app.call({
path: 'panels',
diff --git a/src/pages/editor/index.tsx b/src/pages/editor/index.tsx
index 1607613..579be14 100644
--- a/src/pages/editor/index.tsx
+++ b/src/pages/editor/index.tsx
@@ -1,2 +1,62 @@
-import { Editor } from '@/modules/editor';
-export { Editor };
+import { TextEditor } from '@/modules/tiptap/editor';
+import { useEffect, useRef, useState } from 'react';
+import clsx from 'clsx';
+
+export const useListenCtrlEnter = (callback: () => void) => {
+ useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.ctrlKey && event.key === 'Enter') {
+ event.preventDefault();
+ callback();
+ }
+ };
+ window.addEventListener('keydown', handleKeyDown);
+ return () => {
+ window.removeEventListener('keydown', handleKeyDown);
+ };
+ }, []);
+};
+type EditorProps = {
+ className?: string;
+ value?: string;
+ id?: string;
+ onChange?: (value: string) => void;
+};
+export const AiEditor = ({ className, value, onChange, id }: EditorProps) => {
+ const textEditorRef = useRef(null);
+ const editorRef = useRef(null);
+ const [mount, setMount] = useState(false);
+ useEffect(() => {
+ const editor = new TextEditor();
+ textEditorRef.current = editor;
+ editor.createEditor(editorRef.current!, {
+ html: value,
+ onUpdateHtml: (html) => {
+ onChange?.(html);
+ },
+ });
+ setMount(true);
+ return () => {
+ editor.destroy();
+ };
+ }, []);
+ useListenCtrlEnter(() => {
+ context?.app.call({
+ path: 'command',
+ key: 'handle',
+ payload: {
+ html: textEditorRef.current?.getHtml() || '',
+ },
+ });
+ });
+ useEffect(() => {
+ if (textEditorRef.current && id && mount) {
+ textEditorRef.current.setContent(value || '');
+ }
+ }, [id, mount]);
+ return (
+
+ );
+};
diff --git a/src/pages/wall/components/SplitToast.tsx b/src/pages/wall/components/SplitToast.tsx
index 53806bf..5f9fbb6 100644
--- a/src/pages/wall/components/SplitToast.tsx
+++ b/src/pages/wall/components/SplitToast.tsx
@@ -22,3 +22,14 @@ export function SplitButtons({ closeToast }: ToastContentProps) {
);
}
+
+// toast(SplitButtons, {
+// closeButton: false,
+// className: 'p-0 w-[400px] border border-purple-600/40',
+// ariaLabel: 'Email received',
+// onClose: (reason) => {
+// if (reason === 'success') {
+// set({ open: true, selectedNode: data, hasEdited: false });
+// }
+// },
+// });
diff --git a/src/pages/wall/constants.ts b/src/pages/wall/constants.ts
index 0667cd3..b49ca59 100644
--- a/src/pages/wall/constants.ts
+++ b/src/pages/wall/constants.ts
@@ -1 +1,10 @@
export const BlankNoteText = '
double click to edit';
+
+// https://www.reactbits.dev/text-animations/circular-text
+export const CircularText =
+ '
';
+
+export const CircularText2 =
+ '
';
+
+export const CircularText3 = `
`;
diff --git a/src/pages/wall/hooks/tab-node.ts b/src/pages/wall/hooks/tab-node.ts
index 068ce6f..7b580f1 100644
--- a/src/pages/wall/hooks/tab-node.ts
+++ b/src/pages/wall/hooks/tab-node.ts
@@ -4,10 +4,10 @@ import { useWallStore } from '../store/wall';
import { useShallow } from 'zustand/react/shallow';
export const useTabNode = () => {
const reactFlowInstance = useReactFlow();
- const open = useWallStore(useShallow((state) => state.open));
useEffect(() => {
- if (open) return;
const listener = (event: any) => {
+ const selected = reactFlowInstance.getNodes().find((node) => node.selected);
+ if (!selected) return;
if (event.key === 'Tab') {
const nodes = reactFlowInstance.getNodes();
const selectedNode = nodes.find((node) => node.selected);
@@ -56,9 +56,19 @@ export const useTabNode = () => {
event.stopPropagation();
}
};
+ const rightClickListener = (event: any) => {
+ const selected = reactFlowInstance.getNodes().find((node) => node.selected);
+ if (!selected) return;
+ if (event.button === 2) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ };
window.addEventListener('keydown', listener);
+ window.addEventListener('contextmenu', rightClickListener);
return () => {
window.removeEventListener('keydown', listener);
+ window.removeEventListener('contextmenu', rightClickListener);
};
- }, [reactFlowInstance, open]);
+ }, [reactFlowInstance]);
};
diff --git a/src/pages/wall/index.tsx b/src/pages/wall/index.tsx
index f7a015e..bdba154 100644
--- a/src/pages/wall/index.tsx
+++ b/src/pages/wall/index.tsx
@@ -20,7 +20,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useCheckDoubleClick } from './hooks/check-double-click';
import { randomId } from './utils/random';
import { CustomNodeType } from './modules/CustomNode';
-import Drawer from './modules/Drawer';
import { message } from '@/modules/message';
import { useShallow } from 'zustand/react/shallow';
import { BlankNoteText } from './constants';
@@ -33,6 +32,7 @@ import { useListenPaster } from './hooks/listen-copy';
import { ContextMenu } from './modules/ContextMenu';
import { useSelect } from './hooks/use-select';
import clsx from 'clsx';
+import { AppendDemo, DemoLogin } from '../demo-login';
type NodeData = {
id: string;
position: XYPosition;
@@ -47,6 +47,7 @@ export function FlowContent() {
return {
nodes: state.nodes,
saveNodes: state.saveNodes,
+ saveDataNode: state.saveDataNode,
checkAndOpen: state.checkAndOpen,
mouseSelect: state.mouseSelect, // 鼠标模式,不能拖动
setMouseSelect: state.setMouseSelect,
@@ -69,7 +70,7 @@ export function FlowContent() {
wallStore.saveNodes(reactFlowInstance.getNodes().filter((item) => item.id !== change.id));
}
if (change.type === 'position' && change.dragging === false) {
- getNewNodes(false);
+ getNewNodes(false, changes);
}
onNodesChange(changes);
}, []);
@@ -96,9 +97,15 @@ export function FlowContent() {
const onNodeDoubleClick = (event, node) => {
wallStore.checkAndOpen(true, node);
};
- const getNewNodes = (showMessage = true) => {
+ const getNewNodes = (showMessage = true, changes?: NodeChange[]) => {
const nodes = reactFlowInstance.getNodes();
- wallStore.saveNodes(nodes, { showMessage: showMessage });
+ // wallStore.saveNodes(nodes, { showMessage: showMessage });
+ // console.log('change', changes);
+ const operateNodes = nodes.filter((node) => {
+ return changes?.some((change) => change.type === 'position' && change.id === node.id);
+ });
+ console.log('operateNodes', operateNodes);
+ wallStore.saveDataNode(operateNodes);
};
useEffect(() => {
if (mount) {
@@ -125,7 +132,8 @@ export function FlowContent() {
});
setTimeout(() => {
wallStore.checkAndOpen(true, newNode);
- getNewNodes();
+ // getNewNodes();
+ wallStore.saveDataNode([newNode]);
}, 200);
};
const hasFoucedNode = useMemo(() => {
@@ -187,11 +195,10 @@ export function FlowContent() {
-
{contextMenu && }
- {' '}
+
{isSelecting && selectionBox && (
);
}
-export const Flow = ({ checkLogin = true }: { checkLogin?: boolean }) => {
- // const { id } = useParams();
- const id = '';
- // const navigate = useNavigate();
+export const Flow = ({ id }: { checkLogin?: boolean; id?: string }) => {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ return
;
+ }
const wallStore = useWallStore(
useShallow((state) => {
return {
@@ -225,23 +233,14 @@ export const Flow = ({ checkLogin = true }: { checkLogin?: boolean }) => {
useEffect(() => {
wallStore.init(id);
- console.log('checkLogin', checkLogin, id);
- }, [id, checkLogin]);
+ }, [id]);
if (!wallStore.loaded) {
return
loading...
;
} else if (wallStore.loaded === 'error') {
return (
-
获取失败,请稍后刷新重试,或者转到首页
-
+
获取失败,请稍后刷新重试
);
}
@@ -251,13 +250,3 @@ export const Flow = ({ checkLogin = true }: { checkLogin?: boolean }) => {
);
};
-export const FlowStatus = () => {
- const { nodes } = useWallStore();
- const reactFlow = useReactFlow();
- const flowStore = useStore((state) => state);
- return (
-
- );
-};
diff --git a/src/pages/wall/modules/CustomNode.css b/src/pages/wall/modules/CustomNode.css
index 9805b2b..b09d3da 100644
--- a/src/pages/wall/modules/CustomNode.css
+++ b/src/pages/wall/modules/CustomNode.css
@@ -1,5 +1,7 @@
@import 'tailwindcss';
-
+:root {
+ --xy-resize-background-color: #000;
+}
@layer components {
.node-editor {
@apply w-full h-full bg-white;
@@ -27,10 +29,10 @@
:root {
--purple-light: #e0e0ff; /* 默认浅紫色背景 */
- --black: #000000; /* 默认黑色 */
- --white: #ffffff; /* 默认白色 */
- --gray-3: #d3d3d3; /* 默认灰色3 */
- --gray-2: #e5e5e5; /* 默认灰色2 */
+ --black: #000000; /* 默认黑色 */
+ --white: #ffffff; /* 默认白色 */
+ --gray-3: #d3d3d3; /* 默认灰色3 */
+ --gray-2: #e5e5e5; /* 默认灰色2 */
}
.tiptap-preview {
.tiptap {
@@ -80,7 +82,7 @@
.tiptap h2 {
/* margin-top: 3.5rem; */
margin-top: 1rem;
- margin-bottom: .5rem;
+ margin-bottom: 0.5rem;
}
.tiptap h1 {
@@ -131,7 +133,7 @@
}
.tiptap mark {
- background-color: #FAF594;
+ background-color: #faf594;
border-radius: 0.4rem;
box-decoration-break: clone;
padding: 0.1rem 0.3rem;
diff --git a/src/pages/wall/modules/CustomNode.tsx b/src/pages/wall/modules/CustomNode.tsx
index fc17ab3..521afc0 100644
--- a/src/pages/wall/modules/CustomNode.tsx
+++ b/src/pages/wall/modules/CustomNode.tsx
@@ -12,13 +12,10 @@ export type WallData
> = {
html: string;
width?: number;
height?: number;
+ updatedAt?: number;
[key: string]: any;
} & T;
-const ShowContent = (props: { data: WallData; selected: boolean }) => {
- const html = props.data.html;
- const selected = props.selected;
- const showRef = useRef(null);
- if (!html) return 空
;
+const ShowContent = (props: { data: WallData; id: string; selected: boolean }) => {
const [highlightHtml, setHighlightHtml] = useState('');
const highlight = async (html: string) => {
const _html = html.replace(/([\s\S]*?)<\/code><\/pre>/g, (match, p1, p2) => {
@@ -27,19 +24,23 @@ const ShowContent = (props: { data: WallData; selected: boolean }) => {
return _html;
};
useEffect(() => {
- highlight(html).then((res) => {
+ highlight(props.data.html).then((res) => {
setHighlightHtml(res);
});
- }, [html]);
-
+ }, [props.data.html]);
+ useEffect(() => {
+ const id = props.id;
+ const container = document.querySelector('.id' + id);
+ if (container) {
+ container.innerHTML = highlightHtml;
+ }
+ }, [highlightHtml, props.data.updatedAt]);
return (
+ pointerEvents: props.selected ? 'auto' : 'none',
+ }}>
);
};
@@ -52,7 +53,6 @@ export const CustomNode = (props: { id: string; data: WallData; selected: boolea
useShallow((state) => {
return {
id: state.id,
- setSelectedNode: state.setSelectedNode,
saveNodes: state.saveNodes,
checkAndOpen: state.checkAndOpen,
};
@@ -87,9 +87,7 @@ export const CustomNode = (props: { id: string; data: WallData; selected: boolea
});
const width = data.width || 100;
const height = data.height || 100;
- const style: React.CSSProperties = {};
- style.width = width;
- style.height = height;
+
const showOpen = () => {
const node = store.getNode(props.id);
console.log('node eidt', node);
@@ -104,33 +102,33 @@ export const CustomNode = (props: { id: string; data: WallData; selected: boolea
},
},
});
- // if (node) {
- // const dataType: string = (node?.data?.dataType as string) || '';
- // if (dataType && dataType?.startsWith('image')) {
- // message.error('不支持编辑图片');
- // return;
- // } else if (dataType) {
- // message.error('不支持编辑');
- // return;
- // }
- // wallStore.checkAndOpen(true, node);
- // } else {
- // message.error('节点不存在');
- // }
};
- const handleSize = Math.max(10, 10 / zoom);
+ const handleSize = Math.max(8, 8 / zoom);
return (
<>
+ {
showOpen();
- // e.stopPropagation();
e.preventDefault();
}}
- className={clsx('w-full h-full border relative border-gray-300 min-w-[100px] min-h-[50px] tiptap-preview')}
- style={style}>
-
+ className={clsx('w-full h-full border relative border-gray-300 min-w-[100px] min-h-[50px] tiptap-preview', {
+ 'pointer-events-none': !props.selected,
+ 'pointer-events-auto': props.selected,
+ })}
+ style={{
+ width: width,
+ height: height,
+ }}>
+