1
0
mirror of https://github.com/webrecorder/pywb.git synced 2025-03-15 00:03:28 +01:00

2.7.2 patch release (#787)

* Fix 2.7.1 regressions

* Bump to 2.7.2

* fix redirect-to-exact false:
- check if current loaded timestamp is the same as to-redirected to timestamp, and avoid reload

* additional ui fixes:
- location bar: reload with current timestamp, instead of going to calendar
- ensure calendar popup on replay view is scrollable
- 'Live' mode fixes: don't cache live cdx entry, don't add timestamp when navigating in live mode without timestamp
- remember timeline view toggle on replay
- title: add 'Archived Page: ' prefix to document.title, consistent with old version
- ensure 'Archived Page: ' text is localizable
- ui: change ',' to '|' on capture display

* update CHANGES for 2.7.2

Co-authored-by: Ilya Kreymer <ikreymer@gmail.com>
This commit is contained in:
Tessa Walsh 2022-12-08 19:35:39 -05:00 committed by GitHub
parent 2d19b6b18d
commit 3c94da04a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 170 additions and 115 deletions

View File

@ -1,3 +1,15 @@
pywb 2.7.2 changelist
~~~~~~~~~~~~~~~~~~~~~
* Fix regression introduced by improper wombat update in 2.7.1
* Fix `redirect_to_exact: false` functionality: if not set, UI will stay on current timestamp, but will display info on actual capture.
* Location bar nav now keeps current timestamp instead of defaulting to calendar view.
* 'Live' mode fixes, no longer cache live cdx entry, don't add timestamp when navigating in live mode without timestamp
* Calendar dropdown on replay now scrollable.
* Timeline toggle on replay is 'sticky', will stay on if toggled on replay.
* Capture text: use '|' as in 'Current Capture: [title] | [capture date]'
* Document title: Add 'Archived Page: ' prefix to avoid confusion with live pages.
pywb 2.7.1 changelist pywb 2.7.1 changelist
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~

View File

@ -414,6 +414,14 @@ class FrontEndApp(object):
# if coll == self.all_coll: # if coll == self.all_coll:
# coll = '*' # coll = '*'
config = self.warcserver.get_coll_config(coll)
is_live = config.get("index") == "$live"
if is_live:
cache_control = "no-store, no-cache"
else:
cache_control = "max-age=86400, must-revalidate"
cdx_url = base_url.format(coll=coll) cdx_url = base_url.format(coll=coll)
if environ.get('QUERY_STRING'): if environ.get('QUERY_STRING'):
@ -433,7 +441,7 @@ 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")]) headers=[("Cache-Control", cache_control)])
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')

View File

@ -408,6 +408,8 @@ class TopFrameView(BaseInsertView):
timestamp = '' timestamp = ''
if wb_url.timestamp: if wb_url.timestamp:
timestamp = wb_url.timestamp timestamp = wb_url.timestamp
#else:
# timestamp = timestamp_now()
is_proxy = 'wsgiprox.proxy_host' in env is_proxy = 'wsgiprox.proxy_host' in env

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -61,6 +61,7 @@
"capture": "{{ _Q('capture') }}", "capture": "{{ _Q('capture') }}",
"captures": "{{ _Q('captures') }}", "captures": "{{ _Q('captures') }}",
"from {hour1} to {hour2}": "{{ _Q('from {hour1} to {hour2}') }}", "from {hour1} to {hour2}": "{{ _Q('from {hour1} to {hour2}') }}",
"no captures": "{{ _Q('no captures') }}" "no captures": "{{ _Q('no captures') }}",
"Archived Page: ": "{{ _Q('Archived Page: ') }}"
} }
</script> </script>

View File

@ -1,4 +1,4 @@
__version__ = '2.7.1' __version__ = '2.7.2'
if __name__ == '__main__': if __name__ == '__main__':
print(__version__) print(__version__)

View File

