mirror of
https://github.com/webrecorder/pywb.git
synced 2025-03-15 00:03:28 +01:00
Rework Vue banner UI
- Make Vue banner responsive with Bootstrap 4 - Add previous/next year arrows to calendar - Make navbar background, text color, and button outlines configurable via config.yaml - Toggle calendar and timeline separately - Fix bug preventing title from displaying - Make app keyboard-navigable - Fix banner background color configuration - Comment out vue_navbar_background_hash - Display linear timeline tooltip centrally on enter - Improve header styling on small screens - Add titles to font awesome icons - Remove old default banner (calendar retained for advanced search results) - Fix TimelineLinear TypeError that broke calendar - Bump version to 2.7.0b2 - Set Cache-Control header on CDXJ API response to mark returned CDX as stale after 1 day - Add commented out UI values to config.yaml to aid users - Remove timeline and calendar card borders - Fix issues with snapshot navigation - Center search bar and align with buttons - Make Vue app bfcache-ineligible: By adding an empty unload event listener, we make pages serving the Vue app ineligible for bfcache, which prevents unexpected behavior when navigating via the browser's back/forward buttons.
This commit is contained in:
parent
ff7783aa74
commit
c28941a0b6
23
config.yaml
23
config.yaml
@ -2,9 +2,13 @@
|
|||||||
# ========================================
|
# ========================================
|
||||||
#
|
#
|
||||||
debug: true
|
debug: true
|
||||||
ui:
|
|
||||||
vue_calendar_ui: true
|
# Uncomment to set banner colors and logo
|
||||||
vue_timeline_banner: true
|
# ui:
|
||||||
|
# logo: path/relative/from/static/logo.png
|
||||||
|
# navbar_background_hex: 0c49b0
|
||||||
|
# navbar_color_hex: fff
|
||||||
|
# navbar_light_buttons: true
|
||||||
|
|
||||||
collections:
|
collections:
|
||||||
all: $all
|
all: $all
|
||||||
@ -18,7 +22,7 @@ collections:
|
|||||||
# Settings for each collection
|
# Settings for each collection
|
||||||
use_js_obj_proxy: true
|
use_js_obj_proxy: true
|
||||||
|
|
||||||
# Memento support, enable
|
# Eanable Memento support
|
||||||
enable_memento: true
|
enable_memento: true
|
||||||
|
|
||||||
# Replay content in an iframe
|
# Replay content in an iframe
|
||||||
@ -26,11 +30,10 @@ framed_replay: true
|
|||||||
|
|
||||||
redirect_to_exact: true
|
redirect_to_exact: true
|
||||||
|
|
||||||
|
# Uncomment and change to set default locale
|
||||||
# uncomment and change to set default locale
|
|
||||||
# default_locale: en
|
# default_locale: en
|
||||||
|
|
||||||
# uncomment to set available locales
|
# Uncomment to set available locales
|
||||||
locales:
|
# locales:
|
||||||
- en
|
# - en
|
||||||
- ru
|
# - ru
|
||||||
|
@ -432,7 +432,8 @@ class FrontEndApp(object):
|
|||||||
|
|
||||||
return WbResponse.bin_stream(StreamIter(res.raw),
|
return WbResponse.bin_stream(StreamIter(res.raw),
|
||||||
content_type=content_type,
|
content_type=content_type,
|
||||||
status=status_line)
|
status=status_line,
|
||||||
|
headers=[("Cache-Control", "max-age=86400, must-revalidate")])
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return WbResponse.text_response('Error: ' + str(e), status='400 Bad Request')
|
return WbResponse.text_response('Error: ' + str(e), status='400 Bad Request')
|
||||||
|
@ -1,191 +0,0 @@
|
|||||||
|
|
||||||
#_wb_frame_top_banner
|
|
||||||
{
|
|
||||||
display: block !important;
|
|
||||||
top: 0px !important;
|
|
||||||
left: 0px !important;
|
|
||||||
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif !important;
|
|
||||||
width: 100% !important;
|
|
||||||
font-size: 18px !important;
|
|
||||||
background-color: #444 !important;
|
|
||||||
color: white !important;
|
|
||||||
z-index: 2147483643 !important;
|
|
||||||
line-height: normal !important;
|
|
||||||
|
|
||||||
position: absolute !important;
|
|
||||||
border: 0px;
|
|
||||||
height: 44px !important;
|
|
||||||
|
|
||||||
display: flex !important;
|
|
||||||
display: -webkit-box !important;
|
|
||||||
display: -moz-box !important;
|
|
||||||
display: -webkit-flex !important;
|
|
||||||
display: -ms-flexbox !important;
|
|
||||||
|
|
||||||
justify-content: space-between;
|
|
||||||
-webkit-box-pack: justify;
|
|
||||||
-moz-box-pack: justify;
|
|
||||||
-ms-flex-pack: justify;
|
|
||||||
align-items: center;
|
|
||||||
-webkit-box-align: center;
|
|
||||||
-moz-box-align: center;
|
|
||||||
-ms-flex-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#title_or_url
|
|
||||||
{
|
|
||||||
display: block !important;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#_wb_frame_top_banner ._wb_linked_logo
|
|
||||||
{
|
|
||||||
display: block;
|
|
||||||
height: 26px;
|
|
||||||
width: 71px;
|
|
||||||
margin-left: 15px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
-webkit-flex-shrink: 1 0;
|
|
||||||
-moz-flex-shrink: 1 0;
|
|
||||||
-ms-flex: 0 0 71px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#_wb_frame_top_banner ._wb_linked_logo img
|
|
||||||
{
|
|
||||||
width: auto;
|
|
||||||
height: 100%;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#_wb_capture_info
|
|
||||||
{
|
|
||||||
flex-grow: 1;
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-moz-box-flex: 1;
|
|
||||||
-webkit-flex-grow: 1;
|
|
||||||
-ms-flex: 1;
|
|
||||||
|
|
||||||
min-width: 0;
|
|
||||||
margin: 0 15px;
|
|
||||||
|
|
||||||
display: flex !important;
|
|
||||||
display: -webkit-box !important;
|
|
||||||
display: -moz-box !important;
|
|
||||||
display: -webkit-flex !important;
|
|
||||||
display: -ms-flexbox !important;
|
|
||||||
|
|
||||||
flex-direction: column;
|
|
||||||
-webkit-box-direction: normal;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-moz-box-direction: normal;
|
|
||||||
-moz-box-orient: vertical;
|
|
||||||
-ms-flex-direction: column;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
-webkit-box-pack: center;
|
|
||||||
-moz-box-pack: center;
|
|
||||||
-ms-flex-pack: center;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
-webkit-box-align: center;
|
|
||||||
-moz-box-align: center;
|
|
||||||
-ms-flex-align: center;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
}
|
|
||||||
|
|
||||||
._wb_capture_date
|
|
||||||
{
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#_wb_frame_top_banner #_wb_ancillary_links
|
|
||||||
{
|
|
||||||
font-size: 12px;
|
|
||||||
color: #FFF;
|
|
||||||
text-align: right;
|
|
||||||
margin: 0px 15px 0px 0px;
|
|
||||||
padding: inherit;
|
|
||||||
background-color: inherit;
|
|
||||||
width: initial;
|
|
||||||
flex-shrink: 1;
|
|
||||||
-webkit-flex-shrink: 1;
|
|
||||||
-moz-flex-shrink: 1;
|
|
||||||
-ms-flex: 0 0 115px;
|
|
||||||
}
|
|
||||||
#_wb_frame_top_banner #_wb_ancillary_links a:link,
|
|
||||||
#_wb_frame_top_banner #_wb_ancillary_links a:visited,
|
|
||||||
#_wb_frame_top_banner #_wb_ancillary_links a:active
|
|
||||||
{
|
|
||||||
color: #FFF;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
#_wb_frame_top_banner #_wb_ancillary_links a:hover
|
|
||||||
{
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
#_wb_frame_top_banner #_wb_ancillary_links a img
|
|
||||||
{
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#wb_iframe_div
|
|
||||||
{
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 44px 0px 0px 0px;
|
|
||||||
border: none;
|
|
||||||
box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wb_iframe
|
|
||||||
{
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: 2px solid #FFF;
|
|
||||||
border-width: 2px 0 0 0;
|
|
||||||
padding: 0px 0px 0px 0px;
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
._wb_mobile {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
|
||||||
#_wb_frame_top_banner ._wb_linked_logo
|
|
||||||
{
|
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
#_wb_frame_top_banner ._wb_linked_logo img:not(._wb_mobile)
|
|
||||||
{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#_wb_frame_top_banner ._wb_mobile
|
|
||||||
{
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#_wb_capture_info
|
|
||||||
{
|
|
||||||
margin: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#_wb_frame_top_banner ._wb_no-mobile
|
|
||||||
{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,328 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright(c) 2013-2018 Rhizome and Ilya Kreymer. Released under the GNU General Public License.
|
|
||||||
|
|
||||||
This file is part of pywb, https://github.com/webrecorder/pywb
|
|
||||||
|
|
||||||
pywb is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
pywb is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with pywb. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Creates the default pywb banner.
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
try {
|
|
||||||
if (window.parent !== window && window.parent.wbinfo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) { }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default banner class
|
|
||||||
*/
|
|
||||||
function DefaultBanner() {
|
|
||||||
if (!(this instanceof DefaultBanner)) return new DefaultBanner();
|
|
||||||
this.banner = null;
|
|
||||||
this.captureInfo = null;
|
|
||||||
this.last_state = {};
|
|
||||||
this.state = null;
|
|
||||||
this.title = '';
|
|
||||||
this.bannerUrlSet = false;
|
|
||||||
this.onMessage = this.onMessage.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Functions required to be exposed by all banners
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @desc Initialize (display) the banner
|
|
||||||
*/
|
|
||||||
DefaultBanner.prototype.init = function() {
|
|
||||||
this.createBanner('_wb_frame_top_banner');
|
|
||||||
|
|
||||||
if (window.wbinfo) {
|
|
||||||
this.set_banner(
|
|
||||||
window.wbinfo.url,
|
|
||||||
window.wbinfo.timestamp,
|
|
||||||
window.wbinfo.is_live,
|
|
||||||
window.wbinfo.is_framed ? '' : document.title
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @desc Called by ContentFrame to detect if the banner is still showing
|
|
||||||
* that the page is loading
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
DefaultBanner.prototype.stillIndicatesLoading = function() {
|
|
||||||
return !this.bannerUrlSet;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} url - The URL of the replayed page
|
|
||||||
* @param {?string} ts - The timestamp of the replayed page.
|
|
||||||
* If we are in live mode this is undefined/empty string
|
|
||||||
* @param {boolean} is_live - A bool indicating if we are operating in live mode
|
|
||||||
*/
|
|
||||||
DefaultBanner.prototype.updateCaptureInfo = function(url, ts, is_live) {
|
|
||||||
if (is_live && !ts) {
|
|
||||||
ts = new Date().toISOString().replace(/[-T:.Z]/g, '');
|
|
||||||
}
|
|
||||||
this.set_banner(url, ts, is_live, null);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @desc Called by ContentFrame when a message is received from the replay iframe
|
|
||||||
* @param {MessageEvent} event - The message event containing the message received
|
|
||||||
* from the replayed page
|
|
||||||
*/
|
|
||||||
DefaultBanner.prototype.onMessage = function(event) {
|
|
||||||
var type = event.data.wb_type;
|
|
||||||
|
|
||||||
if (type === 'load' || type === 'replace-url') {
|
|
||||||
this.state = event.data;
|
|
||||||
this.last_state = this.state;
|
|
||||||
this.title = event.data.title || this.title;
|
|
||||||
} else if (type === 'title') {
|
|
||||||
this.state = this.last_state;
|
|
||||||
this.title = event.data.title;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// favicon update
|
|
||||||
if (type === 'load') {
|
|
||||||
var head = document.querySelector('head');
|
|
||||||
var oldLink = document.querySelectorAll("link[rel*='icon']");
|
|
||||||
var i = 0;
|
|
||||||
for (; i < oldLink.length; i++) {
|
|
||||||
head.removeChild(oldLink[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.icons) {
|
|
||||||
for (i = 0; i < this.state.icons.length; i++) {
|
|
||||||
var icon = this.state.icons[i];
|
|
||||||
var link = document.createElement('link');
|
|
||||||
link.rel = icon.rel;
|
|
||||||
link.href = icon.href;
|
|
||||||
head.appendChild(link);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set_banner(
|
|
||||||
this.state.url,
|
|
||||||
this.state.ts,
|
|
||||||
this.state.is_live,
|
|
||||||
this.title
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Functions internal to the default banner
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @desc Navigate to different language, if available
|
|
||||||
*/
|
|
||||||
|
|
||||||
DefaultBanner.prototype.changeLanguage = function(lang, evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
var path = window.location.href;
|
|
||||||
if (path.indexOf(window.banner_info.prefix) == 0) {
|
|
||||||
path = path.substring(window.banner_info.prefix.length);
|
|
||||||
if (window.banner_info.locale_prefixes && window.banner_info.locale_prefixes[lang]) {
|
|
||||||
window.location.pathname = window.banner_info.locale_prefixes[lang] + path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @desc Creates the underlying HTML elements comprising the banner
|
|
||||||
* @param {string} bid - The id for the banner
|
|
||||||
*/
|
|
||||||
DefaultBanner.prototype.createBanner = function(bid) {
|
|
||||||
this.header = document.createElement('header');
|
|
||||||
this.header.setAttribute('role', 'banner');
|
|
||||||
this.nav = document.createElement('nav');
|
|
||||||
|
|
||||||
this.banner = document.createElement('wb_div', true);
|
|
||||||
this.banner.setAttribute('id', bid);
|
|
||||||
this.banner.setAttribute('lang', 'en');
|
|
||||||
|
|
||||||
if (window.banner_info.logoImg) {
|
|
||||||
var logo = document.createElement("a");
|
|
||||||
logo.setAttribute("href", "/" + (window.banner_info.curr_locale ? window.banner_info.curr_locale + "/" : ""));
|
|
||||||
logo.setAttribute("class", "_wb_linked_logo");
|
|
||||||
|
|
||||||
var logoContents = "";
|
|
||||||
var logoUrl = window.banner_info.staticPrefix + "/" + window.banner_info.logoImg;
|
|
||||||
logoContents += "<img src='" + logoUrl + "' alt='" + window.banner_info.logoAlt + "'>";
|
|
||||||
logoContents += "<img src='" + logoUrl + "' class='_wb_mobile' alt='" + window.banner_info.logoAlt + "'>";
|
|
||||||
|
|
||||||
logo.innerHTML = logoContents;
|
|
||||||
this.banner.appendChild(logo);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.captureInfo = document.createElement("span");
|
|
||||||
this.captureInfo.setAttribute("id", "_wb_capture_info");
|
|
||||||
this.captureInfo.innerHTML = window.banner_info.loadingLabel;
|
|
||||||
this.banner.appendChild(this.captureInfo);
|
|
||||||
|
|
||||||
var ancillaryLinks = document.createElement("div");
|
|
||||||
ancillaryLinks.setAttribute("id", "_wb_ancillary_links");
|
|
||||||
|
|
||||||
var calendarImg = window.banner_info.calendarImg || window.banner_info.staticPrefix + "/calendar.svg";
|
|
||||||
|
|
||||||
var calendarLink = document.createElement("a");
|
|
||||||
calendarLink.setAttribute("id", "calendarLink");
|
|
||||||
calendarLink.setAttribute("href", "#");
|
|
||||||
calendarLink.innerHTML = "<img src='" + calendarImg + "' alt='" + window.banner_info.calendarAlt + "'><span class='_wb_no-mobile'> " +window.banner_info.calendarLabel + "</span>";
|
|
||||||
ancillaryLinks.appendChild(calendarLink);
|
|
||||||
this.calendarLink = calendarLink;
|
|
||||||
|
|
||||||
if (typeof window.banner_info.locales !== "undefined" && window.banner_info.locales.length > 1) {
|
|
||||||
var locales = window.banner_info.locales;
|
|
||||||
var languages = document.createElement("div");
|
|
||||||
|
|
||||||
var label = document.createElement("span");
|
|
||||||
label.setAttribute("class", "_wb_no-mobile");
|
|
||||||
label.appendChild(document.createTextNode(window.banner_info.choiceLabel + " "));
|
|
||||||
languages.appendChild(label);
|
|
||||||
|
|
||||||
for(var i = 0; i < locales.length; i++) {
|
|
||||||
var locale = locales[i];
|
|
||||||
var langLink = document.createElement("a");
|
|
||||||
langLink.setAttribute("href", "#");
|
|
||||||
langLink.addEventListener("click", this.changeLanguage.bind(this, locale));
|
|
||||||
langLink.appendChild(document.createTextNode(locale));
|
|
||||||
|
|
||||||
languages.appendChild(langLink);
|
|
||||||
if (i !== locales.length - 1) {
|
|
||||||
languages.appendChild(document.createTextNode(" / "));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ancillaryLinks.appendChild(languages);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.banner.appendChild(ancillaryLinks);
|
|
||||||
this.nav.appendChild(this.banner);
|
|
||||||
this.header.appendChild(this.nav);
|
|
||||||
document.body.insertBefore(this.header, document.body.firstChild);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @desc Converts a timestamp to a date string. If is_gmt is truthy then
|
|
||||||
* the returned data string will be the results of date.toGMTString otherwise
|
|
||||||
* its date.toLocaleString()
|
|
||||||
* @param {?string} ts - The timestamp to receive the correct date string for
|
|
||||||
* @param {boolean} is_gmt - Is the returned date string to be in GMT time
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
DefaultBanner.prototype.ts_to_date = function(ts, is_gmt) {
|
|
||||||
if (!ts) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ts.length < 14) {
|
|
||||||
ts += '00000000000000'.substr(ts.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
var datestr =
|
|
||||||
ts.substring(0, 4) +
|
|
||||||
'-' +
|
|
||||||
ts.substring(4, 6) +
|
|
||||||
'-' +
|
|
||||||
ts.substring(6, 8) +
|
|
||||||
'T' +
|
|
||||||
ts.substring(8, 10) +
|
|
||||||
':' +
|
|
||||||
ts.substring(10, 12) +
|
|
||||||
':' +
|
|
||||||
ts.substring(12, 14) +
|
|
||||||
'-00:00';
|
|
||||||
|
|
||||||
var date = new Date(datestr);
|
|
||||||
|
|
||||||
if (is_gmt) {
|
|
||||||
return date.toGMTString();
|
|
||||||
} else {
|
|
||||||
return date.toLocaleString(window.banner_info.locale);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @desc Updates the contents displayed by the banner
|
|
||||||
* @param {?string} url - The URL of the replayed page to be displayed in the banner
|
|
||||||
* @param {?string} ts - A timestamp to be displayed in the banner
|
|
||||||
* @param {boolean} is_live - Are we in live mode
|
|
||||||
* @param {?string} title - The title of the replayed page to be displayed in the banner
|
|
||||||
*/
|
|
||||||
DefaultBanner.prototype.set_banner = function(url, ts, is_live, title) {
|
|
||||||
var capture_str;
|
|
||||||
var title_str;
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
this.captureInfo.innerHTML = window.banner_info.loadingLabel;
|
|
||||||
this.bannerUrlSet = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ts) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (title) {
|
|
||||||
capture_str = title;
|
|
||||||
} else {
|
|
||||||
capture_str = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
title_str = capture_str;
|
|
||||||
|
|
||||||
capture_str = "<b id='title_or_url' title='" + capture_str + "'>" + capture_str + "</b>";
|
|
||||||
|
|
||||||
capture_str += "<span class='_wb_capture_date'>";
|
|
||||||
|
|
||||||
if (is_live) {
|
|
||||||
title_str = window.banner_info.liveMsg + " " + title_str;
|
|
||||||
capture_str += "<b>" + window.banner_info.liveMsg + " </b>";
|
|
||||||
}
|
|
||||||
|
|
||||||
capture_str += this.ts_to_date(ts, window.banner_info.is_gmt);
|
|
||||||
capture_str += "</span>";
|
|
||||||
|
|
||||||
this.calendarLink.setAttribute("href", window.banner_info.prefix + "*/" + url);
|
|
||||||
this.calendarLink.style.display = is_live ? "none" : "";
|
|
||||||
|
|
||||||
this.captureInfo.innerHTML = capture_str;
|
|
||||||
|
|
||||||
window.document.title = title_str;
|
|
||||||
|
|
||||||
this.bannerUrlSet = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// all banners will expose themselves by adding themselves as WBBanner on window
|
|
||||||
window.WBBanner = new DefaultBanner();
|
|
||||||
|
|
||||||
// if wbinfo.url is set and not-framed, init banner in content frame
|
|
||||||
if (window.wbinfo && window.wbinfo.url && !window.wbinfo.is_framed) {
|
|
||||||
if (document.readyState === "loading") {
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
|
||||||
window.WBBanner.init();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
window.WBBanner.init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
1086
pywb/static/query.js
1086
pywb/static/query.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,11 +1,4 @@
|
|||||||
body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
#wb_iframe_div, #replay_iframe {
|
#wb_iframe_div, #replay_iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,23 +22,11 @@ window.banner_info = {
|
|||||||
logoImg: "{{ ui.logo }}"
|
logoImg: "{{ ui.logo }}"
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
{% if is_framed or not ui.vue_timeline_banner %}
|
|
||||||
<!-- default banner, create through js -->
|
|
||||||
<link rel='stylesheet' href='{{ static_prefix }}/default_banner.css'/>
|
|
||||||
<script src='{{ static_prefix }}/default_banner.js'> </script>
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{% if ui.vue_timeline_banner %}
|
|
||||||
<script src="{{ static_prefix }}/loading-spinner/loading-spinner.js"></script>
|
<script src="{{ static_prefix }}/loading-spinner/loading-spinner.js"></script>
|
||||||
<link rel='stylesheet' href='{{ static_prefix }}/vue_banner.css'/>
|
|
||||||
<script src="{{ static_prefix }}/vue/vueui.js"></script>
|
<script src="{{ static_prefix }}/vue/vueui.js"></script>
|
||||||
{% endif %}
|
<link rel="stylesheet" href='{{ static_prefix }}/vue_banner.css'/>
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
{% include 'bootstrap_jquery.html' ignore missing %}
|
||||||
|
|
||||||
{% endautoescape %}
|
{% endautoescape %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -6,13 +6,7 @@
|
|||||||
|
|
||||||
<title>{% block title %}{% endblock %}</title>
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
|
||||||
<!-- jquery and bootstrap dependencies query view -->
|
{% include 'bootstrap_jquery.html' ignore missing %}
|
||||||
<link rel="stylesheet" href="{{ static_prefix }}/css/bootstrap.min.css"/>
|
|
||||||
<link rel="stylesheet" href="{{ static_prefix }}/css/font-awesome.min.css">
|
|
||||||
<link rel="stylesheet" href="{{ static_prefix }}/css/base.css">
|
|
||||||
|
|
||||||
<script src="{{ static_prefix }}/js/jquery-latest.min.js"></script>
|
|
||||||
<script src="{{ static_prefix }}/js/bootstrap.min.js"></script>
|
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{% include 'head.html' ignore missing %}
|
{% include 'head.html' ignore missing %}
|
||||||
|
6
pywb/templates/bootstrap_jquery.html
Normal file
6
pywb/templates/bootstrap_jquery.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<link rel="stylesheet" href="{{ static_prefix }}/css/bootstrap.min.css"/>
|
||||||
|
<link rel="stylesheet" href="{{ static_prefix }}/css/font-awesome.min.css">
|
||||||
|
<link rel="stylesheet" href="{{ static_prefix }}/css/base.css">
|
||||||
|
|
||||||
|
<script src="{{ static_prefix }}/js/jquery-latest.min.js"></script>
|
||||||
|
<script src="{{ static_prefix }}/js/bootstrap.min.js"></script>
|
@ -18,22 +18,16 @@ html, body
|
|||||||
|
|
||||||
{{ banner_html }}
|
{{ banner_html }}
|
||||||
|
|
||||||
{% if ui.vue_timeline_banner %}
|
|
||||||
|
|
||||||
{% include 'vue_loc.html' %}
|
{% include 'vue_loc.html' %}
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body style="margin: 0px; padding: 0px;">
|
<body style="margin: 0px; padding: 0px;">
|
||||||
|
|
||||||
{% if ui.vue_timeline_banner %}
|
|
||||||
<div id="app" style="width: 100%; height: 200px"></div>
|
<div id="app" style="width: 100%; height: 200px"></div>
|
||||||
<script>
|
<script>
|
||||||
VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ wb_prefix }}", "{{ timestamp }}", "{{ ui.logo }}", "{{ env.pywb_lang | default('en') }}",
|
VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ wb_prefix }}", "{{ timestamp }}", "{{ ui.logo }}", "{{ ui.navbar_background_hex | default('f8f9fa') }}", "{{ ui.navbar_color_hex | default('212529') }}", "{{ ui.navbar_light_buttons }}", "{{ env.pywb_lang | default('en') }}",
|
||||||
allLocales, i18nStrings);
|
allLocales, i18nStrings);
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div id="wb_iframe_div">
|
<div id="wb_iframe_div">
|
||||||
<iframe id="replay_iframe" frameborder="0" seamless="seamless" scrolling="yes" class="wb_iframe" allow="autoplay; fullscreen"></iframe>
|
<iframe id="replay_iframe" frameborder="0" seamless="seamless" scrolling="yes" class="wb_iframe" allow="autoplay; fullscreen"></iframe>
|
||||||
|
@ -7,40 +7,17 @@
|
|||||||
{% block head %}
|
{% block head %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
|
||||||
{% if not ui.vue_calendar_ui %}
|
|
||||||
<link rel="stylesheet" href="{{ static_prefix }}/css/query.css">
|
|
||||||
<script src="{{ static_prefix }}/js/url-polyfill.min.js"></script>
|
|
||||||
<script src="{{ static_prefix }}/query.js"></script>
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
<script src="{{ static_prefix }}/loading-spinner/loading-spinner.js"></script>
|
<script src="{{ static_prefix }}/loading-spinner/loading-spinner.js"></script>
|
||||||
<script src="{{ static_prefix }}/vue/vueui.js"></script>
|
<script src="{{ static_prefix }}/vue/vueui.js"></script>
|
||||||
|
|
||||||
{% include 'vue_loc.html' %}
|
{% include 'vue_loc.html' %}
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
{% if not ui.vue_calendar_ui %}
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<h4 class="display-4 text-center text-sm-left p-0">{{ _('Search Results') }}</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container">
|
|
||||||
<div class="row justify-content-center text-center text-sm-left mt-1" id="display-query-type-info"></div>
|
|
||||||
</div>
|
|
||||||
<div class="container mt-3 q-display" id="captures"></div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
{% if ui.vue_calendar_ui %}
|
|
||||||
<div id="app" style="width: 100%; height: 100%"></div>
|
<div id="app" style="width: 100%; height: 100%"></div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var text = {
|
var text = {
|
||||||
@ -75,17 +52,9 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
{% if not ui.vue_calendar_ui %}
|
VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ prefix }}", undefined, "{{ ui.logo }}", "{{ ui.navbar_background_hex | default('f8f9fa') }}", "{{ ui.navbar_color_hex | default('212529') }}", "{{ ui.navbar_light_buttons }}", "{{ env.pywb_lang | default('en') }}",
|
||||||
|
|
||||||
var renderCal = new RenderCalendar({ prefix: "{{ prefix }}", staticPrefix: "{{ static_prefix }}", text: text });
|
|
||||||
renderCal.init();
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ prefix }}", undefined, "{{ ui.logo }}", "{{ env.pywb_lang | default('en') }}",
|
|
||||||
allLocales, i18nStrings);
|
allLocales, i18nStrings);
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
__version__ = '2.7.0b1'
|
__version__ = '2.7.0b2'
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print(__version__)
|
print(__version__)
|
||||||
|
@ -1,36 +1,126 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app" :class="{expanded: showTimelineView}" data-app="webrecorder-replay-app">
|
<div class="app" :class="{expanded: showTimelineView || showFullView }" data-app="webrecorder-replay-app">
|
||||||
<div class="banner">
|
<!-- Top navbar -->
|
||||||
<div class="line">
|
<nav
|
||||||
<div class="logo"><a href="/"><img :src="config.logoImg"/></a></div>
|
class="navbar navbar-light navbar-expand-lg fixed-top top-navbar justify-content-center"
|
||||||
<div class="timeline-wrap">
|
:style="navbarStyle">
|
||||||
<div class="line">
|
<a class="navbar-brand flex-grow-1 my-1" href="/">
|
||||||
<div class="breadcrumbs-wrap">
|
<img :src="config.logoImg" id="logo-img" alt="_('pywb logo')">
|
||||||
<TimelineBreadcrumbs
|
</a>
|
||||||
v-if="currentPeriod && showTimelineView"
|
<div class="flex-grow-1 d-flex" id="searchdiv">
|
||||||
:period="currentPeriod"
|
<form class="form-inline my-2 my-md-0 mx-lg-auto" @submit="gotoUrl">
|
||||||
@goto-period="gotoPeriod"
|
<input id="theurl" type="text" :value="config.url" height="31"></input>
|
||||||
></TimelineBreadcrumbs>
|
</form>
|
||||||
<span v-if="!showTimelineView" v-html="' '"></span><!-- for spacing -->
|
</div>
|
||||||
|
<button
|
||||||
|
class="navbar-toggler btn btn-sm"
|
||||||
|
id="collapse-button"
|
||||||
|
type="button"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-target="#navbarCollapse"
|
||||||
|
aria-controls="navbarCollapse"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="_('Toggle navigation')">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse ml-auto" id="navbarCollapse">
|
||||||
|
<ul class="navbar-nav ml-3" id="toggles">
|
||||||
|
<li class="nav-item">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="{active: showFullView, 'btn-outline-light': lightButtons, 'btn-outline-dark': !lightButtons}"
|
||||||
|
:title="_('Previous capture')"
|
||||||
|
v-if="previousSnapshot"
|
||||||
|
@click="gotoPreviousSnapshot">
|
||||||
|
<i class="fas fa-arrow-left" :title="_('Previous capture')"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="{active: showFullView, 'btn-outline-light': lightButtons, 'btn-outline-dark': !lightButtons}"
|
||||||
|
:title="_('Next capture')"
|
||||||
|
v-if="nextSnapshot"
|
||||||
|
@click="gotoNextSnapshot">
|
||||||
|
<i class="fas fa-arrow-right" :title="_('Next capture')"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item active">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="{active: showFullView, 'btn-outline-light': lightButtons, 'btn-outline-dark': !lightButtons}"
|
||||||
|
:aria-pressed="(showFullView ? true : false)"
|
||||||
|
@click="showFullView = !showFullView"
|
||||||
|
:title="(showFullView ? _('Hide calendar') : _('Show calendar'))">
|
||||||
|
<i class="far fa-calendar-alt" :title="_('Calendar')"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="{active: showTimelineView, 'btn-outline-light': lightButtons, 'btn-outline-dark': !lightButtons}"
|
||||||
|
:aria-pressed="showTimelineView"
|
||||||
|
@click="showTimelineView = !showTimelineView"
|
||||||
|
:title="(showTimelineView ? _('Hide timeline') : _('Show timeline'))">
|
||||||
|
<i class="far fa-chart-bar" :title="_('Timeline')"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item dropdown" v-if="localesAreSet">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm dropdown-toggle"
|
||||||
|
:class="{'btn-outline-light': lightButtons, 'btn-outline-dark': !lightButtons}"
|
||||||
|
type="button"
|
||||||
|
id="locale-dropdown"
|
||||||
|
data-toggle="dropdown"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="false"
|
||||||
|
:title="_('Select language')">
|
||||||
|
<i class="fas fa-globe-africa" :title="_('Language')"></i>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="locale-dropdown">
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
v-for="(locPath, key) in config.allLocales"
|
||||||
|
:key="key"
|
||||||
|
:href="locPath + (currentSnapshot ? currentSnapshot.id : '*') + '/' + config.url">
|
||||||
|
{{ key }}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="toggles">
|
|
||||||
<span class="toggle" :class="{expanded: showFullView}" @click="showFullView = !showFullView" :title="(showTimelineView ? _('show calendar'):_('hide calendar'))">
|
|
||||||
<img src="/static/calendar-icon.png" />
|
|
||||||
</span>
|
|
||||||
<span class="toggle" :class="{expanded: showTimelineView}" @click="showTimelineView = !showTimelineView" :title="(showTimelineView ? _('show timeline'):_('hide timeline'))">
|
|
||||||
<img src="/static/timeline-icon.png" />
|
|
||||||
</span>
|
|
||||||
<ul class="lang-select" role="listbox" :aria-activedescendant="config.locale"
|
|
||||||
:aria-labelledby="_('Language select')">
|
|
||||||
<li v-for="(locPath, key) in config.allLocales" role="option" :id="key">
|
|
||||||
<a :href="locPath + (currentSnapshot ? currentSnapshot.id : '*') + '/' + config.url">{{ key }}</a>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Capture title and date -->
|
||||||
|
<nav
|
||||||
|
class="navbar navbar-light justify-content-center title-nav fixed-top"
|
||||||
|
id="second-navbar"
|
||||||
|
:style="navbarStyle">
|
||||||
|
<span class="hidden" v-if="!currentSnapshot"> </span>
|
||||||
|
<span v-if="currentSnapshot">
|
||||||
|
<span class="strong mr-1">
|
||||||
|
{{_('Current Capture')}}:
|
||||||
|
<span class="ml-1" v-if="config.title">
|
||||||
|
{{ config.title }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="mr-1" v-if="config.title">,</span>
|
||||||
|
{{currentSnapshot.getTimeDateFormatted()}}
|
||||||
|
</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Timeline -->
|
||||||
|
<div class="card border-top-0 border-left-0 border-right-0 timeline-wrap">
|
||||||
|
<div class="card-body" v-if="currentPeriod && showTimelineView">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-12">
|
||||||
|
<TimelineBreadcrumbs
|
||||||
|
:period="currentPeriod"
|
||||||
|
@goto-period="gotoPeriod"
|
||||||
|
></TimelineBreadcrumbs>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col col-12 mt-2">
|
||||||
<Timeline
|
<Timeline
|
||||||
v-if="currentPeriod && showTimelineView"
|
|
||||||
:period="currentPeriod"
|
:period="currentPeriod"
|
||||||
:highlight="timelineHighlight"
|
:highlight="timelineHighlight"
|
||||||
:current-snapshot="currentSnapshot"
|
:current-snapshot="currentSnapshot"
|
||||||
@ -40,21 +130,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="snapshot-title">
|
|
||||||
<form @submit="gotoUrl">
|
|
||||||
<input id="theurl" type="text" :value="config.url"></input>
|
|
||||||
</form>
|
|
||||||
<div v-if="currentSnapshot && !showFullView">
|
|
||||||
<span v-if="config.title">{{ config.title }}</span>
|
|
||||||
{{_('Current Capture')}}: {{currentSnapshot.getTimeDateFormatted()}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<CalendarYear v-if="showFullView && currentPeriod && currentPeriod.children.length"
|
<!-- Calendar -->
|
||||||
|
<div class="card border-0" v-if="currentPeriod && showFullView && currentPeriod.children.length">
|
||||||
|
<div class="card-body">
|
||||||
|
<CalendarYear
|
||||||
:period="currentPeriod"
|
:period="currentPeriod"
|
||||||
:current-snapshot="currentSnapshot"
|
:current-snapshot="currentSnapshot"
|
||||||
@goto-period="gotoPeriod">
|
@goto-period="gotoPeriod">
|
||||||
</CalendarYear>
|
</CalendarYear>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -73,13 +162,15 @@ export default {
|
|||||||
snapshots: [],
|
snapshots: [],
|
||||||
currentPeriod: null,
|
currentPeriod: null,
|
||||||
currentSnapshot: null,
|
currentSnapshot: null,
|
||||||
|
currentSnapshotIndex: null,
|
||||||
msgs: [],
|
msgs: [],
|
||||||
showFullView: true,
|
showFullView: true,
|
||||||
showTimelineView: true,
|
showTimelineView: true,
|
||||||
maxTimelineZoomLevel: PywbPeriod.Type.day,
|
maxTimelineZoomLevel: PywbPeriod.Type.day,
|
||||||
config: {
|
config: {
|
||||||
title: "",
|
title: "",
|
||||||
initialView: {}
|
initialView: {},
|
||||||
|
allLocales: {}
|
||||||
},
|
},
|
||||||
timelineHighlight: false,
|
timelineHighlight: false,
|
||||||
locales: [],
|
locales: [],
|
||||||
@ -87,11 +178,47 @@ export default {
|
|||||||
},
|
},
|
||||||
components: {Timeline, TimelineBreadcrumbs, CalendarYear},
|
components: {Timeline, TimelineBreadcrumbs, CalendarYear},
|
||||||
mounted: function() {
|
mounted: function() {
|
||||||
|
// add empty unload event listener to make this page bfcache ineligible.
|
||||||
|
// bfcache otherwises prevent the query template from reloading as expected
|
||||||
|
// when the user navigates there via browser back/forward buttons
|
||||||
|
addEventListener('unload', (event) => { });
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
sessionStorageUrlKey() {
|
sessionStorageUrlKey() {
|
||||||
// remove http(s), www and trailing slash
|
// remove http(s), www and trailing slash
|
||||||
return 'zoom__' + this.config.url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, '');
|
return 'zoom__' + this.config.url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, '');
|
||||||
|
},
|
||||||
|
localesAreSet() {
|
||||||
|
return Object.entries(this.config.allLocales).length > 0;
|
||||||
|
},
|
||||||
|
navbarStyle() {
|
||||||
|
return {
|
||||||
|
'--navbar-background': `#${this.config.navbarBackground}`,
|
||||||
|
'--navbar-color': `#${this.config.navbarColor}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lightButtons() {
|
||||||
|
return !!this.config.navbarLightButtons;
|
||||||
|
},
|
||||||
|
previousSnapshot() {
|
||||||
|
if (!this.currentSnapshotIndex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.currentSnapshotIndex > 0) {
|
||||||
|
return this.snapshots[this.currentSnapshotIndex - 1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
nextSnapshot() {
|
||||||
|
if (this.currentSnapshotIndex == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(this.currentSnapshotIndex >= 0)
|
||||||
|
&& (this.currentSnapshotIndex !== this.snapshots.length - 1)) {
|
||||||
|
return this.snapshots[this.currentSnapshotIndex + 1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -124,10 +251,13 @@ export default {
|
|||||||
gotoSnapshot(snapshot, fromPeriod, reloadIFrame=false) {
|
gotoSnapshot(snapshot, fromPeriod, reloadIFrame=false) {
|
||||||
this.currentSnapshot = snapshot;
|
this.currentSnapshot = snapshot;
|
||||||
|
|
||||||
|
const isCurrentSnapshot = (snapshotInArray) => snapshotInArray.id == snapshot.id && snapshotInArray.url == snapshot.url;
|
||||||
|
this.currentSnapshotIndex = this.snapshots.findIndex(isCurrentSnapshot);
|
||||||
|
|
||||||
// if the current period doesn't match the current snapshot, update it
|
// if the current period doesn't match the current snapshot, update it
|
||||||
if (fromPeriod && !this.currentPeriod.contains(fromPeriod)) {
|
if (!this.currentPeriod || (fromPeriod && !this.currentPeriod.contains(fromPeriod))) {
|
||||||
const fromPeriodAtMaxZoomLevel = fromPeriod.get(this.maxTimelineZoomLevel);
|
const fromPeriodAtMaxZoomLevel = fromPeriod.get(this.maxTimelineZoomLevel);
|
||||||
if (fromPeriodAtMaxZoomLevel !== this.currentPeriod) {
|
if (!this.currentPeriod || fromPeriodAtMaxZoomLevel !== this.currentPeriod) {
|
||||||
this.currentPeriod = fromPeriodAtMaxZoomLevel;
|
this.currentPeriod = fromPeriodAtMaxZoomLevel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,7 +268,15 @@ export default {
|
|||||||
if (reloadIFrame !== false) {
|
if (reloadIFrame !== false) {
|
||||||
this.$emit("show-snapshot", snapshot);
|
this.$emit("show-snapshot", snapshot);
|
||||||
}
|
}
|
||||||
this.showFullView = false;
|
this.hideBannerUtilities();
|
||||||
|
},
|
||||||
|
gotoPreviousSnapshot() {
|
||||||
|
let periodToChangeTo = this.currentPeriod.findByFullId(this.previousSnapshot.getFullId());
|
||||||
|
this.gotoPeriod(periodToChangeTo, false /* onlyZoomToPeriod */);
|
||||||
|
},
|
||||||
|
gotoNextSnapshot() {
|
||||||
|
let periodToChangeTo = this.currentPeriod.findByFullId(this.nextSnapshot.getFullId());
|
||||||
|
this.gotoPeriod(periodToChangeTo, false /* onlyZoomToPeriod */);
|
||||||
},
|
},
|
||||||
gotoUrl(event) {
|
gotoUrl(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -176,130 +314,140 @@ export default {
|
|||||||
// convert to snapshot object to support proper rendering of time/date
|
// convert to snapshot object to support proper rendering of time/date
|
||||||
const snapshot = new PywbSnapshot(view, 0);
|
const snapshot = new PywbSnapshot(view, 0);
|
||||||
|
|
||||||
// set config current URL and title
|
|
||||||
this.config.url = view.url;
|
this.config.url = view.url;
|
||||||
this.config.title = view.title;
|
|
||||||
|
|
||||||
this.gotoSnapshot(snapshot);
|
let periodToChangeTo = this.currentPeriod.findByFullId(snapshot.getFullId());
|
||||||
|
this.gotoPeriod(periodToChangeTo, false /* onlyZoomToPeriod */);
|
||||||
|
},
|
||||||
|
setTimelineView() {
|
||||||
|
this.showTimelineView = !this.showTimelineView;
|
||||||
|
if (this.showTimelineView === true) {
|
||||||
|
this.showFullView = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideBannerUtilities() {
|
||||||
|
this.showFullView = false;
|
||||||
|
this.showTimelineView = false;
|
||||||
|
},
|
||||||
|
updateTitle(title) {
|
||||||
|
this.config.title = title;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
body {
|
||||||
|
padding-top: 89px !important;
|
||||||
|
}
|
||||||
.app {
|
.app {
|
||||||
font-family: Calibri, Arial, sans-serif;
|
font-family: Calibri, Arial, sans-serif;
|
||||||
border-bottom: 1px solid lightcoral;
|
/*border-bottom: 1px solid lightcoral;*/
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.app.expanded {
|
.app.expanded {
|
||||||
height: 150px;
|
height: 130px;
|
||||||
}
|
}
|
||||||
.full-view {
|
.full-view {
|
||||||
/*position: fixed;*/
|
/*position: fixed;*/
|
||||||
/*top: 150px;*/
|
/*top: 150px;*/
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
.navbar {
|
||||||
|
background-color: var(--navbar-background);
|
||||||
|
color: var(--navbar-color);
|
||||||
|
}
|
||||||
|
.top-navbar {
|
||||||
|
z-index: 90;
|
||||||
|
padding: 2px 16px 0 16px;
|
||||||
|
}
|
||||||
|
.top-navbar span.navbar-toggler-icon {
|
||||||
|
margin: .25rem !important;
|
||||||
|
}
|
||||||
|
#logo-img {
|
||||||
|
max-height: 40px;
|
||||||
|
}
|
||||||
|
.title-nav {
|
||||||
|
margin-top: 50px;
|
||||||
|
z-index: 80;
|
||||||
|
}
|
||||||
|
#secondNavbar {
|
||||||
|
height: 24px !important;
|
||||||
|
}
|
||||||
|
#navbarCollapse {
|
||||||
|
justify-content: right;
|
||||||
|
}
|
||||||
|
#navbarCollapse ul#toggles {
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
#navbarCollapse:not(.show) ul#toggles li:not(:first-child) {
|
||||||
|
margin-left: .25rem;
|
||||||
|
}
|
||||||
|
#navbarCollapse.show {
|
||||||
|
padding-bottom: 1em;
|
||||||
|
}
|
||||||
|
#navbarCollapse.show ul#toggles li {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
#navbarCollapse.show ul#toggles li {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
.iframe iframe {
|
.iframe iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
}
|
}
|
||||||
.logo {
|
#searchdiv {
|
||||||
margin-right: 30px;
|
height: 31px;
|
||||||
width: 180px;
|
|
||||||
}
|
}
|
||||||
.banner {
|
#theurl {
|
||||||
width: 100%;
|
width: 250px;
|
||||||
max-width: 1200px; /* limit width */
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
.banner .line {
|
@media (min-width: 576px) {
|
||||||
display: flex;
|
#theurl {
|
||||||
justify-content: flex-start;
|
width: 350px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.banner .logo {
|
@media (min-width: 768px) {
|
||||||
flex-shrink: initial;
|
#theurl {
|
||||||
/* for any content/image inside the logo container */
|
width: 500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
#theurl {
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
#theurl {
|
||||||
|
width: 900px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#toggles {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.breadcrumb-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.banner .logo img {
|
div.timeline-wrap div.card {
|
||||||
flex-shrink: 1;
|
margin-top: 55px;
|
||||||
}
|
}
|
||||||
|
div.timeline-wrap div.card-body {
|
||||||
.banner .timeline-wrap {
|
display: flex;
|
||||||
flex-grow: 2;
|
align-items: center;
|
||||||
overflow-x: hidden;
|
justify-content: center;
|
||||||
text-align: left;
|
|
||||||
margin: 0 25px;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
div.timeline-wrap div.card-body div.row {
|
||||||
.timeline-wrap .line .breadcrumbs-wrap {
|
width: 100%;
|
||||||
display: inline-block;
|
align-items: center;
|
||||||
flex-grow: 1;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.timeline-wrap .line .toggles {
|
.strong {
|
||||||
display: inline-block;
|
|
||||||
flex-shrink: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggles > .toggle {
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 0 4px;
|
|
||||||
height: 100%;
|
|
||||||
cursor: zoom-in;
|
|
||||||
}
|
|
||||||
.toggles > .toggle > img {
|
|
||||||
height: 18px;
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
.toggles .toggle:hover {
|
|
||||||
background-color: #eeeeee;
|
|
||||||
}
|
|
||||||
.toggles .toggle.expanded {
|
|
||||||
background-color: #eeeeee;
|
|
||||||
cursor: zoom-out;
|
|
||||||
}
|
|
||||||
.snapshot-title {
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 16px;
|
|
||||||
}
|
}
|
||||||
#theurl {
|
.hidden {
|
||||||
width: 400px;
|
color: var(--navbar-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.lang-select {
|
|
||||||
display: inline-block;
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 24px 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.lang-select li {
|
|
||||||
display: inline-block;
|
|
||||||
padding-left: 6px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: smaller;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.lang-select li:not(:last-child):after {
|
|
||||||
content: ' / ';
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.lang-select a:link,
|
|
||||||
ul.lang-select a:visited,
|
|
||||||
ul.lang-select a:active {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.lang-select a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -9,14 +9,13 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
.calendar-month:hover {
|
.calendar-month:hover {
|
||||||
background-color: #eeeeee;
|
background-color: #eeeeee;
|
||||||
border-radius: 10px;
|
|
||||||
}
|
}
|
||||||
.calendar-month.current {
|
.calendar-month.current {
|
||||||
background-color: #fff7ce;
|
background-color: #fff7ce;
|
||||||
border-radius: 5px;
|
|
||||||
}
|
}
|
||||||
.calendar-month.contains-current-snapshot {
|
.calendar-month.contains-current-snapshot {
|
||||||
border: solid 1px red;
|
border: solid 1px red;
|
||||||
@ -95,7 +94,7 @@
|
|||||||
<h3>{{getLongMonthName(month.id)}} <span v-if="month.snapshotCount">({{ month.snapshotCount }})</span></h3>
|
<h3>{{getLongMonthName(month.id)}} <span v-if="month.snapshotCount">({{ month.snapshotCount }})</span></h3>
|
||||||
<div v-if="month.snapshotCount">
|
<div v-if="month.snapshotCount">
|
||||||
<span v-for="(dayInitial) in dayInitials" class="day" :style="dayStyle">{{dayInitial}}</span><br/>
|
<span v-for="(dayInitial) in dayInitials" class="day" :style="dayStyle">{{dayInitial}}</span><br/>
|
||||||
<span v-for="(day,i) in days"><br v-if="i && i % 7===0"/><span class="day" :class="{empty: !day || !day.snapshotCount, 'contains-current-snapshot':dayContainsCurrentSnapshot(day)}" :style="dayStyle" @click="gotoDay(day, $event)"><template v-if="day"><span class="size" v-if="day.snapshotCount" :style="getDayCountCircleStyle(day.snapshotCount)"> </span><span class="day-id">{{day.id}}</span><span v-if="day.snapshotCount" class="count">{{ $root._(day.snapshotCount !== 1 ? '{count} captures':'{count} capture', {count: day.snapshotCount}) }}</span></template><template v-else v-html="' '"></template></span></span>
|
<span v-for="(day,i) in days"><br v-if="i && i % 7===0"/><span class="day" :class="{empty: !day || !day.snapshotCount, 'contains-current-snapshot':dayContainsCurrentSnapshot(day)}" :style="dayStyle" @click="gotoDay(day, $event)" @keyup.13="gotoDay(day, $event)"><template v-if="day"><span class="size" v-if="day.snapshotCount" :style="getDayCountCircleStyle(day.snapshotCount)" tabindex="0"> </span><span class="day-id">{{day.id}}</span><span v-if="day.snapshotCount" class="count">{{ $root._(day.snapshotCount !== 1 ? '{count} captures':'{count} capture', {count: day.snapshotCount}) }}</span></template><template v-else v-html="' '"></template></span></span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="empty">{{ _('no captures') }}</div>
|
<div v-else class="empty">{{ _('no captures') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,29 +1,22 @@
|
|||||||
<style>
|
|
||||||
.full-view {
|
|
||||||
position: fixed;
|
|
||||||
z-index: 10;
|
|
||||||
height: 80vh;
|
|
||||||
overflow: scroll;
|
|
||||||
width: 100%;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
.full-view .months {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.full-view h2 {
|
|
||||||
margin: 10px 0;
|
|
||||||
font-size: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="full-view">
|
<div class="full-view">
|
||||||
<h2>{{year.id}} ({{ $root._(year.snapshotCount !== 1 ? '{count} captures':'{count} capture', {count: year.snapshotCount}) }})</h2>
|
<h2>
|
||||||
|
<i
|
||||||
|
class="fas fa-arrow-left year-arrow"
|
||||||
|
@click="gotoPreviousYear"
|
||||||
|
@keyup.enter="gotoPreviousYear"
|
||||||
|
v-if="previousYear"
|
||||||
|
tabindex="0"></i>
|
||||||
|
<span class="mx-1">
|
||||||
|
{{year.id}} ({{ $root._(year.snapshotCount !== 1 ? '{count} captures':'{count} capture', {count: year.snapshotCount}) }})
|
||||||
|
</span>
|
||||||
|
<i
|
||||||
|
class="fas fa-arrow-right year-arrow"
|
||||||
|
@click="gotoNextYear"
|
||||||
|
@keyup.enter="gotoNextYear"
|
||||||
|
v-if="nextYear"
|
||||||
|
tabindex="0"></i>
|
||||||
|
</h2>
|
||||||
<div class="months">
|
<div class="months">
|
||||||
<CalendarMonth
|
<CalendarMonth
|
||||||
v-for="month in year.children"
|
v-for="month in year.children"
|
||||||
@ -36,7 +29,10 @@
|
|||||||
@show-day-timeline="setCurrentTimeline"
|
@show-day-timeline="setCurrentTimeline"
|
||||||
></CalendarMonth>
|
></CalendarMonth>
|
||||||
</div>
|
</div>
|
||||||
<Tooltip :position="currentTimelinePos" v-if="currentTimelinePeriod" ref="timelineLinearTooltip">
|
<Tooltip
|
||||||
|
:position="currentTimelinePos"
|
||||||
|
v-if="currentTimelinePeriod"
|
||||||
|
ref="timelineLinearTooltip">
|
||||||
<TimelineLinear
|
<TimelineLinear
|
||||||
:period="currentTimelinePeriod"
|
:period="currentTimelinePeriod"
|
||||||
:current-snapshot="containsCurrentSnapshot ? currentSnapshot : null"
|
:current-snapshot="containsCurrentSnapshot ? currentSnapshot : null"
|
||||||
@ -86,6 +82,17 @@ export default {
|
|||||||
}
|
}
|
||||||
return year;
|
return year;
|
||||||
},
|
},
|
||||||
|
currentYearIndex() {
|
||||||
|
if (this.year.parent) {
|
||||||
|
return this.year.parent.children.findIndex(year => year.fullId === this.year.fullId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
previousYear() {
|
||||||
|
return this.year.getPrevious();
|
||||||
|
},
|
||||||
|
nextYear() {
|
||||||
|
return this.year.getNext();
|
||||||
|
},
|
||||||
currentMonth() { // the month that the timeline period is in
|
currentMonth() { // the month that the timeline period is in
|
||||||
let month = null;
|
let month = null;
|
||||||
if (this.period.type === PywbPeriod.Type.month) {
|
if (this.period.type === PywbPeriod.Type.month) {
|
||||||
@ -101,6 +108,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
gotoPreviousYear() {
|
||||||
|
this.gotoPeriod(this.previousYear, true /* changeYearOnly */);
|
||||||
|
},
|
||||||
|
gotoNextYear() {
|
||||||
|
this.gotoPeriod(this.nextYear, true /* changeYearOnly */);
|
||||||
|
},
|
||||||
resetCurrentTimeline(event) {
|
resetCurrentTimeline(event) {
|
||||||
if (event && this.$refs.timelineLinearTooltip) {
|
if (event && this.$refs.timelineLinearTooltip) {
|
||||||
let el = event.target;
|
let el = event.target;
|
||||||
@ -122,12 +135,18 @@ export default {
|
|||||||
if (!day) {
|
if (!day) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (event.code === "Enter") {
|
||||||
|
let middleXPos = (window.innerWidth / 2) - 60;
|
||||||
|
this.currentTimelinePos = `${middleXPos},200`;
|
||||||
|
} else {
|
||||||
this.currentTimelinePos = `${event.x},${event.y}`;
|
this.currentTimelinePos = `${event.x},${event.y}`;
|
||||||
|
}
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
gotoPeriod(period) {
|
gotoPeriod(period, changeYearOnly=false) {
|
||||||
if (period.snapshot || period.snapshotPeriod) {
|
if (period.snapshot || period.snapshotPeriod || changeYearOnly) {
|
||||||
this.$emit('goto-period', period);
|
this.$emit('goto-period', period);
|
||||||
} else {
|
} else {
|
||||||
this.currentTimelinePeriod = period;
|
this.currentTimelinePeriod = period;
|
||||||
@ -138,3 +157,27 @@ export default {
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.full-view {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 10;
|
||||||
|
height: 80vh;
|
||||||
|
overflow: scroll;
|
||||||
|
width: 100%;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
.full-view .months {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.full-view h2 {
|
||||||
|
margin: 10px 0;
|
||||||
|
font-size: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.year-arrow:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -58,7 +58,7 @@ export default {
|
|||||||
}
|
}
|
||||||
.pywb-loading-spinner-mask {
|
.pywb-loading-spinner-mask {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 10px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
@ -16,7 +16,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-html="'◀'" class="arrow previous" :class="{disabled: isScrollZero && !previousPeriod}" @click="scrollPrev" @dblclick.stop.prevent></div>
|
<div v-html="'◀'"
|
||||||
|
class="arrow previous"
|
||||||
|
:class="{disabled: isScrollZero && !previousPeriod}"
|
||||||
|
@click="scrollPrev"
|
||||||
|
@keyup.enter="scrollPrev"
|
||||||
|
@dblclick.stop.prevent tabindex="0"></div>
|
||||||
<div class="scroll" ref="periodScroll" :class="{highlight: highlight}">
|
<div class="scroll" ref="periodScroll" :class="{highlight: highlight}">
|
||||||
<div class="periods" ref="periods">
|
<div class="periods" ref="periods">
|
||||||
<div v-for="subPeriod in period.children"
|
<div v-for="subPeriod in period.children"
|
||||||
@ -31,16 +36,20 @@
|
|||||||
:style="{height: getHistoLineHeight(histoPeriod.snapshotCount)}"
|
:style="{height: getHistoLineHeight(histoPeriod.snapshotCount)}"
|
||||||
:class="{'has-single-snapshot': histoPeriod.snapshotCount === 1, 'contains-current-snapshot': containsCurrentSnapshot(histoPeriod)}"
|
:class="{'has-single-snapshot': histoPeriod.snapshotCount === 1, 'contains-current-snapshot': containsCurrentSnapshot(histoPeriod)}"
|
||||||
@click="changePeriod(histoPeriod, $event)"
|
@click="changePeriod(histoPeriod, $event)"
|
||||||
|
@keyup.enter="changePeriod(histoPeriod, $event)"
|
||||||
@mouseover="setTooltipPeriod(histoPeriod, $event)"
|
@mouseover="setTooltipPeriod(histoPeriod, $event)"
|
||||||
@mouseout="setTooltipPeriod(null, $event)"
|
@mouseout="setTooltipPeriod(null, $event)"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inner"
|
<div class="inner"
|
||||||
:class="{'has-single-snapshot': subPeriod.snapshotCount === 1}"
|
:class="{'has-single-snapshot': subPeriod.snapshotCount === 1}"
|
||||||
@click="changePeriod(subPeriod, $event)"
|
@click="changePeriod(subPeriod, $event)"
|
||||||
|
@keyup.enter="changePeriod(histoPeriod, $event)"
|
||||||
@mouseover="setTooltipPeriod(subPeriod, $event)"
|
@mouseover="setTooltipPeriod(subPeriod, $event)"
|
||||||
@mouseout="setTooltipPeriod(null, $event)"
|
@mouseout="setTooltipPeriod(null, $event)"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div class="label">
|
<div class="label">
|
||||||
{{subPeriod.getReadableId()}}
|
{{subPeriod.getReadableId()}}
|
||||||
@ -49,7 +58,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-html="'▶'" class="arrow next" :class="{disabled: isScrollMax && !nextPeriod}" @click="scrollNext" @dblclick.stop.prevent></div>
|
<div
|
||||||
|
v-html="'▶'"
|
||||||
|
class="arrow next"
|
||||||
|
:class="{disabled: isScrollMax && !nextPeriod}"
|
||||||
|
@click="scrollNext"
|
||||||
|
@keyup.enter="scrollNext"
|
||||||
|
@dblclick.stop.prevent tabindex="0"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -2,13 +2,23 @@
|
|||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
<template v-if="parents.length">
|
<template v-if="parents.length">
|
||||||
<span class="item">
|
<span class="item">
|
||||||
<span class="goto" @click="changePeriod(parents[0])" :title="getPeriodZoomOutText(parents[0])">
|
<span
|
||||||
|
class="goto"
|
||||||
|
@click="changePeriod(parents[0])"
|
||||||
|
@keyup.enter="changePeriod(parents[0])"
|
||||||
|
:title="getPeriodZoomOutText(parents[0])"
|
||||||
|
tabindex="1">
|
||||||
<img src="/static/zoom-out-icon-333316.png" /> {{parents[0].getReadableId(true)}}
|
<img src="/static/zoom-out-icon-333316.png" /> {{parents[0].getReadableId(true)}}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
>
|
>
|
||||||
<span v-for="(parent,i) in parents" :key="parent.id" class="item" v-if="i > 0">
|
<span v-for="(parent,i) in parents" :key="parent.id" class="item" v-if="i > 0">
|
||||||
<span class="goto" @click="changePeriod(parent)" :title="getPeriodZoomOutText(parent)">
|
<span
|
||||||
|
class="goto"
|
||||||
|
@click="changePeriod(parent)"
|
||||||
|
@keyup.enter="changePeriod(parent)"
|
||||||
|
:title="getPeriodZoomOutText(parent)"
|
||||||
|
tabindex="1">
|
||||||
{{parent.getReadableId(true)}}
|
{{parent.getReadableId(true)}}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="timeline-linear">
|
<div class="timeline-linear">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div>{{ period.getFullReadableId() }}</div>
|
<div>{{ period.getFullReadableId() }}</div>
|
||||||
<div>{{ $root._(period.snapshotCount !== 1 ? '{count} captures':'{count} capture', {count: period.snapshotCount}) }}</div>
|
<div>{{ $root._(period.snapshotCount !== 1 ? '{count} captures':'{count} capture', {count: period.snapshotCount}) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="list">
|
<div class="list">
|
||||||
<div v-for="period in snapshotPeriods">
|
<div v-for="snapshotPeriod in snapshotPeriods">
|
||||||
<a :href="$root.config.prefix + period.id + '/' + $root.config.url" class="link" >{{period.snapshot.getTimeFormatted()}}</a>
|
<span
|
||||||
|
@click="changePeriod(snapshotPeriod)"
|
||||||
|
@keyup.enter="changePeriod(snapshotPeriod)"
|
||||||
|
class="link"
|
||||||
|
tabindex="1">
|
||||||
|
{{ snapshotPeriod.snapshot.getTimeFormatted() }}
|
||||||
|
</span>
|
||||||
<span v-if="isCurrentSnapshot(period)" class="current">{{$root._('current')}}</span>
|
<span v-if="isCurrentSnapshot(period)" class="current">{{$root._('current')}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -29,8 +35,14 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isCurrentSnapshot(period) {
|
isCurrentSnapshot(period) {
|
||||||
|
if (!!this.currentSnapshot && !!period.snapshot) {
|
||||||
return this.currentSnapshot && this.currentSnapshot.id === period.snapshot.id;
|
return this.currentSnapshot && this.currentSnapshot.id === period.snapshot.id;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
changePeriod(period) {
|
||||||
|
this.$emit("goto-period", period);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -42,6 +54,7 @@ export default {
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
z-index: 1100;
|
||||||
}
|
}
|
||||||
.timeline-linear .list {
|
.timeline-linear .list {
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
|
@ -50,7 +50,7 @@ export default {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 40;
|
z-index: 100;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid grey;
|
border: 1px solid grey;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
@ -7,20 +7,23 @@ import Vue from "vue/dist/vue.esm.browser";
|
|||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
export function main(staticPrefix, url, prefix, timestamp, logoUrl, locale, allLocales, i18nStrings) {
|
export function main(staticPrefix, url, prefix, timestamp, logoUrl, navbarBackground, navbarColor, navbarLightButtons, locale, allLocales, i18nStrings) {
|
||||||
PywbI18N.init(locale, i18nStrings);
|
PywbI18N.init(locale, i18nStrings);
|
||||||
new CDXLoader(staticPrefix, url, prefix, timestamp, logoUrl, allLocales);
|
new CDXLoader(staticPrefix, url, prefix, timestamp, logoUrl, navbarBackground, navbarColor, navbarLightButtons, allLocales);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class CDXLoader {
|
class CDXLoader {
|
||||||
constructor(staticPrefix, url, prefix, timestamp, logoUrl, allLocales) {
|
constructor(staticPrefix, url, prefix, timestamp, logoUrl, navbarBackground, navbarColor, navbarLightButtons, allLocales) {
|
||||||
this.loadingSpinner = null;
|
this.loadingSpinner = null;
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.opts = {};
|
this.opts = {};
|
||||||
this.prefix = prefix;
|
this.prefix = prefix;
|
||||||
this.staticPrefix = staticPrefix;
|
this.staticPrefix = staticPrefix;
|
||||||
this.logoUrl = logoUrl;
|
this.logoUrl = logoUrl;
|
||||||
|
this.navbarBackground = navbarBackground;
|
||||||
|
this.navbarColor = navbarColor;
|
||||||
|
this.navbarLightButtons = navbarLightButtons
|
||||||
|
|
||||||
this.isReplay = (timestamp !== undefined);
|
this.isReplay = (timestamp !== undefined);
|
||||||
|
|
||||||
@ -56,7 +59,7 @@ class CDXLoader {
|
|||||||
|
|
||||||
const logoImg = this.staticPrefix + "/" + (this.logoUrl ? this.logoUrl : "pywb-logo-sm.png");
|
const logoImg = this.staticPrefix + "/" + (this.logoUrl ? this.logoUrl : "pywb-logo-sm.png");
|
||||||
|
|
||||||
this.app = this.initApp({logoImg, url, allLocales});
|
this.app = this.initApp({logoImg, navbarBackground, navbarColor, navbarLightButtons, url, allLocales});
|
||||||
this.loadCDX(queryURL).then((cdxList) => {
|
this.loadCDX(queryURL).then((cdxList) => {
|
||||||
this.setAppData(cdxList, timestamp ? {url, timestamp}:null);
|
this.setAppData(cdxList, timestamp ? {url, timestamp}:null);
|
||||||
});
|
});
|
||||||
@ -107,6 +110,7 @@ class CDXLoader {
|
|||||||
this.app.setData(new PywbData(cdxList));
|
this.app.setData(new PywbData(cdxList));
|
||||||
|
|
||||||
if (snapshot) {
|
if (snapshot) {
|
||||||
|
this.app.hideBannerUtilities();
|
||||||
this.app.setSnapshot(snapshot);
|
this.app.setSnapshot(snapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,6 +184,10 @@ class VueBannerWrapper
|
|||||||
if (type === "load" || type === "replace-url") {
|
if (type === "load" || type === "replace-url") {
|
||||||
const surt = this.getSurt(event.data.url);
|
const surt = this.getSurt(event.data.url);
|
||||||
|
|
||||||
|
if (event.data.title) {
|
||||||
|
this.loader.app.updateTitle(event.data.title);
|
||||||
|
}
|
||||||
|
|
||||||
if (surt !== this.lastSurt) {
|
if (surt !== this.lastSurt) {
|
||||||
this.loader.updateSnapshot(event.data.url, event.data.ts);
|
this.loader.updateSnapshot(event.data.url, event.data.ts);
|
||||||
this.lastSurt = surt;
|
this.lastSurt = surt;
|
||||||
|
@ -493,14 +493,14 @@ class TestWbIntegration(BaseConfigTest):
|
|||||||
assert 'The url <b>http://not-exist.example.com/path?A=B</b> could not be found in this collection.' in resp.text
|
assert 'The url <b>http://not-exist.example.com/path?A=B</b> could not be found in this collection.' in resp.text
|
||||||
|
|
||||||
def test_static_content(self):
|
def test_static_content(self):
|
||||||
resp = self.testapp.get('/static/default_banner.css')
|
resp = self.testapp.get('/static/vue_banner.css')
|
||||||
assert resp.status_int == 200
|
assert resp.status_int == 200
|
||||||
assert resp.content_type == 'text/css'
|
assert resp.content_type == 'text/css'
|
||||||
assert resp.content_length > 0
|
assert resp.content_length > 0
|
||||||
|
|
||||||
def test_static_content_filewrapper(self):
|
def test_static_content_filewrapper(self):
|
||||||
from wsgiref.util import FileWrapper
|
from wsgiref.util import FileWrapper
|
||||||
resp = self.testapp.get('/static/default_banner.css', extra_environ = {'wsgi.file_wrapper': FileWrapper})
|
resp = self.testapp.get('/static/vue_banner.css', extra_environ = {'wsgi.file_wrapper': FileWrapper})
|
||||||
assert resp.status_int == 200
|
assert resp.status_int == 200
|
||||||
assert resp.content_type == 'text/css'
|
assert resp.content_type == 'text/css'
|
||||||
assert resp.content_length > 0
|
assert resp.content_length > 0
|
||||||
|
@ -27,7 +27,7 @@ class TestPrefixedDeploy(BaseConfigTest):
|
|||||||
resp = self.get('/prefix/pywb/*/iana.org')
|
resp = self.get('/prefix/pywb/*/iana.org')
|
||||||
self._assert_basic_html(resp)
|
self._assert_basic_html(resp)
|
||||||
|
|
||||||
assert '/prefix/static/query.js' in resp.text
|
assert '/prefix/static/vue/vueui.js' in resp.text
|
||||||
|
|
||||||
def test_replay_content(self, fmod):
|
def test_replay_content(self, fmod):
|
||||||
resp = self.get('/prefix/pywb/20140127171238{0}/http://www.iana.org/', fmod)
|
resp = self.get('/prefix/pywb/20140127171238{0}/http://www.iana.org/', fmod)
|
||||||
@ -35,14 +35,14 @@ class TestPrefixedDeploy(BaseConfigTest):
|
|||||||
|
|
||||||
assert '"20140127171238"' in resp.text, resp.text
|
assert '"20140127171238"' in resp.text, resp.text
|
||||||
assert "'http://localhost:80/prefix/static/wombat.js'" in resp.text
|
assert "'http://localhost:80/prefix/static/wombat.js'" in resp.text
|
||||||
assert "'http://localhost:80/prefix/static/default_banner.js'" in resp.text
|
assert "http://localhost:80/prefix/static/vue/vueui.js" in resp.text
|
||||||
assert '"http://localhost:80/prefix/static/"' in resp.text
|
assert '"http://localhost:80/prefix/static/"' in resp.text
|
||||||
assert '"http://localhost:80/prefix/pywb/"' in resp.text
|
assert '"http://localhost:80/prefix/pywb/"' in resp.text
|
||||||
assert 'WBWombatInit' in resp.text, resp.text
|
assert 'WBWombatInit' in resp.text, resp.text
|
||||||
assert '"/prefix/pywb/20140127171238{0}/http://www.iana.org/time-zones"'.format(fmod) in resp.text, resp.text
|
assert '"/prefix/pywb/20140127171238{0}/http://www.iana.org/time-zones"'.format(fmod) in resp.text, resp.text
|
||||||
|
|
||||||
def test_static_content(self):
|
def test_static_content(self):
|
||||||
resp = self.get('/prefix/static/default_banner.css')
|
resp = self.get('/prefix/static/vue_banner.css')
|
||||||
assert resp.status_int == 200
|
assert resp.status_int == 200
|
||||||
assert resp.content_type == 'text/css'
|
assert resp.content_type == 'text/css'
|
||||||
assert resp.content_length > 0
|
assert resp.content_length > 0
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
from pywb.warcserver.test.testutils import BaseTestClass, TempDirTests, HttpBinLiveTests
|
from pywb.warcserver.test.testutils import BaseTestClass, TempDirTests, HttpBinLiveTests
|
||||||
|
|
||||||
from .base_config_test import CollsDirMixin
|
from .base_config_test import CollsDirMixin
|
||||||
@ -116,7 +117,7 @@ class TestProxy(BaseTestProxy):
|
|||||||
assert 'wbinfo.enable_auto_fetch = false;' in res.text
|
assert 'wbinfo.enable_auto_fetch = false;' in res.text
|
||||||
|
|
||||||
# banner
|
# banner
|
||||||
assert 'default_banner.js' in res.text
|
assert 'vueui.js' in res.text
|
||||||
|
|
||||||
# no redirect check
|
# no redirect check
|
||||||
assert 'window == window.top' not in res.text
|
assert 'window == window.top' not in res.text
|
||||||
@ -147,7 +148,7 @@ class TestProxyDefaultDate(BaseTestProxy):
|
|||||||
assert 'wbinfo.enable_auto_fetch = false;' in res.text
|
assert 'wbinfo.enable_auto_fetch = false;' in res.text
|
||||||
|
|
||||||
# banner
|
# banner
|
||||||
assert 'default_banner.js' in res.text
|
assert 'vueui.js' in res.text
|
||||||
|
|
||||||
# no redirect check
|
# no redirect check
|
||||||
assert 'window == window.top' not in res.text
|
assert 'window == window.top' not in res.text
|
||||||
@ -307,7 +308,7 @@ class TestProxyNoBanner(BaseTestProxy):
|
|||||||
assert 'WB Insert' in res.text
|
assert 'WB Insert' in res.text
|
||||||
|
|
||||||
# no banner
|
# no banner
|
||||||
assert 'default_banner.js' not in res.text
|
assert 'vueui.js' not in res.text
|
||||||
|
|
||||||
# no wombat.js and wombatProxyMode.js
|
# no wombat.js and wombatProxyMode.js
|
||||||
assert 'wombat.js' not in res.text
|
assert 'wombat.js' not in res.text
|
||||||
@ -341,7 +342,7 @@ class TestProxyNoHeadInsert(BaseTestProxy):
|
|||||||
assert 'WB Insert' not in res.text
|
assert 'WB Insert' not in res.text
|
||||||
|
|
||||||
# no banner
|
# no banner
|
||||||
assert 'default_banner.js' not in res.text
|
assert 'vueui.js' not in res.text
|
||||||
|
|
||||||
# no wombat.js and wombatProxyMode.js
|
# no wombat.js and wombatProxyMode.js
|
||||||
assert 'wombat.js' not in res.text
|
assert 'wombat.js' not in res.text
|
||||||
|
Loading…
x
Reference in New Issue
Block a user