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

misc vue + i18n fixes:

- make calendar tooltips into real links and clickable and ctrl+clickable
- ensure advanced view uses old ui (not supported in new ui)
- add language popup on frame_insert for vueui
- i18n: consolidate loc strings for vueui into vue_loc.html
- spinner: for replay, only show over banner
- spinner: show after 500ms delay
- i18n: add one more string ('no captures')
- make calendar popup links regular links to support cmd+click
- i18n: implement lang switcher in vue as well (for query and framed_insert), don't include non-vue language switcher in header
This commit is contained in:
Ilya Kreymer 2022-02-08 21:49:08 -08:00 committed by Tessa Walsh
parent 72cb588936
commit 19032e4512
13 changed files with 233 additions and 172 deletions

View File

@ -33,5 +33,4 @@ redirect_to_exact: true
# uncomment to set available locales
locales:
- en
- fr
- ru

View File

@ -2,7 +2,7 @@ from io import BytesIO
import requests
from fakeredis import FakeStrictRedis
from six.moves.urllib.parse import unquote, urlencode, urlsplit, urlunsplit
from six.moves.urllib.parse import unquote, urlencode, urlsplit, urlunsplit, parse_qsl
from warcio.bufferedreaders import BufferedReader
from warcio.recordloader import ArcWarcRecordLoader
from warcio.timeutils import http_date_to_timestamp, timestamp_to_http_date
@ -802,9 +802,17 @@ class RewriterApp(object):
def handle_query(self, environ, wb_url, kwargs, full_prefix):
prefix = self.get_full_prefix(environ)
res = dict(parse_qsl(environ.get("QUERY_STRING")))
is_advanced = res.get("matchType", "exact") != "exact" or res.get("url", "").endswith("*")
# vue ui not supported for advanced search for now
ui = kwargs.get("ui", {})
if is_advanced:
ui["vue_calendar_ui"] = False
params = dict(url=wb_url.url,
prefix=prefix,
ui=kwargs.get('ui', {}))
ui=ui)
return self.query_view.render_to_string(environ, **params)

View File

