New 404 error page

The page doesn't support translation, yet.

The new 404 error page is used only when accessing ZIM file content
(i.e. as a response from the `/content` API endpoint).

One notable difference from the previous error page is that now no hint
is provided about whether the error is due to trying to access a
non-existent book/ZIM-file or non-existent resource inside a valid
book/ZIM-file (previously such a hint was present in the suggested
search URL). However, when displayed in the viewer this difference can
be seen in the viewer toolbar - book related buttons are hidden if the
URL points to a non-existent book.

This change breaks some unit tests. They will be fixed in a separate
commit.
This commit is contained in:
Veloman Yunkan 2025-02-07 19:56:02 +04:00
parent 107421cdab
commit 5eb31d7286
9 changed files with 223 additions and 15 deletions

View File

@ -1121,15 +1121,6 @@ InternalServer::search_catalog(const RequestContext& request,
namespace
{
ParameterizedMessage suggestSearchMsg(const std::string& searchURL, const std::string& pattern)
{
return ParameterizedMessage("suggest-search",
{
{ "PATTERN", pattern },
{ "SEARCH_URL", searchURL }
});
}
///////////////////////////////////////////////////////////////////////////////
// The content security policy below is set on responses to the /content
// endpoint in order to prevent the ZIM content from interfering with the
@ -1183,9 +1174,7 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
} catch (const std::out_of_range& e) {}
if (archive == nullptr) {
const std::string searchURL = m_root + "/search?pattern=" + kiwix::urlEncode(pattern);
return UrlNotFoundResponse(request)
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
return NewHTTP404Response(request, m_root, m_root + url);
}
const std::string archiveUuid(archive->getUuid());
@ -1230,9 +1219,7 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
if (m_verbose.load())
printf("Failed to find %s\n", urlStr.c_str());
std::string searchURL = m_root + "/search?content=" + bookName + "&pattern=" + kiwix::urlEncode(pattern);
return UrlNotFoundResponse(request)
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
return NewHTTP404Response(request, m_root, m_root + url);
}
}

View File

