mirror of https://github.com/kiwix/libkiwix.git
commit
e13fed8670
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2023 Nikhil Tanwar <2002nikhiltanwar@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_HTML_DUMPER_H
|
||||
#define KIWIX_HTML_DUMPER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "library_dumper.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
/**
|
||||
* A class to dump Library in HTML format.
|
||||
*/
|
||||
class HTMLDumper : public LibraryDumper
|
||||
{
|
||||
public:
|
||||
HTMLDumper(const Library* library, const NameMapper* NameMapper);
|
||||
~HTMLDumper();
|
||||
|
||||
|
||||
/**
|
||||
* Dump library in HTML
|
||||
*
|
||||
* @return HTML content
|
||||
*/
|
||||
std::string dumpPlainHTML(kiwix::Filter filter) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // KIWIX_HTML_DUMPER_H
|
|
@ -120,6 +120,8 @@ class Filter {
|
|||
Filter& maxSize(size_t size);
|
||||
Filter& query(std::string query, bool partial=true);
|
||||
Filter& name(std::string name);
|
||||
Filter& clearLang();
|
||||
Filter& clearCategory();
|
||||
|
||||
bool hasQuery() const;
|
||||
const std::string& getQuery() const { return _query; }
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright 2023 Nikhil Tanwar <2002nikhiltanwar@gmail.com>
|
||||
* Copyright 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_LIBRARY_DUMPER_H
|
||||
#define KIWIX_LIBRARY_DUMPER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
#include "library.h"
|
||||
#include "name_mapper.h"
|
||||
#include <mustache.hpp>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
/**
|
||||
* A base class to dump Library in various formats.
|
||||
*
|
||||
*/
|
||||
class LibraryDumper
|
||||
{
|
||||
public:
|
||||
LibraryDumper(const Library* library, const NameMapper* NameMapper);
|
||||
~LibraryDumper();
|
||||
|
||||
void setLibraryId(const std::string& id) { this->libraryId = id;}
|
||||
|
||||
/**
|
||||
* Set the root location used when generating url.
|
||||
*
|
||||
* @param rootLocation the root location to use.
|
||||
*/
|
||||
void setRootLocation(const std::string& rootLocation) { this->rootLocation = rootLocation; }
|
||||
|
||||
/**
|
||||
* Set some informations about the search results.
|
||||
*
|
||||
* @param totalResult the total number of results of the search.
|
||||
* @param startIndex the start index of the result.
|
||||
* @param count the number of result of the current set (or page).
|
||||
*/
|
||||
void setOpenSearchInfo(int totalResult, int startIndex, int count);
|
||||
|
||||
/**
|
||||
* Sets user default language
|
||||
*
|
||||
* @param userLang the user language to be set
|
||||
*/
|
||||
void setUserLanguage(std::string userLang) { this->m_userLang = userLang; }
|
||||
|
||||
/**
|
||||
* Get the data of categories
|
||||
*/
|
||||
kainjow::mustache::list getCategoryData() const;
|
||||
|
||||
/**
|
||||
* Get the data of languages
|
||||
*/
|
||||
kainjow::mustache::list getLanguageData() const;
|
||||
|
||||
protected:
|
||||
const kiwix::Library* const library;
|
||||
const kiwix::NameMapper* const nameMapper;
|
||||
std::string libraryId;
|
||||
std::string rootLocation;
|
||||
std::string m_userLang;
|
||||
int m_totalResults;
|
||||
int m_startIndex;
|
||||
int m_count;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KIWIX_LIBRARY_DUMPER_H
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include "library.h"
|
||||
#include "name_mapper.h"
|
||||
#include "library_dumper.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -38,11 +39,10 @@ namespace kiwix
|
|||
* A tool to dump a `Library` into a opds stream.
|
||||
*
|
||||
*/
|
||||
class OPDSDumper
|
||||
class OPDSDumper : public LibraryDumper
|
||||
{
|
||||
public:
|
||||
OPDSDumper() = default;
|
||||
OPDSDumper(Library* library, NameMapper* NameMapper);
|
||||
OPDSDumper(const Library* library, const NameMapper* NameMapper);
|
||||
~OPDSDumper();
|
||||
|
||||
/**
|
||||
|
@ -85,38 +85,6 @@ class OPDSDumper
|
|||
* @return The OPDS feed.
|
||||
*/
|
||||
std::string languagesOPDSFeed() const;
|
||||
|
||||
/**
|
||||
* Set the id of the library.
|
||||
*
|
||||
* @param id the id to use.
|
||||
*/
|
||||
void setLibraryId(const std::string& id) { this->libraryId = id;}
|
||||
|
||||
/**
|
||||
* Set the root location used when generating url.
|
||||
*
|
||||
* @param rootLocation the root location to use.
|
||||
*/
|
||||
void setRootLocation(const std::string& rootLocation) { this->rootLocation = rootLocation; }
|
||||
|
||||
/**
|
||||
* Set some informations about the search results.
|
||||
*
|
||||
* @param totalResult the total number of results of the search.
|
||||
* @param startIndex the start index of the result.
|
||||
* @param count the number of result of the current set (or page).
|
||||
*/
|
||||
void setOpenSearchInfo(int totalResult, int startIndex, int count);
|
||||
|
||||
protected:
|
||||
kiwix::Library* library;
|
||||
kiwix::NameMapper* nameMapper;
|
||||
std::string libraryId;
|
||||
std::string rootLocation;
|
||||
int m_totalResults;
|
||||
int m_startIndex;
|
||||
int m_count;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
#include "html_dumper.h"
|
||||
#include "libkiwix-resources.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include "tools.h"
|
||||
#include "tools/regexTools.h"
|
||||
#include "server/i18n.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
/* Constructor */
|
||||
HTMLDumper::HTMLDumper(const Library* library, const NameMapper* nameMapper)
|
||||
: LibraryDumper(library, nameMapper)
|
||||
{
|
||||
}
|
||||
/* Destructor */
|
||||
HTMLDumper::~HTMLDumper()
|
||||
{
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::string humanFriendlyTitle(std::string title)
|
||||
{
|
||||
std::string humanFriendlyString = replaceRegex(title, "_", " ");
|
||||
humanFriendlyString[0] = toupper(humanFriendlyString[0]);
|
||||
return humanFriendlyString;
|
||||
}
|
||||
|
||||
kainjow::mustache::list getTagList(std::string tags)
|
||||
{
|
||||
const auto tagsList = kiwix::split(tags, ";", true, false);
|
||||
kainjow::mustache::list finalTagList;
|
||||
for (auto tag : tagsList) {
|
||||
if (tag[0] != '_')
|
||||
finalTagList.push_back(kainjow::mustache::object{
|
||||
{"tag", tag}
|
||||
});
|
||||
}
|
||||
return finalTagList;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
std::string HTMLDumper::dumpPlainHTML(kiwix::Filter filter) const
|
||||
{
|
||||
kainjow::mustache::list booksData;
|
||||
const auto filteredBooks = library->filter(filter);
|
||||
const auto searchQuery = filter.getQuery();
|
||||
auto languages = getLanguageData();
|
||||
auto categories = getCategoryData();
|
||||
|
||||
for (auto &category : categories) {
|
||||
const auto categoryName = category.get("name")->string_value();
|
||||
if (categoryName == filter.getCategory()) {
|
||||
category["selected"] = true;
|
||||
}
|
||||
category["hf_name"] = humanFriendlyTitle(categoryName);
|
||||
}
|
||||
|
||||
for (auto &language : languages) {
|
||||
if (language.get("lang_code")->string_value() == filter.getLang()) {
|
||||
language["selected"] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for ( const auto& bookId : filteredBooks ) {
|
||||
const auto bookObj = library->getBookById(bookId);
|
||||
const auto bookTitle = bookObj.getTitle();
|
||||
std::string contentId = "";
|
||||
try {
|
||||
contentId = urlEncode(nameMapper->getNameForId(bookId));
|
||||
} catch (...) {}
|
||||
const auto bookDescription = bookObj.getDescription();
|
||||
const auto langCode = bookObj.getCommaSeparatedLanguages();
|
||||
const auto bookIconUrl = rootLocation + "/catalog/v2/illustration/" + bookId + "/?size=48";
|
||||
const auto tags = bookObj.getTags();
|
||||
const auto downloadAvailable = (bookObj.getUrl() != "");
|
||||
std::string faviconAttr = "style=background-image:url(" + bookIconUrl + ")";
|
||||
|
||||
booksData.push_back(kainjow::mustache::object{
|
||||
{"id", contentId},
|
||||
{"title", bookTitle},
|
||||
{"description", bookDescription},
|
||||
{"langCode", langCode},
|
||||
{"faviconAttr", faviconAttr},
|
||||
{"tagList", getTagList(tags)},
|
||||
{"downloadAvailable", downloadAvailable}
|
||||
});
|
||||
}
|
||||
|
||||
auto getTranslation = i18n::GetTranslatedStringWithMsgId(m_userLang);
|
||||
|
||||
const auto translations = kainjow::mustache::object{
|
||||
getTranslation("search"),
|
||||
getTranslation("download"),
|
||||
getTranslation("count-of-matching-books", {{"COUNT", to_string(filteredBooks.size())}}),
|
||||
getTranslation("book-filtering-all-categories"),
|
||||
getTranslation("book-filtering-all-languages"),
|
||||
getTranslation("powered-by-kiwix-html"),
|
||||
getTranslation("welcome-to-kiwix-server"),
|
||||
getTranslation("preview-book"),
|
||||
getTranslation("welcome-page-overzealous-filter", {{"URL", "?lang="}})
|
||||
};
|
||||
|
||||
return render_template(
|
||||
RESOURCE::templates::no_js_library_page_html,
|
||||
kainjow::mustache::object{
|
||||
{"root", rootLocation},
|
||||
{"books", booksData },
|
||||
{"searchQuery", searchQuery},
|
||||
{"languages", languages},
|
||||
{"categories", categories},
|
||||
{"noResults", filteredBooks.size() == 0},
|
||||
{"translations", translations}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
|
@ -878,6 +878,18 @@ Filter& Filter::name(std::string name)
|
|||
return *this;
|
||||
}
|
||||
|
||||
Filter& Filter::clearLang()
|
||||
{
|
||||
activeFilters &= ~LANG;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Filter& Filter::clearCategory()
|
||||
{
|
||||
activeFilters &= ~CATEGORY;
|
||||
return *this;
|
||||
}
|
||||
|
||||
#define ACTIVE(X) (activeFilters & (X))
|
||||
#define FILTER(TAG, TEST) if (ACTIVE(TAG) && !(TEST)) { return false; }
|
||||
bool Filter::hasQuery() const
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
#include "library_dumper.h"
|
||||
#include "tools/stringTools.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include "tools.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
/* Constructor */
|
||||
LibraryDumper::LibraryDumper(const Library* library, const NameMapper* nameMapper)
|
||||
: library(library),
|
||||
nameMapper(nameMapper)
|
||||
{
|
||||
}
|
||||
/* Destructor */
|
||||
LibraryDumper::~LibraryDumper()
|
||||
{
|
||||
}
|
||||
|
||||
void LibraryDumper::setOpenSearchInfo(int totalResults, int startIndex, int count)
|
||||
{
|
||||
m_totalResults = totalResults;
|
||||
m_startIndex = startIndex,
|
||||
m_count = count;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::map<std::string, std::string> iso639_3 = {
|
||||
{"atj", "atikamekw"},
|
||||
{"azb", "آذربایجان دیلی"},
|
||||
{"bcl", "central bikol"},
|
||||
{"bgs", "tagabawa"},
|
||||
{"bxr", "буряад хэлэн"},
|
||||
{"cbk", "chavacano"},
|
||||
{"cdo", "閩東語"},
|
||||
{"dag", "Dagbani"},
|
||||
{"diq", "dimli"},
|
||||
{"dty", "डोटेली"},
|
||||
{"eml", "emiliân-rumagnōl"},
|
||||
{"fbs", "српскохрватски"},
|
||||
{"hbs", "srpskohrvatski"},
|
||||
{"ido", "ido"},
|
||||
{"kbp", "kabɩyɛ"},
|
||||
{"kld", "Gamilaraay"},
|
||||
{"lbe", "лакку маз"},
|
||||
{"lbj", "ལ་དྭགས་སྐད་"},
|
||||
{"map", "Austronesian"},
|
||||
{"mhr", "марий йылме"},
|
||||
{"mnw", "ဘာသာမန်"},
|
||||
{"myn", "mayan"},
|
||||
{"nah", "nahuatl"},
|
||||
{"nai", "north American Indian"},
|
||||
{"nds", "plattdütsch"},
|
||||
{"nrm", "bhasa narom"},
|
||||
{"olo", "livvi"},
|
||||
{"pih", "Pitcairn-Norfolk"},
|
||||
{"pnb", "Western Panjabi"},
|
||||
{"rmr", "Caló"},
|
||||
{"rmy", "romani shib"},
|
||||
{"roa", "romance languages"},
|
||||
{"twi", "twi"},
|
||||
};
|
||||
|
||||
std::once_flag fillLanguagesFlag;
|
||||
|
||||
void fillLanguagesMap()
|
||||
{
|
||||
for (auto icuLangPtr = icu::Locale::getISOLanguages(); *icuLangPtr != NULL; ++icuLangPtr) {
|
||||
const ICULanguageInfo lang(*icuLangPtr);
|
||||
iso639_3.insert({lang.iso3Code(), lang.selfName()});
|
||||
}
|
||||
}
|
||||
|
||||
std::string getLanguageSelfName(const std::string& lang) {
|
||||
const auto itr = iso639_3.find(lang);
|
||||
if (itr != iso639_3.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
return lang;
|
||||
};
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
kainjow::mustache::list LibraryDumper::getCategoryData() const
|
||||
{
|
||||
const auto now = gen_date_str();
|
||||
kainjow::mustache::list categoryData;
|
||||
for ( const auto& category : library->getBooksCategories() ) {
|
||||
const auto urlencodedCategoryName = urlEncode(category);
|
||||
categoryData.push_back(kainjow::mustache::object{
|
||||
{"name", category},
|
||||
{"urlencoded_name", urlencodedCategoryName},
|
||||
{"updated", now},
|
||||
{"id", gen_uuid(libraryId + "/categories/" + urlencodedCategoryName)}
|
||||
});
|
||||
}
|
||||
return categoryData;
|
||||
}
|
||||
|
||||
kainjow::mustache::list LibraryDumper::getLanguageData() const
|
||||
{
|
||||
const auto now = gen_date_str();
|
||||
kainjow::mustache::list languageData;
|
||||
std::call_once(fillLanguagesFlag, fillLanguagesMap);
|
||||
for ( const auto& langAndBookCount : library->getBooksLanguagesWithCounts() ) {
|
||||
const std::string languageCode = langAndBookCount.first;
|
||||
const int bookCount = langAndBookCount.second;
|
||||
const auto languageSelfName = getLanguageSelfName(languageCode);
|
||||
languageData.push_back(kainjow::mustache::object{
|
||||
{"lang_code", languageCode},
|
||||
{"lang_self_name", languageSelfName},
|
||||
{"book_count", to_string(bookCount)},
|
||||
{"updated", now},
|
||||
{"id", gen_uuid(libraryId + "/languages/" + languageCode)}
|
||||
});
|
||||
}
|
||||
return languageData;
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
|
@ -5,6 +5,8 @@ kiwix_sources = [
|
|||
'manager.cpp',
|
||||
'libxml_dumper.cpp',
|
||||
'opds_dumper.cpp',
|
||||
'html_dumper.cpp',
|
||||
'library_dumper.cpp',
|
||||
'downloader.cpp',
|
||||
'server.cpp',
|
||||
'search_renderer.cpp',
|
||||
|
|
|
@ -30,9 +30,8 @@ namespace kiwix
|
|||
{
|
||||
|
||||
/* Constructor */
|
||||
OPDSDumper::OPDSDumper(Library* library, NameMapper* nameMapper)
|
||||
: library(library),
|
||||
nameMapper(nameMapper)
|
||||
OPDSDumper::OPDSDumper(const Library* library, const NameMapper* nameMapper)
|
||||
: LibraryDumper(library, nameMapper)
|
||||
{
|
||||
}
|
||||
/* Destructor */
|
||||
|
@ -40,13 +39,6 @@ OPDSDumper::~OPDSDumper()
|
|||
{
|
||||
}
|
||||
|
||||
void OPDSDumper::setOpenSearchInfo(int totalResults, int startIndex, int count)
|
||||
{
|
||||
m_totalResults = totalResults;
|
||||
m_startIndex = startIndex,
|
||||
m_count = count;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
@ -133,60 +125,6 @@ BooksData getBooksData(const Library* library, const NameMapper* nameMapper, con
|
|||
return booksData;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> iso639_3 = {
|
||||
{"atj", "atikamekw"},
|
||||
{"azb", "آذربایجان دیلی"},
|
||||
{"bcl", "central bikol"},
|
||||
{"bgs", "tagabawa"},
|
||||
{"bxr", "буряад хэлэн"},
|
||||
{"cbk", "chavacano"},
|
||||
{"cdo", "閩東語"},
|
||||
{"dag", "Dagbani"},
|
||||
{"diq", "dimli"},
|
||||
{"dty", "डोटेली"},
|
||||
{"eml", "emiliân-rumagnōl"},
|
||||
{"fbs", "српскохрватски"},
|
||||
{"hbs", "srpskohrvatski"},
|
||||
{"ido", "ido"},
|
||||
{"kbp", "kabɩyɛ"},
|
||||
{"kld", "Gamilaraay"},
|
||||
{"lbe", "лакку маз"},
|
||||
{"lbj", "ལ་དྭགས་སྐད་"},
|
||||
{"map", "Austronesian"},
|
||||
{"mhr", "марий йылме"},
|
||||
{"mnw", "ဘာသာမန်"},
|
||||
{"myn", "mayan"},
|
||||
{"nah", "nahuatl"},
|
||||
{"nai", "north American Indian"},
|
||||
{"nds", "plattdütsch"},
|
||||
{"nrm", "bhasa narom"},
|
||||
{"olo", "livvi"},
|
||||
{"pih", "Pitcairn-Norfolk"},
|
||||
{"pnb", "Western Panjabi"},
|
||||
{"rmr", "Caló"},
|
||||
{"rmy", "romani shib"},
|
||||
{"roa", "romance languages"},
|
||||
{"twi", "twi"},
|
||||
};
|
||||
|
||||
std::once_flag fillLanguagesFlag;
|
||||
|
||||
void fillLanguagesMap()
|
||||
{
|
||||
for (auto icuLangPtr = icu::Locale::getISOLanguages(); *icuLangPtr != NULL; ++icuLangPtr) {
|
||||
const ICULanguageInfo lang(*icuLangPtr);
|
||||
iso639_3.insert({lang.iso3Code(), lang.selfName()});
|
||||
}
|
||||
}
|
||||
|
||||
std::string getLanguageSelfName(const std::string& lang) {
|
||||
const auto itr = iso639_3.find(lang);
|
||||
if (itr != iso639_3.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
return lang;
|
||||
};
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const std::string& query) const
|
||||
|
@ -240,17 +178,7 @@ std::string OPDSDumper::dumpOPDSCompleteEntry(const std::string& bookId) const
|
|||
std::string OPDSDumper::categoriesOPDSFeed() const
|
||||
{
|
||||
const auto now = gen_date_str();
|
||||
kainjow::mustache::list categoryData;
|
||||
for ( const auto& category : library->getBooksCategories() ) {
|
||||
const auto urlencodedCategoryName = urlEncode(category);
|
||||
categoryData.push_back(kainjow::mustache::object{
|
||||
{"name", category},
|
||||
{"urlencoded_name", urlencodedCategoryName},
|
||||
{"updated", now},
|
||||
{"id", gen_uuid(libraryId + "/categories/" + urlencodedCategoryName)}
|
||||
});
|
||||
}
|
||||
|
||||
kainjow::mustache::list categoryData = getCategoryData();
|
||||
return render_template(
|
||||
RESOURCE::templates::catalog_v2_categories_xml,
|
||||
kainjow::mustache::object{
|
||||
|
@ -265,21 +193,7 @@ std::string OPDSDumper::categoriesOPDSFeed() const
|
|||
std::string OPDSDumper::languagesOPDSFeed() const
|
||||
{
|
||||
const auto now = gen_date_str();
|
||||
kainjow::mustache::list languageData;
|
||||
std::call_once(fillLanguagesFlag, fillLanguagesMap);
|
||||
for ( const auto& langAndBookCount : library->getBooksLanguagesWithCounts() ) {
|
||||
const std::string languageCode = langAndBookCount.first;
|
||||
const int bookCount = langAndBookCount.second;
|
||||
const auto languageSelfName = getLanguageSelfName(languageCode);
|
||||
languageData.push_back(kainjow::mustache::object{
|
||||
{"lang_code", languageCode},
|
||||
{"lang_self_name", languageSelfName},
|
||||
{"book_count", to_string(bookCount)},
|
||||
{"updated", now},
|
||||
{"id", gen_uuid(libraryId + "/languages/" + languageCode)}
|
||||
});
|
||||
}
|
||||
|
||||
kainjow::mustache::list languageData = getLanguageData();
|
||||
return render_template(
|
||||
RESOURCE::templates::catalog_v2_languages_xml,
|
||||
kainjow::mustache::object{
|
||||
|
|
|
@ -69,6 +69,28 @@ private:
|
|||
const std::string m_lang;
|
||||
};
|
||||
|
||||
class GetTranslatedStringWithMsgId
|
||||
{
|
||||
typedef kainjow::mustache::basic_data<std::string> MustacheString;
|
||||
typedef std::pair<std::string, MustacheString> MsgIdAndTranslation;
|
||||
|
||||
public:
|
||||
explicit GetTranslatedStringWithMsgId(const std::string& lang) : m_lang(lang) {}
|
||||
|
||||
MsgIdAndTranslation operator()(const std::string& key) const
|
||||
{
|
||||
return {key, getTranslatedString(m_lang, key)};
|
||||
}
|
||||
|
||||
MsgIdAndTranslation operator()(const std::string& key, const Parameters& params) const
|
||||
{
|
||||
return {key, expandParameterizedString(m_lang, key, params)};
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string m_lang;
|
||||
};
|
||||
|
||||
} // namespace i18n
|
||||
|
||||
struct ParameterizedMessage
|
||||
|
|
|
@ -53,6 +53,7 @@ extern "C" {
|
|||
#include "name_mapper.h"
|
||||
#include "search_renderer.h"
|
||||
#include "opds_dumper.h"
|
||||
#include "html_dumper.h"
|
||||
#include "i18n.h"
|
||||
|
||||
#include <zim/uuid.h>
|
||||
|
@ -627,6 +628,9 @@ std::unique_ptr<Response> InternalServer::handle_request(const RequestContext& r
|
|||
if (isEndpointUrl(url, "search"))
|
||||
return handle_search(request);
|
||||
|
||||
if (isEndpointUrl(url, "nojs"))
|
||||
return handle_no_js(request);
|
||||
|
||||
if (isEndpointUrl(url, "suggest"))
|
||||
return handle_suggest(request);
|
||||
|
||||
|
@ -755,6 +759,73 @@ std::unique_ptr<Response> InternalServer::handle_viewer_settings(const RequestCo
|
|||
return ContentResponse::build(*this, RESOURCE::templates::viewer_settings_js, data, "application/javascript; charset=utf-8");
|
||||
}
|
||||
|
||||
std::string InternalServer::getNoJSDownloadPageHTML(const std::string& bookId, const std::string& userLang) const
|
||||
{
|
||||
const auto book = mp_library->getBookById(bookId);
|
||||
auto bookUrl = kiwix::stripSuffix(book.getUrl(), ".meta4");
|
||||
auto getTranslation = i18n::GetTranslatedStringWithMsgId(userLang);
|
||||
const auto translations = kainjow::mustache::object{
|
||||
getTranslation("download-links-heading", {{"BOOK_TITLE", book.getTitle()}}),
|
||||
getTranslation("download-links-title"),
|
||||
getTranslation("direct-download-link-text"),
|
||||
getTranslation("hash-download-link-text"),
|
||||
getTranslation("magnet-link-text"),
|
||||
getTranslation("torrent-download-link-text")
|
||||
};
|
||||
|
||||
return render_template(
|
||||
RESOURCE::templates::no_js_download_html,
|
||||
kainjow::mustache::object{
|
||||
{"url", bookUrl},
|
||||
{"translations", translations}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_no_js(const RequestContext& request)
|
||||
{
|
||||
const auto url = request.get_url();
|
||||
const auto urlParts = kiwix::split(url, "/", true, false);
|
||||
HTMLDumper htmlDumper(mp_library, mp_nameMapper);
|
||||
htmlDumper.setRootLocation(m_root);
|
||||
htmlDumper.setLibraryId(getLibraryId());
|
||||
auto userLang = request.get_user_language();
|
||||
htmlDumper.setUserLanguage(userLang);
|
||||
std::string content;
|
||||
|
||||
if (urlParts.size() == 1) {
|
||||
auto filter = get_search_filter(request);
|
||||
try {
|
||||
if (request.get_argument("category") == "") {
|
||||
filter.clearCategory();
|
||||
}
|
||||
} catch (...) {}
|
||||
try {
|
||||
if (request.get_argument("lang") == "") {
|
||||
filter.clearLang();
|
||||
}
|
||||
} catch (...) {}
|
||||
content = htmlDumper.dumpPlainHTML(filter);
|
||||
} else if ((urlParts.size() == 3) && (urlParts[1] == "download")) {
|
||||
try {
|
||||
const auto bookId = mp_nameMapper->getIdForName(urlParts[2]);
|
||||
content = getNoJSDownloadPageHTML(bookId, userLang);
|
||||
} catch (const std::out_of_range&) {
|
||||
return HTTP404Response(*this, request)
|
||||
+ urlNotFoundMsg;
|
||||
}
|
||||
} else {
|
||||
return HTTP404Response(*this, request)
|
||||
+ urlNotFoundMsg;
|
||||
}
|
||||
|
||||
return ContentResponse::build(
|
||||
*this,
|
||||
content,
|
||||
"text/html; charset=utf-8"
|
||||
);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
|
|
@ -131,6 +131,7 @@ class InternalServer {
|
|||
std::unique_ptr<Response> handle_catalog_v2_entries(const RequestContext& request, bool partial);
|
||||
std::unique_ptr<Response> handle_catalog_v2_complete_entry(const RequestContext& request, const std::string& entryId);
|
||||
std::unique_ptr<Response> handle_catalog_v2_categories(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_no_js(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_catalog_v2_languages(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_catalog_v2_illustration(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_search(const RequestContext& request);
|
||||
|
@ -155,6 +156,8 @@ class InternalServer {
|
|||
|
||||
std::string getLibraryId() const;
|
||||
|
||||
std::string getNoJSDownloadPageHTML(const std::string& bookId, const std::string& userLang) const;
|
||||
|
||||
private: // types
|
||||
class LockableSuggestionSearcher;
|
||||
typedef ConcurrentCache<SearchInfo, std::shared_ptr<zim::Search>> SearchCache;
|
||||
|
|
|
@ -415,6 +415,17 @@ bool kiwix::startsWith(const std::string& base, const std::string& start)
|
|||
&& std::equal(start.begin(), start.end(), base.begin());
|
||||
}
|
||||
|
||||
std::string kiwix::stripSuffix(const std::string& str, const std::string& suffix)
|
||||
{
|
||||
if (str.size() > suffix.size()) {
|
||||
const auto subStr = str.substr(str.size() - suffix.size(), str.size());
|
||||
if (subStr == suffix) {
|
||||
return str.substr(0, str.size() - suffix.size());
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
std::vector<std::string> kiwix::getTitleVariants(const std::string& title) {
|
||||
std::vector<std::string> variants;
|
||||
variants.push_back(title);
|
||||
|
|
|
@ -93,6 +93,8 @@ std::string extractFromString(const std::string& str);
|
|||
|
||||
bool startsWith(const std::string& base, const std::string& start);
|
||||
|
||||
std::string stripSuffix(const std::string& str, const std::string& suffix);
|
||||
|
||||
std::vector<std::string> getTitleVariants(const std::string& title);
|
||||
} //namespace kiwix
|
||||
#endif
|
||||
|
|
|
@ -37,6 +37,8 @@ templates/catalog_v2_categories.xml
|
|||
templates/catalog_v2_languages.xml
|
||||
templates/url_of_search_results_css
|
||||
templates/viewer_settings.js
|
||||
templates/no_js_library_page.html
|
||||
templates/no_js_download.html
|
||||
opensearchdescription.xml
|
||||
ft_opensearchdescription.xml
|
||||
catalog_v2_searchdescription.xml
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
, "random-page-button-text": "Go to a randomly selected page"
|
||||
, "searchbox-tooltip": "Search '{{BOOK_TITLE}}'"
|
||||
, "confusion-of-tongues": "Two or more books in different languages would participate in search, which may lead to confusing results."
|
||||
, "welcome-page-overzealous-filter": "No result. Would you like to <a href=\"#lang=\">reset filter</a>?"
|
||||
, "welcome-page-overzealous-filter": "No result. Would you like to <a href=\"{{URL}}\">reset filter</a>?"
|
||||
, "powered-by-kiwix-html": "Powered by <a href=\"https://kiwix.org\">Kiwix</a>"
|
||||
, "search": "Search"
|
||||
, "book-filtering-all-categories": "All categories"
|
||||
|
@ -47,4 +47,8 @@
|
|||
, "filter-by-tag": "Filter by tag \"{{TAG}}\""
|
||||
, "stop-filtering-by-tag": "Stop filtering by tag \"{{TAG}}\""
|
||||
, "library-opds-feed-parameterised": "Library OPDS Feed - entries matching {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}"
|
||||
, "welcome-to-kiwix-server": "Welcome to Kiwix Server"
|
||||
, "download-links-heading": "Download links for <b><i>{{BOOK_TITLE}}</i></b>"
|
||||
, "download-links-title": "Download book"
|
||||
, "preview-book": "Preview"
|
||||
}
|
||||
|
|
|
@ -48,5 +48,9 @@
|
|||
"filter-by-tag": "Hint for a link that would load results filtered by a single tag",
|
||||
"stop-filtering-by-tag": "Tooltip for the button that cancels filtering by tag",
|
||||
"library-opds-feed-all-entries": "Hint for the library OPDS feed for all entries",
|
||||
"library-opds-feed-parameterised": "Hint for the library OPDS feed for filtered entries"
|
||||
"library-opds-feed-parameterised": "Hint for the library OPDS feed for filtered entries",
|
||||
"welcome-to-kiwix-server": "Title shown in browser's title bar/page tab",
|
||||
"download-links-heading": "Heading for no-js download page",
|
||||
"download-links-title": "Title for no-js download page",
|
||||
"preview-book": "Tooltip of book-tile leading to the book"
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
, "home-button-text": "[I18N TESTING] Jump to the main page of '{{BOOK_TITLE}}'"
|
||||
, "random-page-button-text": "[I18N TESTING] I am tired of determinism"
|
||||
, "searchbox-tooltip": "[I18N TESTING] Let's search in '{{BOOK_TITLE}}'"
|
||||
, "welcome-page-overzealous-filter": "[I18N TESTING] Nothing found. <a href=\"?lang=\">Reset filter</a>"
|
||||
, "welcome-page-overzealous-filter": "[I18N TESTING] Nothing found. <a href=\"{{URL}}\">Reset filter</a>"
|
||||
, "powered-by-kiwix-html": "[I18N TESTING] Powered by <a href=\"https://kiwix.org\">Kiwix</a> (nominal power: 1.23 kW)"
|
||||
, "search": "[I18N Search TESTING]"
|
||||
, "book-filtering-all-categories": "All [I18N TESTING] categories"
|
||||
|
@ -36,4 +36,8 @@
|
|||
, "filter-by-tag": "Filter [I18N] by [TESTING] tag \"{{TAG}}\""
|
||||
, "stop-filtering-by-tag": "[I18N] Stop filtering [TESTING] by tag \"{{TAG}}\""
|
||||
, "library-opds-feed-parameterised": "[I18N] Library OPDS Feed - [TESTING] entries matching {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}"
|
||||
, "welcome-to-kiwix-server": "[I18N] Welcome to Kiwix Server [TESTING]"
|
||||
, "download-links-heading": "[I18N] Download links for <b><i>{{BOOK_TITLE}}</i></b> [TESTING]"
|
||||
, "download-links-title": "[I18N TESTING]Download book"
|
||||
, "preview-book": "[I18N] Preview [TESTING]"
|
||||
}
|
||||
|
|
|
@ -315,7 +315,7 @@
|
|||
const kiwixHomeBody = document.querySelector('.kiwixHomeBody');
|
||||
const divTag = document.createElement('div');
|
||||
divTag.setAttribute('class', 'noResults');
|
||||
divTag.innerHTML = $t("welcome-page-overzealous-filter");
|
||||
divTag.innerHTML = $t("welcome-page-overzealous-filter", {URL: '#lang='});
|
||||
kiwixHomeBody.append(divTag);
|
||||
kiwixHomeBody.setAttribute('style', 'display: flex; justify-content: center; align-items: center');
|
||||
loader.setAttribute('style', 'position: absolute; top: 50%');
|
||||
|
|
|
@ -44,6 +44,29 @@
|
|||
<script type="text/javascript" src="{{root}}/skin/index.js?KIWIXCACHEID" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<style>
|
||||
.kiwixNav, .kiwixHomeBody, #feedLink, .kiwixfooter {
|
||||
display: none;
|
||||
}
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
.noScriptLinkContainer {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-moz-transform: translateX(-50%) translateY(-50%);
|
||||
-webkit-transform: translateX(-50%) translateY(-50%);
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
font-size: 16px;
|
||||
font-family: roboto;
|
||||
}
|
||||
</style>
|
||||
<div class="noScriptLinkContainer">
|
||||
<span id="noScriptLinkText">This page cannot be accessed if JavaScript is not enabled. Please head over to <a href="{{root}}/nojs">nojs endpoint.</a></span>
|
||||
</div>
|
||||
</noscript>
|
||||
<div class='kiwixNav'>
|
||||
<a href="{{root}}/catalog/v2/entries" id="feedLink">
|
||||
<img src="{{root}}/skin/feed.svg?KIWIXCACHEID"
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{translations.download-links-title}}</title>
|
||||
</head>
|
||||
<style>
|
||||
.downloadLinksTitle {
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div class="downloadLinksTitle">
|
||||
{{{translations.download-links-heading}}}
|
||||
</div>
|
||||
<a href="{{url}}" download>
|
||||
<div>{{translations.direct-download-link-text}}</div>
|
||||
</a>
|
||||
<a href="{{url}}.sha256" download>
|
||||
<div>{{translations.hash-download-link-text}}</div>
|
||||
</a>
|
||||
<a href="{{url}}.magnet" target="_blank">
|
||||
<div>{{translations.magnet-link-text}}</div>
|
||||
</a>
|
||||
<a href="{{url}}.torrent" download>
|
||||
<div>{{translations.torrent-download-link-text}}</div>
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,140 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<link type="root" href="{{root}}">
|
||||
<title>{{translations.welcome-to-kiwix-server}}</title>
|
||||
<link
|
||||
type="text/css"
|
||||
href="{{root}}/skin/index.css?KIWIXCACHEID"
|
||||
rel="Stylesheet"
|
||||
/>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{root}}/skin/favicon/apple-touch-icon.png?KIWIXCACHEID">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{root}}/skin/favicon/favicon-32x32.png?KIWIXCACHEID">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{root}}/skin/favicon/favicon-16x16.png?KIWIXCACHEID">
|
||||
<link rel="manifest" href="{{root}}/skin/favicon/site.webmanifest?KIWIXCACHEID">
|
||||
<link rel="mask-icon" href="{{root}}/skin/favicon/safari-pinned-tab.svg?KIWIXCACHEID" color="#5bbad5">
|
||||
<link rel="shortcut icon" href="{{root}}/skin/favicon/favicon.ico?KIWIXCACHEID">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="msapplication-config" content="{{root}}/skin/favicon/browserconfig.xml?KIWIXCACHEID">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "poppins";
|
||||
src: url("{{root}}/skin/fonts/Poppins.ttf?KIWIXCACHEID") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "roboto";
|
||||
src: url("{{root}}/skin/fonts/Roboto.ttf?KIWIXCACHEID") format("truetype");
|
||||
}
|
||||
|
||||
.book__list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.book__wrapper:hover {
|
||||
transform: scale(1.0);
|
||||
}
|
||||
|
||||
.tag__link {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.book__link__wrapper {
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
|
||||
.book__link {
|
||||
grid-row: 2 / 3;
|
||||
}
|
||||
|
||||
.kiwixHomeBody__results {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
#book__title>a, .book__download a {
|
||||
text-decoration: none;
|
||||
all: unset;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='kiwixNav'>
|
||||
<div class="kiwixNav__filters">
|
||||
<div class="kiwixNav__select">
|
||||
<select name="lang" id="languageFilter" class='kiwixNav__kiwixFilter filter' form="kiwixSearchForm">
|
||||
<option value="" selected>{{translations.book-filtering-all-languages}}</option>
|
||||
{{#languages}}
|
||||
<option value="{{lang_code}}"{{#selected}} selected {{/selected}}>{{lang_self_name}}</option>
|
||||
{{/languages}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="kiwixNav__select">
|
||||
<select name="category" id="categoryFilter" class='kiwixNav__kiwixFilter filter' form="kiwixSearchForm">
|
||||
<option value="">{{translations.book-filtering-all-categories}}</option>
|
||||
{{#categories}}
|
||||
<option value="{{name}}"{{#selected}} selected {{/selected}}>{{hf_name}}</option>
|
||||
{{/categories}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<form id='kiwixSearchForm' class='kiwixNav__SearchForm' action="{{root}}/nojs">
|
||||
<input type="text" name="q" placeholder="{{translations.search}}" id="searchFilter" class='kiwixSearch filter' value="{{searchQuery}}">
|
||||
<input type="submit" class="kiwixButton kiwixButtonHover" value="{{translations.search}}"/>
|
||||
</form>
|
||||
</div>
|
||||
<div class="kiwixHomeBody">
|
||||
{{#noResults}}
|
||||
<style>
|
||||
.book__list {
|
||||
display: none;
|
||||
}
|
||||
.kiwixHomeBody {
|
||||
justify-content: center;
|
||||
}
|
||||
.noResults {
|
||||
font-size: 16px;
|
||||
font-family: roboto;
|
||||
}
|
||||
</style>
|
||||
<div class="noResults">
|
||||
{{{translations.welcome-page-overzealous-filter}}}
|
||||
</div>
|
||||
</style>
|
||||
{{/noResults}}
|
||||
<div class="book__list">
|
||||
<h3 class="kiwixHomeBody__results">{{translations.count-of-matching-books}}</h3>
|
||||
{{#books}}
|
||||
<div class="book__wrapper">
|
||||
<div class="book__link__wrapper">
|
||||
<div class="book__icon" {{faviconAttr}}></div>
|
||||
<div class="book__header">
|
||||
<div id="book__title"><a href="{{root}}/content/{{id}}">{{title}}</a></div>
|
||||
{{#downloadAvailable}}
|
||||
<div class="book__download"><span><a href="{{root}}/nojs/download/{{id}}">{{translations.download}}</a></span></div>
|
||||
{{/downloadAvailable}}
|
||||
</div>
|
||||
<a class="book__link" href="{{root}}/content/{{id}}" title="{{translations.preview-book}}" aria-label="{{translations.preview-book}}">
|
||||
<div class="book__description" title="{{description}}">{{description}}</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="book__languageTag" {{languageAttr}}>{{langCode}}</div>
|
||||
<div class="book__tags"><div class="book__tags--wrapper">
|
||||
{{#tagList}}
|
||||
<span class="tag__link" aria-label='{{tag}}' title='{{tag}}'>{{tag}}</span>
|
||||
{{/tagList}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/books}}
|
||||
</div>
|
||||
</div>
|
||||
<div id="kiwixfooter" class="kiwixfooter">{{{translations.powered-by-kiwix-html}}}</div>
|
||||
</body>
|
||||
</html>
|
|
@ -945,6 +945,259 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
|||
EXPECT_EQ(r1->status, 404);
|
||||
}
|
||||
|
||||
#define HTML_PREAMBLE \
|
||||
"<!DOCTYPE html>\n" \
|
||||
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" \
|
||||
" <head>\n" \
|
||||
" <meta charset=\"UTF-8\" />\n" \
|
||||
" <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n" \
|
||||
" <link type=\"root\" href=\"/ROOT%23%3F\">\n" \
|
||||
" <title>Welcome to Kiwix Server</title>\n" \
|
||||
" <link\n" \
|
||||
" type=\"text/css\"\n" \
|
||||
" href=\"/ROOT%23%3F/skin/index.css?cacheid=e4d76d16\"\n" \
|
||||
" rel=\"Stylesheet\"\n" \
|
||||
" />\n" \
|
||||
" <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/ROOT%23%3F/skin/favicon/apple-touch-icon.png?cacheid=f86f8df3\">\n" \
|
||||
" <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/ROOT%23%3F/skin/favicon/favicon-32x32.png?cacheid=79ded625\">\n" \
|
||||
" <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/ROOT%23%3F/skin/favicon/favicon-16x16.png?cacheid=a986fedc\">\n" \
|
||||
" <link rel=\"manifest\" href=\"/ROOT%23%3F/skin/favicon/site.webmanifest?cacheid=bc396efb\">\n" \
|
||||
" <link rel=\"mask-icon\" href=\"/ROOT%23%3F/skin/favicon/safari-pinned-tab.svg?cacheid=8d487e95\" color=\"#5bbad5\">\n" \
|
||||
" <link rel=\"shortcut icon\" href=\"/ROOT%23%3F/skin/favicon/favicon.ico?cacheid=92663314\">\n" \
|
||||
" <meta name=\"msapplication-TileColor\" content=\"#da532c\">\n" \
|
||||
" <meta name=\"msapplication-config\" content=\"/ROOT%23%3F/skin/favicon/browserconfig.xml?cacheid=f29a7c4a\">\n" \
|
||||
" <meta name=\"theme-color\" content=\"#ffffff\">\n" \
|
||||
" <style>\n" \
|
||||
" @font-face {\n" \
|
||||
" font-family: \"poppins\";\n" \
|
||||
" src: url(\"/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837\") format(\"truetype\");\n" \
|
||||
" }\n\n" \
|
||||
" @font-face {\n" \
|
||||
" font-family: \"roboto\";\n" \
|
||||
" src: url(\"/ROOT%23%3F/skin/fonts/Roboto.ttf?cacheid=84d10248\") format(\"truetype\");\n" \
|
||||
" }\n\n" \
|
||||
" .book__list {\n" \
|
||||
" display: flex;\n" \
|
||||
" flex-direction: row;\n" \
|
||||
" flex-wrap: wrap;\n" \
|
||||
" align-items: center;\n" \
|
||||
" }\n\n" \
|
||||
" .book__wrapper:hover {\n" \
|
||||
" transform: scale(1.0);\n" \
|
||||
" }\n\n" \
|
||||
" .tag__link {\n" \
|
||||
" pointer-events: none;\n" \
|
||||
" }\n\n" \
|
||||
" .book__link__wrapper {\n" \
|
||||
" grid-column: 1 / 3;\n" \
|
||||
" grid-row: 1 / 3;\n" \
|
||||
" }\n\n" \
|
||||
" .book__link {\n" \
|
||||
" grid-row: 2 / 3;\n" \
|
||||
" }\n\n" \
|
||||
" .kiwixHomeBody__results {\n" \
|
||||
" flex-basis: 100%;\n" \
|
||||
" }\n\n" \
|
||||
" #book__title>a, .book__download a {\n" \
|
||||
" text-decoration: none;\n" \
|
||||
" all: unset;\n" \
|
||||
" }\n" \
|
||||
" </style>\n" \
|
||||
" </head>\n" \
|
||||
" <body>\n" \
|
||||
" <div class='kiwixNav'>\n"
|
||||
|
||||
#define CHARLES_RAY_BOOK_HTML \
|
||||
" <div class=\"book__wrapper\">\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/charlesray/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\"><a href=\"/ROOT%23%3F/content/zimfile%26other\">Charles, Ray</a></div>\n" \
|
||||
" <div class=\"book__download\"><span><a href=\"/ROOT%23%3F/nojs/download/zimfile%26other\">Download</a></span></div>\n" \
|
||||
" </div>\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile%26other\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__languageTag\" >fra</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='unittest' title='unittest'>unittest</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n"
|
||||
|
||||
#define RAY_CHARLES_BOOK_HTML \
|
||||
" <div class=\"book__wrapper\">\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\"><a href=\"/ROOT%23%3F/content/zimfile\">Ray Charles</a></div>\n" \
|
||||
" <div class=\"book__download\"><span><a href=\"/ROOT%23%3F/nojs/download/zimfile\">Download</a></span></div>\n" \
|
||||
" </div>\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__languageTag\" >eng</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='public_tag_without_a_value' title='public_tag_without_a_value'>public_tag_without_a_value</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n"
|
||||
|
||||
#define RAY_CHARLES_UNCTZ_BOOK_HTML \
|
||||
" <div class=\"book__wrapper\">\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles_uncategorized/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\"><a href=\"/ROOT%23%3F/content/zimfile\">Ray (uncategorized) Charles</a></div>\n" \
|
||||
" <div class=\"book__download\"><span><a href=\"/ROOT%23%3F/nojs/download/zimfile\">Download</a></span></div>\n" \
|
||||
" </div>\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__description\" title=\"No category is assigned to this library entry.\">No category is assigned to this library entry.</div>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__languageTag\" >rus,eng</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='public_tag_with_a_value:value_of_a_public_tag' title='public_tag_with_a_value:value_of_a_public_tag'>public_tag_with_a_value:value_of_a_public_tag</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n"
|
||||
|
||||
#define FINAL_HTML_TEXT \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" <div id=\"kiwixfooter\" class=\"kiwixfooter\">Powered by <a href=\"https://kiwix.org\">Kiwix</a></div>\n" \
|
||||
" </body>\n" \
|
||||
"</html>"
|
||||
|
||||
#define FILTERS_HTML(SELECTED_ENG) \
|
||||
" <div class=\"kiwixNav__filters\">\n" \
|
||||
" <div class=\"kiwixNav__select\">\n" \
|
||||
" <select name=\"lang\" id=\"languageFilter\" class='kiwixNav__kiwixFilter filter' form=\"kiwixSearchForm\">\n" \
|
||||
" <option value=\"\" selected>All languages</option>\n" \
|
||||
" <option value=\"eng\"" SELECTED_ENG ">English</option>\n" \
|
||||
" <option value=\"fra\">français</option>\n" \
|
||||
" <option value=\"rus\">русский</option>\n" \
|
||||
" </select>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"kiwixNav__select\">\n" \
|
||||
" <select name=\"category\" id=\"categoryFilter\" class='kiwixNav__kiwixFilter filter' form=\"kiwixSearchForm\">\n" \
|
||||
" <option value=\"\">All categories</option>\n" \
|
||||
" <option value=\"jazz\">Jazz</option>\n" \
|
||||
" <option value=\"wikipedia\">Wikipedia</option>\n" \
|
||||
" </select>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" <form id='kiwixSearchForm' class='kiwixNav__SearchForm' action=\"/ROOT%23%3F/nojs\">\n" \
|
||||
" <input type=\"text\" name=\"q\" placeholder=\"Search\" id=\"searchFilter\" class='kiwixSearch filter' value=\"\">\n" \
|
||||
" <input type=\"submit\" class=\"kiwixButton kiwixButtonHover\" value=\"Search\"/>\n" \
|
||||
" </form>\n" \
|
||||
" </div>\n"
|
||||
|
||||
#define HOME_BODY_TEXT(X) \
|
||||
" <div class=\"kiwixHomeBody\">\n" \
|
||||
" \n" \
|
||||
" <div class=\"book__list\">\n" \
|
||||
" <h3 class=\"kiwixHomeBody__results\">" X " book(s)</h3>\n"
|
||||
|
||||
#define HOME_BODY_0_RESULTS \
|
||||
" <div class=\"kiwixHomeBody\">\n" \
|
||||
" <style>\n" \
|
||||
" .book__list {\n" \
|
||||
" display: none;\n" \
|
||||
" }\n" \
|
||||
" .kiwixHomeBody {\n" \
|
||||
" justify-content: center;\n" \
|
||||
" }\n" \
|
||||
" .noResults {\n" \
|
||||
" font-size: 16px;\n" \
|
||||
" font-family: roboto;\n" \
|
||||
" }\n" \
|
||||
" </style>\n" \
|
||||
" <div class=\"noResults\">\n" \
|
||||
" No result. Would you like to <a href=\"?lang=\">reset filter</a>?\n" \
|
||||
" </div>\n" \
|
||||
" </style>\n" \
|
||||
" <div class=\"book__list\">\n" \
|
||||
" <h3 class=\"kiwixHomeBody__results\">0 book(s)</h3>\n" \
|
||||
" \n"
|
||||
|
||||
#define RAY_CHARLES_UNCTZ_DOWNLOAD \
|
||||
"<!DOCTYPE html>\n" \
|
||||
"<html lang=\"en\">\n" \
|
||||
"<head>\n" \
|
||||
" <meta charset=\"UTF-8\">\n" \
|
||||
" <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n" \
|
||||
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" \
|
||||
" <title>Download book</title>\n" \
|
||||
"</head>\n" \
|
||||
"<style>\n" \
|
||||
" .downloadLinksTitle {\n" \
|
||||
" text-align: center;\n" \
|
||||
" font-size: 32px;\n" \
|
||||
" margin-bottom: 8px;\n" \
|
||||
" }\n" \
|
||||
"</style>\n" \
|
||||
"<body>\n" \
|
||||
" <div class=\"downloadLinksTitle\">\n" \
|
||||
" Download links for <b><i>Ray (uncategorized) Charles</i></b>\n" \
|
||||
" </div>\n" \
|
||||
" <a href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim\" download>\n" \
|
||||
" <div>Direct</div>\n" \
|
||||
" </a>\n" \
|
||||
" <a href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim.sha256\" download>\n" \
|
||||
" <div>Sha256 hash</div>\n" \
|
||||
" </a>\n" \
|
||||
" <a href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim.magnet\" target=\"_blank\">\n" \
|
||||
" <div>Magnet link</div>\n" \
|
||||
" </a>\n" \
|
||||
" <a href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim.torrent\" download>\n" \
|
||||
" <div>Torrent file</div>\n" \
|
||||
" </a>\n" \
|
||||
"</body>\n" \
|
||||
"</html>"
|
||||
|
||||
TEST_F(LibraryServerTest, noJS) {
|
||||
// no_js_default
|
||||
auto r = zfs1_->GET("/ROOT%23%3F/nojs");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(r->body,
|
||||
HTML_PREAMBLE
|
||||
FILTERS_HTML("")
|
||||
HOME_BODY_TEXT("3")
|
||||
CHARLES_RAY_BOOK_HTML
|
||||
RAY_CHARLES_BOOK_HTML
|
||||
RAY_CHARLES_UNCTZ_BOOK_HTML
|
||||
FINAL_HTML_TEXT);
|
||||
|
||||
// no_js_eng_lang
|
||||
r = zfs1_->GET("/ROOT%23%3F/nojs?lang=eng");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(r->body,
|
||||
HTML_PREAMBLE
|
||||
FILTERS_HTML(" selected ")
|
||||
HOME_BODY_TEXT("2")
|
||||
RAY_CHARLES_UNCTZ_BOOK_HTML
|
||||
RAY_CHARLES_BOOK_HTML
|
||||
FINAL_HTML_TEXT);
|
||||
|
||||
// no_js_no_books
|
||||
r = zfs1_->GET("/ROOT%23%3F/nojs?lang=fas");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(r->body,
|
||||
HTML_PREAMBLE
|
||||
FILTERS_HTML("")
|
||||
HOME_BODY_0_RESULTS
|
||||
FINAL_HTML_TEXT);
|
||||
|
||||
// no_js_download
|
||||
r = zfs1_->GET("/ROOT%23%3F/nojs/download/zimfile");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(r->body, RAY_CHARLES_UNCTZ_DOWNLOAD);
|
||||
}
|
||||
|
||||
#undef EXPECT_SEARCH_RESULTS
|
||||
|
|
|
@ -63,7 +63,7 @@ const ResourceCollection resources200Compressible{
|
|||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=e4d76d16" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=78cfd6a2" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=d38d9ef1" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/isotope.pkgd.min.js" },
|
||||
|
@ -97,6 +97,8 @@ const ResourceCollection resources200Compressible{
|
|||
|
||||
{ ZIM_CONTENT, "/ROOT%23%3F/raw/zimfile/content/A/index" },
|
||||
{ ZIM_CONTENT, "/ROOT%23%3F/raw/zimfile/content/A/Ray_Charles" },
|
||||
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/nojs"},
|
||||
};
|
||||
|
||||
const ResourceCollection resources200Uncompressible{
|
||||
|
@ -179,6 +181,8 @@ const ResourceCollection resources200Uncompressible{
|
|||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/suggest?content=zimfile" },
|
||||
{ ZIM_CONTENT, "/ROOT%23%3F/raw/zimfile/meta/Creator" },
|
||||
{ ZIM_CONTENT, "/ROOT%23%3F/raw/zimfile/meta/Title" },
|
||||
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/nojs/download/zimfile"},
|
||||
};
|
||||
|
||||
ResourceCollection all200Resources()
|
||||
|
@ -284,7 +288,7 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/index.css?cacheid=e4d76d16"
|
|||
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=b00b12db" defer></script>
|
||||
<script src="/ROOT%23%3F/skin/isotope.pkgd.min.js?cacheid=2e48d392" defer></script>
|
||||
<script src="/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3"></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=78cfd6a2" defer></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=d38d9ef1" defer></script>
|
||||
<img src="/ROOT%23%3F/skin/feed.svg?cacheid=055b333f"
|
||||
<img src="/ROOT%23%3F/skin/langSelector.svg?cacheid=00b59961"
|
||||
)EXPECTEDRESULT"
|
||||
|
@ -395,6 +399,8 @@ const char* urls404[] = {
|
|||
// zimfile has no Favicon nor Illustration_48x48@1 meta item
|
||||
"/ROOT%23%3F/raw/zimfile/meta/Favicon",
|
||||
"/ROOT%23%3F/raw/zimfile/meta/Illustration_48x48@1",
|
||||
|
||||
"/ROOT%23%3F/nojs/download/thiszimdoesntexist"
|
||||
};
|
||||
|
||||
TEST_F(ServerTest, 404)
|
||||
|
|
|
@ -163,4 +163,11 @@ TEST(stringTools, urlDecode)
|
|||
EXPECT_EQ(urlDecode(encodedUriDelimSymbols, false), encodedUriDelimSymbols);
|
||||
}
|
||||
|
||||
TEST(stringTools, stripSuffix)
|
||||
{
|
||||
EXPECT_EQ(stripSuffix("abc123", "123"), "abc");
|
||||
EXPECT_EQ(stripSuffix("abc123", "123456789"), "abc123");
|
||||
EXPECT_EQ(stripSuffix("abc123", "987"), "abc123");
|
||||
}
|
||||
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue