220 lines
8.1 KiB
JavaScript
220 lines
8.1 KiB
JavaScript
// 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(
|
|
// "<id do botão>", "<app_key>", "<nome de exibição>")` 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 = `
|
|
<div class="modal-card ajuda-modal__card">
|
|
<h2 class="ajuda-modal__titulo"></h2>
|
|
<div class="ajuda-modal__texto"></div>
|
|
<div class="ajuda-modal__editor" contenteditable="true" hidden data-placeholder="Escreva aqui — cole (Ctrl+V) ou arraste uma imagem para incluí-la no texto"></div>
|
|
<p class="ajuda-modal__editor-hint" hidden>Cole (Ctrl+V) ou arraste uma imagem para incluí-la no texto.</p>
|
|
<p class="modal-error" hidden></p>
|
|
<div class="modal-actions">
|
|
<button type="button" class="btn-outline ajuda-modal__editar-btn" hidden>Editar</button>
|
|
<button type="button" class="btn-outline ajuda-modal__cancelar-btn" hidden>Cancelar</button>
|
|
<button type="button" class="btn-solid ajuda-modal__salvar-btn" hidden>Salvar</button>
|
|
<button type="button" class="btn-outline ajuda-modal__fechar-btn">Fechar</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
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));
|
|
}
|