@ -368,6 +368,21 @@ std::unique_ptr<ContentResponse> ContentResponseBlueprint::generateResponseObjec
return r;
}
NewHTTP404Response::NewHTTP404Response(const RequestContext& request,
const std::string& root,
const std::string& urlPath)
: ContentResponseBlueprint(&request,
MHD_HTTP_NOT_FOUND,
"text/html; charset=utf-8",
RESOURCE::templates::sexy404_html,
/*includeKiwixResponseData=*/false)
{
*this->m_data = Data(Data::Object{
{"root", root },
{"url_path", urlPath}
});
}
HTTPErrorResponse::HTTPErrorResponse(const RequestContext& request,
int httpStatusCode,
const std::string& pageTitleMsgId,

View File

@ -145,6 +145,13 @@ protected: //data
std::unique_ptr<Data> m_data;
};
struct NewHTTP404Response : ContentResponseBlueprint
{
NewHTTP404Response(const RequestContext& request,
const std::string& root,
const std::string& urlPath);
};
struct HTTPErrorResponse : ContentResponseBlueprint
{
HTTPErrorResponse(const RequestContext& request,

View File

@ -1,6 +1,7 @@
skin/caret.png
skin/bittorrent.png
skin/magnet.png
skin/404.svg
skin/feed.svg
skin/langSelector.svg
skin/download.png
@ -11,9 +12,11 @@ skin/iso6391To3.js
skin/isotope.pkgd.min.js
skin/index.js
skin/autoComplete/autoComplete.min.js
skin/error.css
skin/kiwix.css
skin/taskbar.css
skin/index.css
skin/fonts/DMSans-Regular.ttf
skin/fonts/Poppins.ttf
skin/fonts/Roboto.ttf
skin/search_results.css
@ -42,6 +45,7 @@ templates/url_of_search_results_css.tmpl
templates/viewer_settings.js
templates/no_js_library_page.html
templates/no_js_download.html
templates/sexy404.html
opensearchdescription.xml
ft_opensearchdescription.xml
catalog_v2_searchdescription.xml

1
static/skin/404.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

153
static/skin/error.css Normal file
View File

@ -0,0 +1,153 @@
@font-face {
font-family:"DM Sans";
font-style: normal;
font-weight: 400;
src : url('../skin/fonts/DMSans-Regular.ttf?KIWIXCACHEID');
}
@font-face {
font-family:"DM Sans Bold";
font-style: normal;
font-weight: 700;
src : url('../skin/fonts/DMSans-Regular.ttf?KIWIXCACHEID');
}
body {
background: linear-gradient(to bottom right, #ffffff, #e6e6e6);
background-repeat: no-repeat;
background-attachment: fixed;
}
header {
width: 100%;
margin: auto;
text-align: center;
margin-top: 15%;
margin-bottom: 15%;
}
header img {
width: 60%;
min-width: 200px;
max-width: 500px;
max-height: 300px;
}
section {
display: flex;
flex-direction: column;
align-items: center;
}
header, .intro {
font-family: "DM Sans";
}
.intro {
font-size: 1em;
padding: 0 10%;
line-height: 1.2em;
text-align: center;
}
.intro h1 {
line-height: 1.1em;
font-family: "DM Sans Bold";
font-size: 1.2em;
}
.intro code {
font-family: monospace;
font-size: 1.1em;
word-break: break-all;
}
.advice {
width: 80%;
margin: auto;
margin-bottom: 15%;
margin-top: 5em;
background-color: #ffffff;
border-radius: 1rem;
border: 1px solid #b7b7b7;
padding: 2em;
font-family: "DM Sans";
font-size: .9em;
box-sizing: border-box;
align-items: normal;
}
.advice p {
margin-bottom: 1em;
}
.advice p:first-child {
margin-top: 0;
}
.advice p.list-intro {
margin: 0;
}
.advice ul {
list-style-type: square;
margin: 0;
padding: 0 1em;
}
.advice ul li {
line-height: 2em;
}
.advice p:last-child {
margin-bottom: 0;
}
/* sm: 640px+ */
@media (width >= 40rem) {
header {
margin-bottom: 1em;
margin-top: 5em;
}
header img {
width: 50%;
}
.intro h1 {
font-size: 2em;
}
.advice {
width: 50%;
}
}
/* xl: 1280px+ */
@media (width >= 80rem) {
.intro h1 {
font-size: 3.4em;
}
}
/* 2xl: 1536px+ */
@media (width >= 96rem) {
header img {
width: 25%;
min-width: 200px;
max-width: 500px;
max-height: 300px;
}
.advice {
width: 25%;
min-width: 200px;
min-width: 300px;
max-width: 500px;
}
}

Binary file not shown.

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Page not found</title>
<link type="text/css" href="{{root}}/skin/error.css?KIWIXCACHEID" rel="Stylesheet" />
</head>
<body>
<header>
<img src="{{root}}/skin/404.svg?KIWIXCACHEID"
alt="Not found!"
aria-label="Not found!"
title="Not found!">
</header>
<section class="intro">
<h1>Oops. Page not found.</h1>
<p>The requested path was not found:</p>
<p><code>{{url_path}}</code></p>
</section>
<section class="advice">
<p>The content you're looking for may still be available, but it might be located at a different place within the ZIM file.</p>
<p class="list-intro">Please:</p>
<ul>
<li>Try using the search function to find the content you want</li>
<li>Look for keywords or titles related to the information you're seeking</li>
</ul>
<p>This approach should help you locate the desired content, even if the original link isn't working properly.</p>
</section>
</body>
</html>

View File

@ -58,6 +58,8 @@ const ResourceCollection resources200Compressible{
{ STATIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/css/autoComplete.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/css/autoComplete.css?cacheid=f2d376c4" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/error.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/error.css?cacheid=c49d1586" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/i18n.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=071abc9a" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" },
@ -106,6 +108,8 @@ const ResourceCollection resources200Compressible{
};
const ResourceCollection resources200Uncompressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/404.svg" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/404.svg?cacheid=b6d648af" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/bittorrent.png" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/bittorrent.png?cacheid=4f5c6882" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/blank.html" },
@ -339,6 +343,12 @@ R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=3948b8
/* url */ "/ROOT%23%3F/content/zimfile/A/index",
""
},
{
/* url */ "/ROOT%23%3F/content/invalid-book/whatever",
R"EXPECTEDRESULT( <link type="text/css" href="/ROOT%23%3F/skin/error.css?cacheid=c49d1586" rel="Stylesheet" />
<img src="/ROOT%23%3F/skin/404.svg?cacheid=b6d648af"
)EXPECTEDRESULT"
},
{
// Searching in a ZIM file without a full-text index returns
// a page rendered from static/templates/no_search_result_html