Skip to content

Commit 561464b

Browse files
authored
Merge pull request #117 from bocan:chore/updates2
chore: update dependencies in package.json
2 parents 45d8cc0 + ac72278 commit 561464b

6 files changed

Lines changed: 386 additions & 241 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/gitleaks/gitleaks
3-
rev: v8.30.0
3+
rev: v8.30.1
44
hooks:
55
- id: gitleaks
66
args: ["--verbose"]

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"@types/react-dom": "^19.2.3",
3636
"@types/react-syntax-highlighter": "^15.5.13",
3737
"@vitejs/plugin-react": "^6.0.1",
38-
"jsdom": "^29.0.0",
38+
"jsdom": "^29.0.1",
3939
"typescript": "^5.3.3",
4040
"vite": "8.00",
4141
"vitest": "^4.1.0"

client/src/components/PageList.css

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,3 +427,130 @@
427427
min-height: 44px;
428428
}
429429
}
430+
431+
/* Move page modal - tree-based folder picker */
432+
.move-modal-overlay {
433+
position: fixed;
434+
inset: 0;
435+
background: rgba(0, 0, 0, 0.5);
436+
display: flex;
437+
align-items: center;
438+
justify-content: center;
439+
z-index: 2000;
440+
}
441+
442+
.move-modal {
443+
background: var(--bg-primary);
444+
border: 1px solid var(--border-color);
445+
border-radius: var(--radius-lg, 8px);
446+
padding: 20px;
447+
width: 320px;
448+
max-width: 90vw;
449+
box-shadow: 0 8px 32px var(--shadow-lg, rgba(0, 0, 0, 0.3));
450+
display: flex;
451+
flex-direction: column;
452+
gap: 12px;
453+
}
454+
455+
.move-modal-title {
456+
margin: 0;
457+
font-size: var(--text-base, 16px);
458+
font-weight: 600;
459+
color: var(--text-primary);
460+
}
461+
462+
.move-modal-subtitle {
463+
margin: 0;
464+
font-size: var(--text-sm, 14px);
465+
color: var(--text-secondary);
466+
}
467+
468+
.move-picker {
469+
border: 1px solid var(--border-color);
470+
border-radius: var(--radius-md, 4px);
471+
max-height: 240px;
472+
overflow-y: auto;
473+
display: flex;
474+
flex-direction: column;
475+
}
476+
477+
.move-picker-item {
478+
display: flex;
479+
align-items: center;
480+
gap: 6px;
481+
padding: 7px 8px;
482+
border: none;
483+
border-bottom: 1px solid var(--border-color);
484+
background: none;
485+
text-align: left;
486+
cursor: pointer;
487+
font-size: var(--text-sm, 14px);
488+
color: var(--text-primary);
489+
transition: background 0.15s ease;
490+
}
491+
492+
.move-picker-item:last-child {
493+
border-bottom: none;
494+
}
495+
496+
.move-picker-item:hover {
497+
background: var(--bg-hover, var(--bg-tertiary));
498+
}
499+
500+
.move-picker-item.selected {
501+
background: var(--accent-bg, rgba(59, 130, 246, 0.1));
502+
color: var(--accent-color);
503+
font-weight: 500;
504+
}
505+
506+
.move-picker-root {
507+
font-style: italic;
508+
color: var(--text-secondary);
509+
}
510+
511+
.move-picker-root.selected {
512+
color: var(--accent-color);
513+
}
514+
515+
.move-modal-actions {
516+
display: flex;
517+
gap: 8px;
518+
justify-content: flex-end;
519+
}
520+
521+
.move-modal-cancel {
522+
padding: 6px 14px;
523+
border: 1px solid var(--border-color);
524+
border-radius: var(--radius-md, 4px);
525+
background: none;
526+
color: var(--text-secondary);
527+
cursor: pointer;
528+
font-size: var(--text-sm, 14px);
529+
transition: background 0.15s ease;
530+
}
531+
532+
.move-modal-cancel:hover:not(:disabled) {
533+
background: var(--bg-hover, var(--bg-tertiary));
534+
}
535+
536+
.move-modal-confirm {
537+
padding: 6px 14px;
538+
border: none;
539+
border-radius: var(--radius-md, 4px);
540+
background: var(--accent-color);
541+
color: var(--accent-text);
542+
cursor: pointer;
543+
font-size: var(--text-sm, 14px);
544+
font-weight: 500;
545+
transition: background 0.15s ease;
546+
}
547+
548+
.move-modal-confirm:hover:not(:disabled) {
549+
background: var(--accent-hover);
550+
}
551+
552+
.move-modal-cancel:disabled,
553+
.move-modal-confirm:disabled {
554+
opacity: 0.5;
555+
cursor: not-allowed;
556+
}

client/src/components/PageList.tsx

