mirror of https://github.com/kiwix/libkiwix.git
added filter functionality
This commit is contained in:
parent
3a4e8303a0
commit
bb92f26b60
|
@ -304,7 +304,7 @@ InternalServer::get_matching_if_none_match_etag(const RequestContext& r) const
|
||||||
|
|
||||||
std::unique_ptr<Response> InternalServer::build_homepage(const RequestContext& request)
|
std::unique_ptr<Response> InternalServer::build_homepage(const RequestContext& request)
|
||||||
{
|
{
|
||||||
return ContentResponse::build(*this, RESOURCE::templates::index_html, get_default_data(), "text/html; charset=utf-8");
|
return ContentResponse::build(*this, RESOURCE::templates::index_html, get_default_data(), "text/html; charset=utf-8", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Response> InternalServer::handle_meta(const RequestContext& request)
|
std::unique_ptr<Response> InternalServer::handle_meta(const RequestContext& request)
|
||||||
|
|
|
@ -106,7 +106,7 @@ class InternalServer {
|
||||||
std::string m_server_id;
|
std::string m_server_id;
|
||||||
|
|
||||||
friend std::unique_ptr<Response> Response::build(const InternalServer& server);
|
friend std::unique_ptr<Response> Response::build(const InternalServer& server);
|
||||||
friend std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype);
|
friend std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool isHomePage);
|
||||||
friend std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item);
|
friend std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item);
|
||||||
friend std::unique_ptr<Response> Response::build_500(const InternalServer& server, const std::string& msg);
|
friend std::unique_ptr<Response> Response::build_500(const InternalServer& server, const std::string& msg);
|
||||||
|
|
||||||
|
|
|
@ -349,21 +349,21 @@ ContentResponse::ContentResponse(const std::string& root, bool verbose, bool wit
|
||||||
add_header(MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType);
|
add_header(MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype)
|
std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool isHomePage)
|
||||||
{
|
{
|
||||||
return std::unique_ptr<ContentResponse>(new ContentResponse(
|
return std::unique_ptr<ContentResponse>(new ContentResponse(
|
||||||
server.m_root,
|
server.m_root,
|
||||||
server.m_verbose.load(),
|
server.m_verbose.load(),
|
||||||
server.m_withTaskbar,
|
server.m_withTaskbar && !isHomePage,
|
||||||
server.m_withLibraryButton,
|
server.m_withLibraryButton,
|
||||||
server.m_blockExternalLinks,
|
server.m_blockExternalLinks,
|
||||||
content,
|
content,
|
||||||
mimetype));
|
mimetype));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& template_str, kainjow::mustache::data data, const std::string& mimetype) {
|
std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& template_str, kainjow::mustache::data data, const std::string& mimetype, bool isHomePage) {
|
||||||
auto content = render_template(template_str, data);
|
auto content = render_template(template_str, data);
|
||||||
return ContentResponse::build(server, content, mimetype);
|
return ContentResponse::build(server, content, mimetype, isHomePage);
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange) :
|
ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange) :
|
||||||
|
|
|
@ -79,8 +79,8 @@ class Response {
|
||||||
class ContentResponse : public Response {
|
class ContentResponse : public Response {
|
||||||
public:
|
public:
|
||||||
ContentResponse(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton, bool blockExternalLinks, const std::string& content, const std::string& mimetype);
|
ContentResponse(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton, bool blockExternalLinks, const std::string& content, const std::string& mimetype);
|
||||||
static std::unique_ptr<ContentResponse> build(const InternalServer& server, const std::string& content, const std::string& mimetype);
|
static std::unique_ptr<ContentResponse> build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool isHomePage = false);
|
||||||
static std::unique_ptr<ContentResponse> build(const InternalServer& server, const std::string& template_str, kainjow::mustache::data data, const std::string& mimetype);
|
static std::unique_ptr<ContentResponse> build(const InternalServer& server, const std::string& template_str, kainjow::mustache::data data, const std::string& mimetype, bool isHomePage = false);
|
||||||
|
|
||||||
void set_taskbar(const std::string& bookName, const std::string& bookTitle);
|
void set_taskbar(const std::string& bookName, const std::string& bookTitle);
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,9 @@ skin/jquery-ui/jquery-ui.theme.min.css
|
||||||
skin/jquery-ui/jquery-ui.min.css
|
skin/jquery-ui/jquery-ui.min.css
|
||||||
skin/caret.png
|
skin/caret.png
|
||||||
skin/taskbar.js
|
skin/taskbar.js
|
||||||
|
skin/langList.json
|
||||||
|
skin/categoryList.json
|
||||||
|
skin/isotope.pkgd.min.js
|
||||||
skin/index.js
|
skin/index.js
|
||||||
skin/taskbar.css
|
skin/taskbar.css
|
||||||
skin/block_external.js
|
skin/block_external.js
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"other": "Other",
|
||||||
|
"gutenberg": "Gutenberg",
|
||||||
|
"mooc": "Mooc",
|
||||||
|
"phet": "Phet",
|
||||||
|
"psiram": "Psiram",
|
||||||
|
"stack_exchange": "Stack Exchange",
|
||||||
|
"ted": "Ted",
|
||||||
|
"vikidia": "Vikidia",
|
||||||
|
"wikibooks": "Wikibooks",
|
||||||
|
"wikinews": "Wikinews",
|
||||||
|
"wikipedia": "Wikipedia",
|
||||||
|
"wikiquote": "Wikiquote",
|
||||||
|
"wikisource": "Wikisource",
|
||||||
|
"wikiversity": "Wikiversity",
|
||||||
|
"wikivoyage": "Wikivoyage",
|
||||||
|
"wiktionary": "Wiktionary"
|
||||||
|
}
|
|
@ -1,16 +1,19 @@
|
||||||
(function() {
|
(function() {
|
||||||
const root = $(`link[type='root']`).attr('href');
|
const root = $(`link[type='root']`).attr('href');
|
||||||
|
let isFetching = false;
|
||||||
|
let iso;
|
||||||
|
let bookMap = {};
|
||||||
const incrementalLoadingParams = {
|
const incrementalLoadingParams = {
|
||||||
start: 0,
|
start: 0,
|
||||||
count: viewPortToCount()
|
count: viewPortToCount()
|
||||||
};
|
};
|
||||||
let isFetching = false;
|
let params = new URLSearchParams(window.location.search);
|
||||||
let timer;
|
const filterTypes = ['lang', 'category', 'q'];
|
||||||
|
|
||||||
function queryUrlBuilder() {
|
function queryUrlBuilder() {
|
||||||
let url = `${root}/catalog/search?`;
|
let url = `${root}/catalog/search?`;
|
||||||
url += Object.keys(incrementalLoadingParams).map(key => `${key}=${incrementalLoadingParams[key]}`).join("&");
|
url += Object.keys(incrementalLoadingParams).map(key => `${key}=${incrementalLoadingParams[key]}`).join("&");
|
||||||
return url;
|
return (url + (params.toString() ? `&${params.toString()}` : ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
function viewPortToCount(){
|
function viewPortToCount(){
|
||||||
|
@ -21,41 +24,109 @@
|
||||||
return node.querySelector(query).innerHTML;
|
return node.querySelector(query).innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateBookHtml(book) {
|
function generateBookHtml(book, sort = false) {
|
||||||
const link = book.querySelector('link').getAttribute('href');
|
const link = book.querySelector('link').getAttribute('href');
|
||||||
const title = getInnerHtml(book, 'title');
|
const title = getInnerHtml(book, 'title');
|
||||||
const description = getInnerHtml(book, 'summary');
|
const description = getInnerHtml(book, 'summary');
|
||||||
|
const linkTag = document.createElement('a');
|
||||||
const id = getInnerHtml(book, 'id');
|
const id = getInnerHtml(book, 'id');
|
||||||
const iconUrl = getInnerHtml(book, 'icon');
|
const iconUrl = getInnerHtml(book, 'icon');
|
||||||
const articleCount = getInnerHtml(book, 'articleCount');
|
const articleCount = getInnerHtml(book, 'articleCount');
|
||||||
const mediaCount = getInnerHtml(book, 'mediaCount');
|
const mediaCount = getInnerHtml(book, 'mediaCount');
|
||||||
|
linkTag.setAttribute('class', 'book');
|
||||||
|
linkTag.setAttribute('data-id', id);
|
||||||
|
linkTag.setAttribute('href', link);
|
||||||
|
if (sort) {
|
||||||
|
linkTag.setAttribute('data-idx', bookMap[id]);
|
||||||
|
}
|
||||||
|
|
||||||
return `<a href='${link}' data-id='${id}'><div class='book'>
|
linkTag.innerHTML = `<div class='book__background' style="background-image: url('${iconUrl}');">
|
||||||
<div class='book__background' style="background-image: url('${iconUrl}');">
|
|
||||||
<div class='book__title' title='${title}'>${title}</div>
|
<div class='book__title' title='${title}'>${title}</div>
|
||||||
<div class='book__description' title='${description}'>${description}</div>
|
<div class='book__description' title='${description}'>${description}</div>
|
||||||
<div class='book__info'>${articleCount} articles, ${mediaCount} medias</div>
|
<div class='book__info'>${articleCount} articles, ${mediaCount} medias</div>
|
||||||
</div>
|
</div>`;
|
||||||
</div></a>`;
|
return linkTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadAndDisplayBooks() {
|
async function loadBooks() {
|
||||||
if (isFetching) return;
|
return await fetch(queryUrlBuilder()).then(async (resp) => {
|
||||||
isFetching = true;
|
|
||||||
fetch(queryUrlBuilder()).then(async (resp) => {
|
|
||||||
const data = new window.DOMParser().parseFromString(await resp.text(), 'application/xml');
|
const data = new window.DOMParser().parseFromString(await resp.text(), 'application/xml');
|
||||||
const books = data.querySelectorAll('entry');
|
const books = data.querySelectorAll('entry');
|
||||||
let bookHtml = '';
|
books.forEach((book, idx) => {
|
||||||
books.forEach((book) => {bookHtml += generateBookHtml(book)});
|
bookMap[getInnerHtml(book, 'id')] = idx;
|
||||||
document.querySelector('.book__list').innerHTML += bookHtml;
|
});
|
||||||
incrementalLoadingParams.start += books.length;
|
incrementalLoadingParams.start += books.length;
|
||||||
if (books.length < incrementalLoadingParams.count) {
|
if (books.length < incrementalLoadingParams.count) {
|
||||||
incrementalLoadingParams.count = 0;
|
incrementalLoadingParams.count = 0;
|
||||||
}
|
}
|
||||||
isFetching = false;
|
return books;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadAndDisplayOptions(nodeQuery, query) {
|
||||||
|
// currently taking an array in place of query, will replace it with query while fetching data from backend later on.
|
||||||
|
await fetch(query)
|
||||||
|
.then(async (resp) => {
|
||||||
|
const data = await resp.json();
|
||||||
|
Object.keys(data).forEach((option) => {
|
||||||
|
document.querySelector(nodeQuery).innerHTML += `<option value='${option}'>${data[option]}</option>`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadAndDisplayBooks(sort = false) {
|
||||||
|
if (isFetching) return;
|
||||||
|
isFetching = true;
|
||||||
|
let books = await loadBooks();
|
||||||
|
const booksToFilter = new Set();
|
||||||
|
const booksToDelete = new Set();
|
||||||
|
iso.arrange({
|
||||||
|
filter: function (idx, elem) {
|
||||||
|
const id = elem.getAttribute('data-id');
|
||||||
|
const retVal = bookMap.hasOwnProperty(id);
|
||||||
|
if (retVal) {
|
||||||
|
booksToFilter.add(id);
|
||||||
|
if (sort) {
|
||||||
|
elem.setAttribute('data-idx', bookMap[id]);
|
||||||
|
iso.updateSortData(elem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
booksToDelete.add(elem);
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
books = [...books].filter((book) => {return !booksToFilter.has(getInnerHtml(book, 'id'))});
|
||||||
|
booksToDelete.forEach(book => {iso.remove(book);});
|
||||||
|
books.forEach((book) => {iso.insert(generateBookHtml(book, sort))});
|
||||||
|
isFetching = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function filterBooks(filterType, filterValue) {
|
||||||
|
isFetching = false;
|
||||||
|
incrementalLoadingParams.start = 0;
|
||||||
|
incrementalLoadingParams.count = viewPortToCount();
|
||||||
|
bookMap = {};
|
||||||
|
params = new URLSearchParams(window.location.search);
|
||||||
|
if (!filterValue) {
|
||||||
|
params.delete(filterType);
|
||||||
|
} else {
|
||||||
|
params.set(filterType, filterValue);
|
||||||
|
}
|
||||||
|
window.history.pushState({}, null, `${window.location.href.split('?')[0]}?${params.toString()}`);
|
||||||
|
await loadAndDisplayBooks(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('popstate', async () => {
|
||||||
|
bookMap = {};
|
||||||
|
isFetching = false;
|
||||||
|
incrementalLoadingParams.start = 0;
|
||||||
|
incrementalLoadingParams.count = viewPortToCount();
|
||||||
|
params = new URLSearchParams(window.location.search);
|
||||||
|
filterTypes.forEach(key => {document.getElementsByName(key)[0].value = params.get(key) || ''});
|
||||||
|
loadAndDisplayBooks(true);
|
||||||
|
});
|
||||||
|
|
||||||
async function loadSubset() {
|
async function loadSubset() {
|
||||||
if (incrementalLoadingParams.count && window.innerHeight + window.scrollY >= document.body.offsetHeight) {
|
if (incrementalLoadingParams.count && window.innerHeight + window.scrollY >= document.body.offsetHeight) {
|
||||||
loadAndDisplayBooks();
|
loadAndDisplayBooks();
|
||||||
|
@ -73,6 +144,26 @@
|
||||||
window.addEventListener('scroll', loadSubset);
|
window.addEventListener('scroll', loadSubset);
|
||||||
|
|
||||||
window.onload = async () => {
|
window.onload = async () => {
|
||||||
|
iso = new Isotope( '.book__list', {
|
||||||
|
itemSelector: '.book',
|
||||||
|
getSortData:{
|
||||||
|
weight: function( itemElem ) {
|
||||||
|
const index = itemElem.getAttribute('data-idx');
|
||||||
|
return index ? parseInt(index) : Infinity;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sortBy: 'weight'
|
||||||
|
});
|
||||||
loadAndDisplayBooks();
|
loadAndDisplayBooks();
|
||||||
|
loadAndDisplayOptions('#languageFilter', `${root}/skin/langList.json`);
|
||||||
|
loadAndDisplayOptions('#categoryFilter', `${root}/skin/categoryList.json`);
|
||||||
|
for (const key of params.keys()) {
|
||||||
|
document.getElementsByName(key)[0].value = params.get(key);
|
||||||
|
}
|
||||||
|
filterTypes.forEach((filter) => {
|
||||||
|
const filterTag = document.getElementsByName(filter)[0];
|
||||||
|
filterTag.addEventListener('change', () => {filterBooks(filterTag.name, filterTag.value)});
|
||||||
|
});
|
||||||
|
document.getElementById('kiwixSearchForm').onsubmit = (event) => {event.preventDefault()}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,124 @@
|
||||||
|
{
|
||||||
|
"aar": "Afar",
|
||||||
|
"afr": "Afrikaans",
|
||||||
|
"aka": "Akan",
|
||||||
|
"amh": "Amharic",
|
||||||
|
"ara": "Arabic",
|
||||||
|
"asm": "Assamese",
|
||||||
|
"aze": "Azerbaijani",
|
||||||
|
"bak": "Bashkir",
|
||||||
|
"bel": "Belarusian",
|
||||||
|
"bul": "Bulgarian",
|
||||||
|
"bam": "Bambara",
|
||||||
|
"ben": "Bengali",
|
||||||
|
"bod": "Tibetan",
|
||||||
|
"bre": "Breton",
|
||||||
|
"bos": "Bosnian",
|
||||||
|
"cat": "Catalan",
|
||||||
|
"che": "Chechen",
|
||||||
|
"cos": "Corsican",
|
||||||
|
"ces": "Czech",
|
||||||
|
"chv": "Chuvash",
|
||||||
|
"cym": "Welsh",
|
||||||
|
"dan": "Danish",
|
||||||
|
"deu": "German",
|
||||||
|
"dzo": "Dzongkha",
|
||||||
|
"ewe": "Ewe",
|
||||||
|
"eng": "English",
|
||||||
|
"spa": "Spanish",
|
||||||
|
"est": "Estonian",
|
||||||
|
"eus": "Basque",
|
||||||
|
"fas": "Persian",
|
||||||
|
"ful": "Fulah",
|
||||||
|
"fin": "Finnish",
|
||||||
|
"fao": "Faroese",
|
||||||
|
"fra": "French",
|
||||||
|
"gle": "Irish",
|
||||||
|
"glg": "Galician",
|
||||||
|
"grn": "Guarani",
|
||||||
|
"guj": "Gujarati",
|
||||||
|
"glv": "Manx",
|
||||||
|
"hau": "Hausa",
|
||||||
|
"heb": "Hebrew",
|
||||||
|
"hin": "Hindi",
|
||||||
|
"hrv": "Croatian",
|
||||||
|
"hun": "Hungarian",
|
||||||
|
"hye": "Armenian",
|
||||||
|
"ind": "Indonesian",
|
||||||
|
"ibo": "Igbo",
|
||||||
|
"isl": "Icelandic",
|
||||||
|
"ita": "Italian",
|
||||||
|
"iku": "Inuktitut",
|
||||||
|
"jpn": "Japanese",
|
||||||
|
"jav": "Javanese",
|
||||||
|
"kat": "Georgian",
|
||||||
|
"kik": "Kikuyu",
|
||||||
|
"kaz": "Kazakh",
|
||||||
|
"khm": "Khmer",
|
||||||
|
"kan": "Kannada",
|
||||||
|
"kor": "Korean",
|
||||||
|
"kas": "Kashmiri",
|
||||||
|
"kur": "Kurdish",
|
||||||
|
"cor": "Cornish",
|
||||||
|
"kir": "Kirghiz",
|
||||||
|
"ltz": "Luxembourgish",
|
||||||
|
"lug": "Ganda",
|
||||||
|
"lin": "Lingala",
|
||||||
|
"lao": "Lao",
|
||||||
|
"lit": "Lithuanian",
|
||||||
|
"lav": "Latvian",
|
||||||
|
"mlg": "Malagasy",
|
||||||
|
"mri": "Maori",
|
||||||
|
"mkd": "Macedonian",
|
||||||
|
"mal": "Malayalam",
|
||||||
|
"mon": "Mongolian",
|
||||||
|
"mar": "Marathi",
|
||||||
|
"mlt": "Maltese",
|
||||||
|
"mya": "Burmese",
|
||||||
|
"nld": "Dutch",
|
||||||
|
"nya": "Nyanja",
|
||||||
|
"orm": "Oromo",
|
||||||
|
"pol": "Polish",
|
||||||
|
"por": "Portuguese",
|
||||||
|
"que": "Quechua",
|
||||||
|
"roh": "Romansh",
|
||||||
|
"run": "Rundi",
|
||||||
|
"ron": "Romanian",
|
||||||
|
"rus": "Russian",
|
||||||
|
"kin": "Kinyarwanda",
|
||||||
|
"san": "Sanskrit",
|
||||||
|
"snd": "Sindhi",
|
||||||
|
"sag": "Sango",
|
||||||
|
"sin": "Sinhala",
|
||||||
|
"slk": "Slovak",
|
||||||
|
"slv": "Slovenian",
|
||||||
|
"sna": "Shona",
|
||||||
|
"som": "Somali",
|
||||||
|
"sqi": "Albanian",
|
||||||
|
"srp": "Serbian",
|
||||||
|
"ssw": "Swati",
|
||||||
|
"swe": "Swedish",
|
||||||
|
"tam": "Tamil",
|
||||||
|
"tel": "Telugu",
|
||||||
|
"tgk": "Tajik",
|
||||||
|
"tha": "Thai",
|
||||||
|
"tir": "Tigrinya",
|
||||||
|
"tuk": "Turkmen",
|
||||||
|
"fil": "Filipino",
|
||||||
|
"tsn": "Tswana",
|
||||||
|
"tur": "Turkish",
|
||||||
|
"tso": "Tsonga",
|
||||||
|
"tat": "Tatar",
|
||||||
|
"uig": "Uighur",
|
||||||
|
"ukr": "Ukrainian",
|
||||||
|
"urd": "Urdu",
|
||||||
|
"uzb": "Uzbek",
|
||||||
|
"ven": "Venda",
|
||||||
|
"vie": "Vietnamese",
|
||||||
|
"wln": "Walloon",
|
||||||
|
"wol": "Wolof",
|
||||||
|
"xho": "Xhosa",
|
||||||
|
"yor": "Yoruba",
|
||||||
|
"zho": "Chinese",
|
||||||
|
"zul": "Zulu"
|
||||||
|
}
|
|
@ -49,7 +49,27 @@
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
background-color: #f1f1f1;
|
background-color: #f1f1f1;
|
||||||
box-shadow: 2px 2px 5px 0px #ccc;
|
box-shadow: 2px 2px 5px 0 #ccc;
|
||||||
|
}
|
||||||
|
#kiwixfooter {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
.kiwixHomeNavbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.kiwixFilter {
|
||||||
|
margin: 8px 10px;
|
||||||
|
}
|
||||||
|
.kiwixSearchForm {
|
||||||
|
margin: 8px 10px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.kiwixHomeBody {
|
||||||
|
padding: 0 125px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.book:hover {
|
.book:hover {
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
|
@ -70,7 +90,7 @@
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
}
|
}
|
||||||
.book__description {
|
.book__description {
|
||||||
padding: 5px 55px 5px 0px;
|
padding: 5px 55px 5px 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -83,14 +103,32 @@
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
}
|
}
|
||||||
|
a:link {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:visited {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="text/javascript" src="{{root}}/skin/index.js" async></script>
|
<script src="{{root}}/skin/isotope.pkgd.min.js" defer></script>
|
||||||
|
<script type="text/javascript" src="{{root}}/skin/index.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="kiwix">
|
<body class="kiwix">
|
||||||
<div class="kiwix">
|
<div class='kiwixHomeNavbar'>
|
||||||
|
<select name="lang" id="languageFilter" class='kiwixFilter'>
|
||||||
|
<option value="" selected>All languages</option>
|
||||||
|
</select>
|
||||||
|
<select name="category" id="categoryFilter" class='kiwixFilter'>
|
||||||
|
<option value="" selected>ALl categories</option>
|
||||||
|
</select>
|
||||||
|
<form id='kiwixSearchForm' class='kiwixSearchForm'>
|
||||||
|
<input type="text" name="q" id="searchFilter" class='kiwixSearch'>
|
||||||
|
<input type="submit" value="Submit"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="kiwixHomeBody">
|
||||||
<div class="book__list"></div>
|
<div class="book__list"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="kiwixfooter">Powered by <a href="https://kiwix.org">Kiwix</a></div>
|
<div id="kiwixfooter">Powered by <a href="https://kiwix.org">Kiwix</a></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue