// Botão "?" reutilizável ao lado do nome de uma aplicação — ao passar o // mouse mostra "Mais informações" (ver .info-tooltip em components.css); ao // clicar, abre um modal com o texto cadastrado em GET /api/ajuda-aplicacoes/{app_key}/. // Visualizar é livre a qualquer autenticado; editar é restrito a quem tem o // perfil Inovação (me.eh_perfil_inovacao, já calculado no servidor) — regra // fixa, não uma flag na árvore de permissões (ver CLAUDE.md). // // Um único modal é criado sob demanda e reaproveitado por todos os botões da // página — só um pode estar aberto por vez. Para adicionar o botão a uma // aplicação nova: no HTML, ao lado do nome da aplicação, `pidCriarBotaoAjuda( // "", "", "")` na página que a usa. // // O texto aceita imagens embutidas (colar/arrastar), mesmo mecanismo das // Observações de Acessos Gerais (`.ag-richtext` em acessos-gerais.js) — o // HTML digitado aqui só é sanitizado no servidor (`AjudaAplicacaoSerializer. // validate_texto`), então o editor em si não precisa filtrar nada. const PID_AJUDA_IMAGE_MAX_BYTES = 2 * 1024 * 1024; function pidAjudaAplicacaoModal() { let modal = document.getElementById("ajuda-aplicacao-modal"); if (modal) return modal; modal = document.createElement("div"); modal.className = "modal-overlay"; modal.id = "ajuda-aplicacao-modal"; modal.hidden = true; modal.dataset.editing = "false"; modal.innerHTML = ` `; document.body.appendChild(modal); modal.addEventListener("click", (event) => { if (event.target === modal) pidFecharAjudaAplicacaoModal(); }); modal.querySelector(".ajuda-modal__fechar-btn").addEventListener("click", pidFecharAjudaAplicacaoModal); const editor = modal.querySelector(".ajuda-modal__editor"); const erro = modal.querySelector(".modal-error"); function placeCaretAtPoint(x, y) { const selection = window.getSelection(); if (document.caretRangeFromPoint) { const range = document.caretRangeFromPoint(x, y); if (range) { selection.removeAllRanges(); selection.addRange(range); } } else if (document.caretPositionFromPoint) { const pos = document.caretPositionFromPoint(x, y); if (pos) { const range = document.createRange(); range.setStart(pos.offsetNode, pos.offset); range.collapse(true); selection.removeAllRanges(); selection.addRange(range); } } } function insertImageFile(file, atPoint) { if (!file || !file.type.startsWith("image/")) return; if (file.size > PID_AJUDA_IMAGE_MAX_BYTES) { erro.textContent = "A imagem deve ter no máximo 2MB."; erro.hidden = false; return; } erro.hidden = true; const reader = new FileReader(); reader.onload = () => { editor.focus(); if (atPoint) placeCaretAtPoint(atPoint.x, atPoint.y); document.execCommand("insertImage", false, reader.result); }; reader.readAsDataURL(file); } editor.addEventListener("paste", (event) => { const items = Array.from((event.clipboardData && event.clipboardData.items) || []); const imageItem = items.find((item) => item.type.startsWith("image/")); if (!imageItem) return; event.preventDefault(); insertImageFile(imageItem.getAsFile()); }); editor.addEventListener("dragover", (event) => { event.preventDefault(); }); editor.addEventListener("drop", (event) => { const file = event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]; if (!file || !file.type.startsWith("image/")) return; event.preventDefault(); insertImageFile(file, { x: event.clientX, y: event.clientY }); }); return modal; } async function pidFecharAjudaAplicacaoModal() { const modal = document.getElementById("ajuda-aplicacao-modal"); if (!modal) return; if (modal.dataset.editing === "true") { const confirmado = await pidConfirm("Sair sem salvar as alterações?", { perigoso: true }); if (!confirmado) return; } modal.dataset.editing = "false"; modal.hidden = true; } function pidAjudaEditorValue(editor) { const hasImage = editor.querySelector("img") !== null; const hasText = editor.textContent.trim() !== ""; return hasImage || hasText ? editor.innerHTML : ""; } async function pidAbrirAjudaAplicacaoModal(appKey, tituloApp) { const modal = pidAjudaAplicacaoModal(); const titulo = modal.querySelector(".ajuda-modal__titulo"); const textoEl = modal.querySelector(".ajuda-modal__texto"); const editor = modal.querySelector(".ajuda-modal__editor"); const editorHint = modal.querySelector(".ajuda-modal__editor-hint"); const erro = modal.querySelector(".modal-error"); const editarBtn = modal.querySelector(".ajuda-modal__editar-btn"); const cancelarBtn = modal.querySelector(".ajuda-modal__cancelar-btn"); const salvarBtn = modal.querySelector(".ajuda-modal__salvar-btn"); modal.dataset.editing = "false"; titulo.textContent = `Mais informações — ${tituloApp}`; textoEl.textContent = "Carregando..."; textoEl.hidden = false; editor.hidden = true; editorHint.hidden = true; erro.hidden = true; editarBtn.hidden = true; cancelarBtn.hidden = true; salvarBtn.hidden = true; modal.hidden = false; let ajuda = null; let podeEditar = false; try { const me = await pidGetMe(); podeEditar = !!me.eh_perfil_inovacao; ajuda = await pidApiRequest(`/ajuda-aplicacoes/${encodeURIComponent(appKey)}/`); } catch (err) { textoEl.textContent = `Não foi possível carregar o texto de ajuda. (${err.message || "erro desconhecido"})`; return; } function renderVisualizacao() { modal.dataset.editing = "false"; if (ajuda.texto) { textoEl.innerHTML = ajuda.texto; } else { textoEl.textContent = "Ainda não há um texto cadastrado para esta ferramenta."; } textoEl.hidden = false; editor.hidden = true; editorHint.hidden = true; editarBtn.hidden = !podeEditar; cancelarBtn.hidden = true; salvarBtn.hidden = true; erro.hidden = true; } function entrarEdicao() { modal.dataset.editing = "true"; editor.innerHTML = ajuda.texto || ""; textoEl.hidden = true; editor.hidden = false; editorHint.hidden = false; editarBtn.hidden = true; cancelarBtn.hidden = false; salvarBtn.hidden = false; erro.hidden = true; editor.focus(); } editarBtn.onclick = entrarEdicao; cancelarBtn.onclick = async () => { if (await pidConfirm("Sair sem salvar as alterações?", { perigoso: true })) renderVisualizacao(); }; salvarBtn.onclick = async () => { salvarBtn.disabled = true; try { ajuda = await pidApiRequest(`/ajuda-aplicacoes/${encodeURIComponent(appKey)}/`, { method: "PATCH", body: { texto: pidAjudaEditorValue(editor) }, }); renderVisualizacao(); } catch (err) { erro.textContent = err.message || "Não foi possível salvar o texto."; erro.hidden = false; } finally { salvarBtn.disabled = false; } }; renderVisualizacao(); } function pidCriarBotaoAjuda(botaoId, appKey, tituloApp) { const botao = document.getElementById(botaoId); if (!botao) return; botao.addEventListener("click", () => pidAbrirAjudaAplicacaoModal(appKey, tituloApp)); }