test1:subject6
(async () => {
const rootHandle = await window.showDirectoryPicker();
const origin = location.origin;
const currentUrl = new URL(location.href);
const textDecoder = new TextDecoder();
const normalizeUrl = (u) => {
try {
const x = new URL(u, location.href);
if (!/^https?:$/.test(x.protocol)) return null;
if (x.origin !== origin) return null; // same-origin only
x.hash = "";
return x;
} catch {
return null;
}
};
const toLocalPathParts = (urlObj, isHtml = false) => {
let path = decodeURIComponent(urlObj.pathname);
if (!path || path === "/") {
return ["index.html"];
}
const parts = path.split("/").filter(Boolean);
const last = parts[parts.length - 1] || "";
const hasExt = /\.[a-zA-Z0-9]{1,10}$/.test(last);
if (isHtml) {
if (hasExt && /\.html?$/i.test(last)) {
return parts;
}
if (hasExt) {
return parts;
}
return [...parts, "index.html"];
}
if (!hasExt) {
return [...parts, "index.html"];
}
return parts;
};
const getOrCreateDir = async (baseHandle, dirParts) => {
let h = baseHandle;
for (const part of dirParts) {
h = await h.getDirectoryHandle(part, { create: true });
}
return h;
};
const writeFile = async (baseHandle, pathParts, blob) => {
const dirs = pathParts.slice(0, -1);
const fileName = pathParts[pathParts.length - 1];
const dirHandle = await getOrCreateDir(baseHandle, dirs);
const fileHandle = await dirHandle.getFileHandle(fileName, { create: true });
const writable = await fileHandle.createWritable();
await writable.write(blob);
await writable.close();
};
const collected = new Map();
const addUrl = (u, type = "asset") => {
const nu = normalizeUrl(u);
if (!nu) return;
const key = nu.href;
if (!collected.has(key)) {
collected.set(key, { url: nu, type });
}
};
// current page
addUrl(location.href, "html");
// DOM resources
document.querySelectorAll("script[src]").forEach(el => addUrl(el.src, "asset"));
document.querySelectorAll('link[href]').forEach(el => addUrl(el.href, "asset"));
document.querySelectorAll("img[src]").forEach(el => addUrl(el.src, "asset"));
document.querySelectorAll("[style]").forEach(el => {
const style = el.getAttribute("style") || "";
const re = /url\((['"]?)(.*?)\1\)/g;
let m;
while ((m = re.exec(style))) addUrl(m[2], "asset");
});
// CSS rules
for (const sheet of Array.from(document.styleSheets)) {
try {
for (const rule of Array.from(sheet.cssRules || [])) {
const txt = rule.cssText || "";
const re = /url\((['"]?)(.*?)\1\)/g;
let m;
while ((m = re.exec(txt))) addUrl(m[2], "asset");
}
} catch (e) {
// cross-origin stylesheet access can fail
}
}
// Performance resources
performance.getEntriesByType("resource").forEach(r => addUrl(r.name, "asset"));
// Save HTML of current page into path-based index.html
const currentHtmlParts = toLocalPathParts(currentUrl, true);
const htmlBlob = new Blob([document.documentElement.outerHTML], {
type: "text/html;charset=utf-8"
});
await writeFile(rootHandle, currentHtmlParts, htmlBlob);
let ok = 0;
let fail = 0;
const failed = [];
for (const { url, type } of collected.values()) {
if (type === "html" && url.href === currentUrl.href) continue;
try {
const res = await fetch(url.href, { credentials: "include" });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const blob = await res.blob();
const pathParts = toLocalPathParts(url, false);
await writeFile(rootHandle, pathParts, blob);
ok++;
console.log("saved:", url.href, "->", pathParts.join("/"));
} catch (e) {
fail++;
failed.push({ url: url.href, error: String(e) });
console.warn("failed:", url.href, e);
}
}
// save fail report
const report = {
page: location.href,
savedHtmlAs: currentHtmlParts.join("/"),
savedCount: ok,
failedCount: fail,
failed
};
await writeFile(
rootHandle,
["_download_report.json"],
new Blob([JSON.stringify(report, null, 2)], { type: "application/json" })
);
console.log("DONE");
console.log("HTML:", currentHtmlParts.join("/"));
console.log("saved assets:", ok);
console.log("failed assets:", fail);
})();
test1/subject6.txt · Last modified: by admin
