mirror of
https://github.com/lordmathis/lemma.git
synced 2025-12-22 17:44:25 +00:00
Compare commits
5 Commits
7424ce4385
...
d9f1a16d94
| Author | SHA1 | Date | |
|---|---|---|---|
| d9f1a16d94 | |||
| 0999fa9315 | |||
| cc8c8fd414 | |||
| 9ba37b3342 | |||
| 140ccd6879 |
76
app/package-lock.json
generated
76
app/package-lock.json
generated
@@ -15,6 +15,7 @@
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/theme-one-dark": "^6.1.3",
|
||||
"@codemirror/view": "^6.38.6",
|
||||
"@floating-ui/react": "^0.27.16",
|
||||
"@mantine/core": "^8.3.7",
|
||||
"@mantine/hooks": "^8.3.7",
|
||||
"@mantine/modals": "^8.3.7",
|
||||
@@ -22,9 +23,9 @@
|
||||
"@react-hook/resize-observer": "^2.0.2",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"codemirror": "^6.0.2",
|
||||
"react": "^18.3.1",
|
||||
"react": "^19.2.0",
|
||||
"react-arborist": "^3.4.3",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dom": "^19.2.0",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"rehype-mathjax": "^7.1.0",
|
||||
"rehype-react": "^8.0.0",
|
||||
@@ -41,8 +42,8 @@
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^18.3.20",
|
||||
"@types/react-dom": "^18.3.6",
|
||||
"@types/react": "^19.2.5",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.4",
|
||||
"@typescript-eslint/parser": "^8.32.1",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
@@ -2558,32 +2559,24 @@
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
||||
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz",
|
||||
"integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==",
|
||||
"version": "19.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.5.tgz",
|
||||
"integrity": "sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "18.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.6.tgz",
|
||||
"integrity": "sha512-nf22//wEbKXusP6E9pfOCDwFdHAX4u172eaJI4YkDRQEZiorm6KfYnSC2SWLDMVWUOWPERmJnN0ujeAfTBLvrw==",
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
@@ -5808,9 +5801,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -7426,13 +7419,10 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"version": "19.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -7494,16 +7484,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"version": "19.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1"
|
||||
"react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
@@ -8089,13 +8078,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
@@ -9157,12 +9143,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
|
||||
"integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/theme-one-dark": "^6.1.3",
|
||||
"@codemirror/view": "^6.38.6",
|
||||
"@floating-ui/react": "^0.27.16",
|
||||
"@mantine/core": "^8.3.7",
|
||||
"@mantine/hooks": "^8.3.7",
|
||||
"@mantine/modals": "^8.3.7",
|
||||
@@ -42,9 +43,9 @@
|
||||
"@react-hook/resize-observer": "^2.0.2",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"codemirror": "^6.0.2",
|
||||
"react": "^18.3.1",
|
||||
"react": "^19.2.0",
|
||||
"react-arborist": "^3.4.3",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dom": "^19.2.0",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"rehype-mathjax": "^7.1.0",
|
||||
"rehype-react": "^8.0.0",
|
||||
@@ -61,8 +62,8 @@
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^18.3.20",
|
||||
"@types/react-dom": "^18.3.6",
|
||||
"@types/react": "^19.2.5",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.4",
|
||||
"@typescript-eslint/parser": "^8.32.1",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
|
||||
@@ -69,7 +69,19 @@ vi.mock('react-arborist', () => ({
|
||||
|
||||
// Mock resize observer hook
|
||||
vi.mock('@react-hook/resize-observer', () => ({
|
||||
default: vi.fn(),
|
||||
default: vi.fn(
|
||||
(
|
||||
_target: unknown,
|
||||
callback: (entry: { contentRect: { width: number; height: number } }) => void
|
||||
) => {
|
||||
// Immediately call the callback with a mock entry to provide size
|
||||
if (callback) {
|
||||
setTimeout(() => {
|
||||
callback({ contentRect: { width: 300, height: 600 } });
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock contexts
|
||||
@@ -172,7 +184,7 @@ describe('FileTree', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders file tree with files', () => {
|
||||
it('renders file tree with files', async () => {
|
||||
const { getByTestId } = render(
|
||||
<TestWrapper>
|
||||
<FileTree
|
||||
@@ -184,7 +196,9 @@ describe('FileTree', () => {
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
expect(getByTestId('file-tree')).toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('file-tree')).toBeInTheDocument();
|
||||
});
|
||||
expect(getByTestId('file-node-1')).toBeInTheDocument();
|
||||
expect(getByTestId('file-node-2')).toBeInTheDocument();
|
||||
});
|
||||
@@ -201,6 +215,10 @@ describe('FileTree', () => {
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('file-node-1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const fileNode = getByTestId('file-node-1');
|
||||
fireEvent.click(fileNode);
|
||||
|
||||
@@ -209,7 +227,7 @@ describe('FileTree', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('filters out hidden files when showHiddenFiles is false', () => {
|
||||
it('filters out hidden files when showHiddenFiles is false', async () => {
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TestWrapper>
|
||||
<FileTree
|
||||
@@ -221,6 +239,10 @@ describe('FileTree', () => {
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('file-node-1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Should show regular files
|
||||
expect(getByTestId('file-node-1')).toBeInTheDocument();
|
||||
expect(getByTestId('file-node-2')).toBeInTheDocument();
|
||||
@@ -229,7 +251,7 @@ describe('FileTree', () => {
|
||||
expect(queryByTestId('file-node-4')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows hidden files when showHiddenFiles is true', () => {
|
||||
it('shows hidden files when showHiddenFiles is true', async () => {
|
||||
const { getByTestId } = render(
|
||||
<TestWrapper>
|
||||
<FileTree
|
||||
@@ -241,13 +263,17 @@ describe('FileTree', () => {
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('file-node-1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Should show all files including hidden
|
||||
expect(getByTestId('file-node-1')).toBeInTheDocument();
|
||||
expect(getByTestId('file-node-2')).toBeInTheDocument();
|
||||
expect(getByTestId('file-node-4')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders empty tree when no files provided', () => {
|
||||
it('renders empty tree when no files provided', async () => {
|
||||
const { getByTestId } = render(
|
||||
<TestWrapper>
|
||||
<FileTree
|
||||
@@ -259,6 +285,10 @@ describe('FileTree', () => {
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('file-tree')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const tree = getByTestId('file-tree');
|
||||
expect(tree).toBeInTheDocument();
|
||||
expect(tree.children).toHaveLength(0);
|
||||
@@ -276,6 +306,10 @@ describe('FileTree', () => {
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('file-node-2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click on folder (has children)
|
||||
const folderNode = getByTestId('file-node-2');
|
||||
fireEvent.click(folderNode);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useRef, useState, useLayoutEffect, useCallback } from 'react';
|
||||
import React, { useRef, useState, useCallback } from 'react';
|
||||
import { Tree, type NodeApi } from 'react-arborist';
|
||||
import {
|
||||
IconFile,
|
||||
@@ -23,15 +23,11 @@ interface FileTreeProps {
|
||||
loadFileList: () => Promise<void>;
|
||||
}
|
||||
|
||||
const useSize = (target: React.RefObject<HTMLElement>): Size | undefined => {
|
||||
const useSize = (
|
||||
target: React.RefObject<HTMLElement | null>
|
||||
): Size | undefined => {
|
||||
const [size, setSize] = useState<Size>();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (target.current) {
|
||||
setSize(target.current.getBoundingClientRect());
|
||||
}
|
||||
}, [target]);
|
||||
|
||||
useResizeObserver(target, (entry) => setSize(entry.contentRect));
|
||||
return size;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useRef, useLayoutEffect, useState } from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Box } from '@mantine/core';
|
||||
import { Tree, type NodeApi } from 'react-arborist';
|
||||
import {
|
||||
@@ -21,15 +21,11 @@ interface Size {
|
||||
height: number;
|
||||
}
|
||||
|
||||
const useSize = (target: React.RefObject<HTMLElement>): Size | undefined => {
|
||||
const useSize = (
|
||||
target: React.RefObject<HTMLElement | null>
|
||||
): Size | undefined => {
|
||||
const [size, setSize] = useState<Size>();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (target.current) {
|
||||
setSize(target.current.getBoundingClientRect());
|
||||
}
|
||||
}, [target]);
|
||||
|
||||
useResizeObserver(target, (entry) => setSize(entry.contentRect));
|
||||
return size;
|
||||
};
|
||||
@@ -239,7 +235,10 @@ export const FolderSelector: React.FC<FolderSelectorProps> = ({
|
||||
}}
|
||||
>
|
||||
{/* Root option */}
|
||||
<RootNode isSelected={selectedPath === ''} onSelect={() => onSelect('')} />
|
||||
<RootNode
|
||||
isSelected={selectedPath === ''}
|
||||
onSelect={() => onSelect('')}
|
||||
/>
|
||||
|
||||
{/* Folder tree */}
|
||||
{size && folders.length > 0 && (
|
||||
@@ -255,7 +254,11 @@ export const FolderSelector: React.FC<FolderSelectorProps> = ({
|
||||
disableDrop={() => true}
|
||||
>
|
||||
{(props) => (
|
||||
<FolderNode {...props} selectedPath={selectedPath} onSelect={onSelect} />
|
||||
<FolderNode
|
||||
{...props}
|
||||
selectedPath={selectedPath}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
)}
|
||||
</Tree>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user