Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8fc6faaa3 | |||
| c5b39d6816 | |||
| 057937938d | |||
| 4e920c57ec |
@@ -6,6 +6,19 @@ All notable changes to kage are recorded here. The format follows
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.3.6] - 2026-06-19
|
||||
|
||||
### Fixed
|
||||
|
||||
- `kage clone --mobile` now produces a correct layout when the source page uses table-based navigation with image maps, as paulgraham.com does.
|
||||
Three rendering problems appeared in Kiwix iOS after the 0.3.5 release: a tall blank box at the top of every page, content clipped on the right edge, and a narrow spacer column occupying screen space.
|
||||
The blank box came from hiding only the `<img usemap>` element while leaving its `<td>` container in place; the cell collapsed to empty but kept its full height.
|
||||
`td:has(>img[usemap])` now hides the entire nav column rather than just the image inside it.
|
||||
The right-side clip came from `width="435"` set directly as an HTML attribute on the inner content table; the earlier `max-width` CSS rule does not override HTML attributes.
|
||||
A new `[width]{width:auto!important;max-width:100%!important}` rule cancels every fixed HTML width attribute on any element — tables, cells, and images alike — so the content column stretches to fill the phone screen.
|
||||
The spacer column (a 26 px `<td>` holding a 1×1 transparent GIF) kept its allocated width for the same reason; `td:has(>img[src*="trans_1x1"]:only-child)` hides it alongside the nav column.
|
||||
Two additional rules round out the fix: `*{box-sizing:border-box}` prevents padding from pushing content past the viewport edge, and `img{max-width:100%;height:auto}` keeps any inline photos within their column.
|
||||
|
||||
## [0.3.5] - 2026-06-19
|
||||
|
||||
### Changed
|
||||
|
||||
+29
-13
@@ -303,21 +303,37 @@ func findElement(n *html.Node, a atom.Atom) *html.Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
// mobileCSS is injected when MobileReadable is set. It targets legacy "font
|
||||
// era" HTML that renders as microscopic text on mobile:
|
||||
// - :root font-size 18 px — baseline all em/rem sizes upward
|
||||
// - body — centre, cap width, add padding, loosen line height
|
||||
// - font element — override the in-HTML size/face attributes that sites like
|
||||
// paulgraham.com embed directly in the markup (e.g. <font size="2">)
|
||||
// - table/td — prevent overflow; add minimal cell breathing room
|
||||
// - img[usemap], map — image-map navigation is useless offline (the image
|
||||
// itself usually 404s from an external CDN); hide both the image and the map
|
||||
const mobileCSS = `body{max-width:720px;margin:0 auto;padding:.75em 1em;line-height:1.7;font-family:Georgia,"Times New Roman",serif}` +
|
||||
// mobileCSS is injected when MobileReadable is set. It rewrites font-era
|
||||
// HTML for comfortable reading on a phone. Key rules:
|
||||
//
|
||||
// - box-sizing:border-box — makes padding predictable in a layout built with
|
||||
// HTML width attributes, so our padding doesn't cause overflow.
|
||||
// - body — no fixed max-width here; let the table rules handle width instead.
|
||||
// overflow-x:hidden catches any stray overflow without a scrollbar.
|
||||
// - font element — overrides in-HTML size/face attributes (e.g. <font size="2">).
|
||||
// - [width],[height] — cancels all HTML attribute widths/heights on every
|
||||
// element (tables, tds, imgs, etc.) so fixed-pixel columns become fluid.
|
||||
// - table — fluid, auto layout, no horizontal scroll.
|
||||
// - td — auto width so a three-column table (nav | spacer | content) collapses
|
||||
// to one usable column once the nav td is hidden.
|
||||
// - img — responsive: never wider than its container.
|
||||
// - td:has(>img[usemap]) — hides the entire nav column td, not just the image
|
||||
// inside it; hiding only the img left a tall empty white box.
|
||||
// - td:has(>img[src*="trans_1x1"]) — hides the 26 px spacer column td (a 1×1
|
||||
// transparent GIF whose only job was spacing in the original table layout).
|
||||
const mobileCSS = `*{box-sizing:border-box}` +
|
||||
`:root{font-size:18px}` +
|
||||
`body{margin:0;padding:.75em 1em;line-height:1.7;font-family:Georgia,"Times New Roman",serif;overflow-x:hidden}` +
|
||||
`font{font-size:1rem!important;font-family:inherit!important;color:inherit!important}` +
|
||||
`table{max-width:100%!important;word-break:break-word}` +
|
||||
`td,th{padding:.25em!important}` +
|
||||
`img[usemap],map{display:none!important}`
|
||||
`[width]{width:auto!important;max-width:100%!important}` +
|
||||
`[height]{height:auto!important}` +
|
||||
`table{width:100%!important;max-width:100%!important;table-layout:auto!important;border-collapse:collapse!important;word-break:break-word}` +
|
||||
`td,th{width:auto!important;max-width:100%!important;padding:.35em .5em!important;vertical-align:top!important;overflow-wrap:break-word}` +
|
||||
`img{max-width:100%!important;height:auto!important}` +
|
||||
`img[usemap],map{display:none!important}` +
|
||||
`td:has(>img[usemap]),td:has(>map){display:none!important}` +
|
||||
`img[src*="trans_1x1"],img[src*="spacer"],img[height="1"],img[width="1"]{display:none!important}` +
|
||||
`td:has(>img[src*="trans_1x1"]:only-child),td:has(>img[height="1"]:only-child){display:none!important}`
|
||||
|
||||
// ensureViewport inserts <meta name="viewport" content="width=device-width,
|
||||
// initial-scale=1"> at the top of <head> when the document does not already
|
||||
|
||||
@@ -218,6 +218,40 @@ func TestMobileReadableInjectsViewportAndCSS(t *testing.T) {
|
||||
if !strings.Contains(s, "font{font-size:1rem") {
|
||||
t.Error("font override not in mobile CSS")
|
||||
}
|
||||
// Fluid table rules must be present.
|
||||
if !strings.Contains(s, "table{width:100%") {
|
||||
t.Error("fluid table rule missing")
|
||||
}
|
||||
// [width] override must be present so fixed HTML width attributes are cancelled.
|
||||
if !strings.Contains(s, "[width]{width:auto") {
|
||||
t.Error("[width] override missing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMobileReadableHidesNavColumnTd(t *testing.T) {
|
||||
// paulgraham.com wraps its image-map nav in a <td>. Hiding only the img
|
||||
// leaves a tall empty box; the CSS must also target the containing td.
|
||||
in := `<html><head><title>T</title></head><body>` +
|
||||
`<table><tr>` +
|
||||
`<td><map name="nav"><area shape="rect" coords="0,0,67,21" href="index.html"></map>` +
|
||||
`<img src="nav.gif" width="69" height="357" usemap="#nav"/></td>` +
|
||||
`<td><img src="https://cdn.example.com/trans_1x1.gif" height="1" width="26"/></td>` +
|
||||
`<td><p>Essay text.</p></td>` +
|
||||
`</tr></table>` +
|
||||
`</body></html>`
|
||||
out, _, err := Strip([]byte(in), Options{MobileReadable: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := string(out)
|
||||
// td:has(>img[usemap]) rule must be present so the whole nav column is hidden.
|
||||
if !strings.Contains(s, "td:has(>img[usemap])") {
|
||||
t.Error("td:has(>img[usemap]) rule missing from mobile CSS")
|
||||
}
|
||||
// td:has(>img[src*="trans_1x1"]) rule must be present for spacer column.
|
||||
if !strings.Contains(s, `td:has(>img[src*="trans_1x1"]`) {
|
||||
t.Error(`td:has(>img[src*="trans_1x1"]) spacer-column rule missing from mobile CSS`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMobileReadableSkipsExistingViewport(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user