AI
This commit is contained in:
1 parent
e61c34bc0d
commit
1911d55d5e
13 files changed
+539
-10
No files matched your search
+1
-1
@@ -10,7 +10,7 @@
|
||||
/>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<body class="{% block body_class %}{% endblock %}">
|
||||
<nav class="navbar">
|
||||
<a href="/" class="nav-brand">QM</a>
|
||||
<div class="nav-links">
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
{% 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 %}
|
||||
@@ -62,6 +62,14 @@
|
||||
<a href="/pastepad" class="btn">Visit</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Extractor</h2>
|
||||
<p>Extract links from HTML or Markdown.</p>
|
||||
<div class="actions">
|
||||
<a href="/extractor" class="btn">Visit</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<details>
|
||||
<summary>System Routes</summary>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
{% extends "base.html" %} {% block title %}PastePad - Queue Manager{% endblock
|
||||
%} {% block content %}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}PastePad - Queue Manager{% endblock %}
|
||||
{% block body_class %}layout-wide{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pastepad-container">
|
||||
<h1>PastePad</h1>
|
||||
|
||||
|
||||
Reference in new issue
Block a user