@ -1,5 +1,9 @@
const smallSize = "75px";
class LoadingSpinner {
static #instanceCount = 0;
constructor(config={}) {
this.config = {initialState:true, animationDuration:500, text:'Loading...', ...config};
@ -75,7 +79,7 @@ class LoadingSpinner {
top: 0;
left: 0;
width: 100vw;
height: 100vh;
height: ${this.config.isSmall ? smallSize : "100vh"};
z-index: 900;
display: flex;
@ -98,8 +102,8 @@ class LoadingSpinner {
display: flex;
justify-content: center;
align-items: center;
width: 200px;
height: 200px;
width: ${this.config.isSmall ? smallSize : "200px"};
height: ${this.config.isSmall ? smallSize : "200px"};
}`,`
[data-loading-spinner^=circle] {
position: absolute;

File diff suppressed because one or more lines are too long

View File

@ -18,64 +18,11 @@ html, body
{{ banner_html }}
<script>
var i18nStrings = {
jan_long: "{{ _Q('January') }}",
feb_long: "{{ _Q('February') }}",
mar_long: "{{ _Q('March') }}",
apr_long: "{{ _Q('April') }}",
may_long: "{{ _Q('May') }}",
jun_long: "{{ _Q('June') }}",
jul_long: "{{ _Q('July') }}",
aug_long: "{{ _Q('August') }}",
sep_long: "{{ _Q('September') }}",
oct_long: "{{ _Q('October') }}",
nov_long: "{{ _Q('November') }}",
dec_long: "{{ _Q('December') }}",
jan_short: "{{ _Q('Jan') }}",
feb_short: "{{ _Q('Feb') }}",
mar_short: "{{ _Q('Mar') }}",
apr_short: "{{ _Q('Apr') }}",
may_short: "{{ _Q('May') }}",
jun_short: "{{ _Q('Jun') }}",
jul_short: "{{ _Q('Jul') }}",
aug_short: "{{ _Q('Aug') }}",
sep_short: "{{ _Q('Sep') }}",
oct_short: "{{ _Q('Oct') }}",
nov_short: "{{ _Q('Nov') }}",
dec_short: "{{ _Q('Dec') }}",
mon_short: "{{ _Q('Mon') }}",
tue_short: "{{ _Q('Tue') }}",
wed_short: "{{ _Q('Wed') }}",
thu_short: "{{ _Q('Thu') }}",
fri_short: "{{ _Q('Fri') }}",
sat_short: "{{ _Q('Sat') }}",
sun_short: "{{ _Q('Sun') }}",
mon_long: "{{ _Q('Monday') }}",
tue_long: "{{ _Q('Tuesday') }}",
wed_long: "{{ _Q('Wednesday') }}",
thu_long: "{{ _Q('Thursday') }}",
fri_long: "{{ _Q('Friday') }}",
sat_long: "{{ _Q('Saturday') }}",
sun_long: "{{ _Q('Sunday') }}",
"All-time": "{{ _Q('All-time') }}",
"show timeline":"{{ _Q('show timeline') }}",
"hide timeline":"{{ _Q('hide timeline') }}",
"show calendar":"{{ _Q('show calendar') }}",
"hide calendar":"{{ _Q('hide calendar') }}",
"View capture on {date}":"{{ _Q('View capture on {date}') }}",
"{count} capture":"{{ _Q('{count} capture') }}",
"{count} captures":"{{ _Q('{count} captures') }}",
"{capture_text} on {date}":"{{ _Q('{capture_text} on {date}') }}",
"{capture_text} in {month}":"{{ _Q('{capture_text} in {month}') }}",
"current":"{{ _Q('current') }}",
"Loading...": "{{ _Q('Loading...') }}",
"Current Capture": "{{ _Q('Current Capture') }}",
"capture": "{{ _Q('capture') }}",
"captures": "{{ _Q('captures') }}",
"from {hour1} to {hour2}": "{{ _Q('from {hour1} to {hour2}') }}",
};
</script>
{% if ui.vue_timeline_banner %}
{% include 'vue_loc.html' %}
{% endif %}
</head>
<body style="margin: 0px; padding: 0px;">
@ -83,7 +30,8 @@ html, body
{% if ui.vue_timeline_banner %}
<div id="app" style="width: 100%; height: 200px"></div>
<script>
VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ wb_prefix }}", "{{ timestamp }}", "{{ ui.logo }}", "{{ env.pywb_lang | default('en') }}", i18nStrings);
VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ wb_prefix }}", "{{ timestamp }}", "{{ ui.logo }}", "{{ env.pywb_lang | default('en') }}",
allLocales, i18nStrings);
</script>
{% endif %}

View File

@ -1,6 +1,6 @@
{# place content to be added at the very beginning of the <body> tag in this file below #}
<header>
{% if not err_msg and locales|length > 1 %}
{% if not err_msg and locales|length > 1 and (not ui or not ui.vue_calendar_ui) %}
<div class="language-select">
{{ _('Language:') }}
<ul role="listbox" aria-activedescendant="{{ env.pywb_lang | default(default_locale) }}" aria-labelledby="{{ _('Language select') }}">

View File

@ -16,66 +16,9 @@
<script src="{{ static_prefix }}/loading-spinner/loading-spinner.js"></script>
<script src="{{ static_prefix }}/vue/vueui.js"></script>
{% endif %}
{% include 'vue_loc.html' %}
<script>
var i18nStrings = {
jan_long: "{{ _Q('January') }}",
feb_long: "{{ _Q('February') }}",
mar_long: "{{ _Q('March') }}",
apr_long: "{{ _Q('April') }}",
may_long: "{{ _Q('May') }}",
jun_long: "{{ _Q('June') }}",
jul_long: "{{ _Q('July') }}",
aug_long: "{{ _Q('August') }}",
sep_long: "{{ _Q('September') }}",
oct_long: "{{ _Q('October') }}",
nov_long: "{{ _Q('November') }}",
dec_long: "{{ _Q('December') }}",
jan_short: "{{ _Q('Jan') }}",
feb_short: "{{ _Q('Feb') }}",
mar_short: "{{ _Q('Mar') }}",
apr_short: "{{ _Q('Apr') }}",
may_short: "{{ _Q('May') }}",
jun_short: "{{ _Q('Jun') }}",
jul_short: "{{ _Q('Jul') }}",
aug_short: "{{ _Q('Aug') }}",
sep_short: "{{ _Q('Sep') }}",
oct_short: "{{ _Q('Oct') }}",
nov_short: "{{ _Q('Nov') }}",
dec_short: "{{ _Q('Dec') }}",
mon_short: "{{ _Q('Mon') }}",
tue_short: "{{ _Q('Tue') }}",
wed_short: "{{ _Q('Wed') }}",
thu_short: "{{ _Q('Thu') }}",
fri_short: "{{ _Q('Fri') }}",
sat_short: "{{ _Q('Sat') }}",
sun_short: "{{ _Q('Sun') }}",
mon_long: "{{ _Q('Monday') }}",
tue_long: "{{ _Q('Tuesday') }}",
wed_long: "{{ _Q('Wednesday') }}",
thu_long: "{{ _Q('Thursday') }}",
fri_long: "{{ _Q('Friday') }}",
sat_long: "{{ _Q('Saturday') }}",
sun_long: "{{ _Q('Sunday') }}",
"All-time": "{{ _Q('All-time') }}",
"show timeline":"{{ _Q('show timeline') }}",
"hide timeline":"{{ _Q('hide timeline') }}",
"show calendar":"{{ _Q('show calendar') }}",
"hide calendar":"{{ _Q('hide calendar') }}",
"View capture on {date}":"{{ _Q('View capture on {date}') }}",
"{count} capture":"{{ _Q('{count} capture') }}",
"{count} captures":"{{ _Q('{count} captures') }}",
"{capture_text} on {date}":"{{ _Q('{capture_text} on {date}') }}",
"{capture_text} in {month}":"{{ _Q('{capture_text} in {month}') }}",
"current":"{{ _Q('current') }}", // translators: current capture in list of captures
"Loading...": "{{ _Q('Loading...') }}",
"Current Capture": "{{ _Q('Current Capture') }}",
"capture": "{{ _Q('capture') }}",
"captures": "{{ _Q('captures') }}",
"from {hour1} to {hour2}": "{{ _Q('from {hour1} to {hour2}') }}",
};
</script>
{% endif %}
{% endblock %}
@ -138,7 +81,8 @@
renderCal.init();
{% else %}
VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ prefix }}", undefined, "{{ ui.logo }}", "{{ env.pywb_lang | default('en') }}", i18nStrings);
VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ prefix }}", undefined, "{{ ui.logo }}", "{{ env.pywb_lang | default('en') }}",
allLocales, i18nStrings);
{% endif %}

View File