Lines changed: 54 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const PageList: React.FC<PageListProps> = ({
6565
const [templatesError, setTemplatesError] = useState<string | null>(null);
6666
const [isDeleting, setIsDeleting] = useState<string | null>(null);
6767
const [isMoving, setIsMoving] = useState(false);
68+
const [moveDestination, setMoveDestination] = useState<string>("");
6869
const [sortField, setSortField] = useState<SortField>("name");
6970
const [sortDirection, setSortDirection] = useState<SortDirection>("asc");
7071
const [selectedIndex, setSelectedIndex] = useState(0);
@@ -332,15 +333,16 @@ export const PageList: React.FC<PageListProps> = ({
332333

333334
const handleMovePage = (path: string) => {
334335
setMovingPage(path);
336+
setMoveDestination("");
335337
setContextMenuPage(null);
336338
};
337339

338-
const handleMoveSubmit = async (targetFolder: string) => {
340+
const handleMoveSubmit = async () => {
339341
if (!movingPage) return;
340342

341343
setIsMoving(true);
342344
try {
343-
const result = await api.movePage(movingPage, targetFolder);
345+
const result = await api.movePage(movingPage, moveDestination);
344346
loadPages();
345347
onRefresh();
346348
if (selectedPage === movingPage) {
@@ -358,24 +360,25 @@ export const PageList: React.FC<PageListProps> = ({
358360
}
359361
};
360362

361-
const getAllFolders = (
363+
/** Recursive folder picker with tree structure and indentation */
364+
const renderPickerNodes = (
362365
node: FolderNode,
363-
prefix: string = "",
364-
): Array<{ path: string; display: string }> => {
365-
const folders: Array<{ path: string; display: string }> = [];
366-
const currentPath = prefix ? `${prefix}/${node.name}` : node.name;
367-
const displayPath = currentPath === "" ? "/" : currentPath;
368-
369-
folders.push({
370-
path: node.path === "/" ? "" : node.path,
371-
display: displayPath,
372-
});
373-
374-
for (const child of node.children) {
375-
folders.push(...getAllFolders(child, currentPath));
376-
}
377-
378-
return folders;
366+
indent: number = 0,
367+
): React.ReactNode => {
368+
return node.children.map((child) => (
369+
<React.Fragment key={child.path}>
370+
<button
371+
className={`move-picker-item ${moveDestination === child.path ? "selected" : ""}`}
372+
style={{ paddingLeft: `${indent * 16 + 8}px` }}
373+
onClick={() => setMoveDestination(child.path)}
374+
role="option"
375+
aria-selected={moveDestination === child.path}
376+
>
377+
<Folder size={14} aria-hidden="true" /> {child.name}
378+
</button>
379+
{renderPickerNodes(child, indent + 1)}
380+
</React.Fragment>
381+
));
379382
};
380383

381384
return (
@@ -531,47 +534,51 @@ export const PageList: React.FC<PageListProps> = ({
531534
{/* Move Page Modal */}
532535
{movingPage && folderTree && (
533536
<div
534-
className="modal-overlay"
537+
className="move-modal-overlay"
535538
onClick={() => !isMoving && setMovingPage(null)}
536539
role="dialog"
537540
aria-modal="true"
538541
aria-labelledby="move-modal-title"
539542
>
540-
<div className="modal" onClick={(e) => e.stopPropagation()}>
541-
<h3 id="move-modal-title">Move Page</h3>
542-
<p>
543-
Select destination folder for{" "}
544-
<strong>{movingPage.split("/").pop()}</strong>:
545-
</p>
543+
<div className="move-modal" onClick={(e) => e.stopPropagation()}>
544+
<h3 className="move-modal-title" id="move-modal-title">
545+
Move "{movingPage.split("/").pop()}"
546+
</h3>
547+
<p className="move-modal-subtitle">Select destination folder:</p>
546548
{isMoving ? (
547549
<div className="modal-loading" role="status" aria-live="polite">
548550
<Loader2 size={24} className="loading-spinner" aria-hidden="true" />
549551
<span>Moving page...</span>
550552
</div>
551553
) : (
552-
<div className="folder-list" role="list">
553-
{getAllFolders(folderTree).map((folder) => (
554-
<button
555-
key={folder.path}
556-
className="folder-option"
557-
onClick={() => handleMoveSubmit(folder.path)}
558-
disabled={isMoving}
559-
role="listitem"
560-
aria-label={`Move to ${folder.display}`}
561-
>
562-
<Folder size={14} aria-hidden="true" /> {folder.display}
563-
</button>
564-
))}
554+
<div className="move-picker" role="listbox" aria-label="Destination folder">
555+
<button
556+
className={`move-picker-item move-picker-root ${moveDestination === "" ? "selected" : ""}`}
557+
onClick={() => setMoveDestination("")}
558+
role="option"
559+
aria-selected={moveDestination === ""}
560+
>
561+
<Folder size={14} aria-hidden="true" /> Root (top level)
562+
</button>
563+
{renderPickerNodes(folderTree, 1)}
565564
</div>
566565
)}
567-
<button
568-
className="cancel-btn"
569-
onClick={() => setMovingPage(null)}
570-
disabled={isMoving}
571-
aria-label="Cancel moving page"
572-
>
573-
Cancel
574-
</button>
566+
<div className="move-modal-actions">
567+
<button
568+
className="move-modal-cancel"
569+
onClick={() => setMovingPage(null)}
570+
disabled={isMoving}
571+
>
572+
Cancel
573+
</button>
574+
<button
575+
className="move-modal-confirm"
576+
onClick={handleMoveSubmit}
577+
disabled={isMoving}
578+
>
579+
{isMoving ? "Moving..." : "Move Here"}
580+
</button>
581+
</div>
575582
</div>
576583
</div>
577584
)}

0 commit comments

Comments
 (0)