← snapshot
1648 bytes
/**
* Client-side mirrors of the Rust validation rules. These never replace the
* server's checks — jac-serve re-validates everything — they only let the
* form say no before the network does.
*/
/** Mirror of `Statement::new`: trimmed, 1..=2000 characters. */
export function statementStatus(text: string): {
trimmedLength: number;
valid: boolean;
reason?: string;
} {
const trimmed = text.trim();
// Count Unicode scalar values like Rust's chars().count(), not UTF-16 units.
const trimmedLength = [...trimmed].length;
if (trimmedLength === 0) {
return { trimmedLength, valid: false, reason: "statement is empty once trimmed" };
}
if (trimmedLength > 2000) {
return {
trimmedLength,
valid: false,
reason: `over by ${trimmedLength - 2000}`,
};
}
return { trimmedLength, valid: true };
}
/** Mirror of `RefName::parse`. */
export function isValidRefName(s: string): boolean {
if (s.length === 0 || s.startsWith("/") || s.endsWith("/")) return false;
return s.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
}
/** Mirror of `RepoPath::parse`. */
export function isValidRepoPath(s: string): boolean {
return isValidRefName(s);
}
/** Mirror of jac-serve's slugify, for the live slug preview. */
export function slugify(name: string): string {
let slug = "";
let lastDash = true;
for (const c of name.trim()) {
if (/[a-zA-Z0-9]/.test(c)) {
slug += c.toLowerCase();
lastDash = false;
} else if (!lastDash) {
slug += "-";
lastDash = true;
}
}
while (slug.endsWith("-")) slug = slug.slice(0, -1);
return slug;
}