62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
import type { Node } from '@/types/api';
|
|
|
|
type DisplayableNode = Pick<Node, 'name'> &
|
|
Partial<Pick<Node, 'displayName' | 'instance' | 'host'>>;
|
|
|
|
const sanitize = (value: string): string => value.trim().toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
|
|
const escapeRegExp = (value: string): string => value.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\$&');
|
|
|
|
const extractHostname = (value: string): string => {
|
|
if (!value) return '';
|
|
const trimmed = value.trim();
|
|
const withoutProtocol = trimmed.replace(/^[a-z]+:\/\//i, '');
|
|
const [hostPart] = withoutProtocol.split('/');
|
|
return hostPart.replace(/:\d+$/, '');
|
|
};
|
|
|
|
export function getNodeDisplayName<T extends DisplayableNode>(node: T): string {
|
|
const display = typeof node.displayName === 'string' ? node.displayName.trim() : '';
|
|
if (display) return display;
|
|
|
|
const nameRaw = typeof node.name === 'string' ? node.name.trim() : '';
|
|
if (nameRaw) return nameRaw;
|
|
|
|
const hostRaw = typeof node.host === 'string' ? node.host.trim() : '';
|
|
const hostname = extractHostname(hostRaw);
|
|
if (hostname) return hostname;
|
|
|
|
const instance = typeof node.instance === 'string' ? node.instance.trim() : '';
|
|
if (instance) return instance;
|
|
|
|
return '';
|
|
}
|
|
|
|
export function hasAlternateDisplayName<T extends DisplayableNode>(node: T): boolean {
|
|
const displayRaw = typeof node.displayName === 'string' ? node.displayName.trim() : '';
|
|
if (!displayRaw) return false;
|
|
|
|
const nameRaw = typeof node.name === 'string' ? node.name.trim() : '';
|
|
if (!nameRaw) return false;
|
|
|
|
const displayLower = displayRaw.toLowerCase();
|
|
const nameLower = nameRaw.toLowerCase();
|
|
|
|
if (displayLower === nameLower) return false;
|
|
|
|
// Normalize values so cosmetic punctuation/domains do not trigger duplicates in the UI
|
|
const sanitizedDisplay = sanitize(displayRaw);
|
|
const sanitizedName = sanitize(nameRaw);
|
|
|
|
if (sanitizedDisplay === sanitizedName) return false;
|
|
|
|
// Catch cases where the raw display text already embeds the node name (e.g. "Friendly (node)")
|
|
const namePattern = new RegExp(`\\b${escapeRegExp(nameLower)}\\b`, 'i');
|
|
if (namePattern.test(displayLower)) return false;
|
|
|
|
const [firstLabel = ''] = displayLower.split('.');
|
|
const sanitizedFirstLabel = sanitize(firstLabel);
|
|
if (sanitizedFirstLabel === sanitizedName) return false;
|
|
|
|
return true;
|
|
}
|