Files
HelixNotes/src/lib/utils/paths.ts
T

128 lines
4.4 KiB
TypeScript

function pathParts(path: string): string[] {
return path.replace(/\\/g, '/').split('/').filter(Boolean);
}
function pathRoot(path: string): string {
const normalized = path.replace(/\\/g, '/');
const drive = normalized.match(/^([A-Za-z]:)\//);
if (drive) return drive[1].toLowerCase();
return normalized.startsWith('/') ? '/' : '';
}
export function relativePath(fromDirectory: string, targetPath: string): string {
const from = pathParts(fromDirectory);
const target = pathParts(targetPath);
if (pathRoot(fromDirectory) !== pathRoot(targetPath)) return targetPath.replace(/\\/g, '/');
const windowsPath = /^[A-Za-z]:[\\/]/.test(fromDirectory) || /^[A-Za-z]:[\\/]/.test(targetPath);
const equal = windowsPath
? (left: string, right: string) => left.toLowerCase() === right.toLowerCase()
: (left: string, right: string) => left === right;
let common = 0;
while (common < from.length && common < target.length && equal(from[common], target[common])) {
common++;
}
const result = [...Array(from.length - common).fill('..'), ...target.slice(common)];
return result.join('/') || '.';
}
export function normalizeLocalAssetPath(path: string): string {
return path
.replace(/\\/g, '/')
.replace(/^\/([A-Za-z]:\/)/, '$1');
}
export function assetUrlToLocalPath(source: string): string | null {
if (!source.startsWith('asset:') && !source.startsWith('http://asset.localhost') && !source.startsWith('https://asset.localhost')) {
return null;
}
try {
const path = normalizeLocalAssetPath(decodeURIComponent(new URL(source).pathname));
// Tauri prepends a URL-path slash to POSIX and UNC paths.
return path.startsWith('//') ? path.substring(1) : path;
} catch {
return null;
}
}
export function resolveVaultFilePath(
targetPath: string,
notePath: string | null,
vaultRoot: string | null,
): string {
const target = normalizeLocalAssetPath(targetPath);
if (target.startsWith('/') || /^[A-Za-z]:\//.test(target)) return target;
const root = vaultRoot?.replace(/\\/g, '/').replace(/\/$/, '') ?? '';
if (target.startsWith('.helixnotes/') && root) {
return resolvePathFromFile(`${root}/.vault-root`, target);
}
if (notePath) return resolvePathFromFile(notePath, target);
if (root) return resolvePathFromFile(`${root}/.vault-root`, target);
return target;
}
export function assetSourceToMarkdown(
source: string,
notePath: string | null,
vaultRoot: string | null,
): string {
if (source.startsWith('blob:')) return '';
if (source.startsWith('imgproxy:') || source.startsWith('http://imgproxy.localhost') || source.startsWith('https://imgproxy.localhost')) {
try {
return decodeURIComponent(new URL(source).pathname.substring(1));
} catch {
return source;
}
}
const absolutePath = assetUrlToLocalPath(source);
if (absolutePath === null) return source;
const normalizedRoot = vaultRoot?.replace(/\\/g, '/').replace(/\/$/, '');
if (!normalizedRoot || !absolutePath.startsWith(normalizedRoot + '/')) return absolutePath;
const vaultRelative = absolutePath.substring(normalizedRoot.length + 1);
if (vaultRelative.startsWith('.helixnotes/')) return vaultRelative;
if (!notePath) return vaultRelative;
const normalizedNotePath = notePath.replace(/\\/g, '/');
const noteDirectory = normalizedNotePath.substring(0, normalizedNotePath.lastIndexOf('/'));
return relativePath(noteDirectory, absolutePath);
}
export function resolvePathFromFile(filePath: string, targetPath: string): string {
const normalizedFile = filePath.replace(/\\/g, '/');
const normalizedTarget = targetPath.replace(/\\/g, '/');
const lastSeparator = normalizedFile.lastIndexOf('/');
const fileDirectory = lastSeparator >= 0 ? normalizedFile.slice(0, lastSeparator) : '';
const combined = normalizedTarget.startsWith('/') || /^[A-Za-z]:\//.test(normalizedTarget)
? normalizedTarget
: `${fileDirectory}/${normalizedTarget}`;
const drive = combined.match(/^([A-Za-z]:)\//);
const unc = combined.startsWith('//');
const absolute = !unc && combined.startsWith('/');
let prefix = '';
let remainder = combined;
if (drive) {
prefix = `${drive[1]}/`;
remainder = combined.slice(drive[0].length);
} else if (unc) {
prefix = '//';
remainder = combined.slice(2);
} else if (absolute) {
prefix = '/';
remainder = combined.slice(1);
}
const resolved: string[] = [];
for (const segment of remainder.split('/')) {
if (!segment || segment === '.') continue;
if (segment === '..') {
resolved.pop();
} else {
resolved.push(segment);
}
}
return prefix + resolved.join('/');
}