diff --git a/changedetectionio/blueprint/ui/templates/diff.html b/changedetectionio/blueprint/ui/templates/diff.html index d97eecb4c..ca9ca7a85 100644 --- a/changedetectionio/blueprint/ui/templates/diff.html +++ b/changedetectionio/blueprint/ui/templates/diff.html @@ -21,7 +21,9 @@
-

{{ watch_a.label }}

+ {# The title is one centred line and ellipsizes only when it is wider than the + whole bar, so keep the full text reachable on hover. #} +

{{ watch_a.label }}

@@ -51,6 +53,10 @@ {##} {% endif %}
+ {# Collapses the diff options below into a popover. Hidden until + diff-overview.js takes over the fieldset, so with scripting off the + options stay inline and reachable. #} +
{%- if versions|length >= 2 -%}
+ {# In the sticky bar the prompt and the two words are hidden and + only the arrows show, so each link carries the full wording as + its accessible name and its tooltip. #} {{ _('Keyboard:') }} - ← {{ _('Previous') }} -   → {{ _('Next') }} + {{ _('Previous') }} + {{ _('Next') }}
{%- endif -%}
diff --git a/changedetectionio/static/js/diff-overview.js b/changedetectionio/static/js/diff-overview.js index e4457119d..f4fc73141 100644 --- a/changedetectionio/static/js/diff-overview.js +++ b/changedetectionio/static/js/diff-overview.js @@ -71,6 +71,86 @@ function setupDiffNavigation() { }, false); } +// The seven diff options collapse behind the 'Filters' button so the sticky bar +// stays one toolbar row. Wired up here rather than purely in CSS: until this +// runs the fieldset is inline and the toggle hidden, so with scripting off the +// options are still reachable and still submit with the form. +function setupDiffFilters() { + var header = document.getElementById('diff-header'); + var toggle = document.getElementById('diff-filters-toggle'); + var panel = document.getElementById('diff-style'); + if (!header || !toggle || !panel) { + return; + } + + header.classList.add('diff-filters-js'); + + function isOpen() { + return header.classList.contains('diff-filters-open'); + } + + // The panel is position: fixed - #diff-header's overflow: auto would clip an + // absolutely positioned one - so it has to be parked under the button by + // hand, and kept inside the viewport on a narrow screen. + function place() { + var button = toggle.getBoundingClientRect(); + var available = document.documentElement.clientWidth - panel.offsetWidth - 8; + panel.style.top = (button.bottom + 4) + 'px'; + panel.style.left = Math.max(8, Math.min(button.left, available)) + 'px'; + } + + function open() { + header.classList.add('diff-filters-open'); + toggle.setAttribute('aria-expanded', 'true'); + place(); + } + + function close(restoreFocus) { + if (!isOpen()) { + return; + } + header.classList.remove('diff-filters-open'); + toggle.setAttribute('aria-expanded', 'false'); + if (restoreFocus) { + toggle.focus(); + } + } + + toggle.addEventListener('click', function (event) { + event.preventDefault(); + if (isOpen()) { + close(false); + } else { + open(); + } + }); + + document.addEventListener('click', function (event) { + if (isOpen() && !panel.contains(event.target) && !toggle.contains(event.target)) { + close(false); + } + }); + + document.addEventListener('keydown', function (event) { + if (event.key === 'Escape') { + close(true); + } + }); + + // The bar is sticky directly under the top menu, so the button keeps its + // viewport position as the page scrolls and only a resize can move it out + // from under the panel. A tab switch hides #settings, and the panel with it, + // so drop the open state rather than leave aria-expanded lying. + window.addEventListener('resize', function () { + if (isOpen()) { + place(); + } + }); + window.addEventListener('hashchange', function () { + close(false); + }); +} + $(document).ready(function () { $('.needs-localtime').each(function () { for (var option of this.options) { @@ -87,6 +167,8 @@ $(document).ready(function () { setupDiffNavigation(); } + setupDiffFilters(); + // Load it when the #screenshot tab is in use, so we dont give a slow experience when waiting for the text diff to load window.addEventListener('hashchange', function (e) { toggle(location.hash); diff --git a/changedetectionio/static/styles/diff.css b/changedetectionio/static/styles/diff.css index 626922d62..cd5742431 100644 --- a/changedetectionio/static/styles/diff.css +++ b/changedetectionio/static/styles/diff.css @@ -1 +1 @@ -#diff-form{background:rgba(0,0,0,.05);padding:1em;border-radius:10px;margin-bottom:1em;color:#fff;font-size:.9rem;text-align:center}#diff-form label.from-to-label{width:4rem;text-decoration:none;padding:.5rem}#diff-form label.from-to-label#change-from{color:#b30000;background:#fadad7}#diff-form label.from-to-label#change-to{background:#eaf2c2;color:#406619}#diff-form #diff-style>span{display:inline-block;padding:.3em}#diff-form #diff-style>span label{font-weight:normal}#diff-form *{vertical-align:middle}body.difference-page .header{position:sticky;top:0;z-index:30;background:var(--color-background-gradient-second);background:linear-gradient(130deg, color-mix(in srgb, var(--color-background-gradient-first) 91%, var(--color-background-page)), color-mix(in srgb, var(--color-background-gradient-second) 91%, var(--color-background-page)) 41.07%, color-mix(in srgb, var(--color-background-gradient-third) 91%, var(--color-background-page)) 84.05%);background-attachment:fixed}body.difference-page .app-main{gap:0}body.difference-page .tab-pane-inner{scroll-margin-top:calc(var(--app-header-height, 0px) + var(--diff-header-height, 0px) + 1rem)}body.difference-page #pure-menu-horizontal-spinner{z-index:29}#diff-header{position:sticky;top:var(--app-header-height, 0px);z-index:20;width:100%;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;max-height:calc(50vh - var(--app-header-height, 0px));max-height:calc(50dvh - var(--app-header-height, 0px));overflow:auto;background:var(--color-background-gradient-second);background:linear-gradient(130deg, color-mix(in srgb, var(--color-background-gradient-first) 91%, var(--color-background-page)), color-mix(in srgb, var(--color-background-gradient-second) 91%, var(--color-background-page)) 41.07%, color-mix(in srgb, var(--color-background-gradient-third) 91%, var(--color-background-page)) 84.05%);background-attachment:fixed}#diff-header>*{max-width:100%}#diff-header #diff-watch-title{margin:0;padding:.25rem 1rem;font-size:1.25rem;overflow-wrap:anywhere;text-align:center;color:#fff}#diff-header #diff-form{margin-bottom:.5rem}#diff-header .diff-fieldset{min-width:0;display:flex;justify-content:center;flex-wrap:wrap;gap:.5rem}#diff-header .diff-fieldset>span{max-width:100%;min-width:0;flex:0 1 auto;display:flex;align-items:center;flex-wrap:wrap}#diff-header .diff-fieldset select{min-width:0;max-width:100%;flex:1}#diff-header .diff-fieldset label{flex-shrink:0}#diff-ui{background:var(--color-background);padding:1rem;border-radius:5px}@media(min-width: 767px){#diff-ui{min-width:50%}}#diff-ui #text{font-size:11px}#diff-ui pre{white-space:break-spaces;overflow-wrap:anywhere}#diff-ui h1{display:inline;font-size:100%}#diff-ui #result{white-space:pre-wrap;word-break:break-word;overflow-wrap:break-word}#diff-ui .source{position:absolute;right:1%;top:.2em}@-moz-document url-prefix(){#diff-ui body{height:99%}}#diff-ui td#diff-col div{text-align:justify;white-space:pre-wrap}#diff-ui .ignored{background-color:#ccc;opacity:.7}#diff-ui .triggered{background-color:#1b98f8}#diff-ui .ignored.triggered{background-color:red}#diff-ui .tab-pane-inner#screenshot{text-align:center}#diff-ui .tab-pane-inner#screenshot img{max-width:99%}#diff-ui .pure-form button.reset-margin{margin:0px}#diff-ui .diff-fieldset{display:flex;align-items:center;gap:4px;flex-wrap:wrap}#diff-ui ul#highlightSnippetActions{list-style-type:none;display:flex;align-items:center;justify-content:center;gap:1.5rem;flex-wrap:wrap;padding:0;margin:0}#diff-ui ul#highlightSnippetActions li{display:flex;flex-direction:column;align-items:center;text-align:center;padding:.5rem;gap:.3rem}#diff-ui ul#highlightSnippetActions li button,#diff-ui ul#highlightSnippetActions li a{white-space:nowrap}#diff-ui ul#highlightSnippetActions span{font-size:.8rem;color:var(--color-text-input-description)}#diff-ui #cell-diff-jump-visualiser{display:flex;flex-direction:row;gap:1px;background:var(--color-background);border-radius:3px;overflow-x:hidden;position:sticky;top:calc(var(--app-header-height, 0px) + var(--diff-header-height, 0px));z-index:10;padding-top:1rem;padding-bottom:1rem;justify-content:center}#diff-ui #cell-diff-jump-visualiser>div{flex:1;min-width:1px;max-width:10px;height:10px;background:var(--color-background-button-cancel);opacity:.3;border-radius:1px;transition:opacity .2s;position:relative}#diff-ui #cell-diff-jump-visualiser>div.deletion{background:#b30000;opacity:1}#diff-ui #cell-diff-jump-visualiser>div.insertion{background:#406619;opacity:1}#diff-ui #cell-diff-jump-visualiser>div.note{background:#406619;opacity:1}#diff-ui #cell-diff-jump-visualiser>div.mixed{background:linear-gradient(to right, #b30000 50%, #406619 50%);opacity:1}#diff-ui #cell-diff-jump-visualiser>div.current-position::after{content:"";position:absolute;bottom:-6px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:4px solid rgba(0,0,0,0);border-right:4px solid rgba(0,0,0,0);border-bottom:4px solid var(--color-text)}#diff-ui #cell-diff-jump-visualiser>div:hover{opacity:.8;cursor:pointer}#text-diff-heading-area .snapshot-age{padding:4px;margin:.5rem 0;background-color:var(--color-background-snapshot-age);border-radius:3px;font-weight:bold;margin-bottom:4px}#text-diff-heading-area .snapshot-age.error{background-color:var(--color-error-background-snapshot-age);color:var(--color-error-text-snapshot-age)}#text-diff-heading-area .snapshot-age>*{padding-right:1rem} +#diff-form{background:rgba(0,0,0,.05);padding:1em;border-radius:10px;margin-bottom:1em;color:#fff;font-size:.9rem;text-align:center}#diff-form label.from-to-label{width:4rem;text-decoration:none;padding:.5rem}#diff-form label.from-to-label#change-from{color:#b30000;background:#fadad7}#diff-form label.from-to-label#change-to{background:#eaf2c2;color:#406619}#diff-form #diff-style>span{display:inline-block;padding:.3em}#diff-form #diff-style>span label{font-weight:normal}#diff-form *{vertical-align:middle}body.difference-page .header{position:sticky;top:0;z-index:30;background:var(--color-background-gradient-second);background:linear-gradient(130deg, color-mix(in srgb, var(--color-background-gradient-first) 91%, var(--color-background-page)), color-mix(in srgb, var(--color-background-gradient-second) 91%, var(--color-background-page)) 41.07%, color-mix(in srgb, var(--color-background-gradient-third) 91%, var(--color-background-page)) 84.05%);background-attachment:fixed}body.difference-page .app-main{gap:0}body.difference-page .tab-pane-inner{scroll-margin-top:calc(var(--app-header-height, 0px) + var(--diff-header-height, 0px) + 1rem)}body.difference-page #pure-menu-horizontal-spinner{z-index:29}body.difference-page .tabs ul li a{padding:.35em .7em}#diff-header{position:sticky;top:var(--app-header-height, 0px);z-index:20;width:100%;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;gap:.25rem;padding:.25rem 1rem;max-height:calc(50vh - var(--app-header-height, 0px));max-height:calc(50dvh - var(--app-header-height, 0px));overflow:auto;background:var(--color-background-gradient-second);background:linear-gradient(130deg, color-mix(in srgb, var(--color-background-gradient-first) 91%, var(--color-background-page)), color-mix(in srgb, var(--color-background-gradient-second) 91%, var(--color-background-page)) 41.07%, color-mix(in srgb, var(--color-background-gradient-third) 91%, var(--color-background-page)) 84.05%);background-attachment:fixed}#diff-header>*{max-width:100%}#diff-header #diff-watch-title{width:100%;margin:0;padding:0;font-size:.9rem;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:center;color:#fff}#diff-header #settings{min-width:0;display:flex;justify-content:center}#diff-header #diff-form{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:.25rem .9rem;padding:.2rem .7rem;margin-bottom:0}#diff-header #diff-form fieldset{margin:0;padding:0;border:0}#diff-header #diff-form .from-to-label{width:auto;min-width:2.4rem;text-align:center;font-size:.75rem;padding:.1rem .35rem;border-radius:3px;line-height:1.5}#diff-header #keyboard-nav{display:flex;align-items:center;gap:.3rem}#diff-header #keyboard-nav strong,#diff-header #keyboard-nav .keyboard-nav-label{display:none}#diff-header #diff-filters-toggle{display:none;font-size:.85rem;padding:.3em .8em}#diff-header.diff-filters-js #diff-filters-toggle{display:inline-block}#diff-header.diff-filters-js #diff-style{display:none}#diff-header.diff-filters-js.diff-filters-open #diff-style{display:block;position:fixed;z-index:25;min-width:14rem;padding:.5rem .75rem;border-radius:8px;box-shadow:0 4px 14px rgba(0,0,0,.28);text-align:left;background:var(--color-background-gradient-second);background:linear-gradient(130deg, color-mix(in srgb, var(--color-background-gradient-first) 91%, var(--color-background-page)), color-mix(in srgb, var(--color-background-gradient-second) 91%, var(--color-background-page)) 41.07%, color-mix(in srgb, var(--color-background-gradient-third) 91%, var(--color-background-page)) 84.05%);background-attachment:fixed}#diff-header.diff-filters-js.diff-filters-open #diff-form #diff-style>span{display:block;padding:.2em 0}#diff-header .diff-fieldset{min-width:0;display:flex;justify-content:center;flex-wrap:wrap;gap:.5rem}#diff-header .diff-fieldset>span{max-width:100%;min-width:0;gap:.25rem;flex:0 1 auto;display:flex;align-items:center;flex-wrap:wrap}#diff-header .diff-fieldset select{min-width:0;max-width:100%;flex:1}#diff-header .diff-fieldset label{flex-shrink:0}@media(max-width: 700px){#diff-header>#settings{width:100%}#diff-header #diff-form{width:100%;box-sizing:border-box;padding-left:.2rem;padding-right:.2rem}#diff-header{padding-left:.5rem;padding-right:.5rem}#diff-header #diff-form>.diff-fieldset{flex:1 1 100%}#diff-header .diff-fieldset>span{flex:1 1 100%}#diff-header .diff-fieldset select{font-size:.75rem;padding-left:.3em;padding-right:.3em}}#diff-ui{background:var(--color-background);padding:1rem;border-radius:5px}@media(min-width: 767px){#diff-ui{min-width:50%}}#diff-ui #text{font-size:11px}#diff-ui pre{white-space:break-spaces;overflow-wrap:anywhere}#diff-ui h1{display:inline;font-size:100%}#diff-ui #result{white-space:pre-wrap;word-break:break-word;overflow-wrap:break-word}#diff-ui .source{position:absolute;right:1%;top:.2em}@-moz-document url-prefix(){#diff-ui body{height:99%}}#diff-ui td#diff-col div{text-align:justify;white-space:pre-wrap}#diff-ui .ignored{background-color:#ccc;opacity:.7}#diff-ui .triggered{background-color:#1b98f8}#diff-ui .ignored.triggered{background-color:red}#diff-ui .tab-pane-inner#screenshot{text-align:center}#diff-ui .tab-pane-inner#screenshot img{max-width:99%}#diff-ui .pure-form button.reset-margin{margin:0px}#diff-ui .diff-fieldset{display:flex;align-items:center;gap:4px;flex-wrap:wrap}#diff-ui ul#highlightSnippetActions{list-style-type:none;display:flex;align-items:center;justify-content:center;gap:1.5rem;flex-wrap:wrap;padding:0;margin:0}#diff-ui ul#highlightSnippetActions li{display:flex;flex-direction:column;align-items:center;text-align:center;padding:.5rem;gap:.3rem}#diff-ui ul#highlightSnippetActions li button,#diff-ui ul#highlightSnippetActions li a{white-space:nowrap}#diff-ui ul#highlightSnippetActions span{font-size:.8rem;color:var(--color-text-input-description)}#diff-ui #cell-diff-jump-visualiser{display:flex;flex-direction:row;gap:1px;background:var(--color-background);border-radius:3px;overflow-x:hidden;position:sticky;top:calc(var(--app-header-height, 0px) + var(--diff-header-height, 0px));z-index:10;padding-top:1rem;padding-bottom:1rem;justify-content:center}#diff-ui #cell-diff-jump-visualiser>div{flex:1;min-width:1px;max-width:10px;height:10px;background:var(--color-background-button-cancel);opacity:.3;border-radius:1px;transition:opacity .2s;position:relative}#diff-ui #cell-diff-jump-visualiser>div.deletion{background:#b30000;opacity:1}#diff-ui #cell-diff-jump-visualiser>div.insertion{background:#406619;opacity:1}#diff-ui #cell-diff-jump-visualiser>div.note{background:#406619;opacity:1}#diff-ui #cell-diff-jump-visualiser>div.mixed{background:linear-gradient(to right, #b30000 50%, #406619 50%);opacity:1}#diff-ui #cell-diff-jump-visualiser>div.current-position::after{content:"";position:absolute;bottom:-6px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:4px solid rgba(0,0,0,0);border-right:4px solid rgba(0,0,0,0);border-bottom:4px solid var(--color-text)}#diff-ui #cell-diff-jump-visualiser>div:hover{opacity:.8;cursor:pointer}#text-diff-heading-area .snapshot-age{padding:4px;margin:.5rem 0;background-color:var(--color-background-snapshot-age);border-radius:3px;font-weight:bold;margin-bottom:4px}#text-diff-heading-area .snapshot-age.error{background-color:var(--color-error-background-snapshot-age);color:var(--color-error-text-snapshot-age)}#text-diff-heading-area .snapshot-age>*{padding-right:1rem} diff --git a/changedetectionio/static/styles/scss/diff.scss b/changedetectionio/static/styles/scss/diff.scss index 0c50b2c50..ba6dcb877 100644 --- a/changedetectionio/static/styles/scss/diff.scss +++ b/changedetectionio/static/styles/scss/diff.scss @@ -92,6 +92,13 @@ body.difference-page { #pure-menu-horizontal-spinner { z-index: 29; } + + // The bar is the page's whole chrome now, so trim the tabs' vertical padding + // (0.7em on all four sides) to half. Saves ~11px of every scroll position and + // leaves the horizontal padding, and so the tab widths, as they are. + .tabs ul li a { + padding: 0.35em 0.7em; + } } #diff-header { @@ -104,9 +111,12 @@ body.difference-page { box-sizing: border-box; // Center children at their intrinsic width, the same way section.content does // for the non-sticky siblings it still lays out (align-items: center). + // Three rows: title, the toolbar, the tabs. display: flex; flex-direction: column; align-items: center; + gap: 0.25rem; + padding: 0.25rem 1rem; // Keep the diff readable even with wrapped controls, zoom, or a short viewport. // The budget is for the whole sticky stack, so the top menu above comes out of // it - otherwise the two bars together take 64% of a 390px-tall landscape phone. @@ -124,17 +134,123 @@ body.difference-page { max-width: 100%; } + // The title owns the full width of the bar on its own row above the controls, + // so it truncates only when it is wider than the whole viewport - the common + // long title fits in full. One line rather than a two-line clamp, matching the + // watch URL's existing truncation and keeping the bar's height constant. The + // template carries the untruncated text in the title attribute for hover. #diff-watch-title { + width: 100%; margin: 0; - padding: 0.25rem 1rem; - font-size: 1.25rem; - overflow-wrap: anywhere; + padding: 0; + font-size: 0.9rem; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; text-align: center; color: #fff; // match #diff-form's white-on-gradient text } + #settings { + min-width: 0; + display: flex; + justify-content: center; + } + + // One toolbar row: From/To chips and their selects, the version arrows, and + // the Filters toggle. #diff-form { - margin-bottom: 0.5rem; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 0.25rem 0.9rem; + padding: 0.2rem 0.7rem; + margin-bottom: 0; + + fieldset { + margin: 0; + padding: 0; + border: 0; + } + + // The From/To labels shrink to chips rather than hiding: display:none takes + // the