65 lines
3.2 KiB
TypeScript
65 lines
3.2 KiB
TypeScript
type OfflineContent = {
|
|
title: string;
|
|
kind: string;
|
|
sourcePath: string;
|
|
rows: [string, string][];
|
|
sections: { title: string; text: string }[];
|
|
notice: string;
|
|
};
|
|
|
|
export const offlineValue = (value?: string | null) =>
|
|
value?.trim() || 'Não indicado';
|
|
const escapeHtml = (value: string) =>
|
|
value.replace(
|
|
/[&<>"']/g,
|
|
(character) =>
|
|
({
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": ''',
|
|
})[character]!,
|
|
);
|
|
|
|
/** All content is plain text; assets and executable content are excluded. */
|
|
export function publicRecordOfflineHtml(
|
|
content: OfflineContent,
|
|
origin: string,
|
|
savedAt: Date,
|
|
): string {
|
|
const base = new URL(origin);
|
|
if (!['http:', 'https:'].includes(base.protocol))
|
|
throw new Error('Origem do portal inválida.');
|
|
const source = new URL(content.sourcePath, base.origin);
|
|
if (
|
|
source.origin !== base.origin ||
|
|
!content.sourcePath.startsWith('/') ||
|
|
content.sourcePath.startsWith('//')
|
|
)
|
|
throw new Error('Referência da ficha inválida.');
|
|
return `<!doctype html><html lang="pt-MZ"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'"><title>${escapeHtml(content.title)} — consulta offline</title><style>body{font:1rem/1.6 system-ui,sans-serif;margin:2rem auto;padding:0 1rem;max-width:55rem;color:#172b23;background:white}h1{line-height:1.2;overflow-wrap:anywhere}dt{font-weight:700}dd{margin:0 0 1rem;white-space:pre-wrap;overflow-wrap:anywhere}p{white-space:pre-wrap;overflow-wrap:anywhere}a{color:#14623d}footer{border-top:1px solid #ccc;margin-top:2rem;padding-top:1rem}</style></head><body><main><h1>${escapeHtml(content.title)}</h1><p>${escapeHtml(content.kind)} — InvesteMoz</p><p>Cópia guardada em ${escapeHtml(savedAt.toISOString())}. Consulte a ficha online para verificar actualizações.</p><dl>${content.rows.map(([label, value]) => `<dt>${escapeHtml(label)}</dt><dd>${escapeHtml(value)}</dd>`).join('')}</dl>${content.sections.map((section) => `<h2>${escapeHtml(section.title)}</h2><p>${escapeHtml(section.text)}</p>`).join('')}<p>${escapeHtml(content.notice)}</p><p><a href="${escapeHtml(source.href)}" rel="noreferrer">Consultar a ficha de origem online</a></p></main><footer>Fonte: ficha pública no portal InvesteMoz. Esta cópia conserva a informação apresentada no momento em que foi guardada.</footer></body></html>`;
|
|
}
|
|
|
|
export function downloadOfflineHtml(
|
|
html: string,
|
|
prefix: string,
|
|
slug: string,
|
|
): void {
|
|
const url = URL.createObjectURL(
|
|
new Blob([html], { type: 'text/html;charset=utf-8' }),
|
|
);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download =
|
|
`${prefix}-${slug}`.replace(/[^a-zA-Z0-9_-]/g, '-').slice(0, 120) +
|
|
'.html';
|
|
try {
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
} finally {
|
|
link.remove();
|
|
window.setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
}
|
|
}
|