mirror of
https://github.com/xroche/httrack.git
synced 2026-08-04 06:45:56 +03:00
* One guide for WinHTTrack, WebHTTrack and Android, instead of 18 thin pages The GUI documentation was 18 files whose bodies were mostly a copy-pasted style block, illustrated with 1998-era screenshots of WinHTTrack, with the Android app documented separately and WebHTTrack not shown at all. html/guide.html replaces them with a single page: one walkthrough and one option reference, and a switcher that swaps the screenshots and the platform-specific notes between the three versions. Each option now also names its command-line equivalent. The screenshots are the three capture sets from httrack-works, prepared by tools/doc-images.py. Deep links carry the platform, so guide.html#win/opt-limits opens the Limits section already showing the Windows dialog; the WebHTTrack option pages use that form. The old filenames stay as redirect stubs for the forum links that still point at them. Scripting is an enhancement throughout: without it the page shows every platform's text and screenshots rather than hiding any of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * Document the options the old pages never had A coverage probe against the WebHTTrack form controls found the reference inherited the old doc's blind spots: WARC, the CDXJ index and WACZ, --changes, sitemap seeding, cookies-file, the urlhack opt-outs, strip-query, the proxy type selector and the random inter-file pause were all exposed in the UI and absent here. Labels and descriptions taken from lang.def, CLI flags from htsalias.c. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> --------- Signed-off-by: Xavier Roche <roche@httrack.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
183 lines
5.1 KiB
JavaScript
183 lines
5.1 KiB
JavaScript
/* Progressive enhancement for guide.html. Everything here is optional: with
|
|
scripting off the page still carries every platform's text and screenshots. */
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
var PLATFORMS = ["win", "web", "droid"];
|
|
var STORE = "httrack-guide-platform";
|
|
|
|
/* Chrome denies storage to file:// origins, and the doc is read from disk. */
|
|
function remembered() {
|
|
try {
|
|
return localStorage.getItem(STORE);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function remember(platform) {
|
|
try {
|
|
localStorage.setItem(STORE, platform);
|
|
} catch (e) {
|
|
/* not fatal: the hash still carries the choice */
|
|
}
|
|
}
|
|
|
|
/* "#win/opt-limits" -> {platform: "win", target: "opt-limits"}, and a bare
|
|
"#win" is a platform, not a target of that name. */
|
|
function parseHash() {
|
|
var hash = location.hash.replace(/^#/, "");
|
|
var slash = hash.indexOf("/");
|
|
if (slash < 0) {
|
|
return PLATFORMS.indexOf(hash) !== -1
|
|
? { platform: hash, target: "" }
|
|
: { platform: null, target: hash };
|
|
}
|
|
if (PLATFORMS.indexOf(hash.slice(0, slash)) !== -1) {
|
|
return { platform: hash.slice(0, slash), target: hash.slice(slash + 1) };
|
|
}
|
|
return { platform: null, target: hash };
|
|
}
|
|
|
|
function setPlatform(platform) {
|
|
document.documentElement.setAttribute("data-platform", platform);
|
|
}
|
|
|
|
/* Runs before first paint, so the page never flashes the other platforms. */
|
|
var initial = parseHash().platform || remembered() ||
|
|
(/Android/i.test(navigator.userAgent) ? "droid" :
|
|
/Windows/i.test(navigator.userAgent) ? "win" : "web");
|
|
setPlatform(initial);
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
var current = document.documentElement.getAttribute("data-platform");
|
|
|
|
/* ---- platform switcher ---- */
|
|
|
|
var buttons = [].slice.call(document.querySelectorAll(".platforms button"));
|
|
|
|
function select(platform, push) {
|
|
current = platform;
|
|
setPlatform(platform);
|
|
remember(platform);
|
|
buttons.forEach(function (b) {
|
|
b.setAttribute("aria-pressed", String(b.dataset.platform === platform));
|
|
});
|
|
if (push) {
|
|
var target = parseHash().target;
|
|
history.replaceState(null, "", "#" + platform + (target ? "/" + target : ""));
|
|
}
|
|
}
|
|
|
|
buttons.forEach(function (b) {
|
|
b.addEventListener("click", function () {
|
|
select(b.dataset.platform, true);
|
|
});
|
|
});
|
|
select(current, false);
|
|
|
|
/* A "#win/opt-limits" link has no element of that id: scroll it ourselves. */
|
|
function jumpToHash() {
|
|
var parsed = parseHash();
|
|
if (parsed.platform) {
|
|
select(parsed.platform, false);
|
|
}
|
|
if (parsed.target) {
|
|
var el = document.getElementById(parsed.target);
|
|
if (el) {
|
|
el.scrollIntoView();
|
|
}
|
|
}
|
|
}
|
|
|
|
window.addEventListener("hashchange", jumpToHash);
|
|
if (parseHash().platform) {
|
|
jumpToHash();
|
|
}
|
|
|
|
/* ---- click to zoom ---- */
|
|
|
|
var zoom = document.getElementById("zoom");
|
|
if (zoom && typeof zoom.showModal === "function") {
|
|
var zoomImage = zoom.querySelector("img");
|
|
document.addEventListener("click", function (event) {
|
|
var img = event.target.closest("figure img");
|
|
if (!img) {
|
|
return;
|
|
}
|
|
event.preventDefault();
|
|
zoomImage.src = img.src;
|
|
zoomImage.alt = img.alt;
|
|
zoom.showModal();
|
|
});
|
|
zoom.addEventListener("click", function () {
|
|
zoom.close();
|
|
});
|
|
}
|
|
|
|
/* ---- option filter ---- */
|
|
|
|
var filter = document.getElementById("optfilter");
|
|
var count = document.getElementById("optcount");
|
|
if (filter) {
|
|
var options = [].slice.call(document.querySelectorAll(".opt"));
|
|
options.forEach(function (opt) {
|
|
opt.dataset.haystack = opt.textContent.toLowerCase();
|
|
});
|
|
filter.addEventListener("input", function () {
|
|
var needle = filter.value.trim().toLowerCase();
|
|
var shown = 0;
|
|
options.forEach(function (opt) {
|
|
var hit = !needle || opt.dataset.haystack.indexOf(needle) !== -1;
|
|
opt.hidden = !hit;
|
|
shown += hit ? 1 : 0;
|
|
});
|
|
/* Fold away a tab whose options all filtered out. */
|
|
document.querySelectorAll("section.tab").forEach(function (section) {
|
|
section.hidden = needle !== "" &&
|
|
!section.querySelector(".opt:not([hidden])");
|
|
});
|
|
count.textContent = needle ? shown + " of " + options.length : "";
|
|
});
|
|
}
|
|
|
|
/* ---- highlight the section being read ---- */
|
|
|
|
var marks = [].slice.call(document.querySelectorAll(".toc a, .tabstrip a"))
|
|
.filter(function (a) { return a.hash.length > 1; });
|
|
if (!marks.length || !window.IntersectionObserver) {
|
|
return;
|
|
}
|
|
|
|
var byId = {};
|
|
marks.forEach(function (a) {
|
|
var id = a.hash.slice(1);
|
|
(byId[id] = byId[id] || []).push(a);
|
|
});
|
|
|
|
var visible = {};
|
|
var observer = new IntersectionObserver(function (entries) {
|
|
entries.forEach(function (entry) {
|
|
visible[entry.target.id] = entry.isIntersecting;
|
|
});
|
|
var top = Object.keys(byId).filter(function (id) { return visible[id]; })[0];
|
|
marks.forEach(function (a) {
|
|
a.removeAttribute("aria-current");
|
|
});
|
|
if (top) {
|
|
byId[top].forEach(function (a) {
|
|
a.setAttribute("aria-current", "true");
|
|
});
|
|
}
|
|
}, { rootMargin: "-10% 0px -70% 0px" });
|
|
|
|
Object.keys(byId).forEach(function (id) {
|
|
var el = document.getElementById(id);
|
|
if (el) {
|
|
observer.observe(el);
|
|
}
|
|
});
|
|
});
|
|
})();
|