222 lines
6.9 KiB
HTML
222 lines
6.9 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"
|
|
/>
|
|
<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('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", function (e) {
|
|
/** @type {File} */
|
|
const file = e.target.files[0];
|
|
if (!file) return;
|
|
|
|
document.getElementById("file-name").textContent = file.name;
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = function (e) {
|
|
/** @type {HTMLTextAreaElement} */
|
|
const textarea = document.getElementById("input-content");
|
|
textarea.value = e.target.result;
|
|
|
|
// Optional: Auto-detect mode based on extension
|
|
if (
|
|
file.name.endsWith(".md") ||
|
|
file.name.endsWith(".markdown")
|
|
) {
|
|
extractLinks("markdown");
|
|
} else if (
|
|
file.name.endsWith(".html") ||
|
|
file.name.endsWith(".htm")
|
|
) {
|
|
extractLinks("html");
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
});
|
|
|
|
/**
|
|
* Extracts links from the input content based on the specified mode.
|
|
* @param {'html' | 'markdown' | 'auto'} mode
|
|
*/
|
|
function extractLinks(mode) {
|
|
/** @type {string} */
|
|
const content = document.getElementById("input-content").value;
|
|
if (!content.trim()) return;
|
|
|
|
/** @type {string[]} */
|
|
let links = [];
|
|
|
|
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 %}
|