datasette-search-all/datasette_search_all/templates/search_all.html
{% extends "base.html" %}
{% block title %}Search{% if q %}: {{ q }}{% else %} all tables{% endif %}{% endblock %}
{% block extra_head %}
<style type="text/css">
.search-all-loading a {
padding-left: 0.5em;
}
.search-all-loading:before {
content: " ";
opacity: 0.5;
display: inline-block;
width: 0.4em;
height: 0.4em;
border-radius: 50%;
border: 4px solid black;
border-color: black transparent black transparent;
animation: rotate-360 1.2s linear infinite;
}
@keyframes rotate-360 {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
{% endblock %}
{% block content %}
<h1>Search all tables</h1>
{% if not searchable_tables %}
<p>There are no tables that have been configured for search.</p>
{% else %}
<form action="{{ urls.path("/-/search") }}" method="get">
<p>
<input type="search" name="q" value="{{ q }}" id="search-all-q">
<input type="submit" value="Search">
</p>
</form>
{% endif %}
<div id="search-all-results" style="margin-top: 1em;">
{% if q and searchable_tables %}
<ul>
{% for searchable_table in searchable_tables %}
<li data-searchable-url="{{ searchable_table.url }}"><a href="{{ searchable_table.url }}?_search={{ q|urlencode }}">Search {{ searchable_table.database }}: {{ searchable_table.table }} for "{{ q }}"</a></li>
{% endfor %}
</ul>
{% endif %}
</div>
<div id="search-all-no-results"></div>
<script>
var NUM_RESULTS = 5;
var searchable_tables = {{ searchable_tables_json|safe }};
var q = document.getElementById("search-all-q");
var search_results = document.getElementById("search-all-results");
var search_no_results = document.getElementById("search-all-no-results");
function isUrl(s) {
let url;
try {
url = new URL(s);
} catch (_) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:";
}
function htmlEscape(html) {
return html.replace(
/&/g, '&'
).replace(
/>/g, '>'
).replace(
/</g, '<'
).replace(
/"/g, '"'
).replace(
/'/g, '''
);
}
class Safe extends String {}
function safe(s) {
if (!(s instanceof Safe)) {
return new Safe(s);
} else {
return s;
}
}
var autoescape = (fragments, ...inserts) => safe(fragments.map(
(fragment, i) => {
var insert = (inserts[i] || '');
if (!(insert instanceof Safe)) {
insert = htmlEscape(insert.toString());
}
return fragment + insert;
}
).join(''));
function displayCell(cell) {
// cell is either a value or a {"value:", "label": } object
let value;
if (cell && typeof(cell.label) !== "undefined") {
value = cell.label || cell.value;
} else {
value = cell;
}
if (isUrl(value)) {
return safe(`<a href="${htmlEscape(value)}">${htmlEscape(value)}</a>`);
}
return value;
}
function displayResults(data, base_url) {
var rows = data.rows;
var database = data.database;
var table = data.table;
var columns = [];
if (data.rows.length) {
columns = Object.keys(data.rows[0]);
}
var count = data.filtered_table_rows_count || data.count;
var ths = safe(columns.map(c => autoescape`<th>${c}</th>`).join(""));
var tr_rows = safe(rows.map(row => autoescape`<tr>${safe(columns.map(column => autoescape`<td>${displayCell(row[column])}</td>`).join(""))}</tr>`).join(""));
var view_more = '';
var search_url = `${base_url}?_search=${encodeURIComponent(q.value)}`;
if (count > NUM_RESULTS) {
var more_count = count - NUM_RESULTS;
view_more = autoescape`<p><a href="${search_url}">${Number(more_count).toLocaleString()} more result${more_count == 1 ? '' : 's'}</a></p>`;
}
var html, div;
if (count) {
html = autoescape`
<h2><a href="${search_url}">${Number(count).toLocaleString()} result${count == 1 ? '' : 's'}</a> in ${database}: ${table}</h2>
<div style="overflow: auto">
<table>
<tr>${ths}</tr>
${tr_rows}
</table>
${view_more}
</div>
`;
div = document.createElement('div');
div.innerHTML = html;
search_results.appendChild(div);
} else {
search_no_results.innerHTML += autoescape`
<p>No results in <a href="${base_url}">${database}: ${table}</a></p>
`;
}
}
if (q.value) {
searchable_tables.forEach(item => {
var base_url = item.url;
var li = document.querySelector(`[data-searchable-url="${base_url}"]`);
li.classList.add('search-all-loading');
var json_url = `${item.url_json}?_shape=objects&_labels=on&_extra=count&_extra=database&_extra=table&_size=${NUM_RESULTS}&_search=${encodeURIComponent(q.value)}`;
fetch(json_url).then(r => r.json()).then((data) => {
li.style.display = 'none';
displayResults(data, base_url);
});
});
}
</script>
{% endblock %}