104 lines
4.1 KiB
TypeScript
104 lines
4.1 KiB
TypeScript
// *************************************************************************
|
|
// * (C)opyright 2026 by Ruben Carlo Benante *
|
|
// * *
|
|
// * This program is free software; you can redistribute it and/or modify *
|
|
// * it under the terms of the GNU 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 General Public License for more details. *
|
|
// * *
|
|
// * You should have received a copy of the GNU General Public License *
|
|
// * along with this program. If not, see http://www.gnu.org/licenses/. *
|
|
// * *
|
|
// * Contact author at: *
|
|
// * Ruben Carlo Benante *
|
|
// * rcb@beco.cc *
|
|
// *************************************************************************
|
|
|
|
// The portal drives its three inputs with Inputmask, so the values we store
|
|
// have to already be in the shape the masks expect (the placeholders on the
|
|
// page: "2000101010", "01/01/2000", "000.000.000-00"). Normalising here means
|
|
// the options page accepts a raw paste of digits and stores something the
|
|
// masks will not fight with.
|
|
|
|
/** Strip everything that is not a digit. */
|
|
export function digits(value: string): string {
|
|
return value.replace(/\D+/g, '');
|
|
}
|
|
|
|
/** Registration number: digits only, at most 10. */
|
|
export function formatRa(value: string): string {
|
|
return digits(value).slice(0, 10);
|
|
}
|
|
|
|
/** Birth date as dd/mm/yyyy, formatted progressively while typing. */
|
|
export function formatDate(value: string): string {
|
|
const d = digits(value).slice(0, 8);
|
|
const parts: string[] = [];
|
|
if (d.length > 0) parts.push(d.slice(0, 2));
|
|
if (d.length > 2) parts.push(d.slice(2, 4));
|
|
if (d.length > 4) parts.push(d.slice(4, 8));
|
|
return parts.join('/');
|
|
}
|
|
|
|
/** CPF as 000.000.000-00, formatted progressively while typing. */
|
|
export function formatCpf(value: string): string {
|
|
const d = digits(value).slice(0, 11);
|
|
let out = d.slice(0, 3);
|
|
if (d.length > 3) out += `.${d.slice(3, 6)}`;
|
|
if (d.length > 6) out += `.${d.slice(6, 9)}`;
|
|
if (d.length > 9) out += `-${d.slice(9, 11)}`;
|
|
return out;
|
|
}
|
|
|
|
export function isCompleteRa(value: string): boolean {
|
|
return digits(value).length > 0;
|
|
}
|
|
|
|
/**
|
|
* A complete, calendar-valid date. Deliberately checks the round trip rather
|
|
* than just the digit count, so 31/02/2000 is rejected before it ever reaches
|
|
* the portal.
|
|
*/
|
|
export function isCompleteDate(value: string): boolean {
|
|
const m = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value);
|
|
if (!m) return false;
|
|
const day = Number(m[1]);
|
|
const month = Number(m[2]);
|
|
const year = Number(m[3]);
|
|
const date = new Date(year, month - 1, day);
|
|
return (
|
|
date.getFullYear() === year &&
|
|
date.getMonth() === month - 1 &&
|
|
date.getDate() === day
|
|
);
|
|
}
|
|
|
|
export function isCompleteCpf(value: string): boolean {
|
|
return digits(value).length === 11;
|
|
}
|
|
|
|
/**
|
|
* Origin of a user-typed site address, or null if it cannot be parsed.
|
|
* Accepts input without a scheme ("portal.example.br") by assuming
|
|
* https, which is what someone pasting an address from the URL bar expects.
|
|
*/
|
|
export function toOrigin(value: string): string | null {
|
|
const trimmed = value.trim();
|
|
if (!trimmed) return null;
|
|
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)
|
|
? trimmed
|
|
: `https://${trimmed}`;
|
|
try {
|
|
const url = new URL(withScheme);
|
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') return null;
|
|
return url.origin;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|