Files
qrcode-from-onetab/templates/extractor.html
T
2025-12-26 01:16:48 +07:00

261 lines
8.3 KiB
HTML

{% extends "base.html" %} {% block title %}Link Extractor{% endblock %}{% block
body_class %}layout-wide{% endblock %}{% block content %}
<div class="container">
<h1>Link Extractor</h1>
<p>Paste HTML or Markdown below to extract all links.</p>
<div class="card" style="width: 100%; max-width: 800px">
<div style="margin-bottom: 10px; text-align: left">
<label for="file-upload" class="btn btn-sm" style="cursor: pointer"
>📂 Upload File</label
>
<input
type="file"
id="file-upload"
style="display: none"
accept=".html,.htm,.md,.txt,.markdown"
multiple
/>
<span
id="file-name"
style="margin-left: 10px; color: #aaa; font-size: 0.9em"
></span>
</div>
<textarea
id="input-content"
class="form-control"
placeholder="Paste content here..."
style="min-height: 200px; font-family: monospace"
></textarea>
<div class="actions" style="margin-top: 15px">
<button onclick="extractLinks('html')" class="btn">
Extract from HTML
</button>
<button onclick="extractLinks('markdown')" class="btn">
Extract from Markdown
</button>
<button onclick="extractLinks('jumblang')" class="btn">
Extract from Messy Text
</button>
<button onclick="extractLinks('auto')" class="btn btn-primary">
Auto Detect & Extract
</button>
</div>
</div>
<div
id="results-container"
class="card hidden"
style="
width: 100%;
max-width: 800px;
margin-top: 20px;
text-align: left;
"
>
<div
style="
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
"
>
<h2 style="margin: 0">
Found <span id="link-count">0</span> Links
</h2>
<div>
<button onclick="copyAll()" class="btn-sm">Copy All</button>
<button onclick="sendToQueue()" class="btn-sm btn-primary">
Send to Queue
</button>
</div>
</div>
<textarea
id="output-links"
class="form-control"
readonly
style="min-height: 150px; white-space: pre"
></textarea>
</div>
</div>
<script>
// File Upload Handler
/** @type {HTMLInputElement} */
const fileInput = document.getElementById("file-upload");
fileInput.addEventListener("change", async function (e) {
/** @type {FileList} */
const files = e.target.files;
if (!files || files.length === 0) return;
document.getElementById(
"file-name"
).textContent = `${files.length} file(s) selected`;
/** @type {HTMLTextAreaElement} */
const textarea = document.getElementById("input-content");
textarea.value = ""; // Clear previous content
// Read all files
for (let i = 0; i < files.length; i++) {
const file = files[i];
const text = await readFileAsText(file);
textarea.value += `\n\n<!-- File: ${file.name} -->\n${text}`;
}
// Auto-detect mode based on the first file's extension (heuristic)
const firstFile = files[0];
if (
firstFile.name.endsWith(".md") ||
firstFile.name.endsWith(".markdown")
) {
extractLinks("markdown");
} else if (
firstFile.name.endsWith(".html") ||
firstFile.name.endsWith(".htm")
) {
extractLinks("html");
}
});
/**
* Reads a file as text using a Promise wrapper around FileReader.
* @param {File} file
* @returns {Promise<string>}
*/
function readFileAsText(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.onerror = (e) => reject(e);
reader.readAsText(file);
});
}
/**
* Extracts links from the input content based on the specified mode.
* @param {'html' | 'markdown' | 'auto' | 'jumblang'} mode
*/
async function extractLinks(mode) {
/** @type {string} */
const content = document.getElementById("input-content").value;
if (!content.trim()) return;
/** @type {string[]} */
let links = [];
if (mode === "jumblang") {
try {
const response = await fetch("/api/extract/jumblang", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: content }),
});
if (!response.ok) throw new Error("Extraction failed");
links = await response.json();
} catch (e) {
alert("Extraction failed: " + e.message);
return;
}
} else {
if (mode === "auto") {
// Simple heuristic: if it has <a href, it's likely HTML.
mode =
content.includes("<a href") ||
content.includes("<!DOCTYPE html>")
? "html"
: "markdown";
}
if (mode === "html") {
const parser = new DOMParser();
const doc = parser.parseFromString(content, "text/html");
const anchors = doc.querySelectorAll("a[href]");
links = Array.from(anchors)
.map((a) => a.href)
.filter((h) => h.startsWith("http"));
} else {
// Markdown regex for [text](url) and <url>
const mdLinkRegex = /\[.*?\]\((https?:\/\/[^\s)]+)\)/g;
const rawLinkRegex = /<(https?:\/\/[^>]+)>/g;
let match;
while ((match = mdLinkRegex.exec(content)) !== null) {
links.push(match[1]);
}
while ((match = rawLinkRegex.exec(content)) !== null) {
links.push(match[1]);
}
// Also catch plain URLs if they are on their own line or space-separated
// This is a bit aggressive, so maybe optional? For now let's stick to explicit MD syntax
// or use a library if we want full robustness.
}
}
// Deduplicate
links = [...new Set(links)];
displayResults(links);
}
/**
* Displays the extracted links in the results container.
* @param {string[]} links
*/
function displayResults(links) {
const container = document.getElementById("results-container");
/** @type {HTMLTextAreaElement} */
const output = document.getElementById("output-links");
const count = document.getElementById("link-count");
output.value = links.join("\n");
count.textContent = links.length.toString();
container.classList.remove("hidden");
container.scrollIntoView({ behavior: "smooth" });
}
/**
* Copies all extracted links to the clipboard.
*/
function copyAll() {
/** @type {HTMLTextAreaElement} */
const output = document.getElementById("output-links");
output.select();
navigator.clipboard.writeText(output.value);
}
/**
* Sends the extracted links to the queue.
*/
async function sendToQueue() {
/** @type {HTMLTextAreaElement} */
const output = document.getElementById("output-links");
const links = output.value;
if (!links) return;
const formData = new FormData();
formData.append("lines", links);
try {
await fetch("/queuein", {
method: "POST",
body: formData,
});
alert("Links sent to queue!");
} catch (e) {
alert("Failed to send to queue");
}
}
</script>
<style>
.hidden {
display: none;
}
</style>
{% endblock %}