import { isWindowsAbsolutePath } from "@t3tools/shared/path"; import { safeDecodeURIComponent } from "./markdownLinks.ts"; /** The authored media location, never the temporary URL used to load its bytes. */export type MediaReference = | { readonly kind: "file"; readonly path: string; readonly relativePath?: string; } | { readonly kind: "url"; readonly url: string }; function absolutePathParts(path: string) { const windows = isWindowsAbsolutePath(path) || path.startsWith("//"); const normalized = windows ? path.replaceAll("\\", "/") : path; const prefix = windows ? /^(?:[a-z]:\/|\/\/[^/]+\/[^/]+(?:\/|$))/i.exec(normalized)?.[0] : normalized.startsWith("/") ? "/" : undefined; if (!prefix) return undefined; const segments: string[] = []; for (const segment of normalized.slice(prefix.length).split("/")) { if (!segment || segment === ".") continue; if (segment === "..") segments.pop(); else segments.push(segment); } const root = prefix.replace(/\/$/, ""); return { root: windows ? root.toLowerCase() : root, segments, windows };} /** Compares paths lexically for the copy menu; it does not resolve filesystem symlinks. */export function mediaFileReference( path: string, workspaceRoot?: string | null,): Extract<MediaReference, { kind: "file" }> { const target = absolutePathParts(path); const workspace = workspaceRoot ? absolutePathParts(workspaceRoot) : undefined; if ( !target || !workspace || target.windows !== workspace.windows || target.root !== workspace.root || target.segments.length <= workspace.segments.length || !workspace.segments.every((segment, index) => workspace.windows ? segment.toLowerCase() === target.segments[index]?.toLowerCase() : segment === target.segments[index], ) ) { return { kind: "file", path }; } return { kind: "file", path, relativePath: target.segments.slice(workspace.segments.length).join("/"), };} /** Pass the authored source, not a generated URL used by the media player. */export function mediaUrlReference( url: string,): Extract<MediaReference, { kind: "url" }> | undefined { if (!/^(?:https?:\/\/|\/\/)/i.test(url)) return undefined; try { const parsed = new URL(url.startsWith("//") ? `https:${url}` : url); return parsed.protocol === "http:" || parsed.protocol === "https:" ? { kind: "url", url } : undefined; } catch { return undefined; }} /** Local paths are already decoded; URL filename escapes are decoded exactly once. */export function mediaReferenceFileName(reference: MediaReference): string | undefined { if (reference.kind === "file") { const windows = isWindowsAbsolutePath(reference.path) || reference.path.startsWith("//"); return reference.path.split(windows ? /[\\/]/ : "/").at(-1) || undefined; } let basename: string | undefined; try { const url = reference.url; basename = new URL(url.startsWith("//") ? `https:${url}` : url).pathname.split("/").at(-1); } catch { return undefined; } return basename ? safeDecodeURIComponent(basename) : undefined;}
import { isWindowsAbsolutePath } from "@t3tools/shared/path";
import { safeDecodeURIComponent } from "./markdownLinks.ts";
/** The authored media location, never the temporary URL used to load its bytes. */
export type MediaReference =
| {
readonly kind: "file";
readonly path: string;
readonly relativePath?: string;
}
| { readonly kind: "url"; readonly url: string };
function absolutePathParts(path: string) {
const windows = isWindowsAbsolutePath(path) || path.startsWith("//");
const normalized = windows ? path.replaceAll("\\", "/") : path;
const prefix = windows
? /^(?:[a-z]:\/|\/\/[^/]+\/[^/]+(?:\/|$))/i.exec(normalized)?.[0]
: normalized.startsWith("/")
? "/"
: undefined;
if (!prefix) return undefined;
const segments: string[] = [];
for (const segment of normalized.slice(prefix.length).split("/")) {
if (!segment || segment === ".") continue;
if (segment === "..") segments.pop();
else segments.push(segment);
}
const root = prefix.replace(/\/$/, "");
return { root: windows ? root.toLowerCase() : root, segments, windows };
}
/** Compares paths lexically for the copy menu; it does not resolve filesystem symlinks. */
export function mediaFileReference(
path: string,
workspaceRoot?: string | null,
): Extract<MediaReference, { kind: "file" }> {
const target = absolutePathParts(path);
const workspace = workspaceRoot ? absolutePathParts(workspaceRoot) : undefined;
if (
!target ||
!workspace ||
target.windows !== workspace.windows ||
target.root !== workspace.root ||
target.segments.length <= workspace.segments.length ||
!workspace.segments.every((segment, index) =>
workspace.windows
? segment.toLowerCase() === target.segments[index]?.toLowerCase()
: segment === target.segments[index],
)
) {
return { kind: "file", path };
}
return {
kind: "file",
path,
relativePath: target.segments.slice(workspace.segments.length).join("/"),
};
}
/** Pass the authored source, not a generated URL used by the media player. */
export function mediaUrlReference(
url: string,
): Extract<MediaReference, { kind: "url" }> | undefined {
if (!/^(?:https?:\/\/|\/\/)/i.test(url)) return undefined;
try {
const parsed = new URL(url.startsWith("//") ? `https:${url}` : url);
return parsed.protocol === "http:" || parsed.protocol === "https:"
? { kind: "url", url }
: undefined;
} catch {
return undefined;
}
}
/** Local paths are already decoded; URL filename escapes are decoded exactly once. */
export function mediaReferenceFileName(reference: MediaReference): string | undefined {
if (reference.kind === "file") {
const windows = isWindowsAbsolutePath(reference.path) || reference.path.startsWith("//");
return reference.path.split(windows ? /[\\/]/ : "/").at(-1) || undefined;
}
let basename: string | undefined;
try {
const url = reference.url;
basename = new URL(url.startsWith("//") ? `https:${url}` : url).pathname.split("/").at(-1);
} catch {
return undefined;
}
return basename ? safeDecodeURIComponent(basename) : undefined;
}