Files
your-everyday-tools/templates/tools/html_formatter.html
T
2026-04-20 14:43:43 +07:00

134 lines
5.1 KiB
HTML

{% extends "base.html" %}
{% block title %}HTML Formatter - EveryTools{% endblock %}
{% block top_title %}HTML Formatter{% endblock %}
{% block content %}
<div class="client-tool">
<div class="tool-header">
<h1>HTML Formatter</h1>
<p>Beautify or minify HTML source</p>
</div>
<div class="split-pane">
<div class="pane">
<div class="pane-header">
<span>Input</span>
<div>
<button class="btn btn-small" onclick="formatHTML()">Beautify</button>
<button class="btn btn-small" onclick="minifyHTML()">Minify</button>
</div>
</div>
<div class="pane-body">
<textarea id="html-input" placeholder="<div><p>Hello</p></div>"></textarea>
</div>
</div>
<div class="pane">
<div class="pane-header">
<span>Output</span>
<button class="btn btn-small" onclick="copyOutput()"><i class="bi bi-clipboard"></i> Copy</button>
</div>
<div class="pane-body">
<pre id="html-output"></pre>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const VOID_TAGS = new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);
const INLINE_TAGS = new Set(["a","abbr","b","bdo","br","cite","code","em","i","img","input","kbd","label","mark","q","s","small","span","strong","sub","sup","time","u","var"]);
function tokenize(src) {
const tokens = [];
let i = 0;
while (i < src.length) {
if (src[i] === "<") {
const end = src.indexOf(">", i);
if (end === -1) { tokens.push({type: "text", value: src.slice(i)}); break; }
const tag = src.slice(i, end + 1);
if (tag.startsWith("<!--")) {
const c = src.indexOf("-->", i);
tokens.push({type: "comment", value: src.slice(i, c === -1 ? src.length : c + 3)});
i = c === -1 ? src.length : c + 3;
} else if (tag.startsWith("<!")) {
tokens.push({type: "doctype", value: tag});
i = end + 1;
} else if (tag.startsWith("</")) {
tokens.push({type: "close", value: tag, name: tag.slice(2, -1).trim().toLowerCase()});
i = end + 1;
} else {
const name = tag.slice(1).split(/[\s>/]/)[0].toLowerCase();
const selfClosing = tag.endsWith("/>") || VOID_TAGS.has(name);
tokens.push({type: selfClosing ? "void" : "open", value: tag, name});
i = end + 1;
if (["script","style","pre","textarea"].includes(name) && !selfClosing) {
const closeRe = new RegExp(`</${name}\\s*>`, "i");
const m = src.slice(i).match(closeRe);
if (m) {
tokens.push({type: "raw", value: src.slice(i, i + m.index)});
tokens.push({type: "close", value: m[0], name});
i += m.index + m[0].length;
}
}
}
} else {
const next = src.indexOf("<", i);
const text = src.slice(i, next === -1 ? src.length : next);
if (text.trim()) tokens.push({type: "text", value: text.trim()});
i = next === -1 ? src.length : next;
}
}
return tokens;
}
function formatHTML() {
const src = document.getElementById("html-input").value;
const tokens = tokenize(src);
const out = [];
let depth = 0;
const pad = () => " ".repeat(depth);
for (let i = 0; i < tokens.length; i++) {
const t = tokens[i];
const next = tokens[i + 1];
if (t.type === "open") {
// Inline tag with just text inside, keep on one line
if (INLINE_TAGS.has(t.name) && next && next.type === "text" &&
tokens[i + 2] && tokens[i + 2].type === "close" && tokens[i + 2].name === t.name) {
out.push(pad() + t.value + next.value + tokens[i + 2].value);
i += 2;
} else {
out.push(pad() + t.value);
depth++;
}
} else if (t.type === "close") {
depth = Math.max(0, depth - 1);
out.push(pad() + t.value);
} else if (t.type === "void" || t.type === "doctype" || t.type === "comment") {
out.push(pad() + t.value);
} else if (t.type === "text") {
out.push(pad() + t.value);
} else if (t.type === "raw") {
out.push(t.value);
}
}
document.getElementById("html-output").textContent = out.join("\n");
}
function minifyHTML() {
const src = document.getElementById("html-input").value;
const out = src
.replace(/<!--[\s\S]*?-->/g, "")
.replace(/>\s+</g, "><")
.replace(/\s{2,}/g, " ")
.trim();
document.getElementById("html-output").textContent = out;
}
function copyOutput() {
navigator.clipboard.writeText(document.getElementById("html-output").textContent);
}
</script>
{% endblock %}