mirror of
https://github.com/kiwix/libkiwix.git
synced 2025-12-24 06:57:59 -05:00
Compare commits
60 Commits
widgetEndp
...
bookFilter
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cd057941e | ||
|
|
8a3a0b08c2 | ||
|
|
956c597e80 | ||
|
|
bd38ea97f9 | ||
|
|
48a0b3bdc7 | ||
|
|
b84eaad748 | ||
|
|
266e29dff2 | ||
|
|
11051b4eed | ||
|
|
86eacea74e | ||
|
|
3a75facfdc | ||
|
|
0a0f52f1e2 | ||
|
|
0994a8f1b0 | ||
|
|
fa67b45f50 | ||
|
|
defa38719d | ||
|
|
cac2d212c6 | ||
|
|
4e06bb6a08 | ||
|
|
796e729f52 | ||
|
|
ae01790375 | ||
|
|
da23e4eca4 | ||
|
|
2be9ac342f | ||
|
|
369406fb5d | ||
|
|
b81cb3a8e9 | ||
|
|
6cc677b8ad | ||
|
|
a674561110 | ||
|
|
685e7f8ad4 | ||
|
|
0ce36e6246 | ||
|
|
eb0a45b13e | ||
|
|
c988511561 | ||
|
|
c73e6f9a81 | ||
|
|
0cf4850a9b | ||
|
|
40c496d401 | ||
|
|
9a193735fb | ||
|
|
2083c390b5 | ||
|
|
29efb88d48 | ||
|
|
948435794f | ||
|
|
7ed01e7678 | ||
|
|
eadc0ac72b | ||
|
|
77d9777208 | ||
|
|
4a55b136f6 | ||
|
|
a9446714ea | ||
|
|
17ff2a094d | ||
|
|
0c4d9e8730 | ||
|
|
7be7a8ed5f | ||
|
|
f41e71b2d7 | ||
|
|
58e45711ff | ||
|
|
5b545d81bd | ||
|
|
7c6c315ead | ||
|
|
228e31cddd | ||
|
|
4105be9bd2 | ||
|
|
e5f97d95b1 | ||
|
|
4db443eca6 | ||
|
|
dea674ef38 | ||
|
|
4b6c6452c0 | ||
|
|
5130bf9774 | ||
|
|
ee3514d2d6 | ||
|
|
e1847cb058 | ||
|
|
dd2b82a6be | ||
|
|
1062bd73a3 | ||
|
|
cd56277123 | ||
|
|
5e8b977bec |
73
README.md
73
README.md
@@ -101,6 +101,33 @@ meson . build -Dwrapper=android -Dwerror=false
|
||||
ninja -C build
|
||||
```
|
||||
|
||||
Static files compilation
|
||||
------------------------
|
||||
|
||||
Libkiwix has a few static files 'compiled' within the binary
|
||||
code. This is mostly Javascript/HTML/pictures necessary for the HTTP
|
||||
daemon.
|
||||
|
||||
These static files are available in the `static` directory and are
|
||||
compiled by custom Python code available in this repository `scripts`
|
||||
directory. This happens automatically at compilation time without any
|
||||
additional command to run.
|
||||
|
||||
To avoid HTTP caching issues, the URLs (to the static content) are
|
||||
appended with a `cacheid` parameter (this is called "cache
|
||||
busting"). This `cacheid` value derived from the
|
||||
[sha1sum](https://en.wikipedia.org/wiki/Sha1sum) of each targeted
|
||||
static file. As a consequence, each time you change a static file, the
|
||||
corresponding `cacheid` value will change.
|
||||
|
||||
To properly test this feature, this `cacheid` needs to be added
|
||||
manually to the automated tests and has to be commited. After
|
||||
modifying the needed static file, [run the automated
|
||||
tests](#Testing). They will fail, but the inspection of the testing
|
||||
log will give you the new `cacheid` value(s). Finally update
|
||||
`test/server.cpp` with the appropriate `cacheid` value(s) which have
|
||||
changed.
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
@@ -124,7 +151,7 @@ where you want to install the libraries. After the installation
|
||||
succeeded, you may need to run `ldconfig` (as `root`).
|
||||
|
||||
Uninstallation
|
||||
------------
|
||||
--------------
|
||||
|
||||
If you want to uninstall the Kiwix library:
|
||||
```bash
|
||||
@@ -134,28 +161,6 @@ ninja -C build uninstall
|
||||
Like for the installation, you might need to run the command as `root`
|
||||
(or using `sudo`).
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
If you need to install Meson "manually":
|
||||
```bash
|
||||
virtualenv -p python3 ./ # Create virtualenv
|
||||
source bin/activate # Activate the virtualenv
|
||||
pip3 install meson # Install Meson
|
||||
hash -r # Refresh bash paths
|
||||
```
|
||||
|
||||
If you need to install Ninja "manually":
|
||||
```bash
|
||||
git clone git://github.com/ninja-build/ninja.git
|
||||
cd ninja
|
||||
git checkout release
|
||||
./configure.py --bootstrap
|
||||
mkdir ../bin
|
||||
cp ninja ../bin
|
||||
cd ..
|
||||
```
|
||||
|
||||
Custom Index Page
|
||||
-----------------
|
||||
|
||||
@@ -205,6 +210,28 @@ distribution. Try then with a source tarball distributed by the
|
||||
problematic upstream project or even directly from the source code
|
||||
repository.
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
If you need to install Meson "manually":
|
||||
```bash
|
||||
virtualenv -p python3 ./ # Create virtualenv
|
||||
source bin/activate # Activate the virtualenv
|
||||
pip3 install meson # Install Meson
|
||||
hash -r # Refresh bash paths
|
||||
```
|
||||
|
||||
If you need to install Ninja "manually":
|
||||
```bash
|
||||
git clone git://github.com/ninja-build/ninja.git
|
||||
cd ninja
|
||||
git checkout release
|
||||
./configure.py --bootstrap
|
||||
mkdir ../bin
|
||||
cp ninja ../bin
|
||||
cd ..
|
||||
```
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
|
||||
@@ -12,4 +12,3 @@ Welcome to libkiwix's documentation!
|
||||
|
||||
usage
|
||||
api/ref_api
|
||||
widget
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
Kiwix serve widget
|
||||
====================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
The kiwix-serve widget provides an easy to embed way to show the `kiwix-serve` homepage.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
To use the widget, simply add an iframe with its `src` attribute set to the `widget` endpoint.
|
||||
Example HTML Page ::
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Widget Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="http://192.168.18.8:8080/widget?disabledesc&disablefilter&disabledownload" width=1000 height=1000></iframe>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
This creates an iframe with the kiwix-serve homepage contents.
|
||||
|
||||
Arguments are explained below.
|
||||
|
||||
Possible Arguments
|
||||
-------------------
|
||||
|
||||
Currently, the following arguments are supported.
|
||||
|
||||
disabledesc (value = N/A)
|
||||
Disables the description part of a tile.
|
||||
|
||||
disablefilter (value = N/A)
|
||||
Disables the search filters: language, category, tag and search function.
|
||||
|
||||
disableclick (value = N/A)
|
||||
Disables clicking the book to open it for reading.
|
||||
|
||||
disabledownload (value = N/A)
|
||||
Disables the download button (if avaialable at all) on the tile.
|
||||
|
||||
|
||||
Custom CSS and JS
|
||||
-----------------
|
||||
|
||||
You can add your custom CSS rules and Javascript code to the widget.
|
||||
|
||||
To do that, use the following code as template::
|
||||
|
||||
<iframe id="receiver" src="http://192.168.18.8:8080/widget?disabledesc=&disablefilter=&disabledownload=" width="1000" height="1000">
|
||||
<p>Your browser does not support iframes.</p>
|
||||
</iframe>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
var receiver = document.getElementById('receiver').contentWindow;
|
||||
function sendMessage() {
|
||||
let msg = {
|
||||
css: `
|
||||
.book__header {
|
||||
color:red;
|
||||
}`,
|
||||
js: `
|
||||
function widgetTest() {
|
||||
console.log("Testing widget");
|
||||
}
|
||||
widgetTest();
|
||||
`
|
||||
}
|
||||
receiver.postMessage(msg, 'http://192.168.18.8:8080/widget');
|
||||
}
|
||||
sendMessage();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
The CSS/JS fields are optional, you may send both or only one.
|
||||
|
||||
@@ -54,6 +54,7 @@ enum supportedListMode {
|
||||
class Filter {
|
||||
public: // types
|
||||
using Tags = std::vector<std::string>;
|
||||
using AliasNames = std::vector<std::string>;
|
||||
|
||||
private: // data
|
||||
uint64_t activeFilters;
|
||||
@@ -67,6 +68,7 @@ class Filter {
|
||||
std::string _query;
|
||||
bool _queryIsPartial;
|
||||
std::string _name;
|
||||
AliasNames _aliasNames;
|
||||
|
||||
public: // functions
|
||||
Filter();
|
||||
@@ -112,6 +114,7 @@ class Filter {
|
||||
Filter& maxSize(size_t size);
|
||||
Filter& query(std::string query, bool partial=true);
|
||||
Filter& name(std::string name);
|
||||
Filter& aliasNames(const AliasNames& aliasNames);
|
||||
|
||||
bool hasQuery() const;
|
||||
const std::string& getQuery() const { return _query; }
|
||||
@@ -135,6 +138,8 @@ class Filter {
|
||||
const Tags& getAcceptTags() const { return _acceptTags; }
|
||||
const Tags& getRejectTags() const { return _rejectTags; }
|
||||
|
||||
const AliasNames& getAliasNames() const { return _aliasNames; }
|
||||
|
||||
private: // functions
|
||||
friend class Library;
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ class HumanReadableNameMapper : public NameMapper {
|
||||
virtual ~HumanReadableNameMapper() = default;
|
||||
virtual std::string getNameForId(const std::string& id) const;
|
||||
virtual std::string getIdForName(const std::string& name) const;
|
||||
static std::string removeDateFromBookId(const std::string& bookId);
|
||||
};
|
||||
|
||||
class UpdatableNameMapper : public NameMapper {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "tools/stringTools.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include "tools/concurrent_cache.h"
|
||||
#include "name_mapper.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
#include <algorithm>
|
||||
@@ -461,6 +462,9 @@ void Library::updateBookDB(const Book& book)
|
||||
indexer.index_text(normalizeText(book.getPublisher()), 1, "XP");
|
||||
indexer.index_text(normalizeText(book.getName()), 1, "XN");
|
||||
indexer.index_text(normalizeText(book.getCategory()), 1, "XC");
|
||||
const auto bookName = book.getHumanReadableIdFromPath();
|
||||
const auto aliasName = HumanReadableNameMapper::removeDateFromBookId(bookName);
|
||||
indexer.index_text(normalizeText(aliasName), 1, "XF");
|
||||
|
||||
for ( const auto& tag : split(normalizeText(book.getTags()), ";") ) {
|
||||
doc.add_boolean_term("XT" + tag);
|
||||
@@ -505,6 +509,7 @@ Xapian::Query buildXapianQueryFromFilterQuery(const Filter& filter)
|
||||
queryParser.add_prefix("publisher", "XP");
|
||||
queryParser.add_prefix("creator", "A");
|
||||
queryParser.add_prefix("tag", "XT");
|
||||
queryParser.add_prefix("filename", "XF");
|
||||
const auto partialQueryFlag = filter.queryIsPartial()
|
||||
? Xapian::QueryParser::FLAG_PARTIAL
|
||||
: 0;
|
||||
@@ -521,6 +526,16 @@ Xapian::Query buildXapianQueryFromFilterQuery(const Filter& filter)
|
||||
return queryParser.parse_query(normalizeText(filter.getQuery()), flags);
|
||||
}
|
||||
|
||||
Xapian::Query makePhraseQuery(const std::string& query, const std::string& prefix)
|
||||
{
|
||||
Xapian::QueryParser queryParser;
|
||||
queryParser.set_default_op(Xapian::Query::OP_OR);
|
||||
queryParser.set_stemming_strategy(Xapian::QueryParser::STEM_NONE);
|
||||
const auto flags = 0;
|
||||
const auto q = queryParser.parse_query(normalizeText(query), flags, prefix);
|
||||
return Xapian::Query(Xapian::Query::OP_PHRASE, q.get_terms_begin(), q.get_terms_end(), q.get_length());
|
||||
}
|
||||
|
||||
Xapian::Query nameQuery(const std::string& name)
|
||||
{
|
||||
return Xapian::Query("XN" + normalizeText(name));
|
||||
@@ -531,6 +546,18 @@ Xapian::Query categoryQuery(const std::string& category)
|
||||
return Xapian::Query("XC" + normalizeText(category));
|
||||
}
|
||||
|
||||
Xapian::Query aliasNamesQuery(const Filter::AliasNames& aliasNames)
|
||||
{
|
||||
Xapian::Query q = Xapian::Query(std::string());
|
||||
std::vector<Xapian::Query> queryVec;
|
||||
for (const auto& aliasName : aliasNames) {
|
||||
queryVec.push_back(makePhraseQuery(aliasName, "XF"));
|
||||
}
|
||||
Xapian::Query combinedQuery(Xapian::Query::OP_OR, queryVec.begin(), queryVec.end());
|
||||
q = Xapian::Query(Xapian::Query::OP_FILTER, q, combinedQuery);
|
||||
return q;
|
||||
}
|
||||
|
||||
Xapian::Query langQuery(const std::string& lang)
|
||||
{
|
||||
return Xapian::Query("L" + normalizeText(lang));
|
||||
@@ -538,22 +565,12 @@ Xapian::Query langQuery(const std::string& lang)
|
||||
|
||||
Xapian::Query publisherQuery(const std::string& publisher)
|
||||
{
|
||||
Xapian::QueryParser queryParser;
|
||||
queryParser.set_default_op(Xapian::Query::OP_OR);
|
||||
queryParser.set_stemming_strategy(Xapian::QueryParser::STEM_NONE);
|
||||
const auto flags = 0;
|
||||
const auto q = queryParser.parse_query(normalizeText(publisher), flags, "XP");
|
||||
return Xapian::Query(Xapian::Query::OP_PHRASE, q.get_terms_begin(), q.get_terms_end(), q.get_length());
|
||||
return makePhraseQuery(publisher, "XP");
|
||||
}
|
||||
|
||||
Xapian::Query creatorQuery(const std::string& creator)
|
||||
{
|
||||
Xapian::QueryParser queryParser;
|
||||
queryParser.set_default_op(Xapian::Query::OP_OR);
|
||||
queryParser.set_stemming_strategy(Xapian::QueryParser::STEM_NONE);
|
||||
const auto flags = 0;
|
||||
const auto q = queryParser.parse_query(normalizeText(creator), flags, "A");
|
||||
return Xapian::Query(Xapian::Query::OP_PHRASE, q.get_terms_begin(), q.get_terms_end(), q.get_length());
|
||||
return makePhraseQuery(creator, "A");
|
||||
}
|
||||
|
||||
Xapian::Query tagsQuery(const Filter::Tags& acceptTags, const Filter::Tags& rejectTags)
|
||||
@@ -593,6 +610,9 @@ Xapian::Query buildXapianQuery(const Filter& filter)
|
||||
const auto tq = tagsQuery(filter.getAcceptTags(), filter.getRejectTags());
|
||||
q = Xapian::Query(Xapian::Query::OP_AND, q, tq);;
|
||||
}
|
||||
if ( !filter.getAliasNames().empty() ) {
|
||||
q = Xapian::Query(Xapian::Query::OP_AND, q, aliasNamesQuery(filter.getAliasNames()));
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
@@ -742,6 +762,7 @@ enum filterTypes {
|
||||
QUERY = FLAG(12),
|
||||
NAME = FLAG(13),
|
||||
CATEGORY = FLAG(14),
|
||||
ALIASNAMES = FLAG(15),
|
||||
};
|
||||
|
||||
Filter& Filter::local(bool accept)
|
||||
@@ -844,6 +865,13 @@ Filter& Filter::name(std::string name)
|
||||
return *this;
|
||||
}
|
||||
|
||||
Filter& Filter::aliasNames(const AliasNames& aliasNames)
|
||||
{
|
||||
_aliasNames = aliasNames;
|
||||
activeFilters |= ALIASNAMES;
|
||||
return *this;
|
||||
}
|
||||
|
||||
#define ACTIVE(X) (activeFilters & (X))
|
||||
#define FILTER(TAG, TEST) if (ACTIVE(TAG) && !(TEST)) { return false; }
|
||||
bool Filter::hasQuery() const
|
||||
|
||||
@@ -45,7 +45,7 @@ config_h = configure_file(output : 'kiwix_config.h',
|
||||
input : 'config.h.in')
|
||||
install_headers(config_h, subdir:'kiwix')
|
||||
|
||||
kiwixlib = library('kiwix',
|
||||
libkiwix = library('kiwix',
|
||||
kiwix_sources,
|
||||
include_directories : inc,
|
||||
dependencies : all_deps,
|
||||
|
||||
@@ -34,7 +34,7 @@ HumanReadableNameMapper::HumanReadableNameMapper(kiwix::Library& library, bool w
|
||||
if (!withAlias)
|
||||
continue;
|
||||
|
||||
auto aliasName = replaceRegex(bookName, "", "_[[:digit:]]{4}-[[:digit:]]{2}$");
|
||||
auto aliasName = removeDateFromBookId(bookName);
|
||||
if (aliasName == bookName) {
|
||||
continue;
|
||||
}
|
||||
@@ -51,6 +51,10 @@ HumanReadableNameMapper::HumanReadableNameMapper(kiwix::Library& library, bool w
|
||||
}
|
||||
}
|
||||
|
||||
std::string HumanReadableNameMapper::removeDateFromBookId(const std::string& bookId) {
|
||||
return replaceRegex(bookId, "", "_[[:digit:]]{4}-[[:digit:]]{2}$");
|
||||
}
|
||||
|
||||
std::string HumanReadableNameMapper::getNameForId(const std::string& id) const {
|
||||
return m_idToName.at(id);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "opds_dumper.h"
|
||||
#include "book.h"
|
||||
|
||||
#include "kiwixlib-resources.h"
|
||||
#include "libkiwix-resources.h"
|
||||
#include <mustache.hpp>
|
||||
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <zim/search.h>
|
||||
|
||||
#include <mustache.hpp>
|
||||
#include "kiwixlib-resources.h"
|
||||
#include "libkiwix-resources.h"
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
namespace kiwix
|
||||
|
||||
@@ -68,7 +68,7 @@ extern "C" {
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include "kiwixlib-resources.h"
|
||||
#include "libkiwix-resources.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <arpa/inet.h>
|
||||
@@ -119,6 +119,9 @@ Filter get_search_filter(const RequestContext& request, const std::string& prefi
|
||||
try {
|
||||
filter.rejectTags(kiwix::split(request.get_argument(prefix+"notag"), ";"));
|
||||
} catch (...) {}
|
||||
try {
|
||||
filter.aliasNames(request.get_arguments(prefix + "book"));
|
||||
} catch (...) {}
|
||||
return filter;
|
||||
}
|
||||
|
||||
@@ -553,9 +556,12 @@ std::unique_ptr<Response> InternalServer::handle_request(const RequestContext& r
|
||||
if (url == "/" )
|
||||
return build_homepage(request);
|
||||
|
||||
if (isEndpointUrl(url, "skin"))
|
||||
if (isEndpointUrl(url, "viewer") || isEndpointUrl(url, "skin"))
|
||||
return handle_skin(request);
|
||||
|
||||
if (url == "/viewer_settings.js")
|
||||
return handle_viewer_settings(request);
|
||||
|
||||
if (isEndpointUrl(url, "content"))
|
||||
return handle_content(request);
|
||||
|
||||
@@ -577,9 +583,6 @@ std::unique_ptr<Response> InternalServer::handle_request(const RequestContext& r
|
||||
if (isEndpointUrl(url, "catch"))
|
||||
return handle_catch(request);
|
||||
|
||||
if (isEndpointUrl(url, "widget"))
|
||||
return handle_widget(request);
|
||||
|
||||
std::string contentUrl = m_root + "/content" + url;
|
||||
const std::string query = request.get_query();
|
||||
if ( ! query.empty() )
|
||||
@@ -626,7 +629,7 @@ InternalServer::get_matching_if_none_match_etag(const RequestContext& r) const
|
||||
|
||||
std::unique_ptr<Response> InternalServer::build_homepage(const RequestContext& request)
|
||||
{
|
||||
return ContentResponse::build(*this, m_indexTemplateString, get_default_data(), "text/html; charset=utf-8", true);
|
||||
return ContentResponse::build(*this, m_indexTemplateString, get_default_data(), "text/html; charset=utf-8");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -656,8 +659,7 @@ std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& r
|
||||
|
||||
if (archive == nullptr) {
|
||||
return HTTP404Response(*this, request)
|
||||
+ noSuchBookErrorMsg(bookName)
|
||||
+ TaskbarInfo(bookName);
|
||||
+ noSuchBookErrorMsg(bookName);
|
||||
}
|
||||
|
||||
const auto queryString = request.get_optional_param("term", std::string());
|
||||
@@ -717,13 +719,30 @@ std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& r
|
||||
return std::move(response);
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_viewer_settings(const RequestContext& request)
|
||||
{
|
||||
if (m_verbose.load()) {
|
||||
printf("** running handle_viewer_settings\n");
|
||||
}
|
||||
|
||||
const kainjow::mustache::object data{
|
||||
{"enable_toolbar", m_withTaskbar ? "true" : "false" },
|
||||
{"enable_link_blocking", m_blockExternalLinks ? "true" : "false" },
|
||||
{"enable_library_button", m_withLibraryButton ? "true" : "false" }
|
||||
};
|
||||
return ContentResponse::build(*this, RESOURCE::templates::viewer_settings_js, data, "application/javascript; charset=utf-8");
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_skin(const RequestContext& request)
|
||||
{
|
||||
if (m_verbose.load()) {
|
||||
printf("** running handle_skin\n");
|
||||
}
|
||||
|
||||
auto resourceName = request.get_url().substr(1);
|
||||
const bool isRequestForViewer = request.get_url() == "/viewer";
|
||||
auto resourceName = isRequestForViewer
|
||||
? "viewer.html"
|
||||
: request.get_url().substr(1);
|
||||
try {
|
||||
auto response = ContentResponse::build(
|
||||
*this,
|
||||
@@ -780,11 +799,15 @@ std::unique_ptr<Response> InternalServer::handle_search(const RequestContext& re
|
||||
"404-page-heading",
|
||||
cssUrl);
|
||||
response += nonParameterizedMessage("no-search-results");
|
||||
// XXX: Now this has to be handled by the iframe-based viewer which
|
||||
// XXX: has to resolve if the book selection resulted in a single book.
|
||||
/*
|
||||
if(bookIds.size() == 1) {
|
||||
auto bookId = *bookIds.begin();
|
||||
auto bookName = mp_nameMapper->getNameForId(bookId);
|
||||
response += TaskbarInfo(bookName, mp_library->getArchiveById(bookId).get());
|
||||
}
|
||||
*/
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -814,16 +837,18 @@ std::unique_ptr<Response> InternalServer::handle_search(const RequestContext& re
|
||||
renderer.setSearchProtocolPrefix(m_root + "/search");
|
||||
renderer.setPageLength(pageLength);
|
||||
if (request.get_requested_format() == "xml") {
|
||||
return ContentResponse::build(*this, renderer.getXml(), "application/rss+xml; charset=utf-8",
|
||||
/*isHomePage =*/false,
|
||||
/*raw =*/true);
|
||||
return ContentResponse::build(*this, renderer.getXml(), "application/rss+xml; charset=utf-8");
|
||||
}
|
||||
auto response = ContentResponse::build(*this, renderer.getHtml(), "text/html; charset=utf-8");
|
||||
// XXX: Now this has to be handled by the iframe-based viewer which
|
||||
// XXX: has to resolve if the book selection resulted in a single book.
|
||||
/*
|
||||
if(bookIds.size() == 1) {
|
||||
auto bookId = *bookIds.begin();
|
||||
auto bookName = mp_nameMapper->getNameForId(bookId);
|
||||
response->set_taskbar(bookName, mp_library->getArchiveById(bookId).get());
|
||||
}
|
||||
*/
|
||||
return std::move(response);
|
||||
} catch (const Error& e) {
|
||||
return HTTP400Response(*this, request)
|
||||
@@ -855,8 +880,7 @@ std::unique_ptr<Response> InternalServer::handle_random(const RequestContext& re
|
||||
|
||||
if (archive == nullptr) {
|
||||
return HTTP404Response(*this, request)
|
||||
+ noSuchBookErrorMsg(bookName)
|
||||
+ TaskbarInfo(bookName);
|
||||
+ noSuchBookErrorMsg(bookName);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -864,16 +888,10 @@ std::unique_ptr<Response> InternalServer::handle_random(const RequestContext& re
|
||||
return build_redirect(bookName, getFinalItem(*archive, entry));
|
||||
} catch(zim::EntryNotFound& e) {
|
||||
return HTTP404Response(*this, request)
|
||||
+ nonParameterizedMessage("random-article-failure")
|
||||
+ TaskbarInfo(bookName, archive.get());
|
||||
+ nonParameterizedMessage("random-article-failure");
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_widget(const RequestContext& request)
|
||||
{
|
||||
return ContentResponse::build(*this, RESOURCE::templates::widget_html, get_default_data(), "text/html; charset=utf-8", true);
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_captured_external(const RequestContext& request)
|
||||
{
|
||||
std::string source = "";
|
||||
@@ -1018,8 +1036,7 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
|
||||
const std::string searchURL = m_root + "/search?pattern=" + kiwix::urlEncode(pattern, true);
|
||||
return HTTP404Response(*this, request)
|
||||
+ urlNotFoundMsg
|
||||
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern))
|
||||
+ TaskbarInfo(bookName);
|
||||
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
|
||||
}
|
||||
|
||||
auto urlStr = url.substr(prefixLength + bookName.size());
|
||||
@@ -1035,9 +1052,6 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
|
||||
return build_redirect(bookName, getFinalItem(*archive, entry));
|
||||
}
|
||||
auto response = ItemResponse::build(*this, request, entry.getItem());
|
||||
try {
|
||||
dynamic_cast<ContentResponse&>(*response).set_taskbar(bookName, archive.get());
|
||||
} catch (std::bad_cast& e) {}
|
||||
|
||||
if (m_verbose.load()) {
|
||||
printf("Found %s\n", entry.getPath().c_str());
|
||||
@@ -1052,8 +1066,7 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
|
||||
std::string searchURL = m_root + "/search?content=" + bookName + "&pattern=" + kiwix::urlEncode(pattern, true);
|
||||
return HTTP404Response(*this, request)
|
||||
+ urlNotFoundMsg
|
||||
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern))
|
||||
+ TaskbarInfo(bookName, archive.get());
|
||||
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1101,13 +1114,13 @@ std::unique_ptr<Response> InternalServer::handle_raw(const RequestContext& reque
|
||||
try {
|
||||
if (kind == "meta") {
|
||||
auto item = archive->getMetadataItem(itemPath);
|
||||
return ItemResponse::build(*this, request, item, /*raw=*/true);
|
||||
return ItemResponse::build(*this, request, item);
|
||||
} else {
|
||||
auto entry = archive->getEntryByPath(itemPath);
|
||||
if (entry.isRedirect()) {
|
||||
return build_redirect(bookName, entry.getItem(true));
|
||||
}
|
||||
return ItemResponse::build(*this, request, entry.getItem(), /*raw=*/true);
|
||||
return ItemResponse::build(*this, request, entry.getItem());
|
||||
}
|
||||
} catch (zim::EntryNotFound& e ) {
|
||||
if (m_verbose.load()) {
|
||||
@@ -1144,9 +1157,7 @@ std::unique_ptr<Response> InternalServer::handle_locally_customized_resource(con
|
||||
|
||||
return ContentResponse::build(*this,
|
||||
resourceData,
|
||||
crd.mimeType,
|
||||
/*isHomePage=*/false,
|
||||
/*raw=*/true);
|
||||
crd.mimeType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ class InternalServer {
|
||||
std::unique_ptr<Response> handle_request(const RequestContext& request);
|
||||
std::unique_ptr<Response> build_redirect(const std::string& bookName, const zim::Item& item) const;
|
||||
std::unique_ptr<Response> build_homepage(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_viewer_settings(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_skin(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_catalog(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_catalog_v2(const RequestContext& request);
|
||||
@@ -143,7 +144,6 @@ class InternalServer {
|
||||
std::unique_ptr<Response> handle_content(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_raw(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_locally_customized_resource(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_widget(const RequestContext& request);
|
||||
|
||||
std::vector<std::string> search_catalog(const RequestContext& request,
|
||||
kiwix::OPDSDumper& opdsDumper);
|
||||
@@ -184,8 +184,8 @@ class InternalServer {
|
||||
std::unique_ptr<CustomizedResources> m_customizedResources;
|
||||
|
||||
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, bool isHomePage, bool raw);
|
||||
friend std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw);
|
||||
friend std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype);
|
||||
friend std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "request_context.h"
|
||||
#include "response.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include "kiwixlib-resources.h"
|
||||
#include "libkiwix-resources.h"
|
||||
|
||||
#include <mustache.hpp>
|
||||
|
||||
@@ -158,7 +158,11 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_illustration(const R
|
||||
auto book = mp_library->getBookByIdThreadSafe(bookId);
|
||||
auto size = request.get_argument<unsigned int>("size");
|
||||
auto illustration = book.getIllustration(size);
|
||||
return ContentResponse::build(*this, illustration->getData(), illustration->mimeType);
|
||||
return ContentResponse::build(
|
||||
*this,
|
||||
illustration->getData(),
|
||||
illustration->mimeType
|
||||
);
|
||||
} catch(...) {
|
||||
return HTTP404Response(*this, request)
|
||||
+ urlNotFoundMsg;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "response.h"
|
||||
#include "request_context.h"
|
||||
#include "internalServer.h"
|
||||
#include "kiwixlib-resources.h"
|
||||
#include "libkiwix-resources.h"
|
||||
|
||||
#include "tools/regexTools.h"
|
||||
#include "tools/stringTools.h"
|
||||
@@ -140,9 +140,6 @@ std::unique_ptr<ContentResponse> ContentResponseBlueprint::generateResponseObjec
|
||||
{
|
||||
auto r = ContentResponse::build(m_server, m_template, m_data, m_mimeType);
|
||||
r->set_code(m_httpStatusCode);
|
||||
if ( m_taskbarInfo ) {
|
||||
r->set_taskbar(m_taskbarInfo->bookName, m_taskbarInfo->archive);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -236,29 +233,12 @@ HTTP500Response::HTTP500Response(const InternalServer& server,
|
||||
|
||||
std::unique_ptr<ContentResponse> HTTP500Response::generateResponseObject() const
|
||||
{
|
||||
// We want a 500 response to be a minimalistic one (so that the server doesn't
|
||||
// have to provide additional resources required for its proper rendering)
|
||||
// ";raw=true" in the MIME-type below disables response decoration
|
||||
// (see ContentResponse::contentDecorationAllowed())
|
||||
const std::string mimeType = "text/html;charset=utf-8;raw=true";
|
||||
const std::string mimeType = "text/html;charset=utf-8";
|
||||
auto r = ContentResponse::build(m_server, m_template, m_data, mimeType);
|
||||
r->set_code(m_httpStatusCode);
|
||||
return r;
|
||||
}
|
||||
|
||||
ContentResponseBlueprint& ContentResponseBlueprint::operator+(const TaskbarInfo& taskbarInfo)
|
||||
{
|
||||
this->m_taskbarInfo.reset(new TaskbarInfo(taskbarInfo));
|
||||
return *this;
|
||||
}
|
||||
|
||||
ContentResponseBlueprint& ContentResponseBlueprint::operator+=(const TaskbarInfo& taskbarInfo)
|
||||
{
|
||||
// operator+() is already a state-modifying operator (akin to operator+=)
|
||||
return *this + taskbarInfo;
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<Response> Response::build_416(const InternalServer& server, size_t resourceLength)
|
||||
{
|
||||
auto response = Response::build(server);
|
||||
@@ -337,52 +317,6 @@ void print_response_info(int retCode, MHD_Response* response)
|
||||
}
|
||||
|
||||
|
||||
void ContentResponse::introduce_taskbar(const std::string& lang)
|
||||
{
|
||||
i18n::GetTranslatedString t(lang);
|
||||
kainjow::mustache::object data{
|
||||
{"root", m_root},
|
||||
{"content", m_bookName},
|
||||
{"hascontent", (!m_bookName.empty() && !m_bookTitle.empty())},
|
||||
{"title", m_bookTitle},
|
||||
{"withlibrarybutton", m_withLibraryButton},
|
||||
{"LIBRARY_BUTTON_TEXT", t("library-button-text")},
|
||||
{"HOME_BUTTON_TEXT", t("home-button-text", {{"BOOK_TITLE", m_bookTitle}}) },
|
||||
{"RANDOM_PAGE_BUTTON_TEXT", t("random-page-button-text") },
|
||||
{"SEARCHBOX_TOOLTIP", t("searchbox-tooltip", {{"BOOK_TITLE", m_bookTitle}}) },
|
||||
};
|
||||
auto head_content = render_template(RESOURCE::templates::head_taskbar_html, data);
|
||||
m_content = prependToFirstOccurence(
|
||||
m_content,
|
||||
"</head[ \\t]*>",
|
||||
head_content);
|
||||
|
||||
auto taskbar_part = render_template(RESOURCE::templates::taskbar_part_html, data);
|
||||
m_content = appendToFirstOccurence(
|
||||
m_content,
|
||||
"<body[^>]*>",
|
||||
taskbar_part);
|
||||
}
|
||||
|
||||
|
||||
void ContentResponse::inject_externallinks_blocker()
|
||||
{
|
||||
kainjow::mustache::data data;
|
||||
data.set("root", m_root);
|
||||
auto script_tag = render_template(RESOURCE::templates::external_blocker_part_html, data);
|
||||
m_content = prependToFirstOccurence(
|
||||
m_content,
|
||||
"</head[ \\t]*>",
|
||||
script_tag);
|
||||
}
|
||||
|
||||
void ContentResponse::inject_root_link(){
|
||||
m_content = prependToFirstOccurence(
|
||||
m_content,
|
||||
"</head[ \\t]*>",
|
||||
"<link type=\"root\" href=\"" + m_root + "\">");
|
||||
}
|
||||
|
||||
bool
|
||||
ContentResponse::can_compress(const RequestContext& request) const
|
||||
{
|
||||
@@ -391,16 +325,6 @@ ContentResponse::can_compress(const RequestContext& request) const
|
||||
&& (m_content.size() > KIWIX_MIN_CONTENT_SIZE_TO_COMPRESS);
|
||||
}
|
||||
|
||||
bool
|
||||
ContentResponse::contentDecorationAllowed() const
|
||||
{
|
||||
if (m_raw) {
|
||||
return false;
|
||||
}
|
||||
return (startsWith(m_mimeType, "text/html")
|
||||
&& m_mimeType.find(";raw=true") == std::string::npos);
|
||||
}
|
||||
|
||||
MHD_Response*
|
||||
Response::create_mhd_response(const RequestContext& request)
|
||||
{
|
||||
@@ -411,17 +335,6 @@ Response::create_mhd_response(const RequestContext& request)
|
||||
MHD_Response*
|
||||
ContentResponse::create_mhd_response(const RequestContext& request)
|
||||
{
|
||||
if (contentDecorationAllowed()) {
|
||||
inject_root_link();
|
||||
|
||||
if (m_withTaskbar) {
|
||||
introduce_taskbar(request.get_user_language());
|
||||
}
|
||||
if (m_blockExternalLinks) {
|
||||
inject_externallinks_blocker();
|
||||
}
|
||||
}
|
||||
|
||||
const bool isCompressed = can_compress(request) && compress(m_content);
|
||||
|
||||
MHD_Response* response = MHD_create_response_from_buffer(
|
||||
@@ -461,24 +374,11 @@ MHD_Result Response::send(const RequestContext& request, MHD_Connection* connect
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ContentResponse::set_taskbar(const std::string& bookName, const zim::Archive* archive)
|
||||
{
|
||||
m_bookName = bookName;
|
||||
m_bookTitle = archive ? getArchiveTitle(*archive) : "";
|
||||
}
|
||||
|
||||
|
||||
ContentResponse::ContentResponse(const std::string& root, bool verbose, bool raw, bool withTaskbar, bool withLibraryButton, bool blockExternalLinks, const std::string& content, const std::string& mimetype) :
|
||||
ContentResponse::ContentResponse(const std::string& root, bool verbose, const std::string& content, const std::string& mimetype) :
|
||||
Response(verbose),
|
||||
m_root(root),
|
||||
m_content(content),
|
||||
m_mimeType(mimetype),
|
||||
m_raw(raw),
|
||||
m_withTaskbar(withTaskbar),
|
||||
m_withLibraryButton(withLibraryButton),
|
||||
m_blockExternalLinks(blockExternalLinks),
|
||||
m_bookName(""),
|
||||
m_bookTitle("")
|
||||
m_mimeType(mimetype)
|
||||
{
|
||||
add_header(MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType);
|
||||
}
|
||||
@@ -486,17 +386,11 @@ ContentResponse::ContentResponse(const std::string& root, bool verbose, bool raw
|
||||
std::unique_ptr<ContentResponse> ContentResponse::build(
|
||||
const InternalServer& server,
|
||||
const std::string& content,
|
||||
const std::string& mimetype,
|
||||
bool isHomePage,
|
||||
bool raw)
|
||||
const std::string& mimetype)
|
||||
{
|
||||
return std::unique_ptr<ContentResponse>(new ContentResponse(
|
||||
server.m_root,
|
||||
server.m_verbose.load(),
|
||||
raw,
|
||||
server.m_withTaskbar && !isHomePage,
|
||||
server.m_withLibraryButton,
|
||||
server.m_blockExternalLinks,
|
||||
content,
|
||||
mimetype));
|
||||
}
|
||||
@@ -505,11 +399,10 @@ std::unique_ptr<ContentResponse> ContentResponse::build(
|
||||
const InternalServer& server,
|
||||
const std::string& template_str,
|
||||
kainjow::mustache::data data,
|
||||
const std::string& mimetype,
|
||||
bool isHomePage)
|
||||
const std::string& mimetype)
|
||||
{
|
||||
auto content = render_template(template_str, data);
|
||||
return ContentResponse::build(server, content, mimetype, isHomePage);
|
||||
return ContentResponse::build(server, content, mimetype);
|
||||
}
|
||||
|
||||
ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange) :
|
||||
@@ -522,14 +415,14 @@ ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::strin
|
||||
add_header(MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType);
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw)
|
||||
std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item)
|
||||
{
|
||||
const std::string mimetype = get_mime_type(item);
|
||||
auto byteRange = request.get_range().resolve(item.getSize());
|
||||
const bool noRange = byteRange.kind() == ByteRange::RESOLVED_FULL_CONTENT;
|
||||
if (noRange && is_compressible_mime_type(mimetype)) {
|
||||
// Return a contentResponse
|
||||
auto response = ContentResponse::build(server, item.getData(), mimetype, /*isHomePage=*/false, raw);
|
||||
auto response = ContentResponse::build(server, item.getData(), mimetype);
|
||||
response->set_cacheable();
|
||||
response->m_byteRange = byteRange;
|
||||
return std::move(response);
|
||||
|
||||
@@ -83,60 +83,32 @@ class ContentResponse : public Response {
|
||||
ContentResponse(
|
||||
const std::string& root,
|
||||
bool verbose,
|
||||
bool raw,
|
||||
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,
|
||||
bool isHomePage = false,
|
||||
bool raw = false);
|
||||
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 zim::Archive* archive);
|
||||
const std::string& mimetype);
|
||||
|
||||
private:
|
||||
MHD_Response* create_mhd_response(const RequestContext& request);
|
||||
|
||||
void introduce_taskbar(const std::string& lang);
|
||||
void inject_externallinks_blocker();
|
||||
void inject_root_link();
|
||||
bool can_compress(const RequestContext& request) const;
|
||||
bool contentDecorationAllowed() const;
|
||||
|
||||
|
||||
private:
|
||||
std::string m_root;
|
||||
std::string m_content;
|
||||
std::string m_mimeType;
|
||||
bool m_raw;
|
||||
bool m_withTaskbar;
|
||||
bool m_withLibraryButton;
|
||||
bool m_blockExternalLinks;
|
||||
std::string m_bookName;
|
||||
std::string m_bookTitle;
|
||||
};
|
||||
|
||||
struct TaskbarInfo
|
||||
{
|
||||
const std::string bookName;
|
||||
const zim::Archive* const archive;
|
||||
|
||||
TaskbarInfo(const std::string& bookName, const zim::Archive* a = nullptr)
|
||||
: bookName(bookName)
|
||||
, archive(a)
|
||||
{}
|
||||
};
|
||||
|
||||
class ContentResponseBlueprint
|
||||
{
|
||||
public: // functions
|
||||
@@ -165,9 +137,6 @@ public: // functions
|
||||
}
|
||||
|
||||
|
||||
ContentResponseBlueprint& operator+(const TaskbarInfo& taskbarInfo);
|
||||
ContentResponseBlueprint& operator+=(const TaskbarInfo& taskbarInfo);
|
||||
|
||||
protected: // functions
|
||||
std::string getMessage(const std::string& msgId) const;
|
||||
virtual std::unique_ptr<ContentResponse> generateResponseObject() const;
|
||||
@@ -179,7 +148,6 @@ public: //data
|
||||
const std::string m_mimeType;
|
||||
const std::string m_template;
|
||||
kainjow::mustache::data m_data;
|
||||
std::unique_ptr<TaskbarInfo> m_taskbarInfo;
|
||||
};
|
||||
|
||||
struct HTTPErrorResponse : ContentResponseBlueprint
|
||||
@@ -191,8 +159,6 @@ struct HTTPErrorResponse : ContentResponseBlueprint
|
||||
const std::string& headingMsgId,
|
||||
const std::string& cssUrl = "");
|
||||
|
||||
using ContentResponseBlueprint::operator+;
|
||||
using ContentResponseBlueprint::operator+=;
|
||||
HTTPErrorResponse& operator+(const std::string& msg);
|
||||
HTTPErrorResponse& operator+(const ParameterizedMessage& errorDetails);
|
||||
HTTPErrorResponse& operator+=(const ParameterizedMessage& errorDetails);
|
||||
@@ -238,7 +204,7 @@ private: // overrides
|
||||
class ItemResponse : public Response {
|
||||
public:
|
||||
ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange);
|
||||
static std::unique_ptr<Response> build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw = false);
|
||||
static std::unique_ptr<Response> build(const InternalServer& server, const RequestContext& request, const zim::Item& item);
|
||||
|
||||
private:
|
||||
MHD_Response* create_mhd_response(const RequestContext& request);
|
||||
|
||||
@@ -75,41 +75,3 @@ std::string replaceRegex(const std::string& content,
|
||||
uresult.toUTF8String(tmp);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
std::string appendToFirstOccurence(const std::string& content,
|
||||
const std::string& regex,
|
||||
const std::string& replacement)
|
||||
{
|
||||
ucnv_setDefaultName("UTF-8");
|
||||
icu::UnicodeString ucontent(content.c_str());
|
||||
icu::UnicodeString ureplacement(replacement.c_str());
|
||||
auto matcher = buildMatcher(regex, ucontent);
|
||||
if (matcher->find()) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
ucontent.insert(matcher->end(status), ureplacement);
|
||||
std::string tmp;
|
||||
ucontent.toUTF8String(tmp);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
std::string prependToFirstOccurence(const std::string& content,
|
||||
const std::string& regex,
|
||||
const std::string& replacement)
|
||||
{
|
||||
ucnv_setDefaultName("UTF-8");
|
||||
icu::UnicodeString ucontent(content.c_str());
|
||||
icu::UnicodeString ureplacement(replacement.c_str());
|
||||
auto matcher = buildMatcher(regex, ucontent);
|
||||
if (matcher->find()) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
ucontent.insert(matcher->start(status), ureplacement);
|
||||
std::string tmp;
|
||||
ucontent.toUTF8String(tmp);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -26,11 +26,5 @@ bool matchRegex(const std::string& content, const std::string& regex);
|
||||
std::string replaceRegex(const std::string& content,
|
||||
const std::string& replacement,
|
||||
const std::string& regex);
|
||||
std::string appendToFirstOccurence(const std::string& content,
|
||||
const std::string& regex,
|
||||
const std::string& replacement);
|
||||
std::string prependToFirstOccurence(const std::string& content,
|
||||
const std::string& regex,
|
||||
const std::string& replacement);
|
||||
|
||||
#endif
|
||||
|
||||
31
static/i18n/cs.json
Normal file
31
static/i18n/cs.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Spotter"
|
||||
]
|
||||
},
|
||||
"name": "Čeština",
|
||||
"suggest-full-text-search": "obsahující '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Žádná taková kniha: {{BOOK_NAME}}",
|
||||
"too-many-books": "Bylo požadováno příliš mnoho knih ({{NB_BOOKS}}), kde je limit {{LIMIT}}",
|
||||
"no-book-found": "Výběrovým kritériím nevyhovuje žádná kniha",
|
||||
"url-not-found": "Požadovaná adresa URL \"{{url}}\" nebyla na tomto serveru nalezena.",
|
||||
"suggest-search": "Proveďte fulltextové vyhledávání <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Jejda! Nepodařilo se vybrat náhodný článek :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} není platný požadavek na nezpracovaný obsah.",
|
||||
"no-value-for-arg": "Pro argument {{ARGUMENT}} nebyla zadána žádná hodnota",
|
||||
"no-query": "Nebyl poskytnut žádný dotaz.",
|
||||
"raw-entry-not-found": "Nelze najít položku {{DATATYPE}} {{ENTRY}}",
|
||||
"400-page-title": "Neplatný požadavek",
|
||||
"400-page-heading": "Neplatný požadavek",
|
||||
"404-page-title": "Obsah nenalezen",
|
||||
"404-page-heading": "Nenalezeno",
|
||||
"500-page-title": "Interní chyba serveru",
|
||||
"500-page-heading": "Interní chyba serveru",
|
||||
"fulltext-search-unavailable": "Fulltextové vyhledávání není k dispozici",
|
||||
"no-search-results": "Fulltextový vyhledávač není pro tento obsah dostupný.",
|
||||
"library-button-text": "Přejít na uvítací stránku",
|
||||
"home-button-text": "Přejít na hlavní stránku '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Přejít na náhodně vybranou stránku",
|
||||
"searchbox-tooltip": "Hledat '{{BOOK_TITLE}}'"
|
||||
}
|
||||
@@ -1,15 +1,27 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Albano",
|
||||
"Beta16"
|
||||
]
|
||||
},
|
||||
"name": "italiano",
|
||||
"suggest-full-text-search": "contenente '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Nessun libro del genere: {{BOOK_NAME}}",
|
||||
"too-many-books": "Troppi libri richiesti ({{NB_BOOKS}}) dove il limite è {{LIMIT}}",
|
||||
"no-book-found": "Nessun libro corrisponde ai criteri di selezione",
|
||||
"url-not-found": "L'URL richiesto \"{{url}}\" non è stato trovato in questo server.",
|
||||
"suggest-search": "Effettua una ricerca di testo completo per <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Ops! Impossibile selezionare un articolo casuale :(",
|
||||
"no-value-for-arg": "Nessun valore fornito per l'argomento {{ARGUMENT}}",
|
||||
"400-page-title": "Richiesta non valida",
|
||||
"400-page-heading": "Richiesta non valida",
|
||||
"404-page-title": "Contenuto non trovato",
|
||||
"404-page-heading": "Non trovato",
|
||||
"home-button-text": "Vai alla pagina principale di '{{BOOK_TITLE}}'"
|
||||
"500-page-title": "Errore interno del server",
|
||||
"500-page-heading": "Errore interno del server",
|
||||
"library-button-text": "Vai alla pagina di benvenuto",
|
||||
"home-button-text": "Vai alla pagina principale di '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Vai a una pagina selezionata casualmente",
|
||||
"searchbox-tooltip": "Cerca '{{BOOK_TITLE}}'"
|
||||
}
|
||||
|
||||
31
static/i18n/nqo.json
Normal file
31
static/i18n/nqo.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Lancine.kounfantoh.fofana"
|
||||
]
|
||||
},
|
||||
"name": "ߒߞߏ",
|
||||
"suggest-full-text-search": "ߞߣߐߞߍߣߍ߲߫ ߦߋ߫ '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "ߞߊ߬ߝߊ߫ ߛߎ߮ ߏ߬ ߕߴߦߋ߲߬: {{BOOK_NAME}}",
|
||||
"too-many-books": "ߞߝߊ߬ ߛߌߦߊߡߊ߲߫ ߡߊߢߌ߬ߣߌ߲߬ߞߊ߬ߣߍ߲߫ ߞߏߖߎ߰ {{NB_BOOKS}} ߡߍ߲ ߞߐߘߊ߲ ߦߋ߫ {{LIMIT}} ߘߌ߫",
|
||||
"no-book-found": "ߞߝߊ߬ ߛߌ߫ ߓߍ߬ߣߍ߲߬ ߕߍ߫ ߛߎߥߊ߲ߘߟߌ ߛߙߊߕߌ ߏ߬ ߡߊ߬",
|
||||
"url-not-found": "ߍ߲߬ߤߍ߲߫ ''URL ''{{url}} ߡߊߢߌ߬ߣߌ߲߬ߞߊ߬ߣߍ߲ ߡߊ߫ ߛߐ߬ߘߐ߲߫ ߡߊ߬ߟߐ߬ߟߊ߲ ߣߌ߲߬ ߠߊ߫.",
|
||||
"suggest-search": "ߛߓߍߟߌ߫ ߘߝߊߣߍ߲ ߢߌߣߌ߲ߠߌ߲ ߞߍ߫ <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "ߏ߯߹ ߞߢߊ߬ ߓߘߊ߫ ߞߍ߫ ߓߍ߲߬ߛߋ߲߬ߡߊ߬ ߞߎߡߘߊ ߛߐ߬ߘߐ߲߬ ߠߊ߫:(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} ߕߍ߫ ߡߞߊ߬ߣߌ߲߬ߣߍ߲߫ ߓߍ߲߬ߣߍ߲߫ ߘߌ߫ ߞߣߐߘߐ߫ ߘߝߊߣߍ߲ ߞߊ߲ߡߊ߬.",
|
||||
"no-value-for-arg": "ߡߐ߬ߟߐ߲߬ ߡߊ߫ ߡߊߛߐ߫ ߘߊߘߐߡߌߣߊߞߎ߲ߧߊ {{ARGUMENT}} ߞߊ߲ߡߊ߬",
|
||||
"no-query": "ߡߞߊ߬ߣߌ߲߬ߠߌ߲߫ ߛߌ߫ ߡߊ߫ ߡߊߛߐ߫.",
|
||||
"raw-entry-not-found": "ߟߊ߬ߘߏ߲߬ߣߍ߲ {{ENTRY}} {{DATATYPE}} ߕߍ߫ ߣߊ߬ ߡߊߛߐ߬ߘߐ߲߫ ߠߊ߫",
|
||||
"400-page-title": "ߡߞߊ߬ߣߌ߲߬ߠߌ߲߫ ߓߍ߲߬ߓߊߟߌ",
|
||||
"400-page-heading": "ߡߞߊ߬ߣߌ߲߬ߠߌ߲߫ ߓߍ߲߬ߓߊߟߌ",
|
||||
"404-page-title": "ߞߣߐߘߐ ߡߊ߫ ߛߐ߬ߘߐ߲߫",
|
||||
"404-page-heading": "ߡߊ߫ ߛߐ߬ߘߐ߲߫",
|
||||
"500-page-title": "ߞߣߐߟߊ ߡߊ߬ߛߐ߬ߟߊ߲ ߝߎ߬ߕߎ߲߬ߕߌ",
|
||||
"500-page-heading": "ߞߣߐߟߊ ߡߊ߬ߛߐ߬ߟߊ߲ ߝߎ߬ߕߎ߲߬ߕߌ",
|
||||
"fulltext-search-unavailable": "ߛߓߍߟߌ߫ ߘߝߊߣߍ߲ ߢߌߣߌ߲ߠߌ߲ ߕߍ߫ ߦߋ߲߬",
|
||||
"no-search-results": "ߛߓߍߟߌ߫ ߘߝߊߣߍ߲ ߢߌߣߌ߲ߠߌ߲߫ ߣߌߘߐ ߕߍ߫ ߦߋ߲߬ ߞߣߐߘߐ ߣߌ߲߬ ߢߍ߫.",
|
||||
"library-button-text": "ߕߊ߯ ߟߊ߬ߛߣߍ߬ߟߌ߫ ߞߐߜߍ ߞߊ߲߬",
|
||||
"home-button-text": "ߕߊ߯ {{BOOK_TITLE}} ߓߏ߬ߟߏ߲߬ߘߊ ߞߐߜߍ ߞߊ߲߬",
|
||||
"random-page-button-text": "ߕߊ߯ ߓߍ߲߬ߛߋ߲߬ߡߊ߬ ߞߐߜߍ߫ ߛߎߥߊ߲ߘߌߣߍ߲ ߠߎ߬ ߞߊ߲߬",
|
||||
"searchbox-tooltip": "ߕߌߙߌ߲ߠߌ߲ {{BOOK_TITLE}}"
|
||||
}
|
||||
@@ -3,15 +3,22 @@
|
||||
"authors": [
|
||||
"Fenixs-ru",
|
||||
"Kareyac",
|
||||
"Okras",
|
||||
"Pacha Tchernof"
|
||||
]
|
||||
},
|
||||
"name": "русский",
|
||||
"suggest-full-text-search": "содержащее '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Такой книги нет: {{BOOK_NAME}}",
|
||||
"too-many-books": "Запрошено слишком много книг ({{NB_BOOKS}}), максимальное количество — {{LIMIT}}.",
|
||||
"no-book-found": "Ни одна книга не соответствует критериям отбора",
|
||||
"url-not-found": "Запрошенный URL \"{{url}}\" не найден на этом сервере.",
|
||||
"suggest-search": "Выполните полнотекстовый поиск для <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Ой! Не удалось выбрать случайную статью :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} не является допустимым запросом необработанного контента.",
|
||||
"no-value-for-arg": "Не указано значение для аргумента {{ARGUMENT}}",
|
||||
"no-query": "Не предоставлен запрос.",
|
||||
"raw-entry-not-found": "Не удаётся найти запись {{ENTRY}} типа {{DATATYPE}}",
|
||||
"400-page-title": "Недействительный запрос",
|
||||
"400-page-heading": "Недействительный запрос",
|
||||
"404-page-title": "Содержание не найдено",
|
||||
|
||||
@@ -7,10 +7,14 @@
|
||||
"name": "Sardu",
|
||||
"suggest-full-text-search": "chi cuntenet '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Perunu libru cun custu nùmene: {{BOOK_NAME}}",
|
||||
"too-many-books": "Tropu libros pedidos, {{NB_BOOKS}} cando su lìmite est de {{LIMIT}}",
|
||||
"no-book-found": "Perunu libru currispondet a sos critèrios de seletzione",
|
||||
"url-not-found": "S'URL pedidu \"{{url}}\" non s'est atzapadu in custu serbidore.",
|
||||
"suggest-search": "Faghe una chirca de testu intreu pro <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Oops! Sa seletzione de un'artìculu a casu est fallida :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} no est una rechesta vàlida pro cuntenutu puru.",
|
||||
"no-value-for-arg": "Perunu valore frunidu pro s'argumentu {{ARGUMENT}}",
|
||||
"no-query": "Peruna chirca frunida.",
|
||||
"raw-entry-not-found": "Non faghet a atzapare s'elementu {{ENTRY}} de genia {{DATATYPE}}",
|
||||
"400-page-title": "Rechesta non vàlida",
|
||||
"400-page-heading": "Rechesta non vàlida",
|
||||
|
||||
@@ -7,10 +7,14 @@
|
||||
"name": "slovenčina",
|
||||
"suggest-full-text-search": "obsahuje '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Žiadna kniha ako: {{BOOK_NAME}}",
|
||||
"too-many-books": "Príliš veľa požadovaných kníh ({{NB_BOOKS}}), limit je {{LIMIT}}",
|
||||
"no-book-found": "Kritériám výberu nevyhovuje žiadna kniha",
|
||||
"url-not-found": "Požadovaná adresa URL \"{{url}}\" na tomto serveri nebola nájdená.",
|
||||
"suggest-search": "Spustite hľadanie celého textu <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Nepodarilo sa vybrať náhodný článok :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} nie je platná požiadavka pre surový obsah.",
|
||||
"no-value-for-arg": "Pre argument {{ARGUMENT}} nebola poskytnutá žiadna hodnota",
|
||||
"no-query": "Nebol poskytnutý žiadny dopyt.",
|
||||
"raw-entry-not-found": "Nepodarilo sa nájsť {{DATATYPE}} položka {{ENTRY}}",
|
||||
"400-page-title": "Neplatná požiadavka",
|
||||
"400-page-heading": "Neplatná požiadavka",
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Sabelöga"
|
||||
"Sabelöga",
|
||||
"WikiPhoenix"
|
||||
]
|
||||
},
|
||||
"name": "Svenska",
|
||||
"suggest-full-text-search": "innehåller '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Ingen sådan bok: {{BOOK_NAME}}",
|
||||
"too-many-books": "För många böcker begärda ({{NB_BOOKS}}) där gränsen är {{LIMIT}}",
|
||||
"url-not-found": "Den begärda webbadressen \"{{url}}\" hittades inte på denna server.",
|
||||
"suggest-search": "Utför en fulltextsökning för <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Hoppsan! Kunde inte välja en slumpartikel :(",
|
||||
|
||||
@@ -7,10 +7,14 @@
|
||||
"name": "Türkçe",
|
||||
"suggest-full-text-search": "'{{{SEARCH_TERMS}}}' içeriyor...",
|
||||
"no-such-book": "Böyle bir kitap yok: {{BOOK_NAME}}",
|
||||
"too-many-books": "Sınır {{LIMIT}} olduğunda çok fazla ({{NB_BOOKS}}) kitap istendi",
|
||||
"no-book-found": "Seçim kriterleriyle eşleşen kitap yok",
|
||||
"url-not-found": "İstenen \"{{url}}\" URL'si bu sunucuda bulunamadı.",
|
||||
"suggest-search": "<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> için tam metin araması yapın",
|
||||
"random-article-failure": "Hata! Rastgele bir madde seçilemedi :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}}, ham içerik için geçerli bir istek değil.",
|
||||
"no-value-for-arg": "{{ARGUMENT}} bağımsız değişkeni için değer sağlanmadı",
|
||||
"no-query": "Sorgu sağlanmadı.",
|
||||
"raw-entry-not-found": "{{DATATYPE}} {{ENTRY}} girişi bulunamadı",
|
||||
"400-page-title": "Geçersiz istek",
|
||||
"400-page-heading": "Geçersiz istek",
|
||||
|
||||
16
static/i18n/zh-hans.json
Normal file
16
static/i18n/zh-hans.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"GuoPC",
|
||||
"StarrySky"
|
||||
]
|
||||
},
|
||||
"name": "英语",
|
||||
"no-query": "未提供查询。",
|
||||
"400-page-title": "无效请求",
|
||||
"400-page-heading": "无效请求",
|
||||
"404-page-heading": "未找到",
|
||||
"500-page-title": "内部服务器错误",
|
||||
"500-page-heading": "内部服务器错误",
|
||||
"library-button-text": "前往欢迎页面"
|
||||
}
|
||||
@@ -1,15 +1,32 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Kly",
|
||||
"Winston Sung"
|
||||
]
|
||||
},
|
||||
"name": "繁體中文",
|
||||
"suggest-full-text-search": "正在包含「{{{SEARCH_TERMS}}}」…",
|
||||
"no-such-book": "沒有這樣的書籍:{{BOOK_NAME}}",
|
||||
"too-many-books": "請求太多個書籍({{NB_BOOKS}}),上限是 {{LIMIT}} 個",
|
||||
"no-book-found": "沒有書籍符合選擇標準",
|
||||
"url-not-found": "在此伺服器上找不到請求的 URL「{{url}}」。",
|
||||
"suggest-search": "建立 <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> 使用的全文搜尋",
|
||||
"random-article-failure": "哎呀!隨機挑選條目失敗 :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}}不是原始內容的有效請求。",
|
||||
"no-value-for-arg": "沒有為引數 {{ARGUMENT}} 提供內容",
|
||||
"no-query": "未提供查詢。",
|
||||
"raw-entry-not-found": "找不到{{DATATYPE}}項目{{ENTRY}}",
|
||||
"400-page-title": "無效請求",
|
||||
"400-page-heading": "無效請求",
|
||||
"404-page-title": "查無內容",
|
||||
"404-page-heading": "查無頁面",
|
||||
"500-page-title": "內部伺服器錯誤",
|
||||
"500-page-heading": "內部伺服器錯誤",
|
||||
"fulltext-search-unavailable": "全文搜尋無效",
|
||||
"no-search-results": "全文搜尋引擎不適用此內容。",
|
||||
"library-button-text": "前往歡迎首頁",
|
||||
"home-button-text": "前往「{{BOOK_TITLE}}」的首頁",
|
||||
"random-page-button-text": "前往隨機選取頁面",
|
||||
"searchbox-tooltip": "在{{BOOK_TITLE}}搜尋"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
i18n/bn.json
|
||||
i18n/cs.json
|
||||
i18n/en.json
|
||||
i18n/fr.json
|
||||
i18n/he.json
|
||||
@@ -8,10 +9,12 @@ i18n/ja.json
|
||||
i18n/ko.json
|
||||
i18n/ku-latn.json
|
||||
i18n/mk.json
|
||||
i18n/nqo.json
|
||||
i18n/pl.json
|
||||
i18n/ru.json
|
||||
i18n/sc.json
|
||||
i18n/sk.json
|
||||
i18n/sv.json
|
||||
i18n/tr.json
|
||||
i18n/zh-hans.json
|
||||
i18n/zh-hant.json
|
||||
|
||||
@@ -15,7 +15,7 @@ preprocessed_resources = custom_target('preprocessed_resource_files',
|
||||
|
||||
lib_resources = custom_target('resources',
|
||||
input: preprocessed_resources,
|
||||
output: ['kiwixlib-resources.cpp', 'kiwixlib-resources.h'],
|
||||
output: ['libkiwix-resources.cpp', 'libkiwix-resources.h'],
|
||||
command:[res_compiler,
|
||||
'--cxxfile', '@OUTPUT0@',
|
||||
'--hfile', '@OUTPUT1@',
|
||||
|
||||
@@ -4,28 +4,24 @@ skin/magnet.png
|
||||
skin/download.png
|
||||
skin/hash.png
|
||||
skin/search-icon.svg
|
||||
skin/taskbar.js
|
||||
skin/iso6391To3.js
|
||||
skin/isotope.pkgd.min.js
|
||||
skin/index.js
|
||||
skin/autoComplete.min.js
|
||||
skin/widget.js
|
||||
skin/taskbar.css
|
||||
skin/index.css
|
||||
skin/fonts/Poppins.ttf
|
||||
skin/fonts/Roboto.ttf
|
||||
skin/block_external.js
|
||||
skin/search_results.css
|
||||
skin/blank.html
|
||||
skin/viewer.js
|
||||
viewer.html
|
||||
templates/search_result.html
|
||||
templates/search_result.xml
|
||||
templates/error.html
|
||||
templates/error.xml
|
||||
templates/index.html
|
||||
templates/widget.html
|
||||
templates/suggestion.json
|
||||
templates/head_taskbar.html
|
||||
templates/taskbar_part.html
|
||||
templates/external_blocker_part.html
|
||||
templates/captured_external.html
|
||||
templates/catalog_entries.xml
|
||||
templates/catalog_v2_root.xml
|
||||
@@ -34,6 +30,7 @@ templates/catalog_v2_entry.xml
|
||||
templates/catalog_v2_categories.xml
|
||||
templates/catalog_v2_languages.xml
|
||||
templates/url_of_search_results_css
|
||||
templates/viewer_settings.js
|
||||
opensearchdescription.xml
|
||||
ft_opensearchdescription.xml
|
||||
catalog_v2_searchdescription.xml
|
||||
|
||||
11
static/skin/blank.html
Normal file
11
static/skin/blank.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Blank page</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,74 +0,0 @@
|
||||
const root = document.querySelector( `link[type='root']` ).getAttribute("href");
|
||||
// `block_path` variable used by openzim/warc2zim to detect whether URL blocking is enabled or not
|
||||
var block_path = `${root}/catch/external`;
|
||||
// called only on external links
|
||||
function capture_event(e, target) { target.setAttribute("href", encodeURI(block_path + "?source=" + target.href)); }
|
||||
|
||||
// called on all link clicks. filters external and call capture_event
|
||||
function on_click_event(e) {
|
||||
var target = findParent("a", e.target);
|
||||
if (target !== null && "href" in target) {
|
||||
var href = target.href;
|
||||
if (window.location.pathname.indexOf(block_path) == 0) // already in catch page
|
||||
return;
|
||||
if (href.indexOf(window.location.origin) == 0)
|
||||
return;
|
||||
if (href.substr(0, 2) == "//")
|
||||
return capture_event(e, target);
|
||||
if (href.substr(0, 5) == "http:")
|
||||
return capture_event(e, target);
|
||||
if (href.substr(0, 6) == "https:")
|
||||
return capture_event(e, target);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// script entrypoint (called on document ready)
|
||||
function run() { live('a', 'click', on_click_event); }
|
||||
|
||||
// find first parent with tagname
|
||||
function findParent(tagname, el) {
|
||||
while (el) {
|
||||
if ((el.nodeName || el.tagName).toLowerCase() === tagname.toLowerCase()) {
|
||||
return el;
|
||||
}
|
||||
el = el.parentNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// matches polyfill
|
||||
this.Element && function(ElementPrototype) {
|
||||
ElementPrototype.matches = ElementPrototype.matches ||
|
||||
ElementPrototype.matchesSelector ||
|
||||
ElementPrototype.webkitMatchesSelector ||
|
||||
ElementPrototype.msMatchesSelector ||
|
||||
function(selector) {
|
||||
var node = this, nodes = (node.parentNode || node.document).querySelectorAll(selector), i = -1;
|
||||
while (nodes[++i] && nodes[i] != node);
|
||||
return !!nodes[i];
|
||||
}
|
||||
}(Element.prototype);
|
||||
|
||||
// helper for enabling IE 8 event bindings
|
||||
function addEvent(el, type, handler) {
|
||||
if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler);
|
||||
}
|
||||
|
||||
// live binding helper using matchesSelector
|
||||
function live(selector, event, callback, context) {
|
||||
addEvent(context || document, event, function(e) {
|
||||
var found, el = e.target || e.srcElement;
|
||||
while (el && el.matches && el !== context && !(found = el.matches(selector))) el = el.parentElement;
|
||||
if (found) callback.call(el, e);
|
||||
});
|
||||
}
|
||||
|
||||
// in case the document is already rendered
|
||||
if (document.readyState!='loading') run();
|
||||
// modern browsers
|
||||
else if (document.addEventListener) document.addEventListener('DOMContentLoaded', run);
|
||||
// IE <= 8
|
||||
else document.attachEvent('onreadystatechange', function(){
|
||||
if (document.readyState=='complete') run();
|
||||
});
|
||||
@@ -61,7 +61,7 @@ body {
|
||||
padding: 7px 10px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.kiwixNav__kiwixFilter:-ms-expand {
|
||||
display: none;
|
||||
}
|
||||
@@ -213,7 +213,7 @@ body {
|
||||
display: grid;
|
||||
font-family: poppins;
|
||||
color: black;
|
||||
padding: 12px 10px 0 2px;
|
||||
padding: 5px 10px 0 7px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
@@ -482,4 +482,4 @@ body {
|
||||
.kiwixNav__filters {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const kiwixServe = (function() {
|
||||
(function() {
|
||||
const root = document.querySelector(`link[type='root']`).getAttribute('href');
|
||||
const incrementalLoadingParams = {
|
||||
start: 0,
|
||||
@@ -17,7 +17,6 @@ const kiwixServe = (function() {
|
||||
let params = new URLSearchParams(window.location.search || filters || '');
|
||||
let timer;
|
||||
let languages = {};
|
||||
let allowBookClick = true;
|
||||
|
||||
function queryUrlBuilder() {
|
||||
let url = `${root}/catalog/search?`;
|
||||
@@ -86,7 +85,7 @@ const kiwixServe = (function() {
|
||||
}
|
||||
|
||||
function generateBookHtml(book, sort = false) {
|
||||
let link = book.querySelector('link[type="text/html"]').getAttribute('href');
|
||||
const link = book.querySelector('link[type="text/html"]').getAttribute('href');
|
||||
let iconUrl;
|
||||
book.querySelectorAll('link[rel="http://opds-spec.org/image/thumbnail"]').forEach(link => {
|
||||
if (link.getAttribute('type').split(';')[1] == 'width=48' && !iconUrl) {
|
||||
@@ -111,6 +110,9 @@ const kiwixServe = (function() {
|
||||
} catch {
|
||||
downloadLink = '';
|
||||
}
|
||||
const bookName = link.split('/').pop();
|
||||
const viewerLink = `${root}/viewer#${bookName}`;
|
||||
|
||||
const humanFriendlyZimSize = humanFriendlySize(zimSize);
|
||||
|
||||
const divTag = document.createElement('div');
|
||||
@@ -121,12 +123,9 @@ const kiwixServe = (function() {
|
||||
}
|
||||
const faviconAttr = iconUrl != undefined ? `style="background-image: url('${iconUrl}')"` : '';
|
||||
const languageAttr = langCode != '' ? `title="${language}" aria-label="${language}"` : 'style="background-color: transparent"';
|
||||
if (!allowBookClick) {
|
||||
link = "javascript:void(0)";
|
||||
}
|
||||
divTag.innerHTML = `
|
||||
<div class="book__wrapper">
|
||||
<a class="book__link" href="${link}" data-hover="Preview">
|
||||
<a class="book__link" href="${viewerLink}" data-hover="Preview">
|
||||
<div class="book__link__wrapper">
|
||||
<div class="book__icon" ${faviconAttr}></div>
|
||||
<div class="book__header">
|
||||
@@ -183,12 +182,12 @@ const kiwixServe = (function() {
|
||||
<div onclick="closeModal()" class="modal-close-button">
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.7071 1.70711C14.0976 1.31658 14.0976
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.7071 1.70711C14.0976 1.31658 14.0976
|
||||
0.683417 13.7071 0.292893C13.3166 -0.0976311 12.6834 -0.0976311 12.2929 0.292893L7 5.58579L1.70711
|
||||
0.292893C1.31658 -0.0976311 0.683417 -0.0976311 0.292893 0.292893C-0.0976311 0.683417
|
||||
-0.0976311 1.31658 0.292893 1.70711L5.58579 7L0.292893 12.2929C-0.0976311 12.6834
|
||||
-0.0976311 13.3166 0.292893 13.7071C0.683417 14.0976 1.31658 14.0976 1.70711 13.7071L7
|
||||
8.41421L12.2929 13.7071C12.6834 14.0976 13.3166 14.0976 13.7071 13.7071C14.0976 13.3166
|
||||
8.41421L12.2929 13.7071C12.6834 14.0976 13.3166 14.0976 13.7071 13.7071C14.0976 13.3166
|
||||
14.0976 12.6834 13.7071 12.2929L8.41421 7L13.7071 1.70711Z" fill="black" />
|
||||
</svg>
|
||||
</div>
|
||||
@@ -207,7 +206,7 @@ const kiwixServe = (function() {
|
||||
<div>Sha256 hash</div>
|
||||
</a>
|
||||
</div>
|
||||
${magnetLink ?
|
||||
${magnetLink ?
|
||||
`<div class="modal-regular-download">
|
||||
<a href="${magnetLink}" target="_blank">
|
||||
<img src="../skin/magnet.png?KIWIXCACHEID" alt="download magnet" />
|
||||
@@ -251,16 +250,14 @@ const kiwixServe = (function() {
|
||||
toggleFooter();
|
||||
}
|
||||
const kiwixResultText = document.querySelector('.kiwixHomeBody__results')
|
||||
if (kiwixResultText) {
|
||||
if (results) {
|
||||
let resultText = `${results} books`;
|
||||
if (results === 1) {
|
||||
resultText = `${results} book`;
|
||||
}
|
||||
kiwixResultText.innerHTML = resultText;
|
||||
} else {
|
||||
kiwixResultText.innerHTML = ``;
|
||||
if (results) {
|
||||
let resultText = `${results} books`;
|
||||
if (results === 1) {
|
||||
resultText = `${results} book`;
|
||||
}
|
||||
kiwixResultText.innerHTML = resultText;
|
||||
} else {
|
||||
kiwixResultText.innerHTML = ``;
|
||||
}
|
||||
loader.style.display = 'none';
|
||||
return books;
|
||||
@@ -271,20 +268,16 @@ const kiwixServe = (function() {
|
||||
await fetch(query).then(async (resp) => {
|
||||
const data = new window.DOMParser().parseFromString(await resp.text(), 'application/xml');
|
||||
let optionStr = '';
|
||||
const entryList = data.querySelectorAll('entry');
|
||||
const nodeQueryElem = document.querySelector(nodeQuery);
|
||||
if (entryList && nodeQueryElem) {
|
||||
entryList.forEach(entry => {
|
||||
const title = getInnerHtml(entry, 'title');
|
||||
const value = getInnerHtml(entry, valueEntryNode);
|
||||
const hfTitle = humanFriendlyTitle(title);
|
||||
if (valueEntryNode == 'language') {
|
||||
languages[value] = hfTitle;
|
||||
}
|
||||
optionStr += (hfTitle != '') ? `<option value="${value}">${hfTitle}</option>` : '';
|
||||
});
|
||||
nodeQueryElem.innerHTML += optionStr;
|
||||
}
|
||||
data.querySelectorAll('entry').forEach(entry => {
|
||||
const title = getInnerHtml(entry, 'title');
|
||||
const value = getInnerHtml(entry, valueEntryNode);
|
||||
const hfTitle = humanFriendlyTitle(title);
|
||||
if (valueEntryNode == 'language') {
|
||||
languages[value] = hfTitle;
|
||||
}
|
||||
optionStr += (hfTitle != '') ? `<option value="${value}">${hfTitle}</option>` : '';
|
||||
});
|
||||
document.querySelector(nodeQuery).innerHTML += optionStr;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -399,10 +392,6 @@ const kiwixServe = (function() {
|
||||
});
|
||||
}
|
||||
|
||||
function disableBookClick() {
|
||||
allowBookClick = false;
|
||||
}
|
||||
|
||||
function addTagElement(tagValue, resetFilter) {
|
||||
const tagElement = document.getElementsByClassName('tagFilterLabel')[0];
|
||||
tagElement.style.display = 'inline-block';
|
||||
@@ -443,15 +432,13 @@ const kiwixServe = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
function updateBookCount(event) {
|
||||
window.addEventListener('resize', (event) => {
|
||||
if (timer) {clearTimeout(timer)}
|
||||
timer = setTimeout(() => {
|
||||
incrementalLoadingParams.count = incrementalLoadingParams.count && viewPortToCount();
|
||||
loadSubset();
|
||||
}, 100, event);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', (event) => updateBookCount(event));
|
||||
});
|
||||
|
||||
window.addEventListener('scroll', loadSubset);
|
||||
|
||||
@@ -491,11 +478,10 @@ const kiwixServe = (function() {
|
||||
const currentLink = window.location.search;
|
||||
const newLink = `?${params.toString()}`;
|
||||
if (currentLink != newLink) {
|
||||
window.history.pushState({}, null, newLink);
|
||||
window.history.pushState({}, null, newLink);
|
||||
}
|
||||
}
|
||||
updateVisibleParams();
|
||||
updateBookCount();
|
||||
document.getElementById('kiwixSearchForm').onsubmit = (event) => {event.preventDefault()};
|
||||
if (!window.location.search) {
|
||||
const browserLang = navigator.language.split('-')[0];
|
||||
@@ -508,10 +494,5 @@ const kiwixServe = (function() {
|
||||
}
|
||||
setCookie(filterCookieName, params.toString());
|
||||
}
|
||||
|
||||
return {
|
||||
updateBookCount,
|
||||
disableBookClick
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
#kiwixtoolbar {
|
||||
position: fixed;
|
||||
padding: .5em;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background-position-y: 0;
|
||||
transition: 0.3s;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
@@ -135,10 +129,6 @@ a.suggest, a.suggest:visited, a.suggest:hover, a.suggest:active {
|
||||
column-count: 1 !important;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: calc(3em - 5px) !important;
|
||||
}
|
||||
|
||||
@media(min-width:420px) {
|
||||
.kiwix_button_cont {
|
||||
display: inline-block !important;
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
function htmlDecode(input) {
|
||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
||||
return doc.documentElement.textContent;
|
||||
}
|
||||
|
||||
function setupAutoHidingOfTheToolbar() {
|
||||
let lastScrollTop = 0;
|
||||
const delta = 5;
|
||||
let didScroll = false;
|
||||
const kiwixToolBar = document.querySelector('#kiwixtoolbar');
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
didScroll = true;
|
||||
});
|
||||
|
||||
setInterval(function() {
|
||||
if (didScroll) {
|
||||
hasScrolled();
|
||||
didScroll = false;
|
||||
}
|
||||
}, 250);
|
||||
|
||||
function hasScrolled() {
|
||||
const st = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
if (Math.abs(lastScrollTop - st) <= delta)
|
||||
return;
|
||||
|
||||
if (st > lastScrollTop) {
|
||||
kiwixToolBar.style.top = '-100%';
|
||||
} else {
|
||||
kiwixToolBar.style.top = '0';
|
||||
}
|
||||
|
||||
lastScrollTop = st;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const root = document.querySelector(`link[type='root']`).getAttribute("href");
|
||||
const bookName = (window.location.pathname == `${root}/search`)
|
||||
? (new URLSearchParams(window.location.search)).get('content')
|
||||
: window.location.pathname.split(`${root}/`)[1].split('/')[0];
|
||||
|
||||
const autoCompleteJS = new autoComplete(
|
||||
{
|
||||
selector: "#kiwixsearchbox",
|
||||
placeHolder: document.querySelector("#kiwixsearchbox").title,
|
||||
threshold: 1,
|
||||
debounce: 300,
|
||||
data : {
|
||||
src: async (query) => {
|
||||
try {
|
||||
// Fetch Data from external Source
|
||||
const source = await fetch(`${root}/suggest?content=${encodeURIComponent(bookName)}&term=${encodeURIComponent(query)}`);
|
||||
const data = await source.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
},
|
||||
keys: ['label'],
|
||||
},
|
||||
submit: true,
|
||||
searchEngine: (query, record) => {
|
||||
// We accept all records
|
||||
return true;
|
||||
},
|
||||
resultsList: {
|
||||
noResults: true,
|
||||
/* We must display 10 results (requested) + 1 potential link to do a full text search. */
|
||||
maxResults: 11,
|
||||
},
|
||||
resultItem: {
|
||||
element: (item, data) => {
|
||||
let searchLink;
|
||||
if (data.value.kind == "path") {
|
||||
searchLink = `${root}/${bookName}/${htmlDecode(data.value.path)}`;
|
||||
} else {
|
||||
searchLink = `${root}/search?content=${encodeURIComponent(bookName)}&pattern=${encodeURIComponent(htmlDecode(data.value.value))}`;
|
||||
}
|
||||
item.innerHTML = `<a class="suggest" href="${searchLink}">${htmlDecode(data.value.label)}</a>`;
|
||||
},
|
||||
highlight: "autoComplete_highlight",
|
||||
selected: "autoComplete_selected"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
document.querySelector('#kiwixsearchform').addEventListener('submit', function(event) {
|
||||
try {
|
||||
const selectedElemLink = document.querySelector('.autoComplete_selected > a').href;
|
||||
if (selectedElemLink) {
|
||||
event.preventDefault();
|
||||
window.location = selectedElemLink;
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
|
||||
const kiwixSearchBox = document.querySelector('#kiwixsearchbox');
|
||||
const kiwixSearchForm = document.querySelector('.kiwix_searchform');
|
||||
kiwixSearchBox.addEventListener('focus', () => {
|
||||
kiwixSearchForm.classList.add('full_width');
|
||||
document.querySelector('label[for="kiwix_button_show_toggle"]').classList.add('searching');
|
||||
document.querySelector('.kiwix_button_cont').classList.add('searching');
|
||||
});
|
||||
kiwixSearchBox.addEventListener('blur', () => {
|
||||
kiwixSearchForm.classList.remove('full_width');
|
||||
document.querySelector('label[for="kiwix_button_show_toggle"]').classList.remove('searching');
|
||||
document.querySelector('.kiwix_button_cont').classList.remove('searching');
|
||||
});
|
||||
|
||||
// cybook hack
|
||||
if (navigator.userAgent.indexOf("bookeen/cybook") != -1) {
|
||||
document.querySelector('html').classList.add('cybook');
|
||||
}
|
||||
|
||||
if (document.body.clientWidth < 520) {
|
||||
setupAutoHidingOfTheToolbar();
|
||||
}
|
||||
|
||||
});
|
||||
409
static/skin/viewer.js
Normal file
409
static/skin/viewer.js
Normal file
@@ -0,0 +1,409 @@
|
||||
// Terminology
|
||||
//
|
||||
// user url: identifier of the page that has to be displayed in the viewer
|
||||
// and that is used as the hash component of the viewer URL. For
|
||||
// book resources the address url is {book}/{resource} .
|
||||
//
|
||||
// iframe url: the URL to be loaded in the viewer iframe.
|
||||
|
||||
function userUrl2IframeUrl(url) {
|
||||
if ( url == '' ) {
|
||||
return blankPageUrl;
|
||||
}
|
||||
|
||||
if ( url.startsWith('search?') ) {
|
||||
return `${root}/${url}`;
|
||||
}
|
||||
|
||||
return `${root}/content/${url}`;
|
||||
}
|
||||
|
||||
function getBookFromUserUrl(url) {
|
||||
if ( url == '' ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( url.startsWith('search?') ) {
|
||||
const p = new URLSearchParams(url.slice("search?".length));
|
||||
return p.get('books.name') || p.get('content');
|
||||
}
|
||||
return url.split('/')[0];
|
||||
}
|
||||
|
||||
let currentBook = getBookFromUserUrl(location.hash.slice(1));
|
||||
let currentBookTitle = null;
|
||||
|
||||
const bookUIGroup = document.getElementById('kiwix_serve_taskbar_book_ui_group');
|
||||
const homeButton = document.getElementById('kiwix_serve_taskbar_home_button');
|
||||
const contentIframe = document.getElementById('content_iframe');
|
||||
|
||||
|
||||
function gotoMainPageOfCurrentBook() {
|
||||
location.hash = currentBook + '/';
|
||||
}
|
||||
|
||||
function gotoUrl(url) {
|
||||
contentIframe.src = url;
|
||||
}
|
||||
|
||||
function gotoRandomPage() {
|
||||
gotoUrl(`${root}/random?content=${currentBook}`);
|
||||
}
|
||||
|
||||
function performSearch() {
|
||||
const searchbox = document.getElementById('kiwixsearchbox');
|
||||
const q = encodeURIComponent(searchbox.value);
|
||||
gotoUrl(`${root}/search?books.name=${currentBook}&pattern=${q}`);
|
||||
}
|
||||
|
||||
function suggestionsApiURL()
|
||||
{
|
||||
return `${root}/suggest?content=${encodeURIComponent(currentBook)}`;
|
||||
}
|
||||
|
||||
function setCurrentBook(book, title) {
|
||||
currentBook = book;
|
||||
currentBookTitle = title;
|
||||
homeButton.title = `Go to the main page of '${title}'`;
|
||||
homeButton.setAttribute("aria-label", homeButton.title);
|
||||
homeButton.innerHTML = `<button>${title}</button>`;
|
||||
bookUIGroup.style.display = 'inline';
|
||||
updateSearchBoxForBookChange();
|
||||
}
|
||||
|
||||
function noCurrentBook() {
|
||||
currentBook = null;
|
||||
currentBookTitle = null;
|
||||
bookUIGroup.style.display = 'none';
|
||||
updateSearchBoxForBookChange();
|
||||
}
|
||||
|
||||
function updateCurrentBookIfNeeded(userUrl) {
|
||||
const book = getBookFromUserUrl(userUrl);
|
||||
if ( currentBook != book ) {
|
||||
updateCurrentBook(book);
|
||||
}
|
||||
}
|
||||
|
||||
function updateCurrentBook(book) {
|
||||
if ( book == null ) {
|
||||
noCurrentBook();
|
||||
} else {
|
||||
fetch(`./raw/${book}/meta/Title`).then(async (resp) => {
|
||||
if ( resp.ok ) {
|
||||
setCurrentBook(book, await resp.text());
|
||||
} else {
|
||||
noCurrentBook();
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log("Error fetching book title: " + err);
|
||||
noCurrentBook();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function iframeUrl2UserUrl(url, query) {
|
||||
if ( url == blankPageUrl ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( url == `${root}/search` ) {
|
||||
return `search${query}`;
|
||||
}
|
||||
|
||||
url = url.slice(root.length);
|
||||
|
||||
return url.split('/').slice(2).join('/');
|
||||
}
|
||||
|
||||
function getSearchPattern() {
|
||||
const url = window.location.hash.slice(1);
|
||||
if ( url.startsWith('search?') ) {
|
||||
const p = new URLSearchParams(url.slice("search?".length));
|
||||
return p.get("pattern");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
let autoCompleteJS = null;
|
||||
|
||||
function closeSuggestions() {
|
||||
if ( autoCompleteJS ) {
|
||||
autoCompleteJS.close();
|
||||
}
|
||||
}
|
||||
|
||||
function updateSearchBoxForLocationChange() {
|
||||
closeSuggestions();
|
||||
document.getElementById("kiwixsearchbox").value = getSearchPattern();
|
||||
}
|
||||
|
||||
function updateSearchBoxForBookChange() {
|
||||
const searchbox = document.getElementById('kiwixsearchbox');
|
||||
const kiwixSearchFormWrapper = document.querySelector('.kiwix_searchform');
|
||||
if ( currentBookTitle ) {
|
||||
searchbox.title = `Search '${currentBookTitle}'`;
|
||||
searchbox.placeholder = searchbox.title;
|
||||
searchbox.setAttribute("aria-label", searchbox.title);
|
||||
kiwixSearchFormWrapper.style.display = 'inline';
|
||||
} else {
|
||||
kiwixSearchFormWrapper.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function handle_visual_viewport_change() {
|
||||
contentIframe.height = window.visualViewport.height - contentIframe.offsetTop - 4;
|
||||
}
|
||||
|
||||
function handle_location_hash_change() {
|
||||
const hash = window.location.hash.slice(1);
|
||||
console.log("handle_location_hash_change: " + hash);
|
||||
updateCurrentBookIfNeeded(hash);
|
||||
const iframeContentUrl = userUrl2IframeUrl(hash);
|
||||
if ( iframeContentUrl != contentIframe.contentWindow.location.pathname ) {
|
||||
contentIframe.contentWindow.location.replace(iframeContentUrl);
|
||||
}
|
||||
updateSearchBoxForLocationChange();
|
||||
}
|
||||
|
||||
function handle_content_url_change() {
|
||||
const iframeLocation = contentIframe.contentWindow.location;
|
||||
console.log('handle_content_url_change: ' + iframeLocation.href);
|
||||
document.title = contentIframe.contentDocument.title;
|
||||
const iframeContentUrl = iframeLocation.pathname;
|
||||
const iframeContentQuery = iframeLocation.search;
|
||||
const newHash = iframeUrl2UserUrl(iframeContentUrl, iframeContentQuery);
|
||||
const viewerURL = location.origin + location.pathname + location.search;
|
||||
window.location.replace(viewerURL + '#' + newHash);
|
||||
updateCurrentBookIfNeeded(newHash);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// External link blocking
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function matchingAncestorElement(el, context, selector) {
|
||||
while (el && el.matches && el !== context) {
|
||||
if ( el.matches(selector) )
|
||||
return el;
|
||||
el = el.parentElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const block_path = `${root}/catch/external`;
|
||||
|
||||
function blockLink(target) {
|
||||
const encodedHref = encodeURIComponent(target.href);
|
||||
target.setAttribute("href", block_path + "?source=" + encodedHref);
|
||||
}
|
||||
|
||||
function isExternalUrl(url) {
|
||||
if ( url.startsWith(window.location.origin) )
|
||||
return false;
|
||||
|
||||
return url.startsWith("//")
|
||||
|| url.startsWith("http:")
|
||||
|| url.startsWith("https:");
|
||||
}
|
||||
|
||||
function onClickEvent(e) {
|
||||
const iframeDocument = contentIframe.contentDocument;
|
||||
const target = matchingAncestorElement(e.target, iframeDocument, "a");
|
||||
if (target !== null && "href" in target) {
|
||||
if ( isExternalUrl(target.href) ) {
|
||||
target.setAttribute("target", "_top");
|
||||
if ( viewerSettings.linkBlockingEnabled ) {
|
||||
return blockLink(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helper for enabling IE 8 event bindings
|
||||
function addEventHandler(el, eventType, handler) {
|
||||
if (el.attachEvent)
|
||||
el.attachEvent('on'+eventType, handler);
|
||||
else
|
||||
el.addEventListener(eventType, handler);
|
||||
}
|
||||
|
||||
function setupEventHandler(context, selector, eventType, callback) {
|
||||
addEventHandler(context, eventType, function(e) {
|
||||
const eventElement = e.target || e.srcElement;
|
||||
const el = matchingAncestorElement(eventElement, context, selector);
|
||||
if (el)
|
||||
callback.call(el, e);
|
||||
});
|
||||
}
|
||||
|
||||
// matches polyfill
|
||||
this.Element && function(ElementPrototype) {
|
||||
ElementPrototype.matches = ElementPrototype.matches ||
|
||||
ElementPrototype.matchesSelector ||
|
||||
ElementPrototype.webkitMatchesSelector ||
|
||||
ElementPrototype.msMatchesSelector ||
|
||||
function(selector) {
|
||||
var node = this, nodes = (node.parentNode || node.document).querySelectorAll(selector), i = -1;
|
||||
while (nodes[++i] && nodes[i] != node);
|
||||
return !!nodes[i];
|
||||
}
|
||||
}(Element.prototype);
|
||||
|
||||
function setup_external_link_blocker() {
|
||||
setupEventHandler(contentIframe.contentDocument, 'a', 'click', onClickEvent);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// End of external link blocking
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function on_content_load() {
|
||||
handle_content_url_change();
|
||||
setup_external_link_blocker();
|
||||
}
|
||||
|
||||
window.onresize = handle_visual_viewport_change;
|
||||
window.onhashchange = handle_location_hash_change;
|
||||
|
||||
updateCurrentBook(currentBook);
|
||||
handle_location_hash_change();
|
||||
|
||||
function htmlDecode(input) {
|
||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
||||
return doc.documentElement.textContent;
|
||||
}
|
||||
|
||||
function setupAutoHidingOfTheToolbar() {
|
||||
let lastScrollTop = 0;
|
||||
const delta = 5;
|
||||
let didScroll = false;
|
||||
const kiwixToolBar = document.querySelector('#kiwixtoolbar');
|
||||
|
||||
contentIframe.contentWindow.addEventListener('scroll', () => {
|
||||
didScroll = true;
|
||||
});
|
||||
|
||||
setInterval(function() {
|
||||
if (didScroll) {
|
||||
hasScrolled();
|
||||
didScroll = false;
|
||||
}
|
||||
}, 250);
|
||||
|
||||
function hasScrolled() {
|
||||
const iframeDoc = contentIframe.contentDocument;
|
||||
const st = iframeDoc.documentElement.scrollTop || iframeDoc.body.scrollTop;
|
||||
if (Math.abs(lastScrollTop - st) <= delta)
|
||||
return;
|
||||
|
||||
if (st > lastScrollTop) {
|
||||
kiwixToolBar.style.position = 'fixed';
|
||||
kiwixToolBar.style.top = '-100%';
|
||||
} else {
|
||||
kiwixToolBar.style.position = 'static';
|
||||
kiwixToolBar.style.top = '0';
|
||||
}
|
||||
|
||||
lastScrollTop = st;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setupSuggestions() {
|
||||
const kiwixSearchBox = document.querySelector('#kiwixsearchbox');
|
||||
const kiwixSearchFormWrapper = document.querySelector('.kiwix_searchform');
|
||||
|
||||
autoCompleteJS = new autoComplete(
|
||||
{
|
||||
selector: "#kiwixsearchbox",
|
||||
placeHolder: kiwixSearchBox.title,
|
||||
threshold: 1,
|
||||
debounce: 300,
|
||||
data : {
|
||||
src: async (query) => {
|
||||
try {
|
||||
// Fetch Data from external Source
|
||||
const source = await fetch(`${suggestionsApiURL()}&term=${encodeURIComponent(query)}`);
|
||||
const data = await source.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
},
|
||||
keys: ['label'],
|
||||
},
|
||||
submit: true,
|
||||
searchEngine: (query, record) => {
|
||||
// We accept all records
|
||||
return true;
|
||||
},
|
||||
resultsList: {
|
||||
noResults: true,
|
||||
// We must display 10 results (requested) + 1 potential link to do a full text search.
|
||||
maxResults: 11,
|
||||
},
|
||||
resultItem: {
|
||||
element: (item, data) => {
|
||||
let searchLink;
|
||||
if (data.value.kind == "path") {
|
||||
searchLink = `${root}/${currentBook}/${htmlDecode(data.value.path)}`;
|
||||
} else {
|
||||
searchLink = `${root}/search?content=${encodeURIComponent(currentBook)}&pattern=${encodeURIComponent(htmlDecode(data.value.value))}`;
|
||||
}
|
||||
item.innerHTML = `<a class="suggest" href="javascript:gotoUrl('${searchLink}')">${htmlDecode(data.value.label)}</a>`;
|
||||
},
|
||||
highlight: "autoComplete_highlight",
|
||||
selected: "autoComplete_selected"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
document.querySelector('#kiwixsearchform').addEventListener('submit', function(event) {
|
||||
closeSuggestions();
|
||||
try {
|
||||
const selectedElem = document.querySelector('.autoComplete_selected > a');
|
||||
if (selectedElem) {
|
||||
event.preventDefault();
|
||||
selectedElem.click();
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
|
||||
kiwixSearchBox.addEventListener('focus', () => {
|
||||
kiwixSearchFormWrapper.classList.add('full_width');
|
||||
document.querySelector('label[for="kiwix_button_show_toggle"]').classList.add('searching');
|
||||
document.querySelector('.kiwix_button_cont').classList.add('searching');
|
||||
});
|
||||
kiwixSearchBox.addEventListener('blur', () => {
|
||||
kiwixSearchFormWrapper.classList.remove('full_width');
|
||||
document.querySelector('label[for="kiwix_button_show_toggle"]').classList.remove('searching');
|
||||
document.querySelector('.kiwix_button_cont').classList.remove('searching');
|
||||
});
|
||||
}
|
||||
|
||||
function setupViewer() {
|
||||
setInterval(handle_visual_viewport_change, 0);
|
||||
|
||||
const kiwixToolBarWrapper = document.getElementById('kiwixtoolbarwrapper');
|
||||
if ( ! viewerSettings.toolbarEnabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
kiwixToolBarWrapper.style.display = 'block';
|
||||
if ( ! viewerSettings.libraryButtonEnabled ) {
|
||||
document.getElementById("kiwix_serve_taskbar_library_button").remove();
|
||||
}
|
||||
|
||||
setupSuggestions();
|
||||
|
||||
// cybook hack
|
||||
if (navigator.userAgent.indexOf("bookeen/cybook") != -1) {
|
||||
document.querySelector('html').classList.add('cybook');
|
||||
}
|
||||
|
||||
if (document.body.clientWidth < 520) {
|
||||
setupAutoHidingOfTheToolbar();
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
function disableSearchFilters(widgetStyles) {
|
||||
const hideNavRule = `
|
||||
.kiwixNav {
|
||||
display: none;
|
||||
}`;
|
||||
const hideResultsLabelRule = `
|
||||
.kiwixHomeBody__results {
|
||||
display: none;
|
||||
}`;
|
||||
const hideTagFilterRule = `
|
||||
.book__tags {
|
||||
pointer-events: none;
|
||||
}`;
|
||||
insertNewCssRules(widgetStyles, [hideNavRule, hideResultsLabelRule, hideTagFilterRule]);
|
||||
}
|
||||
|
||||
function disableBookClick() {
|
||||
kiwixServe.disableBookClick();
|
||||
}
|
||||
|
||||
function disableDownload(widgetStyles) {
|
||||
const hideBookDownloadRule = `
|
||||
.book__download {
|
||||
display: none;
|
||||
}`;
|
||||
insertNewCssRules(widgetStyles, [hideBookDownloadRule]);
|
||||
}
|
||||
|
||||
function disableDescription(widgetStyles) {
|
||||
const decreaseHeightRule = `
|
||||
.book__wrapper {
|
||||
height:128px;
|
||||
grid-template-rows: 70px 0 1fr 1fr;
|
||||
}`;
|
||||
const hideDescRule = `
|
||||
.book__description {
|
||||
display: none;
|
||||
}`;
|
||||
insertNewCssRules(widgetStyles, [decreaseHeightRule, hideDescRule]);
|
||||
}
|
||||
|
||||
function hideFooter(widgetStyles) {
|
||||
const hideFooterRule = `
|
||||
.kiwixfooter {
|
||||
display: none !important;
|
||||
}`;
|
||||
insertNewCssRules(widgetStyles, [hideFooterRule]);
|
||||
}
|
||||
|
||||
function insertNewCssRules(stylesheet, ruleList) {
|
||||
if (stylesheet) {
|
||||
for (rule of ruleList) {
|
||||
stylesheet.insertRule(rule, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addCustomCss(cssCode) {
|
||||
let customCSS = document.createElement('style');
|
||||
customCSS.innerHTML = cssCode;
|
||||
document.head.appendChild(customCSS);
|
||||
}
|
||||
|
||||
function addCustomJs(jsCode) {
|
||||
new Function(`"use strict";${jsCode}`)();
|
||||
}
|
||||
|
||||
function handleMessages(event) {
|
||||
if ('css' in event.data) {
|
||||
addCustomCss(event.data.css);
|
||||
}
|
||||
if ('js' in event.data) {
|
||||
addCustomJs(event.data.js);
|
||||
}
|
||||
}
|
||||
|
||||
function handleWidget() {
|
||||
const params = new URLSearchParams(window.location.search || filters || '');
|
||||
const widgetStyleElem = document.createElement('style');
|
||||
document.head.appendChild(widgetStyleElem);
|
||||
|
||||
const widgetStyles = widgetStyleElem.sheet;
|
||||
|
||||
const disableFilters = params.has('disablefilter');
|
||||
const disableClick = params.has('disableclick');
|
||||
const disableDwld = params.has('disabledownload');
|
||||
const disableDesc = params.has('disabledesc');
|
||||
|
||||
const blankBase = document.createElement('base');
|
||||
blankBase.target = '_blank';
|
||||
document.head.appendChild(blankBase); // open all links in new tab
|
||||
|
||||
if (disableFilters)
|
||||
disableSearchFilters(widgetStyles);
|
||||
if (disableClick)
|
||||
disableBookClick();
|
||||
if (disableDwld)
|
||||
disableDownload(widgetStyles);
|
||||
if (disableDesc)
|
||||
disableDescription(widgetStyles);
|
||||
|
||||
hideFooter(widgetStyles);
|
||||
kiwixServe.updateBookCount();
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleMessages);
|
||||
handleWidget();
|
||||
@@ -1 +0,0 @@
|
||||
<script type="text/javascript" src="{{root}}/skin/block_external.js"></script>
|
||||
@@ -1,4 +0,0 @@
|
||||
<link type="text/css" href="{{root}}/skin/taskbar.css?KIWIXCACHEID" rel="Stylesheet" />
|
||||
<link type="text/css" href="{{root}}/skin/css/autoComplete.css?KIWIXCACHEID" rel="Stylesheet" />
|
||||
<script type="text/javascript" src="{{root}}/skin/taskbar.js?KIWIXCACHEID" defer></script>
|
||||
<script type="text/javascript" src="{{root}}/skin/autoComplete.min.js?KIWIXCACHEID"></script>
|
||||
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<link type="root" href="{{root}}">
|
||||
<title>Welcome to Kiwix Server</title>
|
||||
<link
|
||||
type="text/css"
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<span class="kiwix">
|
||||
<span id="kiwixtoolbar" class="ui-widget-header">
|
||||
<div class="kiwix_centered">
|
||||
<div class="kiwix_searchform">
|
||||
<form class="kiwixsearch" method="GET" action="{{root}}/search" id="kiwixsearchform">
|
||||
{{#hascontent}}<input type="hidden" name="content" value="{{content}}" />{{/hascontent}}
|
||||
<label for="kiwixsearchbox">🔍</label>
|
||||
<input autocomplete="off" id="kiwixsearchbox" name="pattern" type="text" size="50" title="{{{SEARCHBOX_TOOLTIP}}}" aria-label="{{{SEARCHBOX_TOOLTIP}}}">
|
||||
</form>
|
||||
</div>
|
||||
<input type="checkbox" id="kiwix_button_show_toggle">
|
||||
<label for="kiwix_button_show_toggle"><img src="{{root}}/skin/caret.png?KIWIXCACHEID" alt=""></label>
|
||||
<div class="kiwix_button_cont">
|
||||
{{#withlibrarybutton}}
|
||||
<a id="kiwix_serve_taskbar_library_button" title="{{{LIBRARY_BUTTON_TEXT}}}" aria-label="{{{LIBRARY_BUTTON_TEXT}}}" href="{{root}}/"><button>🏠</button></a>
|
||||
{{/withlibrarybutton}}
|
||||
{{#hascontent}}
|
||||
<a id="kiwix_serve_taskbar_home_button" title="{{{HOME_BUTTON_TEXT}}}" aria-label="{{{HOME_BUTTON_TEXT}}}" href="{{root}}/{{content}}/"><button>{{title}}</button></a>
|
||||
<a id="kiwix_serve_taskbar_random_button" title="{{{RANDOM_PAGE_BUTTON_TEXT}}}" aria-label="{{{RANDOM_PAGE_BUTTON_TEXT}}}"
|
||||
href="{{root}}/random?content={{#urlencoded}}{{{content}}}{{/urlencoded}}"><button>🎲</button></a>
|
||||
{{/hascontent}}
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
5
static/templates/viewer_settings.js
Normal file
5
static/templates/viewer_settings.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const viewerSettings = {
|
||||
toolbarEnabled: {{enable_toolbar}},
|
||||
linkBlockingEnabled: {{enable_link_blocking}},
|
||||
libraryButtonEnabled: {{enable_library_button}}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
<!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" />
|
||||
<title>Welcome to Kiwix Server</title>
|
||||
<link
|
||||
type="text/css"
|
||||
href="{{root}}/skin/index.css?KIWIXCACHEID"
|
||||
rel="Stylesheet"
|
||||
/>
|
||||
<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");
|
||||
}
|
||||
</style>
|
||||
<script src="{{root}}/skin/isotope.pkgd.min.js?KIWIXCACHEID" defer></script>
|
||||
<script src="{{root}}/skin/iso6391To3.js?KIWIXCACHEID"></script>
|
||||
<script type="text/javascript" src="{{root}}/skin/index.js?KIWIXCACHEID" defer></script>
|
||||
<script type="text/javascript" src="{{root}}/skin/widget.js?KIWIXCACHEID" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class='kiwixNav'>
|
||||
<div class="kiwixNav__filters">
|
||||
<div class="kiwixNav__select">
|
||||
<select name="lang" id="languageFilter" class='kiwixNav__kiwixFilter filter'>
|
||||
<option value="" selected>All languages</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="kiwixNav__select">
|
||||
<select name="category" id="categoryFilter" class='kiwixNav__kiwixFilter filter'>
|
||||
<option value="" selected>All categories</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<form id='kiwixSearchForm' class='kiwixNav__SearchForm'>
|
||||
<input type="text" name="q" placeholder="Search" id="searchFilter" class='kiwixSearch filter'>
|
||||
<span class="kiwixButton tagFilterLabel"></span>
|
||||
<input type="submit" class="kiwixButton kiwixButtonHover" value="Search"/>
|
||||
</form>
|
||||
</div>
|
||||
<div class="kiwixHomeBody">
|
||||
<div class="book__list">
|
||||
<h3 class="kiwixHomeBody__results"></h3>
|
||||
</div>
|
||||
<div id="fadeOut" class="fadeOut"></div>
|
||||
</div>
|
||||
<div class="loader" style="position: absolute; top: 50%"><div class="loader-spinner"></div></div>
|
||||
<div id="kiwixfooter" class="kiwixfooter">Powered by <a href="https://kiwix.org">Kiwix</a></div>
|
||||
</body>
|
||||
<script>
|
||||
function closeModal() {
|
||||
for(modal of document.getElementsByClassName('modal-wrapper')) {
|
||||
modal.remove();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
68
static/viewer.html
Normal file
68
static/viewer.html
Normal file
@@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ZIM Viewer</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link type="text/css" href="./skin/taskbar.css?KIWIXCACHEID" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/css/autoComplete.css?KIWIXCACHEID" rel="Stylesheet" />
|
||||
<script type="text/javascript" src="./viewer_settings.js"></script>
|
||||
<script type="text/javascript" src="./skin/viewer.js?KIWIXCACHEID" defer></script>
|
||||
<script type="text/javascript" src="./skin/autoComplete.min.js?KIWIXCACHEID"></script>
|
||||
<script>
|
||||
function getRootLocation() {
|
||||
const p = location.pathname;
|
||||
return p.slice(0, p.length - '/viewer'.length);
|
||||
}
|
||||
|
||||
const root = getRootLocation();
|
||||
const blankPageUrl = `${root}/skin/blank.html`;
|
||||
|
||||
if ( location.hash == '' ) {
|
||||
location.href = root + '/';
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body style="margin:0" onload="setupViewer()">
|
||||
<div class="kiwix" style="display:none" id="kiwixtoolbarwrapper">
|
||||
<div id="kiwixtoolbar" class="ui-widget-header">
|
||||
<div class="kiwix_centered">
|
||||
<div class="kiwix_searchform">
|
||||
<form class="kiwixsearch" method="GET" action="javascript:performSearch()" id="kiwixsearchform">
|
||||
<label for="kiwixsearchbox">🔍</label>
|
||||
<input autocomplete="off" class="ui-autocomplete-input" id="kiwixsearchbox" name="pattern" type="text" title="Search '{{title}}'" aria-label="Search '{{title}}'">
|
||||
</form>
|
||||
</div>
|
||||
<input type="checkbox" id="kiwix_button_show_toggle">
|
||||
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?KIWIXCACHEID" alt=""></label>
|
||||
<div class="kiwix_button_cont">
|
||||
<a id="kiwix_serve_taskbar_library_button" title="Go to welcome page" aria-label="Go to welcome page" href="./"><button>🏠</button></a>
|
||||
<span id="kiwix_serve_taskbar_book_ui_group">
|
||||
<a id="kiwix_serve_taskbar_home_button"
|
||||
title="Go to the main page of the current book"
|
||||
aria-label="Go to the main page of the current book"
|
||||
onclick="gotoMainPageOfCurrentBook()"></a>
|
||||
<a id="kiwix_serve_taskbar_random_button"
|
||||
title="Go to a randomly selected page"
|
||||
aria-label="Go to a randomly selected page"
|
||||
onclick="gotoRandomPage()">
|
||||
<button>🎲</button>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<iframe id="content_iframe"
|
||||
referrerpolicy="same-origin"
|
||||
onload="on_content_load()"
|
||||
src="skin/blank.html" title="ZIM content" width="100%"
|
||||
style="border:0px">
|
||||
</iframe>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -500,6 +500,24 @@ TEST_F(LibraryTest, filterByTags)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, filterByAliasNames)
|
||||
{
|
||||
// filtering for one book
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().aliasNames({"zimfile"}),
|
||||
"Ray Charles"
|
||||
);
|
||||
|
||||
// filerting for more than one book
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().aliasNames({"zimfile", "example"}),
|
||||
"An example ZIM archive",
|
||||
"Ray Charles"
|
||||
);
|
||||
|
||||
// filtering by alias name requires full text match
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().aliasNames({"wrong_name"}),
|
||||
/* no results */
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, filterByQuery)
|
||||
{
|
||||
|
||||
@@ -69,7 +69,7 @@ if gtest_dep.found() and not meson.is_cross_build()
|
||||
test_exe = executable(test_name, [test_name+'.cpp'],
|
||||
implicit_include_directories: false,
|
||||
include_directories : inc,
|
||||
link_with : kiwixlib,
|
||||
link_with : libkiwix,
|
||||
link_args: extra_link_args,
|
||||
dependencies : all_deps + [gtest_dep],
|
||||
build_rpath : '$ORIGIN')
|
||||
|
||||
@@ -73,31 +73,4 @@ TEST(ReplaceRegex, middle)
|
||||
EXPECT_EQ(replaceRegex("abcdefghij", "----", "F"), "abcde----ghij");
|
||||
}
|
||||
|
||||
TEST(append, beggining)
|
||||
{
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "abcd", "----"), "abcd----efghij");
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "abcde", "----"), "abcde----fghij");
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "a.*i", "----"), "abcdefghi----j");
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "AbCd", "----"), "abcd----efghij");
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "A", "----"), "a----bcdefghij");
|
||||
}
|
||||
|
||||
TEST(append, end)
|
||||
{
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "ghij", "----"), "abcdefghij----");
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "fghij", "----"), "abcdefghij----");
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "c.*j", "----"), "abcdefghij----");
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "GhIj", "----"), "abcdefghij----");
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "J", "----"), "abcdefghij----");
|
||||
}
|
||||
|
||||
TEST(append, middle)
|
||||
{
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "cdef", "----"), "abcdef----ghij");
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "cdefgh", "----"), "abcdefgh----ij");
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "c.*f", "----"), "abcdef----ghij");
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "DeFg", "----"), "abcdefg----hij");
|
||||
EXPECT_EQ(appendToFirstOccurence("abcdefghij", "F", "----"), "abcdef----ghij");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
215
test/server.cpp
215
test/server.cpp
@@ -43,9 +43,9 @@ typedef std::vector<Resource> ResourceCollection;
|
||||
const ResourceCollection resources200Compressible{
|
||||
{ WITH_ETAG, "/ROOT/" },
|
||||
|
||||
{ WITH_ETAG, "/ROOT/skin/taskbar.js" },
|
||||
{ WITH_ETAG, "/ROOT/skin/autoComplete.min.js" },
|
||||
{ WITH_ETAG, "/ROOT/skin/css/autoComplete.css" },
|
||||
{ WITH_ETAG, "/ROOT/skin/taskbar.css" },
|
||||
{ WITH_ETAG, "/ROOT/skin/block_external.js" },
|
||||
|
||||
{ NO_ETAG, "/ROOT/catalog/search" },
|
||||
|
||||
@@ -53,8 +53,6 @@ const ResourceCollection resources200Compressible{
|
||||
|
||||
{ NO_ETAG, "/ROOT/suggest?content=zimfile&term=ray" },
|
||||
|
||||
{ NO_ETAG, "/ROOT/catch/external?source=www.example.com" },
|
||||
|
||||
{ WITH_ETAG, "/ROOT/content/zimfile/A/index" },
|
||||
{ WITH_ETAG, "/ROOT/content/zimfile/A/Ray_Charles" },
|
||||
|
||||
@@ -64,6 +62,7 @@ const ResourceCollection resources200Compressible{
|
||||
|
||||
const ResourceCollection resources200Uncompressible{
|
||||
{ WITH_ETAG, "/ROOT/skin/caret.png" },
|
||||
{ WITH_ETAG, "/ROOT/skin/css/images/search.svg" },
|
||||
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/meta/Title" },
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/meta/Description" },
|
||||
@@ -76,6 +75,8 @@ const ResourceCollection resources200Uncompressible{
|
||||
|
||||
{ NO_ETAG, "/ROOT/catalog/v2/illustration/6f1d19d0-633f-087b-fb55-7ac324ff9baf?size=48" },
|
||||
|
||||
{ NO_ETAG, "/ROOT/catch/external?source=www.example.com" },
|
||||
|
||||
{ WITH_ETAG, "/ROOT/content/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg" },
|
||||
|
||||
{ WITH_ETAG, "/ROOT/content/corner_cases/A/empty.html" },
|
||||
@@ -103,7 +104,7 @@ TEST(indexTemplateStringTest, emptyIndexTemplate) {
|
||||
"./test/corner_cases.zim"
|
||||
};
|
||||
|
||||
ZimFileServer zfs(PORT, /*withTaskbar=*/true, ZIMFILES, "");
|
||||
ZimFileServer zfs(PORT, ZimFileServer::DEFAULT_OPTIONS, ZIMFILES, "");
|
||||
EXPECT_EQ(200, zfs.GET("/ROOT/")->status);
|
||||
}
|
||||
|
||||
@@ -114,13 +115,12 @@ TEST(indexTemplateStringTest, indexTemplateCheck) {
|
||||
"./test/corner_cases.zim"
|
||||
};
|
||||
|
||||
ZimFileServer zfs(PORT, /*withTaskbar=*/true, ZIMFILES, "<!DOCTYPE html><head>"
|
||||
ZimFileServer zfs(PORT, ZimFileServer::DEFAULT_OPTIONS, ZIMFILES, "<!DOCTYPE html><head>"
|
||||
"<title>Welcome to kiwix library</title>"
|
||||
"</head>"
|
||||
"</html>");
|
||||
EXPECT_EQ("<!DOCTYPE html><head>"
|
||||
"<title>Welcome to kiwix library</title>"
|
||||
"<link type=\"root\" href=\"/ROOT\">"
|
||||
"</head>"
|
||||
"</html>", zfs.GET("/ROOT/")->body);
|
||||
}
|
||||
@@ -172,7 +172,7 @@ TEST_F(ServerTest, CacheIdsOfStaticResources)
|
||||
const std::vector<UrlAndExpectedResult> testData{
|
||||
{
|
||||
/* url */ "/ROOT/",
|
||||
R"EXPECTEDRESULT( href="/ROOT/skin/index.css?cacheid=56e818cd"
|
||||
R"EXPECTEDRESULT( href="/ROOT/skin/index.css?cacheid=3b470cee"
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/ROOT/skin/favicon/apple-touch-icon.png?cacheid=f86f8df3">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/ROOT/skin/favicon/favicon-32x32.png?cacheid=79ded625">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/ROOT/skin/favicon/favicon-16x16.png?cacheid=a986fedc">
|
||||
@@ -184,7 +184,7 @@ R"EXPECTEDRESULT( href="/ROOT/skin/index.css?cacheid=56e818cd"
|
||||
src: url("/ROOT/skin/fonts/Roboto.ttf?cacheid=84d10248") format("truetype");
|
||||
<script src="/ROOT/skin/isotope.pkgd.min.js?cacheid=2e48d392" defer></script>
|
||||
<script src="/ROOT/skin/iso6391To3.js?cacheid=ecde2bb3"></script>
|
||||
<script type="text/javascript" src="/ROOT/skin/index.js?cacheid=2fcc4ac4" defer></script>
|
||||
<script type="text/javascript" src="/ROOT/skin/index.js?cacheid=2f5a81ac" defer></script>
|
||||
)EXPECTEDRESULT"
|
||||
},
|
||||
{
|
||||
@@ -196,24 +196,24 @@ R"EXPECTEDRESULT( <img src="../skin/download.png?
|
||||
)EXPECTEDRESULT"
|
||||
},
|
||||
{
|
||||
/* url */ "/ROOT/content/zimfile/A/index",
|
||||
R"EXPECTEDRESULT(<link type="root" href="/ROOT"><link type="text/css" href="/ROOT/skin/taskbar.css?cacheid=26082885" rel="Stylesheet" />
|
||||
<link type="text/css" href="/ROOT/skin/css/autoComplete.css?cacheid=08951e06" rel="Stylesheet" />
|
||||
<script type="text/javascript" src="/ROOT/skin/taskbar.js?cacheid=1aec4a68" defer></script>
|
||||
<script type="text/javascript" src="/ROOT/skin/autoComplete.min.js?cacheid=1191aaaf"></script>
|
||||
<label for="kiwix_button_show_toggle"><img src="/ROOT/skin/caret.png?cacheid=22b942b4" alt=""></label>
|
||||
/* url */ "/ROOT/viewer",
|
||||
R"EXPECTEDRESULT( <link type="text/css" href="./skin/taskbar.css?cacheid=216d6b5d" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/css/autoComplete.css?cacheid=08951e06" rel="Stylesheet" />
|
||||
<script type="text/javascript" src="./skin/viewer.js?cacheid=9a336712" defer></script>
|
||||
<script type="text/javascript" src="./skin/autoComplete.min.js?cacheid=1191aaaf"></script>
|
||||
const blankPageUrl = `${root}/skin/blank.html`;
|
||||
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?cacheid=22b942b4" alt=""></label>
|
||||
)EXPECTEDRESULT"
|
||||
},
|
||||
{
|
||||
/* url */ "/ROOT/content/zimfile/A/index",
|
||||
""
|
||||
},
|
||||
{
|
||||
// Searching in a ZIM file without a full-text index returns
|
||||
// a page rendered from static/templates/no_search_result_html
|
||||
/* url */ "/ROOT/search?content=poor&pattern=whatever",
|
||||
R"EXPECTEDRESULT( <link type="text/css" href="/ROOT/skin/search_results.css?cacheid=76d39c84" rel="Stylesheet" />
|
||||
<link type="root" href="/ROOT"><link type="text/css" href="/ROOT/skin/taskbar.css?cacheid=26082885" rel="Stylesheet" />
|
||||
<link type="text/css" href="/ROOT/skin/css/autoComplete.css?cacheid=08951e06" rel="Stylesheet" />
|
||||
<script type="text/javascript" src="/ROOT/skin/taskbar.js?cacheid=1aec4a68" defer></script>
|
||||
<script type="text/javascript" src="/ROOT/skin/autoComplete.min.js?cacheid=1191aaaf"></script>
|
||||
<label for="kiwix_button_show_toggle"><img src="/ROOT/skin/caret.png?cacheid=22b942b4" alt=""></label>
|
||||
)EXPECTEDRESULT"
|
||||
},
|
||||
};
|
||||
@@ -429,13 +429,8 @@ public:
|
||||
std::string expectedResponse() const;
|
||||
|
||||
private:
|
||||
bool isTranslatedVersion() const;
|
||||
virtual std::string pageTitle() const;
|
||||
std::string pageCssLink() const;
|
||||
std::string hiddenBookNameInput() const;
|
||||
std::string searchPatternInput() const;
|
||||
std::string taskbarLinks() const;
|
||||
std::string goToWelcomePageText() const;
|
||||
};
|
||||
|
||||
std::string TestContentIn404HtmlResponse::expectedResponse() const
|
||||
@@ -451,40 +446,8 @@ std::string TestContentIn404HtmlResponse::expectedResponse() const
|
||||
)FRAG",
|
||||
|
||||
R"FRAG(
|
||||
<link type="root" href="/ROOT"><link type="text/css" href="/ROOT/skin/taskbar.css?cacheid=26082885" rel="Stylesheet" />
|
||||
<link type="text/css" href="/ROOT/skin/css/autoComplete.css?cacheid=08951e06" rel="Stylesheet" />
|
||||
<script type="text/javascript" src="/ROOT/skin/taskbar.js?cacheid=1aec4a68" defer></script>
|
||||
<script type="text/javascript" src="/ROOT/skin/autoComplete.min.js?cacheid=1191aaaf"></script>
|
||||
</head>
|
||||
<body><span class="kiwix">
|
||||
<span id="kiwixtoolbar" class="ui-widget-header">
|
||||
<div class="kiwix_centered">
|
||||
<div class="kiwix_searchform">
|
||||
<form class="kiwixsearch" method="GET" action="/ROOT/search" id="kiwixsearchform">
|
||||
)FRAG",
|
||||
|
||||
R"FRAG(
|
||||
<label for="kiwixsearchbox">🔍</label>
|
||||
)FRAG",
|
||||
|
||||
R"FRAG( </form>
|
||||
</div>
|
||||
<input type="checkbox" id="kiwix_button_show_toggle">
|
||||
<label for="kiwix_button_show_toggle"><img src="/ROOT/skin/caret.png?cacheid=22b942b4" alt=""></label>
|
||||
<div class="kiwix_button_cont">
|
||||
<a id="kiwix_serve_taskbar_library_button" title=")FRAG",
|
||||
|
||||
R"FRAG(" aria-label=")FRAG",
|
||||
|
||||
R"FRAG(" href="/ROOT/"><button>🏠</button></a>
|
||||
)FRAG",
|
||||
|
||||
R"FRAG(
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
)FRAG",
|
||||
</head>
|
||||
<body>)FRAG",
|
||||
|
||||
R"FRAG( </body>
|
||||
</html>
|
||||
@@ -496,18 +459,8 @@ std::string TestContentIn404HtmlResponse::expectedResponse() const
|
||||
+ frag[1]
|
||||
+ pageCssLink()
|
||||
+ frag[2]
|
||||
+ hiddenBookNameInput()
|
||||
+ frag[3]
|
||||
+ searchPatternInput()
|
||||
+ frag[4]
|
||||
+ goToWelcomePageText()
|
||||
+ frag[5]
|
||||
+ goToWelcomePageText()
|
||||
+ frag[6]
|
||||
+ taskbarLinks()
|
||||
+ frag[7]
|
||||
+ expectedBody
|
||||
+ frag[8];
|
||||
+ frag[3];
|
||||
}
|
||||
|
||||
std::string TestContentIn404HtmlResponse::pageTitle() const
|
||||
@@ -527,71 +480,6 @@ std::string TestContentIn404HtmlResponse::pageCssLink() const
|
||||
+ R"(" rel="Stylesheet" />)";
|
||||
}
|
||||
|
||||
std::string TestContentIn404HtmlResponse::hiddenBookNameInput() const
|
||||
{
|
||||
return bookName.empty()
|
||||
? ""
|
||||
: R"(<input type="hidden" name="content" value=")" + bookName + R"(" />)";
|
||||
}
|
||||
|
||||
std::string TestContentIn404HtmlResponse::searchPatternInput() const
|
||||
{
|
||||
const std::string searchboxTooltip = isTranslatedVersion()
|
||||
? "Որոնել '" + bookTitle + "'֊ում"
|
||||
: "Search '" + bookTitle + "'";
|
||||
return R"( <input autocomplete="off" id="kiwixsearchbox" name="pattern" type="text" size="50" title=")"
|
||||
+ searchboxTooltip
|
||||
+ R"(" aria-label=")"
|
||||
+ searchboxTooltip
|
||||
+ R"(">
|
||||
)";
|
||||
}
|
||||
|
||||
std::string TestContentIn404HtmlResponse::taskbarLinks() const
|
||||
{
|
||||
if ( bookName.empty() )
|
||||
return "";
|
||||
|
||||
const auto goToMainPageOfBook = isTranslatedVersion()
|
||||
? "Դեպի '" + bookTitle + "'֊ի գլխավոր էջը"
|
||||
: "Go to the main page of '" + bookTitle + "'";
|
||||
|
||||
const std::string goToRandomPage = isTranslatedVersion()
|
||||
? "Բացել պատահական էջ"
|
||||
: "Go to a randomly selected page";
|
||||
|
||||
return R"(<a id="kiwix_serve_taskbar_home_button" title=")"
|
||||
+ goToMainPageOfBook
|
||||
+ R"(" aria-label=")"
|
||||
+ goToMainPageOfBook
|
||||
+ R"(" href="/ROOT/)"
|
||||
+ bookName
|
||||
+ R"(/"><button>)"
|
||||
+ bookTitle
|
||||
+ R"(</button></a>
|
||||
<a id="kiwix_serve_taskbar_random_button" title=")"
|
||||
+ goToRandomPage
|
||||
+ R"(" aria-label=")"
|
||||
+ goToRandomPage
|
||||
+ R"("
|
||||
href="/ROOT/random?content=)"
|
||||
+ bookName
|
||||
+ R"("><button>🎲</button></a>)";
|
||||
}
|
||||
|
||||
bool TestContentIn404HtmlResponse::isTranslatedVersion() const
|
||||
{
|
||||
return url.find("userlang=hy") != std::string::npos;
|
||||
}
|
||||
|
||||
std::string TestContentIn404HtmlResponse::goToWelcomePageText() const
|
||||
{
|
||||
return isTranslatedVersion()
|
||||
? "Գրադարանի էջ"
|
||||
: "Go to welcome page";
|
||||
}
|
||||
|
||||
|
||||
class TestContentIn400HtmlResponse : public TestContentIn404HtmlResponse
|
||||
{
|
||||
public:
|
||||
@@ -1139,11 +1027,13 @@ TEST_F(ServerTest, RawEntry)
|
||||
EXPECT_EQ(200, p->status);
|
||||
EXPECT_EQ(std::string(p->body), std::string(entry.getItem(true).getData()));
|
||||
|
||||
/* Now normal content is not decorated in any way, either
|
||||
// ... but the "normal" content is not
|
||||
p = zfs1_->GET("/ROOT/content/zimfile/A/Ray_Charles");
|
||||
EXPECT_EQ(200, p->status);
|
||||
EXPECT_NE(std::string(p->body), std::string(entry.getItem(true).getData()));
|
||||
EXPECT_TRUE(p->body.find("taskbar") != std::string::npos);
|
||||
EXPECT_TRUE(p->body.find("<link type=\"root\" href=\"/ROOT\">") != std::string::npos);
|
||||
*/
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HeadMethodIsSupported)
|
||||
@@ -1200,7 +1090,7 @@ TEST_F(ServerTest, ETagIsTheSameAcrossHeadAndGet)
|
||||
|
||||
TEST_F(ServerTest, DifferentServerInstancesProduceDifferentETags)
|
||||
{
|
||||
ZimFileServer zfs2(SERVER_PORT + 1, /*withTaskbar=*/true, ZIMFILES);
|
||||
ZimFileServer zfs2(SERVER_PORT + 1, ZimFileServer::DEFAULT_OPTIONS, ZIMFILES);
|
||||
for ( const Resource& res : all200Resources() ) {
|
||||
if ( !res.etag_expected ) continue;
|
||||
const auto h1 = zfs1_->HEAD(res.url);
|
||||
@@ -1591,3 +1481,54 @@ TEST_F(ServerTest, suggestions_in_range)
|
||||
ASSERT_EQ(currCount, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, viewerSettings)
|
||||
{
|
||||
const auto JS_CONTENT_TYPE = "application/javascript; charset=utf-8";
|
||||
{
|
||||
resetServer(ZimFileServer::NO_TASKBAR_NO_LINK_BLOCKING);
|
||||
const auto r = zfs1_->GET("/ROOT/viewer_settings.js");
|
||||
ASSERT_EQ(r->status, 200);
|
||||
ASSERT_EQ(getHeaderValue(r->headers, "Content-Type"), JS_CONTENT_TYPE);
|
||||
ASSERT_EQ(r->body,
|
||||
R"(const viewerSettings = {
|
||||
toolbarEnabled: false,
|
||||
linkBlockingEnabled: false,
|
||||
libraryButtonEnabled: false
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
{
|
||||
resetServer(ZimFileServer::BLOCK_EXTERNAL_LINKS);
|
||||
ASSERT_EQ(zfs1_->GET("/ROOT/viewer_settings.js")->body,
|
||||
R"(const viewerSettings = {
|
||||
toolbarEnabled: false,
|
||||
linkBlockingEnabled: true,
|
||||
libraryButtonEnabled: false
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
{
|
||||
resetServer(ZimFileServer::WITH_TASKBAR);
|
||||
ASSERT_EQ(zfs1_->GET("/ROOT/viewer_settings.js")->body,
|
||||
R"(const viewerSettings = {
|
||||
toolbarEnabled: true,
|
||||
linkBlockingEnabled: false,
|
||||
libraryButtonEnabled: false
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
{
|
||||
resetServer(ZimFileServer::WITH_TASKBAR_AND_LIBRARY_BUTTON);
|
||||
ASSERT_EQ(zfs1_->GET("/ROOT/viewer_settings.js")->body,
|
||||
R"(const viewerSettings = {
|
||||
toolbarEnabled: true,
|
||||
linkBlockingEnabled: false,
|
||||
libraryButtonEnabled: true
|
||||
}
|
||||
)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ std::string makeSearchResultsHtml(const std::string& pattern,
|
||||
|
||||
</style>
|
||||
<title>Search: %PATTERN%</title>
|
||||
<link type="root" href="/ROOT"></head>
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
<div class="header">
|
||||
%HEADER%
|
||||
@@ -1453,16 +1453,13 @@ TEST_F(ServerTest, searchResults)
|
||||
|
||||
for ( const auto& t : testData ) {
|
||||
const std::string htmlSearchUrl = t.url();
|
||||
const auto htmlRes = taskbarlessZimFileServer().GET(htmlSearchUrl.c_str());
|
||||
const auto htmlRes = zfs1_->GET(htmlSearchUrl.c_str());
|
||||
EXPECT_EQ(htmlRes->status, 200);
|
||||
t.checkHtml(htmlRes->body);
|
||||
|
||||
const std::string xmlSearchUrl = t.xmlSearchUrl();
|
||||
const auto xmlRes1 = zfs1_->GET(xmlSearchUrl.c_str());
|
||||
const auto xmlRes2 = taskbarlessZimFileServer().GET(xmlSearchUrl.c_str());
|
||||
EXPECT_EQ(xmlRes1->status, 200);
|
||||
EXPECT_EQ(xmlRes2->status, 200);
|
||||
EXPECT_EQ(xmlRes1->body, xmlRes2->body);
|
||||
t.checkXml(xmlRes1->body);
|
||||
const auto xmlRes = zfs1_->GET(xmlSearchUrl.c_str());
|
||||
EXPECT_EQ(xmlRes->status, 200);
|
||||
t.checkXml(xmlRes->body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,10 +54,23 @@ public: // types
|
||||
typedef std::shared_ptr<httplib::Response> Response;
|
||||
typedef std::vector<std::string> FilePathCollection;
|
||||
|
||||
enum Options
|
||||
{
|
||||
NO_TASKBAR_NO_LINK_BLOCKING = 0,
|
||||
|
||||
WITH_TASKBAR = 1 << 1,
|
||||
WITH_LIBRARY_BUTTON = 1 << 2,
|
||||
BLOCK_EXTERNAL_LINKS = 1 << 3,
|
||||
|
||||
WITH_TASKBAR_AND_LIBRARY_BUTTON = WITH_TASKBAR | WITH_LIBRARY_BUTTON,
|
||||
|
||||
DEFAULT_OPTIONS = WITH_TASKBAR | WITH_LIBRARY_BUTTON
|
||||
};
|
||||
|
||||
public: // functions
|
||||
ZimFileServer(int serverPort, std::string libraryFilePath);
|
||||
ZimFileServer(int serverPort,
|
||||
bool withTaskbar,
|
||||
Options options,
|
||||
const FilePathCollection& zimpaths,
|
||||
std::string indexTemplateString = "");
|
||||
~ZimFileServer();
|
||||
@@ -81,7 +94,7 @@ private: // data
|
||||
std::unique_ptr<kiwix::HumanReadableNameMapper> nameMapper;
|
||||
std::unique_ptr<kiwix::Server> server;
|
||||
std::unique_ptr<httplib::Client> client;
|
||||
const bool withTaskbar = true;
|
||||
const Options options = DEFAULT_OPTIONS;
|
||||
};
|
||||
|
||||
ZimFileServer::ZimFileServer(int serverPort, std::string libraryFilePath)
|
||||
@@ -94,11 +107,11 @@ ZimFileServer::ZimFileServer(int serverPort, std::string libraryFilePath)
|
||||
}
|
||||
|
||||
ZimFileServer::ZimFileServer(int serverPort,
|
||||
bool _withTaskbar,
|
||||
Options _options,
|
||||
const FilePathCollection& zimpaths,
|
||||
std::string indexTemplateString)
|
||||
: manager(&this->library)
|
||||
, withTaskbar(_withTaskbar)
|
||||
, options(_options)
|
||||
{
|
||||
for ( const auto& zimpath : zimpaths ) {
|
||||
if (!manager.addBookFromPath(zimpath, zimpath, "", false))
|
||||
@@ -117,7 +130,8 @@ void ZimFileServer::run(int serverPort, std::string indexTemplateString)
|
||||
server->setPort(serverPort);
|
||||
server->setNbThreads(2);
|
||||
server->setVerbose(false);
|
||||
server->setTaskbar(withTaskbar, withTaskbar);
|
||||
server->setTaskbar(options & WITH_TASKBAR, options & WITH_LIBRARY_BUTTON);
|
||||
server->setBlockExternalLinks(options & BLOCK_EXTERNAL_LINKS);
|
||||
server->setMultiZimSearchLimit(3);
|
||||
if (!indexTemplateString.empty()) {
|
||||
server->setIndexTemplateString(indexTemplateString);
|
||||
@@ -136,9 +150,6 @@ ZimFileServer::~ZimFileServer()
|
||||
|
||||
class ServerTest : public ::testing::Test
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<ZimFileServer> taskbarlessZfs_;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<ZimFileServer> zfs1_;
|
||||
|
||||
@@ -151,19 +162,15 @@ protected:
|
||||
|
||||
protected:
|
||||
void SetUp() override {
|
||||
zfs1_.reset(new ZimFileServer(SERVER_PORT, /*withTaskbar=*/true, ZIMFILES));
|
||||
resetServer(ZimFileServer::DEFAULT_OPTIONS);
|
||||
}
|
||||
|
||||
ZimFileServer& taskbarlessZimFileServer()
|
||||
{
|
||||
if ( ! taskbarlessZfs_ ) {
|
||||
taskbarlessZfs_.reset(new ZimFileServer(SERVER_PORT+1, /*withTaskbar=*/false, ZIMFILES));
|
||||
}
|
||||
return *taskbarlessZfs_;
|
||||
void resetServer(ZimFileServer::Options options) {
|
||||
zfs1_.reset();
|
||||
zfs1_.reset(new ZimFileServer(SERVER_PORT, options, ZIMFILES));
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
zfs1_.reset();
|
||||
taskbarlessZfs_.reset();
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user