A caixa: o que um bloco pode e não pode fazer
O HTML de cada bloco roda isolado, numa caixa: um quadro de outro endereço, sem acesso ao construtor, ao app nem aos dados de quem está usando. As regras abaixo valem igual no construtor, no "ver" e no app: o que funciona no construtor funciona no app.
Em resumo
| Pode | Não pode |
|---|---|
Script, CSS e fonte de seis CDNs, em https | Script, CSS ou fonte de qualquer outro endereço |
Imagem em data: e SVG escrito no HTML | Imagem de um endereço, nem de um CDN |
Chamar os endpoints do cliente com fetch('/api/ops/...') | Qualquer outra conexão: fetch para fora, XMLHttpRequest, WebSocket, EventSource, sendBeacon |
| Guardar dados em variáveis enquanto a Página está aberta | Cookie, localStorage, sessionStorage |
| Um modal feito no próprio HTML | alert(), confirm(), prompt() e window.print() |
| Links para as Páginas do workflow, também numa aba nova | Links e janelas para outro site |
| Baixar um arquivo que a Página gera; copiar texto num clique | Ler a área de transferência |
O primeiro <!doctype html> é opcional: sem ele, a caixa o põe.
Os CDNs
Script, CSS e fontes só vêm destes seis endereços, sempre com https:
https://cdnjs.cloudflare.comhttps://cdn.jsdelivr.nethttps://unpkg.comhttps://cdn.tailwindcss.comhttps://fonts.googleapis.comhttps://fonts.gstatic.com
Funciona: a fonte Inter do Google Fonts e os ícones do Font Awesome no cdnjs.
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body { font-family: 'Inter', sans-serif; margin: 0; padding: 24px; }
</style>
</head>
<body>
<h1><i class="fa-solid fa-file-invoice"></i> Cobranças</h1>
</body>
</html>
Não funciona: o jQuery de code.jquery.com, que não é um dos seis. O script não carrega, e o $ não existe.
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
O mesmo jQuery no cdnjs (https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js) carrega. Também não
carregam: um CDN em http://, e um endereço que só parece um CDN (https://cdnjs.cloudflare.com.outro.site/...).
As imagens
Uma imagem só vem de data: (o arquivo escrito dentro do HTML) ou de um <svg> no próprio HTML. Nenhuma imagem vem
de um endereço, nem de um dos seis CDNs, e isso vale também para o background-image: url(...) do CSS.
Funciona: um ícone em SVG no HTML, e uma imagem em data:.
<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="10" fill="#4f46e5" />
</svg>
<img alt="Ponto verde" width="16" height="16"
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='8' fill='%2316a34a'/%3E%3C/svg%3E">
Não funciona: um logo num endereço.
<img src="https://cdn.jsdelivr.net/gh/exemplo/marca/logo.png" alt="Logo">
Para ícones, uma fonte de ícones de um CDN (como o Font Awesome, acima) ou o SVG escrito no HTML.
Subir uma imagem pelo construtor e usá-la numa Página vem depois. Até lá, uma imagem é data: ou SVG.
A rede: só a ponte
O HTML não tem rede. A única conexão é o fetch para os endpoints do cliente, fetch('/api/ops/...'), que o
construtor (e o app) faz por ele (veja Chamar a API).
Não funciona: buscar um dado de outro site, ou mesmo de um CDN.
// Barrado: outro site. O fetch rejeita com TypeError.
const cotacao = await fetch('https://api.exemplo.com/cotacao');
// Barrado também: um JSON de um CDN.
const dados = await fetch('https://cdn.jsdelivr.net/npm/pacote@1/dados.json');
Também são barrados o XMLHttpRequest (mesmo para /api/ops/), o WebSocket, o EventSource e o
navigator.sendBeacon. Um dado de fora que a Página precisa vem por um endpoint: é um pedido ao time de
desenvolvimento.
Nada fica no navegador
A caixa não tem cookie nem armazenamento: o document.cookie, o localStorage e o sessionStorage dão erro, e o
script para naquela linha.
Não funciona:
localStorage.setItem('filtro', 'abertas'); // erro: o resto do script não roda
Funciona: guardar numa variável enquanto a Página está aberta; levar o valor a outra Página num parâmetro do link; guardar de verdade por um endpoint.
<a href="/cobrancas?status=aberta">Ver as cobranças abertas</a>
<script>
// Na Página /cobrancas:
const status = new URLSearchParams(location.search).get('status'); // 'aberta'
</script>
Sem janelas do navegador
O alert() e o window.print() não fazem nada, o confirm() responde sempre não (false) e o prompt(),
null. Nenhum avisa.
Não funciona: o confirm() responde false, e a exclusão nunca acontece.
if (confirm('Excluir este cliente?')) {
excluir();
}
Funciona: uma confirmação feita no próprio HTML.
<style>
.modal { position: fixed; inset: 0; background: rgb(0 0 0 / 0.4); display: grid; place-items: center; }
.modal[hidden] { display: none; }
.modal > div { background: white; padding: 24px; border-radius: 8px; }
</style>
<button type="button" id="excluir">Excluir</button>
<div id="confirmar" class="modal" hidden>
<div>
<p>Excluir este cliente?</p>
<button type="button" id="nao">Cancelar</button>
<button type="button" id="sim">Excluir</button>
</div>
</div>
<script>
const confirmar = document.getElementById('confirmar');
document.getElementById('excluir').addEventListener('click', () => (confirmar.hidden = false));
document.getElementById('nao').addEventListener('click', () => (confirmar.hidden = true));
document.getElementById('sim').addEventListener('click', () => {
confirmar.hidden = true;
excluir();
});
function excluir() {
// a chamada ao endpoint (veja "Chamar a API")
}
</script>
Para imprimir, gere um PDF (por exemplo, com o jsPDF do cdnjs) e baixe: quem usa imprime o arquivo.
Para fora da caixa
- Abrir numa aba nova: um link com
target="_blank"para uma Página do workflow abre uma aba nova (veja Os links entre Páginas). Para outro site, nada abre, e o endereço aparece como bloqueado. - Baixar: um arquivo que a Página gera (um
<a download>de umblob:ou de umdata:, osave()do jsPDF) baixa. Um<a download>para outro site, não. - Copiar:
navigator.clipboard.writeText()dentro de um clique põe o texto na área de transferência. Ler dela, não. - Formulários: veja Os formulários.
- Não há: iframe dentro do bloco, Worker,
top.location, link comtarget="_top",<base href>.
Funciona: baixar um CSV gerado na Página.
<button type="button" id="baixar">Baixar CSV</button>
<script>
document.getElementById('baixar').addEventListener('click', () => {
const csv = 'nome;preco\nAcme;1500.00\n';
const link = document.createElement('a');
link.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' }));
link.download = 'clientes.csv';
link.click();
});
</script>
A altura da tela
O que o HTML chama de "altura da tela" (100vh, o h-screen e o min-h-screen do Tailwind) depende de onde o bloco
está:
- No canvas, e numa Página de vários blocos: é a altura de uma tela da largura da Página (numa de 1280, 800 px), mesmo num bloco mais estreito. O bloco tem a altura do conteúdo.
- Numa Página de um bloco só, no "ver" e no app: é a janela de verdade. O bloco tem a altura da janela e rola
sozinho: um cabeçalho
position: fixedoustickyfica no alto, e um modalposition: fixed; inset: 0abre no meio da tela.
A troca vale para o CSS que está no HTML (no <style> e no style="..."), não para uma folha de CSS de um CDN.
O que foi barrado: o selo
Quando a caixa barra alguma coisa (um script de fora, uma imagem, um fetch, um link para outro site), o construtor
mostra:
- no canvas: um selo no bloco, como "2 bloqueados"; com o bloco selecionado, o painel lista os endereços;
- no "ver": o mesmo selo, com os endereços ao passar o mouse.
No app, nada aparece: quem usa só vê a Página sem aquilo. Confira o selo antes de publicar.
Quando o bloco para
| A mensagem no bloco | O que aconteceu | O que fazer |
|---|---|---|
| "Este bloco parou." | O HTML levou a caixa para outro endereço (um <meta http-equiv="refresh">, um location.href para fora). No canvas, também um location.href que roda sozinho ao abrir, mesmo para uma Página do workflow. | Tire a navegação para fora, e navegue só depois de um clique. Para ir a outra Página, use um link (veja Os links). |
| "Este bloco travou." | Um script que não termina (um laço sem fim) segurou a caixa. | Corrija o laço. |
| "Não foi possível abrir este bloco: … não respondeu." | O endereço da caixa não respondeu: a rede, ou um filtro de internet da empresa que bloqueia o endereço da caixa. | Recarregue. Se continua, peça à TI para liberar o endereço da mensagem. |
O bloco que parou ou travou fica assim até o HTML dele mudar; recarregar a página tenta todos de novo.