226 lines
7.3 KiB
JavaScript
226 lines
7.3 KiB
JavaScript
document.addEventListener("DOMContentLoaded", () => {
|
|
const dropZone = document.getElementById("drop-zone");
|
|
const fileInput = document.getElementById("file-input");
|
|
const modeBtns = document.querySelectorAll(".mode-btn");
|
|
const progress = document.getElementById("progress");
|
|
const progressBar = progress.querySelector(".progress-bar");
|
|
const progressText = document.getElementById("progress-text");
|
|
const result = document.getElementById("result");
|
|
|
|
let currentMode = "basic";
|
|
|
|
modeBtns.forEach((btn) => {
|
|
btn.addEventListener("click", () => {
|
|
modeBtns.forEach((b) => b.classList.remove("active"));
|
|
btn.classList.add("active");
|
|
currentMode = btn.dataset.mode || "basic";
|
|
});
|
|
});
|
|
|
|
["dragenter", "dragover"].forEach((e) =>
|
|
dropZone.addEventListener(e, (ev) => {
|
|
ev.preventDefault();
|
|
dropZone.classList.add("dragover");
|
|
})
|
|
);
|
|
|
|
["dragleave", "drop"].forEach((e) =>
|
|
dropZone.addEventListener(e, (ev) => {
|
|
ev.preventDefault();
|
|
dropZone.classList.remove("dragover");
|
|
})
|
|
);
|
|
|
|
dropZone.addEventListener("drop", (e) => {
|
|
e.preventDefault();
|
|
fileInput.files = e.dataTransfer.files;
|
|
processFiles();
|
|
});
|
|
|
|
fileInput.addEventListener("change", processFiles);
|
|
|
|
dropZone.querySelector("label")?.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
fileInput.click();
|
|
});
|
|
|
|
function processFiles() {
|
|
const files = fileInput.files;
|
|
if (!files || !files.length) return;
|
|
|
|
const endpoint =
|
|
currentMode === "advanced" ? "/api/upload-advanced" : "/api/upload";
|
|
|
|
const formData = new FormData();
|
|
Array.from(files).forEach((file) => formData.append("files", file));
|
|
|
|
showProgress(currentMode.toUpperCase(), files.length);
|
|
|
|
fetch(endpoint, { method: "POST", body: formData })
|
|
.then((r) => r.json())
|
|
.then((data) => {
|
|
hideProgress();
|
|
if (data && data.success) showResult(data);
|
|
else showError(data?.error || "Unknown error");
|
|
})
|
|
.catch((e) => {
|
|
hideProgress();
|
|
showError(e?.message || "Request failed");
|
|
});
|
|
}
|
|
|
|
function showProgress(mode, count) {
|
|
progress.classList.remove("d-none");
|
|
progressText.classList.remove("d-none");
|
|
progressText.textContent = `${mode} - ${count} files`;
|
|
progressBar.style.width = "45%";
|
|
}
|
|
|
|
function hideProgress() {
|
|
progress.classList.add("d-none");
|
|
progressText.classList.add("d-none");
|
|
}
|
|
|
|
async function showResult(data) {
|
|
const sizeKB = ((data.size || 0) / 1024).toFixed(1);
|
|
|
|
const rawHtml =
|
|
typeof data.aio_html === "string" ? data.aio_html : String(data.aio_html ?? "");
|
|
|
|
const formattedHtml = await formatHtml(rawHtml);
|
|
|
|
result.innerHTML = `
|
|
<div class="alert alert-success">
|
|
<div class="d-flex align-items-start gap-3">
|
|
<i class="fa-solid fa-circle-check mt-1 flex-shrink-0"></i>
|
|
<div class="flex-grow-1">
|
|
<h5 class="mb-1">${escapeHtml(data.message ?? "Done")}</h5>
|
|
<div class="mb-2">
|
|
<small><code>${escapeHtml(data.original ?? "")}</code> → <strong>${sizeKB}KB</strong></small>
|
|
</div>
|
|
</div>
|
|
<div class="btn-group-vertical btn-group-sm">
|
|
<button class="btn btn-success btn-copy-sm" type="button">
|
|
<i class="fa-solid fa-copy me-1"></i>Copy
|
|
</button>
|
|
<a
|
|
href="data:text/html;charset=utf-8,${encodeURIComponent(rawHtml)}"
|
|
download="aio.html"
|
|
class="btn btn-outline-primary btn-sm"
|
|
>
|
|
<i class="fa-solid fa-download me-1"></i>Save
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3">
|
|
<div class="col-12 col-xl-6">
|
|
<div class="card result-card">
|
|
<div class="card-header bg-light">
|
|
<h6 class="mb-0"><i class="fa-regular fa-eye me-2"></i>Preview</h6>
|
|
</div>
|
|
<iframe id="previewFrame" class="preview-iframe" style="height:70vh; min-height:520px;"></iframe>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12 col-xl-6">
|
|
<div class="card result-card">
|
|
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0"><i class="fa-solid fa-code me-2"></i>Code</h6>
|
|
<button class="btn btn-sm btn-success btn-copy-all" type="button">
|
|
<i class="fa-solid fa-copy me-1"></i>Copy All
|
|
</button>
|
|
</div>
|
|
<pre class="code-preview mb-0" style="max-height:70vh; min-height:520px; overflow:auto;">
|
|
<code class="language-markup">${escapeHtml(formattedHtml)}</code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-center mt-4">
|
|
<button class="btn btn-outline-secondary px-4" type="button" id="btn-new">
|
|
<i class="fa-solid fa-arrow-rotate-right me-2"></i>New
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
result.classList.remove("d-none");
|
|
|
|
const frame = result.querySelector("#previewFrame");
|
|
if (frame) frame.srcdoc = rawHtml;
|
|
|
|
if (window.Prism) window.Prism.highlightAllUnder(result);
|
|
|
|
result.querySelectorAll(".btn-copy-sm, .btn-copy-all").forEach((btn) => {
|
|
const originalHtml = btn.innerHTML;
|
|
btn.addEventListener("click", () => {
|
|
navigator.clipboard
|
|
.writeText(rawHtml)
|
|
.then(() => {
|
|
btn.innerHTML = '<i class="fa-solid fa-check me-1"></i>Copied';
|
|
setTimeout(() => (btn.innerHTML = originalHtml), 1200);
|
|
})
|
|
.catch(() => fallbackCopy(rawHtml, btn, originalHtml));
|
|
});
|
|
});
|
|
|
|
result.querySelector("#btn-new")?.addEventListener("click", () => {
|
|
location.reload();
|
|
});
|
|
|
|
result.scrollIntoView({ behavior: "smooth" });
|
|
}
|
|
|
|
function fallbackCopy(text, btn, originalHtml) {
|
|
try {
|
|
const ta = document.createElement("textarea");
|
|
ta.value = text;
|
|
document.body.appendChild(ta);
|
|
ta.select();
|
|
document.execCommand("copy");
|
|
document.body.removeChild(ta);
|
|
btn.innerHTML = '<i class="fa-solid fa-check me-1"></i>Copied';
|
|
setTimeout(() => (btn.innerHTML = originalHtml), 1200);
|
|
} catch {
|
|
showError("Clipboard blocked by browser");
|
|
}
|
|
}
|
|
|
|
function showError(msg) {
|
|
result.innerHTML = `
|
|
<div class="alert alert-danger">
|
|
<i class="fa-solid fa-circle-xmark me-2"></i>${escapeHtml(msg)}
|
|
</div>
|
|
<div class="text-center">
|
|
<button class="btn btn-outline-primary" type="button" id="btn-try">Try Again</button>
|
|
</div>
|
|
`;
|
|
result.classList.remove("d-none");
|
|
result.querySelector("#btn-try")?.addEventListener("click", () => location.reload());
|
|
}
|
|
});
|
|
|
|
async function formatHtml(html) {
|
|
html = typeof html === "string" ? html : String(html ?? "");
|
|
try {
|
|
if (window.prettier && window.prettierPlugins?.html) {
|
|
return await window.prettier.format(html, {
|
|
parser: "html",
|
|
plugins: [window.prettierPlugins.html],
|
|
printWidth: 100,
|
|
tabWidth: 2,
|
|
useTabs: false,
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
return html;
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
text = typeof text === "string" ? text : String(text ?? "");
|
|
const map = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
return text.replace(/[&<>"']/g, (m) => map[m]);
|
|
} |