mirror of
https://github.com/webrecorder/pywb.git
synced 2025-03-15 00:03:28 +01:00
ui template overhaul to simplify customization:
- add base.html template with head, header, footer optional customizations - refactor all top-level templates to extend base.html, except frame_insert.html - localization: add placeholder support for jinja2 localization extension, '{% trans %}' and _('') tags, placeholder null localization - refactor new query UI to support localization - update some text to match localized versions used in ukwa-pywb, update test
This commit is contained in:
parent
1b0c9c6895
commit
3589240431
@ -71,7 +71,9 @@ class RewriterApp(object):
|
||||
self.js_proxy_rw = RewriterWithJSProxy(replay_mod=self.replay_mod)
|
||||
|
||||
if not jinja_env:
|
||||
jinja_env = JinjaEnv(globals={'static_path': 'static'})
|
||||
jinja_env = JinjaEnv(globals={'static_path': 'static'},
|
||||
extensions=['jinja2.ext.i18n', 'jinja2.ext.with_'])
|
||||
jinja_env.jinja_env.install_null_translations()
|
||||
|
||||
self.jinja_env = jinja_env
|
||||
|
||||
|
@ -18,3 +18,18 @@
|
||||
.long-text {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.inherit-height {
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.filter-list {
|
||||
height: 140px;
|
||||
max-height: 140px;
|
||||
overflow-y: scroll
|
||||
}
|
||||
|
||||
.show-optional-bad-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
@ -65,26 +65,7 @@ function RenderCalendar(init) {
|
||||
'=!=': 'Is Not',
|
||||
'=!~': 'Does Not Begins With'
|
||||
};
|
||||
this.text = {
|
||||
months: {
|
||||
'01': 'January',
|
||||
'02': 'February',
|
||||
'03': 'March',
|
||||
'04': 'April',
|
||||
'05': 'May',
|
||||
'06': 'June',
|
||||
'07': 'July',
|
||||
'08': 'August',
|
||||
'09': 'September',
|
||||
'10': 'October',
|
||||
'11': 'November',
|
||||
'12': 'December'
|
||||
},
|
||||
version: 'capture',
|
||||
versions: 'captures',
|
||||
result: 'result',
|
||||
results: 'results'
|
||||
};
|
||||
this.text = init.text;
|
||||
this.versionString = null;
|
||||
}
|
||||
|
||||
@ -268,10 +249,16 @@ RenderCalendar.prototype.makeCDXRequest = function() {
|
||||
var queryWorker = new window.Worker(this.staticPrefix + '/queryWorker.js');
|
||||
var cdxRecordMsg = 'cdxRecord';
|
||||
var done = 'finished';
|
||||
|
||||
var months = this.text.months;
|
||||
|
||||
queryWorker.onmessage = function(msg) {
|
||||
var data = msg.data;
|
||||
var terminate = false;
|
||||
if (data.type === cdxRecordMsg) {
|
||||
|
||||
data.timeInfo.month = months[data.timeInfo.month];
|
||||
|
||||
// render the results sent to us from the worker
|
||||
renderCal.displayedCountStr(
|
||||
data.recordCount,
|
||||
@ -599,14 +586,9 @@ RenderCalendar.prototype.renderDateCalPart = function(
|
||||
* Updates the advanced view with the supplied cdx information
|
||||
* @param {Object} cdxObj - CDX object for this capture
|
||||
*/
|
||||
RenderCalendar.prototype.renderAdvancedSearchPart = function(cdxObj) {
|
||||
RenderCalendar.prototype.renderAdvancedSearchPart = function (cdxObj) {
|
||||
// display the URL of the result
|
||||
var displayedInfo = [
|
||||
{
|
||||
tag: 'small',
|
||||
innerText: 'Date Time: ' + this.tsToDate(cdxObj.timestamp)
|
||||
}
|
||||
];
|
||||
var displayedInfo = [{ tag: 'small', innerText: this.text.dateTime + this.ts_to_date(cdxObj.timestamp) }];
|
||||
// display additional information about the result under the URL
|
||||
if (cdxObj.mime) {
|
||||
displayedInfo.push({
|
||||
@ -626,7 +608,7 @@ RenderCalendar.prototype.renderAdvancedSearchPart = function(cdxObj) {
|
||||
href: this.prefix + '*' + '/' + cdxObj.url,
|
||||
target: '_blank'
|
||||
},
|
||||
child: { tag: 'small', innerText: 'View All Captures' }
|
||||
child: { tag: 'small', innerText: this.text.viewAllCaptures }
|
||||
});
|
||||
this.createAndAddElementTo(this.containers.advancedResultsList, {
|
||||
tag: 'li',
|
||||
|
@ -1,18 +1,4 @@
|
||||
var colon = ':';
|
||||
var monthToText = {
|
||||
'01': 'January',
|
||||
'02': 'February',
|
||||
'03': 'March',
|
||||
'04': 'April',
|
||||
'05': 'May',
|
||||
'06': 'June',
|
||||
'07': 'July',
|
||||
'08': 'August',
|
||||
'09': 'September',
|
||||
'10': 'October',
|
||||
'11': 'November',
|
||||
'12': 'December'
|
||||
};
|
||||
|
||||
var recordCount = 0;
|
||||
|
||||
@ -166,7 +152,7 @@ function handleCDXRecord(binaryCDXRecord) {
|
||||
record: cdxRecord,
|
||||
timeInfo: {
|
||||
year: ts.substring(0, 4),
|
||||
month: monthToText[ts.substring(4, 6)],
|
||||
month: ts.substring(4, 6),
|
||||
day: day.charAt(0) === '0' ? day.charAt(1) : day,
|
||||
time: ts.substring(8, 10) + colon +
|
||||
ts.substring(10, 12) + colon +
|
||||
|
175
pywb/static/search.js
Normal file
175
pywb/static/search.js
Normal file
@ -0,0 +1,175 @@
|
||||
var dtRE = /^\d{4,14}$/;
|
||||
var didSetWasValidated = false;
|
||||
var showBadDateTimeClass = 'show-optional-bad-input';
|
||||
var filterMods = {
|
||||
'=': 'Contains',
|
||||
'==': 'Matches Exactly',
|
||||
'=~': 'Matches Regex',
|
||||
'=!': 'Does Not Contains',
|
||||
'=!=': 'Is Not',
|
||||
'=!~': 'Does Not Begins With'
|
||||
};
|
||||
|
||||
var elemIds = {
|
||||
filtering: {
|
||||
by: 'filter-by',
|
||||
modifier: 'filter-modifier',
|
||||
expression: 'filter-expression',
|
||||
list: 'filter-list',
|
||||
nothing: 'filtering-nothing',
|
||||
add: 'add-filter',
|
||||
clear: 'clear-filters'
|
||||
},
|
||||
dateTime: {
|
||||
from: 'dt-from',
|
||||
fromBad: 'dt-from-bad',
|
||||
to: 'dt-to',
|
||||
toBad: 'dt-to-bad'
|
||||
},
|
||||
match: 'match-type-select',
|
||||
url: 'search-url',
|
||||
form: 'search-form',
|
||||
resultsNewWindow: 'open-results-new-window'
|
||||
};
|
||||
|
||||
|
||||
function makeCheckDateRangeChecker(dtInputId, dtBadNotice) {
|
||||
var dtInput = document.getElementById(dtInputId);
|
||||
dtInput.onblur = function () {
|
||||
if (dtInput.validity.valid && dtBadNotice.classList.contains(showBadDateTimeClass)) {
|
||||
return dtBadNotice.classList.remove(showBadDateTimeClass);
|
||||
}
|
||||
if (dtInput.validity.valueMissing) {
|
||||
if (dtBadNotice.classList.contains(showBadDateTimeClass)) {
|
||||
dtBadNotice.classList.remove(showBadDateTimeClass);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (dtInput.validity.badInput) {
|
||||
if (!dtBadNotice.classList.contains(showBadDateTimeClass)) {
|
||||
dtBadNotice.classList.add(showBadDateTimeClass);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var validInput = dtRE.test(dtInput.value);
|
||||
if (validInput && dtBadNotice.classList.contains(showBadDateTimeClass)) {
|
||||
dtBadNotice.classList.remove(showBadDateTimeClass);
|
||||
} else if (!validInput) {
|
||||
dtBadNotice.classList.add(showBadDateTimeClass);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createAndAddNoFilter(filterList) {
|
||||
var nothing = document.createElement('li');
|
||||
nothing.innerText = 'No Filter';
|
||||
nothing.id = elemIds.filtering.nothing;
|
||||
filterList.appendChild(nothing);
|
||||
}
|
||||
|
||||
function addFilter(event) {
|
||||
var by = document.getElementById(elemIds.filtering.by).value;
|
||||
if (!by) return;
|
||||
var modifier = document.getElementById(elemIds.filtering.modifier).value;
|
||||
var expr = document.getElementById(elemIds.filtering.expression).value;
|
||||
if (!expr) return;
|
||||
var filterExpr = 'filter' + modifier + by + ':' + expr;
|
||||
var filterList = document.getElementById(elemIds.filtering.list);
|
||||
var filterNothing = document.getElementById(elemIds.filtering.nothing);
|
||||
if (filterNothing) {
|
||||
filterList.removeChild(filterNothing);
|
||||
}
|
||||
var li = document.createElement('li');
|
||||
li.innerText = 'By ' + by[0].toUpperCase() + by.substr(1) + ' ' + filterMods[modifier] + ' ' + expr;
|
||||
li.dataset.filter = filterExpr;
|
||||
var nukeButton = document.createElement('button');
|
||||
nukeButton.type = 'button';
|
||||
nukeButton.role = 'button';
|
||||
nukeButton.className = 'btn btn-outline-danger close';
|
||||
nukeButton.setAttribute('aria-label', 'Remove Filter');
|
||||
var buttonX = document.createElement('span');
|
||||
buttonX.className = 'px-2';
|
||||
buttonX.innerHTML = '×';
|
||||
buttonX.setAttribute('aria-hidden', 'true');
|
||||
nukeButton.appendChild(buttonX);
|
||||
nukeButton.onclick = function () {
|
||||
filterList.removeChild(li);
|
||||
if (filterList.children.length === 0) {
|
||||
createAndAddNoFilter(filterList);
|
||||
}
|
||||
};
|
||||
li.appendChild(nukeButton);
|
||||
filterList.appendChild(li);
|
||||
}
|
||||
|
||||
function clearFilters(event) {
|
||||
if (document.getElementById(elemIds.filtering.nothing)) return;
|
||||
var filterList = document.getElementById(elemIds.filtering.list);
|
||||
while (filterList.firstElementChild) {
|
||||
filterList.firstElementChild.onclick = null;
|
||||
filterList.removeChild(filterList.firstElementChild);
|
||||
}
|
||||
createAndAddNoFilter(filterList);
|
||||
}
|
||||
|
||||
function performQuery(url) {
|
||||
var query = [window.wb_prefix + '*?url=' + url];
|
||||
var filterExpressions = document.getElementById(elemIds.filtering.list).children;
|
||||
if (filterExpressions.length) {
|
||||
for (var i = 0; i < filterExpressions.length; ++i) {
|
||||
var fexpr = filterExpressions[i];
|
||||
if (fexpr.dataset && fexpr.dataset.filter) {
|
||||
query.push(fexpr.dataset.filter.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
var matchType = document.getElementById(elemIds.match).value;
|
||||
if (matchType) {
|
||||
query.push('matchType=' + matchType.trim());
|
||||
}
|
||||
var fromT = document.getElementById(elemIds.dateTime.from).value;
|
||||
if (fromT) {
|
||||
query.push('from=' + fromT.trim());
|
||||
}
|
||||
var toT = document.getElementById(elemIds.dateTime.to).value;
|
||||
if (toT) {
|
||||
query.push('to=' + toT.trim());
|
||||
}
|
||||
var builtQuery = query.join('&');
|
||||
if (document.getElementById(elemIds.resultsNewWindow).checked) {
|
||||
try {
|
||||
var win = window.open(builtQuery);
|
||||
win.focus();
|
||||
} catch (e) {
|
||||
document.location.href = builtQuery;
|
||||
}
|
||||
} else {
|
||||
document.location.href = builtQuery;
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip({
|
||||
container: 'body',
|
||||
delay: { show: 1000 }
|
||||
});
|
||||
makeCheckDateRangeChecker(elemIds.dateTime.from, document.getElementById(elemIds.dateTime.fromBad));
|
||||
makeCheckDateRangeChecker(elemIds.dateTime.to, document.getElementById(elemIds.dateTime.toBad));
|
||||
document.getElementById(elemIds.filtering.add).onclick = addFilter;
|
||||
document.getElementById(elemIds.filtering.clear).onclick = clearFilters;
|
||||
var searchURLInput = document.getElementById(elemIds.url);
|
||||
var form = document.getElementById(elemIds.form);
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
var url = searchURLInput.value;
|
||||
if (!url) {
|
||||
if (!didSetWasValidated) {
|
||||
form.classList.add('was-validated');
|
||||
didSetWasValidated = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
performQuery(url);
|
||||
});
|
||||
});
|
35
pywb/templates/base.html
Normal file
35
pywb/templates/base.html
Normal file
@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ env.pywb_lang | default('en') }}">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8;charset=utf-8"/>
|
||||
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
|
||||
<!-- jquery and bootstrap dependencies query view -->
|
||||
<link rel="stylesheet" href="{{ static_prefix }}/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="{{ static_prefix }}/css/query.css">
|
||||
<link rel="stylesheet" href="{{ static_prefix }}/css/font-awesome.min.css">
|
||||
|
||||
<script src="{{ static_prefix }}/js/jquery-latest.min.js"></script>
|
||||
<script src="{{ static_prefix }}/js/bootstrap.min.js"></script>
|
||||
|
||||
{% block head %}
|
||||
{% include 'head.html' ignore missing %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% block header %}
|
||||
{% include 'header.html' ignore missing %}
|
||||
{% endblock %}
|
||||
|
||||
<section>
|
||||
{% block body %}
|
||||
{% endblock %}
|
||||
</section>
|
||||
|
||||
{% block footer %}
|
||||
{% include 'footer.html' ignore missing %}
|
||||
{% endblock footer %}
|
||||
</body>
|
||||
</html>
|
@ -1,26 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8;charset=utf-8">
|
||||
<title>Pywb Error</title>
|
||||
<link rel="stylesheet" href="{{ static_prefix }}/css/bootstrap.min.css">
|
||||
<script src="{{ static_prefix }}/js/jquery-latest.min.js"></script>
|
||||
<script src="{{ static_prefix }}/js/bootstrap.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Pywb Error{% endblock %}
|
||||
{% block body %}
|
||||
<div class="container text-danger">
|
||||
<div class="row justify-content-center">
|
||||
<h2 class="display-2">Pywb Error</h2>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
{% if err_status == 451 %}
|
||||
<p class="lead">Access Blocked to {{ err_msg }}</p>
|
||||
|
||||
{% elif err_status == 404 and err_details == 'coll_not_found' %}
|
||||
<p>Collection not found: <b>{{ err_msg }}</b></p>
|
||||
|
||||
<p><a href="/">See list of valid collections</a></p>
|
||||
|
||||
{% elif err_status == 404 and err_details == 'static_file_not_found' %}
|
||||
<p>Static file not found: <b>{{ err_msg }}</b></p>
|
||||
|
||||
{% else %}
|
||||
|
||||
<p class="lead">{{ err_msg }}</p>
|
||||
|
||||
{% if err_details %}
|
||||
<p class="lead">Error Details:</p>
|
||||
<pre>{{ err_details }}</pre>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
@ -1,13 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8;charset=utf-8">
|
||||
<title>Pywb Wayback Machine Home Page</title>
|
||||
<link rel="stylesheet" href="{{ static_prefix }}/css/bootstrap.min.css">
|
||||
<script src="{{ static_prefix }}/js/jquery-latest.min.js"></script>
|
||||
<script src="{{ static_prefix }}/js/bootstrap.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<h2 class="display-2">Pywb Wayback Machine</h2>
|
||||
@ -26,5 +18,4 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
@ -1,27 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8;charset=utf-8">
|
||||
<title>URL Not Found</title>
|
||||
<link rel="stylesheet" href="{{ static_prefix }}/css/bootstrap.min.css">
|
||||
<script src="{{ static_prefix }}/js/jquery-latest.min.js"></script>
|
||||
<script src="{{ static_prefix }}/js/bootstrap.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}URL Not Found{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<h2 class="display-2">URL Not Found</h2>
|
||||
<p class="lead">
|
||||
The url <b>{{ url }}</b> could not be found in this collection.
|
||||
<div class="col-12">
|
||||
<h4 class="display-2">URL Not Found</h4>
|
||||
</div>
|
||||
<p>
|
||||
The url <b>{{ url }}</b> could not be found in this collection.
|
||||
</p>
|
||||
{% if wbrequest and wbrequest.env.pywb_proxy_magic and url %}
|
||||
<p>
|
||||
<a href="//select.{{ wbrequest and wbrequest.env.pywb_proxy_magic }}/{{ url }}">
|
||||
Try Different Collection
|
||||
</a>
|
||||
</p>
|
||||
{% if wbrequest and wbrequest.env.pywb_proxy_magic and url %}
|
||||
<p>
|
||||
<a href="//select.{{ wbrequest and wbrequest.env.pywb_proxy_magic }}/{{ url }}">
|
||||
Try Different Collection
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -1,28 +1,50 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8;charset=utf-8">
|
||||
<title>Pywb Query Results</title>
|
||||
<!-- jquery and bootstrap dependencies query view -->
|
||||
<link rel="stylesheet" href="{{ static_prefix }}/css/query.css">
|
||||
<link rel="stylesheet" href="{{ static_prefix }}/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="{{ static_prefix }}/css/font-awesome.min.css">
|
||||
</head>
|
||||
<body>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{{ _('Search Results') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<script src="{{ static_prefix }}/query.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block body %}
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<h4 class="display-4 p-0">Pywb Query Results</h4>
|
||||
<h4 class="display-4 p-0">{{ _('Search Results') }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center mt-1" id="display-query-type-info"></div>
|
||||
</div>
|
||||
<div class="container mt-3 q-display" id="captures"></div>
|
||||
<script src="{{ static_prefix }}/js/jquery-latest.min.js"></script>
|
||||
<script src="{{ static_prefix }}/js/bootstrap.min.js"></script>
|
||||
<script src="{{ static_prefix }}/query.js"></script>
|
||||
<script>
|
||||
var renderCal = new RenderCalendar({ prefix: "{{ prefix }}", staticPrefix: "{{ static_prefix }}" });
|
||||
var text = {
|
||||
months: {
|
||||
'01': "{{ _('January') }}",
|
||||
'02': "{{ _('February') }}",
|
||||
'03': "{{ _('March') }}",
|
||||
'04': "{{ _('April') }}",
|
||||
'05': "{{ _('May') }}",
|
||||
'06': "{{ _('June') }}",
|
||||
'07': "{{ _('July') }}",
|
||||
'08': "{{ _('August') }}",
|
||||
'09': "{{ _('September') }}",
|
||||
'10': "{{ _('October') }}",
|
||||
'11': "{{ _('November') }}",
|
||||
'12': "{{ _('December') }}",
|
||||
},
|
||||
version: "{{ _('capture') }}",
|
||||
versions: "{{ _('captures') }}",
|
||||
result: "{{ _('result') }}",
|
||||
results: "{{ _('results') }}",
|
||||
viewAllCaptures: "{{ _('View All Captures') }}",
|
||||
dateTime: "{{ _('Date Time: ') }}",
|
||||
};
|
||||
|
||||
var renderCal = new RenderCalendar({ prefix: "{{ prefix }}", staticPrefix: "{{ static_prefix }}", text: text });
|
||||
renderCal.init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
@ -1,31 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8;charset=utf-8">
|
||||
<title>Pywb {% if metadata %} {{ metadata.title if metadata.title else coll }} {% else %} {{ coll }} {% endif %}
|
||||
Collection Search Page</title>
|
||||
<link rel="stylesheet" href="{{ static_prefix }}/css/bootstrap.min.css">
|
||||
<style>
|
||||
.inherit-height {
|
||||
height: inherit;
|
||||
}
|
||||
{% extends "base.html" %}
|
||||
|
||||
.filter-list {
|
||||
height: 140px;
|
||||
max-height: 140px;
|
||||
overflow-y: scroll
|
||||
}
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
// TODO: cleanup
|
||||
window.wb_prefix = "{{ wb_prefix }}";
|
||||
</script>
|
||||
<script src="{{ static_prefix }}/search.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
.show-optional-bad-input {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="overflow: hidden">
|
||||
{% block body %}
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<h4 class="display-4">
|
||||
Collection Search Page
|
||||
Collection {{ coll }} Search Page
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
@ -33,12 +21,9 @@
|
||||
<form class="needs-validation" id="search-form" novalidate>
|
||||
<div class="form-row">
|
||||
<div class="col-12">
|
||||
<label for="search-url" class="lead" aria-label="Search For Col">Search the
|
||||
<b>{% if metadata %}
|
||||
{{ metadata.title if metadata.title else coll }}
|
||||
{% else %}
|
||||
{{ coll }}
|
||||
{% endif %}</b> collection:
|
||||
<label for="search-url" class="lead" aria-label="Search For Col">
|
||||
{% set coll_title = metadata.title if metadata and metadata.title else coll %}
|
||||
{% trans %}Search the {{ coll_title }} collection by url:{% endtrans %}
|
||||
</label>
|
||||
<input aria-label="url" aria-required="true" class="form-control form-control-lg" id="search-url"
|
||||
name="search" placeholder="Enter a URL to search for"
|
||||
@ -57,7 +42,7 @@
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<button type="submit" class="btn btn-outline-primary float-right" role="button" aria-label="Search">
|
||||
Search
|
||||
{% trans %}Search{% endtrans %}
|
||||
</button>
|
||||
<button class="btn btn-outline-info float-right mr-3" type="button" role="button"
|
||||
data-toggle="collapse" data-target="#advancedOptions"
|
||||
@ -195,184 +180,4 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<script src="{{ static_prefix }}/js/jquery-latest.min.js"></script>
|
||||
<script src="{{ static_prefix }}/js/bootstrap.min.js"></script>
|
||||
<script>
|
||||
var dtRE = /^\d{4,14}$/;
|
||||
var didSetWasValidated = false;
|
||||
var showBadDateTimeClass = 'show-optional-bad-input';
|
||||
var filterMods = {
|
||||
'=': 'Contains',
|
||||
'==': 'Matches Exactly',
|
||||
'=~': 'Matches Regex',
|
||||
'=!': 'Does Not Contains',
|
||||
'=!=': 'Is Not',
|
||||
'=!~': 'Does Not Begins With'
|
||||
};
|
||||
|
||||
var elemIds = {
|
||||
filtering: {
|
||||
by: 'filter-by',
|
||||
modifier: 'filter-modifier',
|
||||
expression: 'filter-expression',
|
||||
list: 'filter-list',
|
||||
nothing: 'filtering-nothing',
|
||||
add: 'add-filter',
|
||||
clear: 'clear-filters'
|
||||
},
|
||||
dateTime: {
|
||||
from: 'dt-from',
|
||||
fromBad: 'dt-from-bad',
|
||||
to: 'dt-to',
|
||||
toBad: 'dt-to-bad'
|
||||
},
|
||||
match: 'match-type-select',
|
||||
url: 'search-url',
|
||||
form: 'search-form',
|
||||
resultsNewWindow: 'open-results-new-window'
|
||||
};
|
||||
|
||||
|
||||
function makeCheckDateRangeChecker(dtInputId, dtBadNotice) {
|
||||
var dtInput = document.getElementById(dtInputId);
|
||||
dtInput.onblur = function () {
|
||||
if (dtInput.validity.valid && dtBadNotice.classList.contains(showBadDateTimeClass)) {
|
||||
return dtBadNotice.classList.remove(showBadDateTimeClass);
|
||||
}
|
||||
if (dtInput.validity.valueMissing) {
|
||||
if (dtBadNotice.classList.contains(showBadDateTimeClass)) {
|
||||
dtBadNotice.classList.remove(showBadDateTimeClass);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (dtInput.validity.badInput) {
|
||||
if (!dtBadNotice.classList.contains(showBadDateTimeClass)) {
|
||||
dtBadNotice.classList.add(showBadDateTimeClass);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var validInput = dtRE.test(dtInput.value);
|
||||
if (validInput && dtBadNotice.classList.contains(showBadDateTimeClass)) {
|
||||
dtBadNotice.classList.remove(showBadDateTimeClass);
|
||||
} else if (!validInput) {
|
||||
dtBadNotice.classList.add(showBadDateTimeClass);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createAndAddNoFilter(filterList) {
|
||||
var nothing = document.createElement('li');
|
||||
nothing.innerText = 'No Filter';
|
||||
nothing.id = elemIds.filtering.nothing;
|
||||
filterList.appendChild(nothing);
|
||||
}
|
||||
|
||||
function addFilter(event) {
|
||||
var by = document.getElementById(elemIds.filtering.by).value;
|
||||
if (!by) return;
|
||||
var modifier = document.getElementById(elemIds.filtering.modifier).value;
|
||||
var expr = document.getElementById(elemIds.filtering.expression).value;
|
||||
if (!expr) return;
|
||||
var filterExpr = 'filter' + modifier + by + ':' + expr;
|
||||
var filterList = document.getElementById(elemIds.filtering.list);
|
||||
var filterNothing = document.getElementById(elemIds.filtering.nothing);
|
||||
if (filterNothing) {
|
||||
filterList.removeChild(filterNothing);
|
||||
}
|
||||
var li = document.createElement('li');
|
||||
li.innerText = 'By ' + by[0].toUpperCase() + by.substr(1) + ' ' + filterMods[modifier] + ' ' + expr;
|
||||
li.dataset.filter = filterExpr;
|
||||
var nukeButton = document.createElement('button');
|
||||
nukeButton.type = 'button';
|
||||
nukeButton.role = 'button';
|
||||
nukeButton.className = 'btn btn-outline-danger close';
|
||||
nukeButton.setAttribute('aria-label', 'Remove Filter');
|
||||
var buttonX = document.createElement('span');
|
||||
buttonX.className = 'px-2';
|
||||
buttonX.innerHTML = '×';
|
||||
buttonX.setAttribute('aria-hidden', 'true');
|
||||
nukeButton.appendChild(buttonX);
|
||||
nukeButton.onclick = function () {
|
||||
filterList.removeChild(li);
|
||||
if (filterList.children.length === 0) {
|
||||
createAndAddNoFilter(filterList);
|
||||
}
|
||||
};
|
||||
li.appendChild(nukeButton);
|
||||
filterList.appendChild(li);
|
||||
}
|
||||
|
||||
function clearFilters(event) {
|
||||
if (document.getElementById(elemIds.filtering.nothing)) return;
|
||||
var filterList = document.getElementById(elemIds.filtering.list);
|
||||
while (filterList.firstElementChild) {
|
||||
filterList.firstElementChild.onclick = null;
|
||||
filterList.removeChild(filterList.firstElementChild);
|
||||
}
|
||||
createAndAddNoFilter(filterList);
|
||||
}
|
||||
|
||||
function performQuery(url) {
|
||||
var query = ['{{ wb_prefix }}*?url=' + url];
|
||||
var filterExpressions = document.getElementById(elemIds.filtering.list).children;
|
||||
if (filterExpressions.length) {
|
||||
for (var i = 0; i < filterExpressions.length; ++i) {
|
||||
var fexpr = filterExpressions[i];
|
||||
if (fexpr.dataset && fexpr.dataset.filter) {
|
||||
query.push(fexpr.dataset.filter.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
var matchType = document.getElementById(elemIds.match).value;
|
||||
if (matchType) {
|
||||
query.push('matchType=' + matchType.trim());
|
||||
}
|
||||
var fromT = document.getElementById(elemIds.dateTime.from).value;
|
||||
if (fromT) {
|
||||
query.push('from=' + fromT.trim());
|
||||
}
|
||||
var toT = document.getElementById(elemIds.dateTime.to).value;
|
||||
if (toT) {
|
||||
query.push('to=' + toT.trim());
|
||||
}
|
||||
var builtQuery = query.join('&');
|
||||
if (document.getElementById(elemIds.resultsNewWindow).checked) {
|
||||
try {
|
||||
var win = window.open(builtQuery);
|
||||
win.focus();
|
||||
} catch (e) {
|
||||
document.location.href = builtQuery;
|
||||
}
|
||||
} else {
|
||||
document.location.href = builtQuery;
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip({
|
||||
container: 'body',
|
||||
delay: { show: 1000 }
|
||||
});
|
||||
makeCheckDateRangeChecker(elemIds.dateTime.from, document.getElementById(elemIds.dateTime.fromBad));
|
||||
makeCheckDateRangeChecker(elemIds.dateTime.to, document.getElementById(elemIds.dateTime.toBad));
|
||||
document.getElementById(elemIds.filtering.add).onclick = addFilter;
|
||||
document.getElementById(elemIds.filtering.clear).onclick = clearFilters;
|
||||
var searchURLInput = document.getElementById(elemIds.url);
|
||||
var form = document.getElementById(elemIds.form);
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
var url = searchURLInput.value;
|
||||
if (!url) {
|
||||
if (!didSetWasValidated) {
|
||||
form.classList.add('was-validated');
|
||||
didSetWasValidated = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
performQuery(url);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
@ -341,14 +341,14 @@ class TestManagedColls(CollsDirMixin, BaseConfigTest):
|
||||
|
||||
with open(filename, 'r+b') as fh:
|
||||
buf = fh.read()
|
||||
buf = buf.replace(b'</html>', b'Custom Test Homepage</html>')
|
||||
buf = buf.replace(b'Pywb Wayback Machine', b'Custom Test Homepage')
|
||||
fh.seek(0)
|
||||
fh.write(buf)
|
||||
|
||||
resp = self.testapp.get('/')
|
||||
resp.charset = 'utf-8'
|
||||
assert resp.content_type == 'text/html'
|
||||
assert 'Custom Test Homepage</html>' in resp.text, resp.text
|
||||
assert 'Custom Test Homepage' in resp.text, resp.text
|
||||
|
||||
@patch('pywb.manager.manager.get_input', lambda x: 'y')
|
||||
def test_add_template_input_yes(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user