@ -0,0 +1,63 @@
<script>
{% autoescape false %}
var allLocales = {{ get_locale_prefixes() }};
{% endautoescape %}
var i18nStrings = {
jan_long: "{{ _Q('January') }}",
feb_long: "{{ _Q('February') }}",
mar_long: "{{ _Q('March') }}",
apr_long: "{{ _Q('April') }}",
may_long: "{{ _Q('May') }}",
jun_long: "{{ _Q('June') }}",
jul_long: "{{ _Q('July') }}",
aug_long: "{{ _Q('August') }}",
sep_long: "{{ _Q('September') }}",
oct_long: "{{ _Q('October') }}",
nov_long: "{{ _Q('November') }}",
dec_long: "{{ _Q('December') }}",
jan_short: "{{ _Q('Jan') }}",
feb_short: "{{ _Q('Feb') }}",
mar_short: "{{ _Q('Mar') }}",
apr_short: "{{ _Q('Apr') }}",
may_short: "{{ _Q('May') }}",
jun_short: "{{ _Q('Jun') }}",
jul_short: "{{ _Q('Jul') }}",
aug_short: "{{ _Q('Aug') }}",
sep_short: "{{ _Q('Sep') }}",
oct_short: "{{ _Q('Oct') }}",
nov_short: "{{ _Q('Nov') }}",
dec_short: "{{ _Q('Dec') }}",
mon_short: "{{ _Q('Mon') }}",
tue_short: "{{ _Q('Tue') }}",
wed_short: "{{ _Q('Wed') }}",
thu_short: "{{ _Q('Thu') }}",
fri_short: "{{ _Q('Fri') }}",
sat_short: "{{ _Q('Sat') }}",
sun_short: "{{ _Q('Sun') }}",
mon_long: "{{ _Q('Monday') }}",
tue_long: "{{ _Q('Tuesday') }}",
wed_long: "{{ _Q('Wednesday') }}",
thu_long: "{{ _Q('Thursday') }}",
fri_long: "{{ _Q('Friday') }}",
sat_long: "{{ _Q('Saturday') }}",
sun_long: "{{ _Q('Sunday') }}",
"All-time": "{{ _Q('All-time') }}",
"show timeline":"{{ _Q('show timeline') }}",
"hide timeline":"{{ _Q('hide timeline') }}",
"show calendar":"{{ _Q('show calendar') }}",
"hide calendar":"{{ _Q('hide calendar') }}",
"View capture on {date}":"{{ _Q('View capture on {date}') }}",
"{count} capture":"{{ _Q('{count} capture') }}",
"{count} captures":"{{ _Q('{count} captures') }}",
"{capture_text} on {date}":"{{ _Q('{capture_text} on {date}') }}",
"{capture_text} in {month}":"{{ _Q('{capture_text} in {month}') }}",
"current":"{{ _Q('current') }}", // translators: current capture in list of captures
"Loading...": "{{ _Q('Loading...') }}",
"Current Capture": "{{ _Q('Current Capture') }}",
"capture": "{{ _Q('capture') }}",
"captures": "{{ _Q('captures') }}",
"from {hour1} to {hour2}": "{{ _Q('from {hour1} to {hour2}') }}",
"no captures": "{{ _Q('no captures') }}"
}
</script>

View File

@ -2,7 +2,7 @@
<div class="app" :class="{expanded: showTimelineView}" data-app="webrecorder-replay-app">
<div class="banner">
<div class="line">
<div class="logo"><a href="/"><img :src="config.logoImg" style="max-width: 80px" /></a></div>
<div class="logo"><a href="/"><img :src="config.logoImg"/></a></div>
<div class="timeline-wrap">
<div class="line">
<div class="breadcrumbs-wrap">
@ -21,6 +21,12 @@
<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>
</ul>
</div>
</div>
<Timeline
@ -76,6 +82,7 @@ export default {
initialView: {}
},
timelineHighlight: false,
locales: [],
};
},
components: {Timeline, TimelineBreadcrumbs, CalendarYear},
@ -262,4 +269,33 @@ export default {
#theurl {
width: 400px;
}
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>

View File

