mirror of
https://github.com/xroche/httrack.git
synced 2026-08-13 11:12:19 +03:00
Nine of the twenty pages linked nowhere at all and seven had a single inbound link, so a reader landing on cache.html from a search engine got no navigation, no breadcrumb and no way back. Each page also carried its own copy of the same inline stylesheet inside a six-table shell with a 400px floor, which is why sixteen of them scrolled sideways on a phone. They now share the guide's chrome: masthead, sidebar, footer, and where the headings allow it a generated "On this page" list. file:// has no server includes, so the navigation is real markup in every page, written by tools/doc-chrome.py and verified by CI; tools/doc-links.py resolves every relative link and fragment. Both refuse to pass vacuously, and both were checked against planted defects. The prose is untouched. For twelve of the fifteen pages the content text is identical word for word; the three exceptions are cmdguide.html losing the contents list the sidebar now carries, contact.html re-encoded to UTF-8, and one font tag whose removal joined two words the browser already ran together. Each page also gains a real title and its own description, replacing the shared blurb whose keyword list still advertised Windows 95 and AIX 4.0. Image zoom and sidebar highlighting move to doc.js so every page has them, leaving guide.js the platform switcher and the option filter. The guide renders identically at 1100px in light and dark, and thirteen behaviours pass unchanged before and after; at 390px one Proxy paragraph that used to overflow its container by 37px now wraps. Nothing scrolls sideways at 390px or 1100px, and no page throws a script error. httrack.man.html keeps its own bare styling for now, since reskinning it belongs in the generator that writes it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com>
128 lines
3.7 KiB
JavaScript
128 lines
3.7 KiB
JavaScript
/* Progressive enhancement for guide.html: the platform switcher and the option
|
|
filter. Image zoom and sidebar highlighting are shared, in doc.js. 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();
|
|
}
|
|
|
|
/* ---- 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. */
|
|
[].slice.call(document.querySelectorAll("section.tab")).forEach(function (section) {
|
|
section.hidden = needle !== "" &&
|
|
!section.querySelector(".opt:not([hidden])");
|
|
});
|
|
count.textContent = needle ? shown + " of " + options.length : "";
|
|
});
|
|
}
|
|
});
|
|
})();
|