@ -69,7 +69,7 @@
class="btn btn-sm" class="btn btn-sm"
:class="{active: showTimelineView, 'btn-outline-light': lightButtons, 'btn-outline-dark': !lightButtons}" :class="{active: showTimelineView, 'btn-outline-light': lightButtons, 'btn-outline-dark': !lightButtons}"
:aria-pressed="showTimelineView" :aria-pressed="showTimelineView"
@click="showTimelineView = !showTimelineView" @click="toggleTimelineView"
:title="(showTimelineView ? _('Hide timeline') : _('Show timeline'))"> :title="(showTimelineView ? _('Hide timeline') : _('Show timeline'))">
<i class="far fa-chart-bar"></i> <i class="far fa-chart-bar"></i>
</button> </button>
@ -113,7 +113,7 @@
{{ config.title }} {{ config.title }}
</span> </span>
</span> </span>
<span class="mr-1" v-if="config.title">,</span> <span class="mr-1" v-if="config.title">|</span>
{{currentSnapshot.getTimeDateFormatted()}} {{currentSnapshot.getTimeDateFormatted()}}
</span> </span>
</nav> </nav>
@ -142,7 +142,7 @@
</div> </div>
<!-- Calendar --> <!-- Calendar -->
<div class="card" v-if="currentPeriod && showFullView && currentPeriod.children.length"> <div class="card" id="calendar-card" v-if="currentPeriod && showFullView && currentPeriod.children.length">
<div class="card-body" id="calendar-card-body"> <div class="card-body" id="calendar-card-body">
<CalendarYear <CalendarYear
:period="currentPeriod" :period="currentPeriod"
@ -173,8 +173,8 @@ export default {
currentSnapshot: null, currentSnapshot: null,
currentSnapshotIndex: null, currentSnapshotIndex: null,
msgs: [], msgs: [],
showFullView: true, showFullView: false,
showTimelineView: true, showTimelineView: false,
maxTimelineZoomLevel: PywbPeriod.Type.day, maxTimelineZoomLevel: PywbPeriod.Type.day,
config: { config: {
title: "", title: "",
@ -194,7 +194,7 @@ export default {
}, },
updated: function() { updated: function() {
// set top frame title equal to value pulled from replay frame // set top frame title equal to value pulled from replay frame
document.title = this.config.title; document.title = this._("Archived Page: ") + this.config.title;
}, },
computed: { computed: {
sessionStorageUrlKey() { sessionStorageUrlKey() {
@ -281,7 +281,7 @@ export default {
if (reloadIFrame !== false) { if (reloadIFrame !== false) {
this.$emit("show-snapshot", snapshot); this.$emit("show-snapshot", snapshot);
} }
this.hideBannerUtilities(); this.initBannerState(true);
}, },
gotoPreviousSnapshot() { gotoPreviousSnapshot() {
let periodToChangeTo = this.currentPeriod.findByFullId(this.previousSnapshot.getFullId()); let periodToChangeTo = this.currentPeriod.findByFullId(this.previousSnapshot.getFullId());
@ -294,10 +294,15 @@ export default {
gotoUrl(event) { gotoUrl(event) {
event.preventDefault(); event.preventDefault();
const newUrl = document.querySelector("#theurl").value; const newUrl = document.querySelector("#theurl").value;
if (newUrl !== this.url) { if (newUrl !== this.config.url) {
window.location.href = this.config.prefix + "*/" + newUrl; const ts = this.config.timestamp === undefined ? "*" : this.config.timestamp;
window.location.href = this.config.prefix + ts + (ts ? "/" : "") + newUrl;
} }
}, },
toggleTimelineView() {
this.showTimelineView = !this.showTimelineView;
window.localStorage.setItem("showTimelineView", this.showTimelineView ? "1" : "0");
},
setData(/** @type {PywbData} data */ data) { setData(/** @type {PywbData} data */ data) {
// data-set will usually happen at App INIT (from parent caller) // data-set will usually happen at App INIT (from parent caller)
@ -321,6 +326,10 @@ export default {
}.bind(this)); }.bind(this));
}, },
setSnapshot(view) { setSnapshot(view) {
if (!this.currentPeriod) {
return false;
}
// turn off calendar (aka full) view // turn off calendar (aka full) view
this.showFullView = false; this.showFullView = false;
@ -332,18 +341,20 @@ export default {
let periodToChangeTo = this.currentPeriod.findByFullId(snapshot.getFullId()); let periodToChangeTo = this.currentPeriod.findByFullId(snapshot.getFullId());
if (periodToChangeTo) { if (periodToChangeTo) {
this.gotoPeriod(periodToChangeTo, false /* onlyZoomToPeriod */); this.gotoPeriod(periodToChangeTo, false /* onlyZoomToPeriod */);
return true;
} }
return false;
}, },
setTimelineView() { initBannerState(isReplay) {
this.showTimelineView = !this.showTimelineView; // if not replay, always show both
if (this.showTimelineView === true) { if (!isReplay) {
this.showFullView = true;
this.showTimelineView = true;
} else {
this.showFullView = false; this.showFullView = false;
this.showTimelineView = window.localStorage.getItem("showTimelineView") === "1";
} }
}, },
hideBannerUtilities() {
this.showFullView = false;
this.showTimelineView = false;
},
updateTitle(title) { updateTitle(title) {
this.config.title = title; this.config.title = title;
} }
@ -361,7 +372,10 @@ export default {
width: 100%; width: 100%;
} }
.app.expanded { .app.expanded {
height: 130px; /*height: 130px;*/
max-height: calc(100vh - 90px);
display: flex;
flex-direction: column;
} }
.full-view { .full-view {
/*position: fixed;*/ /*position: fixed;*/
@ -449,6 +463,10 @@ export default {
div.timeline-wrap div.card { div.timeline-wrap div.card {
margin-top: 55px; margin-top: 55px;
} }
#calendar-card {
overflow-y: auto;
max-height: 100%;
}
div.timeline-wrap div.card-body { div.timeline-wrap div.card-body {
display: flex; display: flex;
align-items: center; align-items: center;
@ -459,6 +477,7 @@ export default {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
#calendar-card-body { #calendar-card-body {
padding: 0; padding: 0;
} }

View File

@ -163,7 +163,7 @@ export default {
flex: 1; flex: 1;
flex-wrap: wrap; flex-wrap: wrap;
z-index: 10; z-index: 10;
overflow-y: scroll; overflow-y: auto;
width: 100%; width: 100%;
background-color: white; background-color: white;
padding-bottom: 1em; padding-bottom: 1em;

View File

@ -23,7 +23,8 @@ class CDXLoader {
this.logoUrl = logoUrl; this.logoUrl = logoUrl;
this.navbarBackground = navbarBackground; this.navbarBackground = navbarBackground;
this.navbarColor = navbarColor; this.navbarColor = navbarColor;
this.navbarLightButtons = navbarLightButtons this.navbarLightButtons = navbarLightButtons;
this.timestamp = timestamp;
this.isReplay = (timestamp !== undefined); this.isReplay = (timestamp !== undefined);
@ -35,11 +36,10 @@ class CDXLoader {
}, 500); }, 500);
if (this.isReplay) { if (this.isReplay) {
window.WBBanner = new VueBannerWrapper(this, url); window.WBBanner = new VueBannerWrapper(this, url, timestamp);
} }
let queryURL; let queryURL;
let isQueryURL = window.location.href.indexOf("*") > -1 ? true : false;
// query form *?=url... // query form *?=url...
if (window.location.href.indexOf("*?") > 0) { if (window.location.href.indexOf("*?") > 0) {
@ -60,9 +60,10 @@ 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, navbarBackground, navbarColor, navbarLightButtons, url, allLocales}); this.app = this.initApp({logoImg, navbarBackground, navbarColor, navbarLightButtons, url, allLocales, timestamp});
this.loadCDX(queryURL).then((cdxList) => { this.loadCDX(queryURL).then((cdxList) => {
this.setAppData(cdxList, url, isQueryURL, timestamp); this.setAppData(cdxList, url, this.timestamp);
}); });
} }
@ -73,19 +74,7 @@ class CDXLoader {
app.$mount("#app"); app.$mount("#app");
// TODO (Ilya): make this work with in-page snapshot/capture/replay updates! app.$on("show-snapshot", (snapshot) => this.loadSnapshot(snapshot));
// app.$on("show-snapshot", snapshot => {
// const replayUrl = app.config.url;
// const url = location.href.replace('/'+replayUrl, '').replace(/\d+$/, '') + snapshot.id + '/' + replayUrl;
// window.history.pushState({url: replayUrl, timestamp: snapshot.id}, document.title, url);
// if (!window.onpopstate) {
// window.onpopstate = (ev) => {
// updateSnapshot(ev.state.url, ev.state.timestamp);
// };
// }
// });
app.$on("show-snapshot", this.loadSnapshot.bind(this));
app.$on("data-set-and-render-completed", () => { app.$on("data-set-and-render-completed", () => {
if (this.loadingSpinner) { if (this.loadingSpinner) {
this.loadingSpinner.setOff(); // only turn off loading-spinner AFTER app has told us it is DONE DONE this.loadingSpinner.setOff(); // only turn off loading-spinner AFTER app has told us it is DONE DONE
@ -101,31 +90,37 @@ class CDXLoader {
params.set("url", url); params.set("url", url);
params.set("output", "json"); params.set("output", "json");
const queryURL = this.prefix + "cdx?" + params.toString(); const queryURL = this.prefix + "cdx?" + params.toString();
let isQueryURL = window.location.href.indexOf("*") > -1 ? true : false;
const cdxList = await this.loadCDX(queryURL); const cdxList = await this.loadCDX(queryURL);
this.setAppData(cdxList, url, isQueryURL, timestamp); this.setAppData(cdxList, url, timestamp);
} }
setAppData(cdxList, url, isQueryURL, timestamp="") { async updateTimestamp(url, timestamp) {
this.app.setData(new PywbData(cdxList)); this.timestamp = timestamp;
// if this is a capture but we don't have a timestamp (e.g. if redirect_to_exact is false) if (this.cdxLoading) {
// set the timestamp to the latest capture return;
if ((!timestamp) && (!isQueryURL)) {
const lastSnapshot = cdxList[cdxList.length - 1];
timestamp = lastSnapshot.timestamp;
} }
this.app.setSnapshot({url, timestamp});
}
setAppData(cdxList, url, timestamp) {
this.app.setData(new PywbData(cdxList));
this.app.initBannerState(this.isReplay);
// if set on initial load, may not have timestamp yet
// will be updated later
if (timestamp) { if (timestamp) {
this.app.hideBannerUtilities(); this.updateTimestamp(url, timestamp);
this.app.setSnapshot({url, timestamp});
} }
} }
async loadCDX(queryURL) { async loadCDX(queryURL) {
// this.loadingSpinner.setOn(); // start loading-spinner when CDX loading begins // this.loadingSpinner.setOn(); // start loading-spinner when CDX loading begins
this.cdxLoading = true;
const queryWorker = new Worker(this.staticPrefix + "/queryWorker.js"); const queryWorker = new Worker(this.staticPrefix + "/queryWorker.js");
const p = new Promise((resolve) => { const p = new Promise((resolve) => {
@ -139,6 +134,7 @@ class CDXLoader {
break; break;
case "finished": case "finished":
this.cdxLoading = false;
resolve(cdxList); resolve(cdxList);
break; break;
} }
@ -162,7 +158,10 @@ class CDXLoader {
if (!this.isReplay) { if (!this.isReplay) {
window.location.href = this.prefix + snapshot.id + "/" + snapshot.url; window.location.href = this.prefix + snapshot.id + "/" + snapshot.url;
} else if (window.cframe) { } else if (window.cframe) {
window.cframe.load_url(snapshot.url, snapshot.id + "", reloadIFrame); const ts = snapshot.id + "";
if (ts !== this.timestamp) {
window.cframe.load_url(snapshot.url, ts, reloadIFrame);
}
} }
} }
} }
@ -171,9 +170,10 @@ class CDXLoader {
// =========================================================================== // ===========================================================================
class VueBannerWrapper class VueBannerWrapper
{ {
constructor(loader, url) { constructor(loader, url, ts) {
this.loading = true; this.loading = true;
this.lastSurt = this.getSurt(url); this.lastSurt = this.getSurt(url);
this.lastTs = ts;
this.loader = loader; this.loader = loader;
} }
@ -200,6 +200,9 @@ class VueBannerWrapper
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;
} else if (event.data.ts !== this.lastTs) {
this.loader.updateTimestamp(event.data.url, event.data.ts);
this.lastTs = event.data.ts;
} }
} }
} }

2
wombat

@ -1 +1 @@
Subproject commit 74a6087d41d335c371e3a1f52f0f008944705118 Subproject commit 04ca325f3a59e7efc8ad0fa5abe25ec1bc9d9620