@ -8,6 +8,7 @@
width: 220px;
text-align: center;
vertical-align: top;
box-sizing: content-box;
}
.calendar-month:hover {
background-color: #eeeeee;
@ -96,7 +97,7 @@
<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="'&nbsp;'"></template></span></span>
</div>
<div v-else class="empty">no captures</div>
<div v-else class="empty">{{ _('no captures') }}</div>
</div>
</template>
@ -146,6 +147,9 @@ export default {
}
},
methods: {
_(id, embeddedVariableStrings=null) {
return PywbI18N.instance.getText(id, embeddedVariableStrings);
},
getLongMonthName(id) {
return PywbI18N.instance.getMonth(id);
},

View File

@ -56,7 +56,7 @@ export default {
font-size: inherit;
}
.breadcrumbs .count {
vertical-align: middle;
/*vertical-align: middle;*/
font-size: inherit;
}

View File

@ -7,7 +7,7 @@
<div class="list">
<div v-for="period in snapshotPeriods">
<span class="link" @click="gotoPeriod(period)" >{{period.snapshot.getTimeFormatted()}}</span>
<a :href="$root.config.prefix + period.id + '/' + $root.config.url" class="link" >{{period.snapshot.getTimeFormatted()}}</a>
<span v-if="isCurrentSnapshot(period)" class="current">{{$root._('current')}}</span>
</div>
</div>
@ -28,9 +28,6 @@ export default {
}
},
methods: {
gotoPeriod(period) {
this.$emit('goto-period', period);
},
isCurrentSnapshot(period) {
return this.currentSnapshot && this.currentSnapshot.id === period.snapshot.id;
}

View File

@ -7,16 +7,16 @@ import Vue from "vue/dist/vue.esm.browser";
// ===========================================================================
export function main(staticPrefix, url, prefix, timestamp, logoUrl, locale, i18nStrings) {
export function main(staticPrefix, url, prefix, timestamp, logoUrl, locale, allLocales, i18nStrings) {
PywbI18N.init(locale, i18nStrings);
const loadingSpinner = new LoadingSpinner({text: PywbI18N.instance?.getText('Loading...')}); // bootstrap loading-spinner EARLY ON
new CDXLoader(staticPrefix, url, prefix, timestamp, logoUrl, loadingSpinner);
new CDXLoader(staticPrefix, url, prefix, timestamp, logoUrl, allLocales);
}
// ===========================================================================
class CDXLoader {
constructor(staticPrefix, url, prefix, timestamp, logoUrl, loadingSpinner) {
this.loadingSpinner = loadingSpinner;
constructor(staticPrefix, url, prefix, timestamp, logoUrl, allLocales) {
this.loadingSpinner = null;
this.loaded = false;
this.opts = {};
this.prefix = prefix;
this.staticPrefix = staticPrefix;
@ -24,6 +24,13 @@ class CDXLoader {
this.isReplay = (timestamp !== undefined);
setTimeout(() => {
if (!this.loaded) {
this.loadingSpinner = new LoadingSpinner({text: PywbI18N.instance?.getText('Loading...'), isSmall: !!timestamp}); // bootstrap loading-spinner EARLY ON
this.loadingSpinner.setOn();
}
}, 500);
if (this.isReplay) {
window.WBBanner = new VueBannerWrapper(this, url);
}
@ -49,7 +56,7 @@ class CDXLoader {
const logoImg = this.staticPrefix + "/" + (this.logoUrl ? this.logoUrl : "pywb-logo-sm.png");
this.app = this.initApp({logoImg, url});
this.app = this.initApp({logoImg, url, allLocales});
this.loadCDX(queryURL).then((cdxList) => {
this.setAppData(cdxList, timestamp ? {url, timestamp}:null);
});
@ -75,7 +82,12 @@ class CDXLoader {
// });
app.$on("show-snapshot", this.loadSnapshot.bind(this));
app.$on("data-set-and-render-completed", () => this.loadingSpinner.setOff()); // only turn off loading-spinner AFTER app has told us it is DONE DONE
app.$on("data-set-and-render-completed", () => {
if (this.loadingSpinner) {
this.loadingSpinner.setOff(); // only turn off loading-spinner AFTER app has told us it is DONE DONE
}
this.loaded = true;
});
return app;
}
@ -100,7 +112,7 @@ class CDXLoader {
}
async loadCDX(queryURL) {
this.loadingSpinner.setOn(); // start loading-spinner when CDX loading begins
// this.loadingSpinner.setOn(); // start loading-spinner when CDX loading begins
const queryWorker = new Worker(this.staticPrefix + "/queryWorker.js");
const p = new Promise((resolve) => {