mirror of
https://github.com/kiwix/libkiwix.git
synced 2025-12-24 15:07:59 -05:00
Compare commits
6 Commits
main
...
widgetEndp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc87def18b | ||
|
|
09dc6f90fd | ||
|
|
58c04b3f77 | ||
|
|
d27220f65d | ||
|
|
efe42c9bbe | ||
|
|
489dfc1123 |
@@ -12,3 +12,4 @@ Welcome to libkiwix's documentation!
|
||||
|
||||
usage
|
||||
api/ref_api
|
||||
widget
|
||||
|
||||
82
docs/widget.rst
Normal file
82
docs/widget.rst
Normal file
@@ -0,0 +1,82 @@
|
||||
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.
|
||||
|
||||
@@ -577,6 +577,9 @@ 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() )
|
||||
@@ -866,6 +869,11 @@ std::unique_ptr<Response> InternalServer::handle_random(const RequestContext& re
|
||||
}
|
||||
}
|
||||
|
||||
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 = "";
|
||||
|
||||
@@ -143,6 +143,7 @@ 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);
|
||||
|
||||
@@ -9,6 +9,7 @@ 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
|
||||
@@ -20,6 +21,7 @@ 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(function() {
|
||||
const kiwixServe = (function() {
|
||||
const root = document.querySelector(`link[type='root']`).getAttribute('href');
|
||||
const incrementalLoadingParams = {
|
||||
start: 0,
|
||||
@@ -17,6 +17,7 @@
|
||||
let params = new URLSearchParams(window.location.search || filters || '');
|
||||
let timer;
|
||||
let languages = {};
|
||||
let allowBookClick = true;
|
||||
|
||||
function queryUrlBuilder() {
|
||||
let url = `${root}/catalog/search?`;
|
||||
@@ -85,7 +86,7 @@
|
||||
}
|
||||
|
||||
function generateBookHtml(book, sort = false) {
|
||||
const link = book.querySelector('link[type="text/html"]').getAttribute('href');
|
||||
let 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) {
|
||||
@@ -120,6 +121,9 @@
|
||||
}
|
||||
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">
|
||||
@@ -247,14 +251,16 @@
|
||||
toggleFooter();
|
||||
}
|
||||
const kiwixResultText = document.querySelector('.kiwixHomeBody__results')
|
||||
if (results) {
|
||||
let resultText = `${results} books`;
|
||||
if (results === 1) {
|
||||
resultText = `${results} book`;
|
||||
if (kiwixResultText) {
|
||||
if (results) {
|
||||
let resultText = `${results} books`;
|
||||
if (results === 1) {
|
||||
resultText = `${results} book`;
|
||||
}
|
||||
kiwixResultText.innerHTML = resultText;
|
||||
} else {
|
||||
kiwixResultText.innerHTML = ``;
|
||||
}
|
||||
kiwixResultText.innerHTML = resultText;
|
||||
} else {
|
||||
kiwixResultText.innerHTML = ``;
|
||||
}
|
||||
loader.style.display = 'none';
|
||||
return books;
|
||||
@@ -265,16 +271,20 @@
|
||||
await fetch(query).then(async (resp) => {
|
||||
const data = new window.DOMParser().parseFromString(await resp.text(), 'application/xml');
|
||||
let 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;
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -388,6 +398,10 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function disableBookClick() {
|
||||
allowBookClick = false;
|
||||
}
|
||||
|
||||
function addTagElement(tagValue, resetFilter) {
|
||||
const tagElement = document.getElementsByClassName('tagFilterLabel')[0];
|
||||
@@ -429,13 +443,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', (event) => {
|
||||
function updateBookCount(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);
|
||||
|
||||
@@ -479,6 +495,7 @@
|
||||
}
|
||||
}
|
||||
updateVisibleParams();
|
||||
updateBookCount();
|
||||
document.getElementById('kiwixSearchForm').onsubmit = (event) => {event.preventDefault()};
|
||||
if (!window.location.search) {
|
||||
const browserLang = navigator.language.split('-')[0];
|
||||
@@ -491,5 +508,10 @@
|
||||
}
|
||||
setCookie(filterCookieName, params.toString());
|
||||
}
|
||||
|
||||
return {
|
||||
updateBookCount,
|
||||
disableBookClick
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
107
static/skin/widget.js
Normal file
107
static/skin/widget.js
Normal file
@@ -0,0 +1,107 @@
|
||||
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();
|
||||
64
static/templates/widget.html
Normal file
64
static/templates/widget.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<!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>
|
||||
@@ -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=76440e7a" defer></script>
|
||||
<script type="text/javascript" src="/ROOT/skin/index.js?cacheid=2fcc4ac4" defer></script>
|
||||
)EXPECTEDRESULT"
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user