mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
initial commit
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// Copyright (c) 2025 rustmailer.com (https://rustmailer.com)
|
||||
//
|
||||
// This file is part of the Bichon Email Archiving Project
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import { MailboxData } from "@/api/mailbox/api";
|
||||
import { TreeDataItem } from "@/components/tree-view";
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { FolderClosed, FolderOpen } from "lucide-react";
|
||||
import React from 'react';
|
||||
|
||||
type BadgeContentFunction = (item: MailboxData) => React.ReactNode;
|
||||
|
||||
export const buildTree = (data: MailboxData[], badgeContent?: BadgeContentFunction, showAttributes?: boolean, showExists?: boolean): TreeDataItem[] => {
|
||||
const root: TreeDataItem = {
|
||||
id: 'root',
|
||||
name: 'Root',
|
||||
icon: FolderClosed,
|
||||
openIcon: FolderOpen,
|
||||
children: [], // Ensure children is initialized as an array
|
||||
};
|
||||
|
||||
const nodeMap: { [key: string]: TreeDataItem } = {};
|
||||
data.sort((a, b) => a.name.localeCompare(b.name));
|
||||
data.forEach((item) => {
|
||||
const { id, name, delimiter, exists, attributes } = item;
|
||||
const badge = showExists ? (badgeContent ? badgeContent(item) : React.createElement(Badge, {
|
||||
className: 'text-[12px]',
|
||||
variant: 'secondary',
|
||||
}, exists)) : null;
|
||||
|
||||
const attributesNode = showAttributes
|
||||
? attributes.map((item, index) =>
|
||||
React.createElement(
|
||||
Badge,
|
||||
{
|
||||
key: index,
|
||||
className: 'text-[12px] mr-1 last:mr-0',
|
||||
variant: 'secondary',
|
||||
},
|
||||
item.attr === 'Extension' ? item.extension || '' : item.attr
|
||||
)
|
||||
)
|
||||
: null;
|
||||
// const badge = badgeContent || React.createElement(Badge, {
|
||||
// className: 'text-xs',
|
||||
// }, exists);
|
||||
// If there is no delimiter, add the item directly as a child of the root
|
||||
if (!delimiter) {
|
||||
root.children!.push({
|
||||
id: id.toString(),
|
||||
name,
|
||||
icon: FolderClosed,
|
||||
openIcon: FolderOpen,
|
||||
badge,
|
||||
attributes: showAttributes ? attributesNode : null,
|
||||
children: undefined, // Leaf node, so children is undefined
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Split the name into parts based on the delimiter
|
||||
const parts = name.split(delimiter); // dir/sub1/sub2
|
||||
let currentParent = root;
|
||||
|
||||
// Traverse or create nodes for each part of the path
|
||||
parts.forEach((part, index) => {
|
||||
const path = parts.slice(0, index + 1).join(delimiter);
|
||||
|
||||
// If the node already exists, set it as the current parent
|
||||
if (nodeMap[path]) {
|
||||
currentParent = nodeMap[path];
|
||||
} else {
|
||||
// Determine if this is a leaf node (last part of the path)
|
||||
const isLeaf = index === parts.length - 1;
|
||||
|
||||
// Create a new node
|
||||
const newNode: TreeDataItem = {
|
||||
id: isLeaf ? id.toString() : path, // Use item.id for leaf nodes, path for non-leaf nodes
|
||||
name: part,
|
||||
icon: FolderClosed,
|
||||
openIcon: FolderOpen,
|
||||
badge,
|
||||
attributes: showAttributes ? attributesNode : null,
|
||||
children: isLeaf ? undefined : [], // Ensure children is initialized as an array for non-leaf nodes
|
||||
};
|
||||
|
||||
// Ensure currentParent.children is initialized as an array
|
||||
if (!currentParent.children) {
|
||||
currentParent.children = [];
|
||||
}
|
||||
|
||||
// Add the new node to the current parent's children
|
||||
currentParent.children.push(newNode);
|
||||
currentParent = newNode; // Update the current parent to the new node
|
||||
nodeMap[path] = newNode; // Store the node in the map for quick lookup
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Return the children of the root as the final tree structure
|
||||
return root.children!;
|
||||
};
|
||||
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// Copyright (c) 2025 rustmailer.com (https://rustmailer.com)
|
||||
//
|
||||
// This file is part of the Bichon Email Archiving Project
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
|
||||
export const formatBytes = (sizeInBytes: number): string => {
|
||||
if (sizeInBytes < 1024) {
|
||||
return `${sizeInBytes} B`;
|
||||
} else if (sizeInBytes < 1024 * 1024) {
|
||||
return `${(sizeInBytes / 1024).toFixed(2)} KB`;
|
||||
} else if (sizeInBytes < 1024 * 1024 * 1024) {
|
||||
return `${(sizeInBytes / (1024 * 1024)).toFixed(2)} MB`;
|
||||
} else {
|
||||
return `${(sizeInBytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const validateFlag = (input: string): string | null => {
|
||||
// Check if the string is empty
|
||||
if (input.length === 0) {
|
||||
return `'input' cannot be empty.`;
|
||||
}
|
||||
|
||||
// Check if the length is greater than 64 characters
|
||||
if (input.length > 64) {
|
||||
return `'input' cannot be longer than 64 characters.`;
|
||||
}
|
||||
|
||||
// Check if the string starts with a letter and contains only letters, numbers, underscores, or dashes
|
||||
const regex = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
||||
if (!regex.test(input)) {
|
||||
return `'input' must start with a letter and can only contain letters, numbers, underscores, or dashes.`;
|
||||
}
|
||||
|
||||
// If all checks pass, return null
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export function mapToRecordOfArrays(
|
||||
map: Map<number, Set<number>>
|
||||
): Record<string, number[]> {
|
||||
return Object.fromEntries(
|
||||
Array.from(map.entries()).map(([key, value]) => [String(key), Array.from(value)])
|
||||
);
|
||||
}
|
||||
|
||||
export function formatNumber(num: number) {
|
||||
return new Intl.NumberFormat('en-US').format(num);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function validateTag(facetPath: string) {
|
||||
if (!facetPath || facetPath.length === 0) {
|
||||
return {
|
||||
valid: false,
|
||||
error: "Tag path cannot be empty"
|
||||
};
|
||||
}
|
||||
|
||||
if (!facetPath.startsWith('/')) {
|
||||
return {
|
||||
valid: false,
|
||||
error: "Tag path must start with '/'"
|
||||
};
|
||||
}
|
||||
|
||||
let escaped = false;
|
||||
for (let i = 1; i < facetPath.length; i++) {
|
||||
const char = facetPath[i];
|
||||
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
} else if (char === '\\') {
|
||||
escaped = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (escaped) {
|
||||
return {
|
||||
valid: false,
|
||||
error: "Tag path has unmatched escape character at the end"
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
Reference in New Issue
Block a user