feat(browser): redesign new-tab and not-found (#1923)

This commit is contained in:
Oleg Shparber
2026-06-30 14:31:51 +03:00
committed by GitHub
parent 525589d97e
commit 076b2b6068
29 changed files with 1589 additions and 401 deletions
+43
View File
@@ -0,0 +1,43 @@
SIL OPEN FONT LICENSE
Version 1.1 - 26 February 2007
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
+22 -7
View File
@@ -8,8 +8,11 @@ path = [
"README.md",
"src/**/*.ui",
"src/app/resources/browser/*.html",
"src/app/resources/browser/assets/css/highlight.css",
"src/app/resources/browser/assets/css/welcome.css",
"src/app/resources/browser/styles/highlight.css",
"src/app/resources/browser/styles/manual.css",
"src/app/resources/browser/styles/pages.css",
"src/app/resources/browser/styles/prose.css",
"src/app/resources/browser/styles/tokens.css",
"src/app/resources/icons/**/*",
"src/app/zeal.qrc",
]
@@ -32,6 +35,7 @@ path = [
".clang-format",
".clang-tidy",
".clangd",
"biome.json",
".editorconfig",
".gitattributes",
".github/**/*",
@@ -43,11 +47,6 @@ path = [
SPDX-FileCopyrightText = "Oleg Shparber, et al. <https://zealdocs.org>"
SPDX-License-Identifier = "MIT"
[[annotations]]
path = "src/app/resources/browser/assets/css/oat.min.css"
SPDX-FileCopyrightText = "Copyright (c) Kailash Nadh <https://github.com/knadh/oat>"
SPDX-License-Identifier = "MIT"
[[annotations]]
path = "src/contrib/cpp-httplib/httplib.h"
SPDX-FileCopyrightText = "Copyright (c) 2023 Yuji Hirose. All rights reserved."
@@ -75,3 +74,19 @@ SPDX-License-Identifier = "CC-BY-4.0"
path = "assets/tabler/*.svg"
SPDX-FileCopyrightText = "Copyright (c) Paweł Kuna <https://tabler.io>"
SPDX-License-Identifier = "MIT"
# OFL-1.1 for the bundled web fonts.
[[annotations]]
path = "src/app/resources/browser/fonts/Inter*.woff2"
SPDX-FileCopyrightText = "Copyright 2016 The Inter Project Authors"
SPDX-License-Identifier = "OFL-1.1"
[[annotations]]
path = "src/app/resources/browser/fonts/SourceSerif4*.woff2"
SPDX-FileCopyrightText = "Copyright 2014-2023 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'"
SPDX-License-Identifier = "OFL-1.1"
[[annotations]]
path = "src/app/resources/browser/fonts/GoogleSansCode*.woff2"
SPDX-FileCopyrightText = "Copyright 2025 The Google Sans Code Project Authors (https://github.com/googlefonts/googlesans-code)"
SPDX-License-Identifier = "OFL-1.1"
+22
View File
@@ -0,0 +1,22 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.1/schema.json",
"formatter": {
"enabled": true,
"useEditorconfig": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noImportantStyles": "off"
},
"style": {
"noDescendingSpecificity": "off"
}
}
},
"files": {
"includes": ["src/app/resources/browser/styles/**"]
}
}
-103
View File
@@ -1,103 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page not found</title>
<link rel="stylesheet" type="text/css" href="assets/css/oat.min.css">
<link rel="stylesheet" type="text/css" href="assets/css/welcome.css">
</head>
<body>
<div class="page">
<div class="brand">
<h1>Page not found</h1>
<p>Encountered a problem? Please <a role="link" tabindex="0" onclick="zAppBridge.openShortUrl('report-bug')">report it</a>.</p>
</div>
<div class="actions">
<h2>Troubleshoot</h2>
<div class="command-block" role="button" tabindex="0" onclick="zAppBridge.triggerAction('openDocsetManager')">
<h3>Docset Library</h3>
<p>Update or reinstall docsets</p>
</div>
<h2>Get in touch</h2>
<div class="command-block" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('discord')">
<h3>Discord</h3>
<p>Chat with the developers and other users</p>
</div>
<div class="command-block" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('github')">
<h3>GitHub</h3>
<p>Contribute to the project</p>
</div>
</div>
</div>
<footer class="footer">
<div class="icon-links">
<!-- Simple Icons: GitHub (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('github')" title="GitHub" aria-label="GitHub">
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>
</svg>
</a>
<!-- Simple Icons: Discord (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('discord')" title="Discord" aria-label="Discord">
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/>
</svg>
</a>
<!-- Simple Icons: Telegram (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('telegram')" title="Telegram" aria-label="Telegram">
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/>
</svg>
</a>
<!-- Simple Icons: X / Twitter (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('x')" title="X" aria-label="X (Twitter)">
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"/>
</svg>
</a>
</div>
<!-- Tabler: bug (MIT) -->
<a class="report-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('report-bug')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 9v-1a3 3 0 0 1 6 0v1"/>
<path d="M8 9h8a6 6 0 0 1 1 3v3a5 5 0 0 1 -10 0v-3a6 6 0 0 1 1 -3"/>
<path d="M3 13l4 0"/>
<path d="M17 13l4 0"/>
<path d="M12 20l0 -6"/>
<path d="M4 19l3.35 -2"/>
<path d="M20 19l-3.35 -2"/>
<path d="M4 7l3.75 2.4"/>
<path d="M20 7l-3.75 2.4"/>
</svg>
<span>Report a problem</span>
</a>
</footer>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script>
new QWebChannel(qt.webChannelTransport, (channel) => {
window.zAppBridge = channel.objects.zAppBridge;
});
document.addEventListener("keydown", (event) => {
const target = event.target;
if (!target.matches("[tabindex='0']")) {
return;
}
// Buttons activate on Enter and Space, links on Enter only.
const isButton = target.getAttribute("role") === "button";
if (event.key === "Enter" || (isButton && event.key === " ")) {
event.preventDefault();
target.click();
}
});
</script>
</body>
</html>
@@ -1,10 +0,0 @@
/* Highlight on navigation to an anchor. */
@keyframes targetNavigatedAnimation {
from { background: #fff; }
50% { background: #ff0; }
to { background: #fff; }
}
*:target {
animation: targetNavigatedAnimation .5s linear;
}
File diff suppressed because one or more lines are too long
@@ -1,158 +0,0 @@
/* Custom styles for Zeal's built-in browser pages. Depends on oat.min.css. */
body {
user-select: none;
min-height: 100dvh;
display: flex;
flex-direction: column;
align-items: center;
}
.page {
flex: 1;
display: grid;
grid-template-columns: 1fr 1.5fr;
align-content: center;
gap: 1.5rem 3rem;
max-width: 720px;
width: 100%;
padding: 2rem;
}
.brand {
display: flex;
flex-direction: column;
justify-content: center;
gap: 0.25rem;
}
.brand h1 {
font-size: 2rem;
line-height: 1.1;
}
.brand .version {
font-weight: 300;
opacity: 0.5;
margin-left: 0.3rem;
font-size: 1rem;
}
.brand > p {
opacity: 0.6;
}
.actions {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.actions > h2 {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.07em;
opacity: 0.45;
margin-top: 0.5rem;
margin-bottom: 0;
}
.command-block {
cursor: pointer;
padding: 0.625rem 0.875rem;
border-radius: 6px;
border: 1px solid #e2e8f0;
transition:
border-color 0.15s,
background 0.15s;
}
.command-block:hover {
border-color: #94a3b8;
background: #f8fafc;
}
.command-block h3 {
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 0.125rem;
}
.command-block p {
font-size: 0.775rem;
opacity: 0.55;
margin: 0;
}
.footer {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.625rem;
width: 100%;
max-width: 720px;
padding: 1rem 2rem;
border-top: 1px solid #e2e8f0;
}
.icon-links {
display: flex;
gap: 1.25rem;
align-items: center;
}
.icon-link {
cursor: pointer;
display: inline-flex;
color: inherit;
text-decoration: none;
opacity: 0.45;
transition: opacity 0.15s;
}
.icon-link:hover {
opacity: 0.85;
text-decoration: none;
}
.icon-link svg {
width: 1.2rem;
height: 1.2rem;
}
.report-link {
display: inline-flex;
align-items: center;
gap: 0.3rem;
font-size: 0.75rem;
cursor: pointer;
opacity: 0.4;
transition: opacity 0.15s;
text-decoration: none;
color: inherit;
}
.report-link:hover {
opacity: 0.7;
text-decoration: none;
}
.report-link svg {
width: 0.85rem;
height: 0.85rem;
}
@media (prefers-color-scheme: dark) {
.command-block {
border-color: #334155;
}
.command-block:hover {
border-color: #475569;
background: #1e293b;
}
.footer {
border-color: #334155;
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+133
View File
@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script>
// Theme to match Zeal, not the OS. Zeal passes #light or #dark in the URL; apply it
// before styles load so the first tab is correct and there is no wrong-theme paint.
var zealTheme = location.hash.slice(1);
if (zealTheme === "light" || zealTheme === "dark") {
document.documentElement.setAttribute("data-theme", zealTheme);
document.documentElement.style.colorScheme = zealTheme;
}
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>New tab</title>
<link rel="stylesheet" type="text/css" href="styles/pages.css">
</head>
<body class="screen">
<main class="page">
<!-- 1 · Brand -->
<div class="brand">
<div class="lockup">
<img class="logo" src="qrc:///zeal.svg" alt="">
<h1 class="wordmark">Zeal</h1>
</div>
<p class="tagline">Your offline reference library</p>
</div>
<!-- 2 · Search teaching -->
<section class="search">
<p class="overline">Search</p>
<p class="search-lead">
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<circle cx="11" cy="11" r="7"></circle>
<path d="M21 21l-4.35-4.35"></path>
</svg>
Start typing to search
</p>
<div class="hints">
<span class="syntax"><code><span class="tok">cpp:</span>vector</code></span>
<span class="desc">Limit the search to one docset</span>
<span class="syntax"><code><span class="tok">python,django:</span>string</code></span>
<span class="desc">Search several docsets at once</span>
</div>
</section>
<!-- 3 · Action -->
<section class="actions">
<p class="overline">Get started</p>
<div class="cards">
<div class="card" role="button" tabindex="0" onclick="zAppBridge.triggerAction('openDocsetManager')">
<div class="body">
<h3>Docset Library</h3>
<p>Install and update docsets</p>
</div>
<svg class="arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M9 6l6 6-6 6"></path>
</svg>
</div>
</div>
</section>
</main>
<!-- 4 · Footer -->
<footer class="footer">
<div class="icon-links">
<!-- Simple Icons: GitHub (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('github')" title="GitHub" aria-label="GitHub">
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path>
</svg>
</a>
<!-- Simple Icons: Discord (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('discord')" title="Discord" aria-label="Discord">
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"></path>
</svg>
</a>
<!-- Simple Icons: Telegram (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('telegram')" title="Telegram" aria-label="Telegram">
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"></path>
</svg>
</a>
<!-- Simple Icons: X / Twitter (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('x')" title="X" aria-label="X (Twitter)">
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"></path>
</svg>
</a>
</div>
</footer>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script>
// The web channel initializes asynchronously. Until zAppBridge is ready, buffer
// calls so a fast click or key press is queued rather than dropped.
const zAppBridgeQueue = [];
window.zAppBridge = {
triggerAction: (action) => zAppBridgeQueue.push(["triggerAction", action]),
openShortUrl: (key) => zAppBridgeQueue.push(["openShortUrl", key]),
};
new QWebChannel(qt.webChannelTransport, (channel) => {
window.zAppBridge = channel.objects.zAppBridge;
for (const [method, arg] of zAppBridgeQueue) {
window.zAppBridge[method](arg);
}
});
document.addEventListener("keydown", (event) => {
// Ignore autorepeat so a held Enter/Space activates the target only once.
if (event.repeat) {
return;
}
const target = event.target;
if (!target.matches("[tabindex='0']")) {
return;
}
// Buttons activate on Enter and Space, links on Enter only.
const isButton = target.getAttribute("role") === "button";
if (event.key === "Enter" || (isButton && event.key === " ")) {
event.preventDefault();
target.click();
}
});
</script>
</body>
</html>
+140
View File
@@ -0,0 +1,140 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script>
// Theme to match Zeal, not the OS. Zeal passes #light or #dark in the URL; apply it
// before styles load so the first tab is correct and there is no wrong-theme paint.
var zealTheme = location.hash.slice(1);
if (zealTheme === "light" || zealTheme === "dark") {
document.documentElement.setAttribute("data-theme", zealTheme);
document.documentElement.style.colorScheme = zealTheme;
}
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page not found</title>
<link rel="stylesheet" type="text/css" href="styles/pages.css">
</head>
<body class="screen">
<main class="page">
<!-- Heading -->
<div class="headline">
<img class="logo" src="qrc:///zeal.svg" alt="" style="width:30px;height:30px;border-radius:3px;margin-bottom:0.5rem;display:block;">
<h1>Page not found</h1>
<p>This page doesn't exist.</p>
</div>
<!-- Troubleshoot -->
<section class="actions">
<p class="overline">Troubleshoot</p>
<div class="cards">
<div class="card" role="button" tabindex="0" onclick="zAppBridge.triggerAction('openDocsetManager')">
<div class="body">
<h3>Docset Library</h3>
<p>Update or reinstall docsets</p>
</div>
<svg class="arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M9 6l6 6-6 6"></path>
</svg>
</div>
</div>
</section>
<!-- Get in touch -->
<section class="actions">
<p class="overline">Get in touch</p>
<div class="cards">
<div class="card" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('discord')">
<div class="body">
<h3>Discord</h3>
<p>Chat with the developers and other users</p>
</div>
<svg class="arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M7 17L17 7"></path>
<path d="M8 7h9v9"></path>
</svg>
</div>
<div class="card" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('github')">
<div class="body">
<h3>GitHub</h3>
<p>Source code and issues</p>
</div>
<svg class="arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M7 17L17 7"></path>
<path d="M8 7h9v9"></path>
</svg>
</div>
</div>
</section>
</main>
<!-- Footer -->
<footer class="footer">
<div class="icon-links">
<!-- Simple Icons: GitHub (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('github')" title="GitHub" aria-label="GitHub">
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path>
</svg>
</a>
<!-- Simple Icons: Discord (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('discord')" title="Discord" aria-label="Discord">
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"></path>
</svg>
</a>
<!-- Simple Icons: Telegram (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('telegram')" title="Telegram" aria-label="Telegram">
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"></path>
</svg>
</a>
<!-- Simple Icons: X / Twitter (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('x')" title="X" aria-label="X (Twitter)">
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"></path>
</svg>
</a>
</div>
</footer>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script>
// The web channel initializes asynchronously. Until zAppBridge is ready, buffer
// calls so a fast click or key press is queued rather than dropped.
const zAppBridgeQueue = [];
window.zAppBridge = {
triggerAction: (action) => zAppBridgeQueue.push(["triggerAction", action]),
openShortUrl: (key) => zAppBridgeQueue.push(["openShortUrl", key]),
};
new QWebChannel(qt.webChannelTransport, (channel) => {
window.zAppBridge = channel.objects.zAppBridge;
for (const [method, arg] of zAppBridgeQueue) {
window.zAppBridge[method](arg);
}
});
document.addEventListener("keydown", (event) => {
// Ignore autorepeat so a held Enter/Space activates the target only once.
if (event.repeat) {
return;
}
const target = event.target;
if (!target.matches("[tabindex='0']")) {
return;
}
// Buttons activate on Enter and Space, links on Enter only.
const isButton = target.getAttribute("role") === "button";
if (event.key === "Enter" || (isButton && event.key === " ")) {
event.preventDefault();
target.click();
}
});
</script>
</body>
</html>
@@ -0,0 +1,18 @@
/* Highlight on navigation to an anchor. */
@keyframes targetNavigatedAnimation {
from {
background: #fff;
}
50% {
background: #ff0;
}
to {
background: #fff;
}
}
@media (prefers-reduced-motion: no-preference) {
*:target {
animation: targetNavigatedAnimation 0.5s linear;
}
}
+191
View File
@@ -0,0 +1,191 @@
/* Reference-manual styles layered on .prose: reading column, section
headings, tables, callouts, menu-path chips. Scoped to .doc.
Per-surface knobs: --docw, --doc-pad, --doc-anchor (defaults below). */
.doc {
max-width: var(--docw, 760px);
margin: 0 auto;
padding: var(--doc-pad, 8px 0 0);
}
.doc section {
padding-top: 2rem;
scroll-margin-top: var(--doc-anchor, 84px);
}
.doc section > h2 {
font-family: var(--display);
font-weight: 600;
font-size: 1.5rem;
line-height: 1.18;
letter-spacing: -0.01em;
font-variation-settings: "opsz" 40;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--rule);
margin: 0 0 1.1rem;
display: flex;
align-items: baseline;
gap: 0.6rem;
}
.doc section > h2 .nn {
font-family: var(--mono);
font-size: 0.72rem;
font-weight: 500;
color: var(--red);
letter-spacing: 0.06em;
}
.doc h3 {
font-family: var(--body);
font-weight: 600;
font-size: 0.98rem;
letter-spacing: 0;
margin: 1.6rem 0 0.5rem;
color: var(--ink);
}
/* Tables */
.doc .table-wrap {
width: 100%;
overflow-x: auto;
margin: 0.4rem 0 1.1rem;
}
.doc table {
width: 100%;
border-collapse: collapse;
font-size: 0.86rem;
}
.doc caption {
text-align: left;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--ink-softer);
padding-bottom: 0.5rem;
}
.doc th {
text-align: left;
font-weight: 600;
color: var(--ink-soft);
padding: 0.5rem 0.9rem;
border-bottom: 1px solid var(--rule);
white-space: nowrap;
}
.doc td {
padding: 0.5rem 0.9rem;
border-bottom: 1px solid var(--rule-soft);
vertical-align: top;
line-height: 1.55;
}
.doc td:first-child {
color: var(--ink);
}
.doc tbody tr:last-child td {
border-bottom: 0;
}
.doc .keys {
display: inline-flex;
gap: 0.25rem;
flex-wrap: wrap;
vertical-align: baseline;
align-items: center;
}
.doc .keys .sep {
color: var(--ink-softer);
font-size: 0.8rem;
padding: 0 0.1rem;
}
.doc td code {
white-space: normal;
overflow-wrap: anywhere;
}
.doc td .tok {
color: var(--accent);
}
/* Stack link cells as blocks; display:flex on a <td> breaks fixed-table row alignment. */
.doc .td-links a {
display: block;
font-family: var(--mono);
font-size: 0.8rem;
color: var(--accent);
text-decoration: none;
overflow-wrap: anywhere;
}
.doc .td-links a + a {
margin-top: 3px;
}
.doc .td-links a:hover {
text-decoration: underline;
text-underline-offset: 2px;
}
/* Callout */
.doc .callout {
background: var(--callout);
border: 1px solid var(--callout-border);
box-shadow: var(--callout-shadow);
border-radius: 6px;
padding: 0.85rem 1rem;
margin: 0.4rem 0 1.1rem;
font-size: 0.85rem;
color: var(--ink-soft);
line-height: 1.55;
}
.doc .callout strong {
color: var(--ink);
font-weight: 600;
}
.doc .callout a {
color: var(--accent);
text-decoration: underline;
text-decoration-color: var(--rule);
text-underline-offset: 2px;
}
.doc .callout a:hover {
color: var(--accent-hover);
text-decoration-color: var(--accent-hover);
}
/* Menu-path chip */
.doc .menu-path {
display: inline-flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
margin: 0 0 0.85rem;
}
.doc .menu-path .seg {
font-family: var(--mono);
font-size: 0.8rem;
color: var(--ink);
background: var(--deep);
border: 1px solid var(--rule);
border-bottom-width: 2px;
border-radius: 3px;
padding: 2px 8px;
}
.doc .menu-path .arr {
color: var(--ink-softer);
}
/* Print */
@media print {
.doc {
max-width: none;
}
.doc section {
padding-top: 1.25rem;
scroll-margin-top: 0;
}
.doc .table-wrap {
overflow: visible;
}
.doc h2,
.doc h3 {
break-after: avoid;
}
.doc tr,
.doc .callout {
break-inside: avoid;
}
}
+530
View File
@@ -0,0 +1,530 @@
/* Styles for Zeal's built-in browser pages (new-tab, not-found, help).
Offline, no frameworks; conservative CSS for Chromium 102. */
@import "tokens.css";
@import "prose.css";
@import "manual.css";
/* Bundled fonts */
@font-face {
font-family: "Inter";
src: url("../fonts/Inter.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inter";
src: url("../fonts/Inter-Italic.woff2") format("woff2");
font-weight: 100 900;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Source Serif 4";
src: url("../fonts/SourceSerif4.woff2") format("woff2");
font-weight: 200 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Source Serif 4";
src: url("../fonts/SourceSerif4-Italic.woff2") format("woff2");
font-weight: 200 900;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Google Sans Code";
src: url("../fonts/GoogleSansCode.woff2") format("woff2");
font-weight: 300 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Google Sans Code";
src: url("../fonts/GoogleSansCode-Italic.woff2") format("woff2");
font-weight: 300 800;
font-style: italic;
font-display: swap;
}
/* App-only tokens */
:root {
/* Declare both schemes so Qt WebEngine does not auto-invert these pages;
they theme themselves, and inversion would recolour the brand-red fills. */
color-scheme: light dark;
--shadow-soft: 0 1px 2px rgb(31 27 20 / 0.05);
--maxw: 460px;
/* manual.css reading-column knobs */
--docw: 760px;
--doc-pad: 1rem clamp(1rem, 4vw, 2rem) 0;
--doc-anchor: 4.5rem;
}
@media (prefers-color-scheme: dark) {
:root {
--shadow-soft: 0 1px 2px rgb(0 0 0 / 0.4);
}
}
/* Reset / base */
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-size: 16px;
}
body {
margin: 0;
min-height: 100vh;
font-family: var(--body);
font-optical-sizing: auto;
font-size: var(--type-body-size);
line-height: 1.55;
color: var(--ink);
background: var(--paper);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
/* Chrome pages should not feel like a selectable document. */
body.screen {
user-select: none;
}
h1,
h2,
h3 {
font-family: var(--display);
font-weight: 600;
letter-spacing: -0.01em;
line-height: 1.18;
margin: 0;
}
p {
margin: 0;
text-wrap: pretty;
}
a {
color: var(--accent);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
cursor: pointer;
transition: color 0.12s cubic-bezier(0.4, 0, 0.2, 1);
}
a:hover {
color: var(--accent-hover);
}
code,
kbd,
samp {
font-family: var(--mono);
}
kbd {
display: inline-block;
min-width: 1.1em;
padding: 2px 6px 1px;
font-family: var(--mono);
font-size: 0.78em;
font-weight: 500;
text-align: center;
color: var(--ink);
background: var(--deep);
border: 1px solid var(--rule);
border-bottom-width: 2px;
border-radius: 3px;
white-space: nowrap;
}
/* Focus */
:focus {
outline: none;
}
:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
border-radius: 3px;
}
/* New-tab / not-found */
.screen {
display: flex;
flex-direction: column;
align-items: center;
}
.page {
flex: 1 1 auto;
width: 100%;
max-width: var(--maxw);
display: flex;
flex-direction: column;
justify-content: center;
gap: 1.75rem;
padding: 3rem 1.75rem 1.5rem;
}
/* Brand lockup */
.brand {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.lockup {
display: flex;
align-items: center;
gap: 0.6rem;
}
.lockup .logo {
width: 36px;
height: 36px;
flex: none;
border-radius: 3.5px;
display: block;
box-shadow: var(--shadow-soft);
}
.wordmark {
font-family: var(--display);
font-weight: 600;
font-size: 1.6rem;
font-variation-settings: "opsz" 36;
line-height: 1;
letter-spacing: -0.01em;
}
.tagline {
font-family: var(--display);
font-style: italic;
font-weight: 400;
font-size: 1.1rem;
line-height: 1.2;
color: var(--ink-soft);
font-variation-settings: "opsz" 36;
}
/* Overline labels */
.overline {
font-family: var(--body);
font-size: 0.66rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.09em;
color: var(--ink-softer);
margin: 0 0 0.7rem;
}
/* Not-found heading */
.headline {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.headline h1 {
font-size: 1.75rem;
font-variation-settings: "opsz" 36;
}
.headline p {
color: var(--ink-soft);
font-size: 0.875rem;
}
/* Search hints */
.search-lead {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.95rem;
color: var(--ink);
margin-bottom: 0.9rem;
}
.search-lead .ico {
width: 15px;
height: 15px;
flex: none;
color: var(--accent);
}
.hints {
display: grid;
grid-template-columns: max-content 1fr;
column-gap: 1rem;
row-gap: 0.5rem;
align-items: baseline;
}
.hints .syntax {
display: inline-flex;
align-items: center;
gap: 0.2rem;
}
.hints .syntax code {
font-size: 0.8rem;
color: var(--ink);
background: var(--prefix-box);
border: 1px solid var(--rule-soft);
border-radius: 3px;
padding: 2px 7px;
white-space: nowrap;
user-select: text; /* copyable */
cursor: text;
}
.hints .syntax .tok {
color: var(--accent);
}
.hints .desc {
font-size: 0.82rem;
color: var(--ink-soft);
line-height: 1.4;
}
/* Action cards */
.cards {
display: flex;
flex-direction: column;
gap: 0.6rem;
}
.card {
display: flex;
align-items: center;
gap: 0.85rem;
width: 100%;
text-align: left;
padding: 0.8rem 0.95rem;
background: var(--card);
border: 1px solid var(--rule-soft);
border-radius: 6px;
cursor: pointer;
color: inherit;
text-decoration: none;
transition:
border-color 0.12s cubic-bezier(0.4, 0, 0.2, 1),
background 0.12s cubic-bezier(0.4, 0, 0.2, 1);
}
.card:hover {
border-color: var(--accent);
background: var(--card-hover);
}
.card .body {
flex: 1 1 auto;
min-width: 0;
}
.card .body h3 {
font-family: var(--body);
font-weight: 600;
font-size: 0.9rem;
letter-spacing: 0;
margin: 0 0 1px;
}
.card .body p {
font-size: 0.8rem;
color: var(--ink-soft);
margin: 0;
max-width: none;
}
.card .arrow {
width: 16px;
height: 16px;
flex: none;
color: var(--ink-softer);
transition:
color 0.12s,
transform 0.12s cubic-bezier(0.4, 0, 0.2, 1);
}
.card:hover .arrow {
color: var(--accent);
transform: translateX(2px);
}
/* Footer */
.footer {
width: 100%;
max-width: var(--maxw);
display: flex;
flex-direction: column;
align-items: center;
gap: 0.7rem;
padding: 1.25rem 1.75rem;
border-top: 1px solid var(--rule-soft);
}
.icon-links {
display: flex;
gap: 1.4rem;
align-items: center;
}
.icon-link {
display: inline-flex;
color: var(--ink-soft);
opacity: 0.65;
text-decoration: none;
transition:
color 0.12s,
opacity 0.12s;
}
.icon-link:hover {
color: var(--accent);
opacity: 1;
}
.icon-link svg {
width: 1.15rem;
height: 1.15rem;
display: block;
}
/* Help page */
html.manual-root {
scroll-behavior: smooth;
}
.topnav {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
gap: 1rem;
padding: 0.7rem clamp(1rem, 4vw, 2rem);
background: var(--cream);
border-bottom: 1px solid var(--rule-soft);
}
.topnav .home {
display: inline-flex;
align-items: center;
gap: 0.55rem;
text-decoration: none;
color: var(--ink);
margin-right: auto;
}
.topnav .home .logo {
width: 26px;
height: 26px;
border-radius: 2.5px;
display: block;
}
.topnav .home .title {
font-family: var(--display);
font-weight: 600;
font-size: 1.05rem;
font-variation-settings: "opsz" 28;
}
.topnav .home .title .sub {
color: var(--ink-soft);
font-weight: 400;
}
.topnav nav {
display: flex;
gap: 0.3rem;
flex-wrap: wrap;
}
.topnav nav a {
font-size: 0.82rem;
color: var(--ink-soft);
text-decoration: none;
padding: 0.3rem 0.6rem;
border-radius: 4px;
transition:
color 0.12s,
background 0.12s;
}
.topnav nav a:hover {
color: var(--accent);
background: var(--nav-hover, var(--manila));
}
.help-links {
display: flex;
flex-direction: column;
gap: 0.6rem;
margin: 0.4rem 0 0;
}
.doc .footer {
max-width: none;
margin-top: 3rem;
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
html.manual-root {
scroll-behavior: auto;
}
}
/* Print */
@page {
margin: 1.6cm;
}
@media print {
:root {
--paper: #fff;
--cream: #fff;
--card: #fff;
--manila: #fff;
--deep: #f3f3f3;
--ink: #000;
--ink-soft: #333;
--ink-softer: #555;
--rule: #bbb;
--rule-soft: #ddd;
--accent: #75201a;
--prefix-box: #f3f3f3;
--callout: #fff;
--callout-border: #bbb;
--callout-shadow: none;
}
html,
body {
background: #fff;
height: auto;
min-height: 0;
overflow: visible;
}
html.manual-root {
scroll-behavior: auto;
}
.topnav {
position: static;
padding-left: 0;
padding-right: 0;
border-bottom: 1px solid #bbb;
}
.topnav nav {
display: none;
}
.footer .icon-links {
display: none;
}
.doc {
max-width: none;
padding: 0 0 1rem;
}
.doc section {
padding-top: 1.25rem;
}
.table-wrap {
overflow: visible;
}
h2,
h3 {
break-after: avoid;
}
tr,
.callout,
.card {
break-inside: avoid;
}
a {
color: #000;
text-decoration: none;
}
}
@@ -0,0 +1,81 @@
/* Shared reading-text styles (lede, paragraphs, inline code, kbd, lists).
Scoped to .prose; consumes the tokens in tokens.css. */
.prose {
color: var(--ink);
line-height: 1.6;
text-wrap: pretty;
}
/* Lede */
.prose .lede {
font-family: var(--display);
font-variation-settings: "opsz" 24;
font-size: 1.18rem;
line-height: 1.5;
letter-spacing: -0.1px;
color: var(--ink);
margin: 2rem 0 0.5rem;
}
/* Paragraphs */
.prose p {
margin: 0 0 0.85rem;
color: var(--ink);
line-height: 1.6;
}
.prose p.muted {
color: var(--ink-soft);
font-size: 0.92rem;
}
.prose strong {
font-weight: 600;
}
/* Inline links */
.prose a.inline {
color: var(--accent);
text-decoration: underline;
text-decoration-color: var(--rule);
text-underline-offset: 2px;
}
.prose a.inline:hover {
color: var(--accent-hover);
text-decoration-color: var(--accent-hover);
}
/* Inline code */
.prose code {
font-family: var(--mono);
font-size: 0.86em;
background: var(--deep);
border: 1px solid var(--rule-soft);
border-radius: 3px;
padding: 1px 5px;
}
/* Keyboard keys */
.prose kbd {
display: inline-block;
min-width: 1.1em;
padding: 2px 6px 1px;
font-family: var(--mono);
font-size: 0.78em;
font-weight: 500;
text-align: center;
color: var(--ink);
background: var(--deep);
border: 1px solid var(--rule);
border-bottom-width: 2px;
border-radius: 3px;
white-space: nowrap;
}
/* Lists */
.prose ul {
margin: 0 0 0.95rem;
padding-left: 1.1rem;
}
.prose li {
margin: 0.25rem 0;
}
+278
View File
@@ -0,0 +1,278 @@
/* Design tokens shared by Zeal's built-in browser pages.
Light by default; dark via prefers-color-scheme and [data-theme="dark"]. */
:root {
/* Surfaces */
--paper: #fbf7ec;
--card: #f5ecd1;
--card-hover: #faf4e2;
--deep: #e0d6bb;
/* Legacy surface aliases */
--cream: var(--card);
--manila: var(--card);
/* Component surfaces */
--prefix-box: #efe7d1;
--callout: #f5ecd1;
--callout-border: var(--rule-soft);
--callout-shadow: none;
--nav-hover: #e9dfc5;
/* Text */
--ink: #1f1b14;
--ink-soft: #55503f;
--ink-softer: #655f4a;
/* Rules */
--rule: #c9bfa3;
--rule-soft: #d9d0b8;
/* Brand accent — --accent for text, --accent-solid for fills */
--accent: #b4322b;
--accent-hover: #75201a;
--accent-solid: #b4322b;
--accent-solid-hover: #75201a;
--on-accent: #faf4e2;
/* Legacy --red aliases */
--red: var(--accent);
--on-red: var(--on-accent);
/* AppStream store-listing branding only */
--brand-banner-light: #f1c3a7;
--brand-banner-dark: #75201a;
/* Taxonomy accents */
--blue: #3e5f7a;
--green: #4a7a4a;
--amber: #a86a14;
--plum: #6b4a82;
/* Semantic aliases */
--background: var(--paper);
--foreground: var(--ink);
--foreground-soft: var(--ink-soft);
--foreground-softer: var(--ink-softer);
--surface-card: var(--card);
--surface-inset: var(--deep);
--border: var(--rule);
--border-soft: var(--rule-soft);
--primary: var(--accent-solid);
--primary-foreground: var(--on-accent);
--danger: var(--accent-solid);
--danger-foreground: var(--on-accent);
--success: var(--green);
--success-foreground: var(--on-accent);
--warning: var(--amber);
--warning-foreground: var(--on-accent);
--info: var(--blue);
--info-foreground: var(--on-accent);
--ring: var(--accent);
/* Type families */
--display: "Source Serif 4", "Lora", Georgia, serif;
--body:
"Inter", system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Helvetica,
Arial, sans-serif;
--mono:
"Google Sans Code", "JetBrains Mono", ui-monospace, SFMono-Regular,
"SF Mono", Menlo, monospace;
--font-sans: var(--body);
--font-mono: var(--mono);
--display-opsz: 60;
--display-opsz-text: 40;
--font-regular: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
/* Type scale */
--text-1: clamp(2.5rem, 2rem + 2.2vw, 3.5rem);
--text-2: clamp(1.75rem, 1.5rem + 1.1vw, 2.25rem);
--text-3: clamp(1.5rem, 1.3rem + 0.8vw, 1.875rem);
--text-4: clamp(1.25rem, 1.1rem + 0.5vw, 1.5rem);
--text-5: 1.125rem;
--text-6: 1rem;
--text-7: 0.875rem;
--text-8: 0.75rem;
--text-9: 0.6875rem;
--leading-tight: 1.2;
--leading-snug: 1.35;
--leading-normal: 1.5;
--leading-loose: 1.65;
/* Type roles */
--type-display-font: var(--display);
--type-display-weight: 400;
--type-display-size: var(--text-1);
--type-display-lh: var(--leading-tight);
--type-h1-font: var(--display);
--type-h1-weight: 600;
--type-h1-size: var(--text-2);
--type-h2-font: var(--display);
--type-h2-weight: 600;
--type-h2-size: var(--text-3);
--type-h3-font: var(--body);
--type-h3-weight: 600;
--type-h3-size: var(--text-4);
--type-body-font: var(--body);
--type-body-weight: 400;
--type-body-size: 0.9375rem; /* 15px */
--type-body-lh: var(--leading-normal);
--type-small-font: var(--body);
--type-small-size: var(--text-7);
--type-meta-font: var(--mono);
--type-meta-size: var(--text-8);
--type-meta-color: var(--ink-soft);
--type-code-font: var(--mono);
--type-code-size: 0.875em;
/* Spacing */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-5: 1.25rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-10: 2.5rem;
--space-12: 3rem;
--space-14: 3.5rem;
--space-16: 4rem;
--space-18: 4.5rem;
--space-24: 6rem;
/* Radii */
--radius-small: 2px;
--radius-medium: 3px;
--radius-large: 6px;
/* Elevation */
--shadow-small: 0 1px 2px 0 rgb(31 27 20 / 0.08);
--shadow-card:
0 2px 4px -1px rgb(31 27 20 / 0.06), 0 8px 24px -8px rgb(31 27 20 / 0.1);
/* Motion */
--transition-fast: 0.12s cubic-bezier(0.4, 0, 0.2, 1);
--transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
--z-dropdown: 50;
--z-modal: 200;
}
/* Dark theme — declared twice: prefers-color-scheme for OS-auto, and
[data-theme] for the manual toggle (a media query can't OR a selector). */
@media (prefers-color-scheme: dark) {
:root {
--paper: #1b1308;
--card: #261e12;
--card-hover: #352a1a;
--deep: #322817;
--cream: var(--card);
--manila: var(--card);
--prefix-box: #15100a;
--callout: #160f07;
--callout-border: var(--rule);
--callout-shadow: inset 0 1px 3px rgb(0 0 0 / 0.5);
--nav-hover: #1b1308;
--ink: #ddd2b6;
--ink-soft: #b3a892;
--ink-softer: #9a9180;
--rule: #6b6253;
--rule-soft: #554c3f;
--accent: #e8534a;
--accent-hover: #f0754f;
--accent-solid: #b4322b;
--accent-solid-hover: #bd3a2f;
--on-accent: #faf4e2;
--blue: #5486ac;
--green: #5f8d64;
--amber: #b07932;
--plum: #8a68a0;
--shadow-small: 0 1px 2px 0 rgb(0 0 0 / 0.4);
--shadow-card:
0 2px 4px -1px rgb(0 0 0 / 0.3), 0 8px 24px -8px rgb(0 0 0 / 0.55);
}
/* Force light under a dark OS */
:root[data-theme="light"] {
--paper: #fbf7ec;
--card: #f5ecd1;
--card-hover: #faf4e2;
--deep: #e0d6bb;
--cream: var(--card);
--manila: var(--card);
--prefix-box: #efe7d1;
--callout: #f5ecd1;
--callout-border: var(--rule-soft);
--callout-shadow: none;
--nav-hover: #e9dfc5;
--ink: #1f1b14;
--ink-soft: #55503f;
--ink-softer: #655f4a;
--rule: #c9bfa3;
--rule-soft: #d9d0b8;
--accent: #b4322b;
--accent-hover: #75201a;
--accent-solid: #b4322b;
--accent-solid-hover: #75201a;
--on-accent: #faf4e2;
--blue: #3e5f7a;
--green: #4a7a4a;
--amber: #a86a14;
--plum: #6b4a82;
--shadow-small: 0 1px 2px 0 rgb(31 27 20 / 0.08);
--shadow-card:
0 2px 4px -1px rgb(31 27 20 / 0.06), 0 8px 24px -8px rgb(31 27 20 / 0.1);
}
}
:root[data-theme="dark"] {
--paper: #1b1308;
--card: #261e12;
--card-hover: #352a1a;
--deep: #322817;
--cream: var(--card);
--manila: var(--card);
--prefix-box: #15100a;
--callout: #160f07;
--callout-border: var(--rule);
--callout-shadow: inset 0 1px 3px rgb(0 0 0 / 0.5);
--nav-hover: #1b1308;
--ink: #ddd2b6;
--ink-soft: #b3a892;
--ink-softer: #9a9180;
--rule: #6b6253;
--rule-soft: #554c3f;
--accent: #e8534a;
--accent-hover: #f0754f;
--accent-solid: #b4322b;
--accent-solid-hover: #bd3a2f;
--on-accent: #faf4e2;
--blue: #5486ac;
--green: #5f8d64;
--amber: #b07932;
--plum: #8a68a0;
--shadow-small: 0 1px 2px 0 rgb(0 0 0 / 0.4);
--shadow-card:
0 2px 4px -1px rgb(0 0 0 / 0.3), 0 8px 24px -8px rgb(0 0 0 / 0.55);
}
-108
View File
@@ -1,108 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>New tab</title>
<link rel="stylesheet" type="text/css" href="assets/css/oat.min.css">
<link rel="stylesheet" type="text/css" href="assets/css/welcome.css">
</head>
<body>
<div class="page">
<div class="brand">
<h1>Zeal<span class="version" id="version"></span></h1>
<p>Your offline reference library</p>
</div>
<div class="actions">
<h2>Get started</h2>
<div class="command-block" role="button" tabindex="0" onclick="zAppBridge.triggerAction('openDocsetManager')">
<h3>Docset Library</h3>
<p>Install and update docsets</p>
</div>
<div class="command-block" role="button" tabindex="0" onclick="zAppBridge.triggerAction('openPreferences')">
<h3>Preferences</h3>
<p>Adjust application settings</p>
</div>
<h2>Get in touch</h2>
<div class="command-block" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('discord')">
<h3>Discord</h3>
<p>Chat with the developers and other users</p>
</div>
<div class="command-block" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('github')">
<h3>GitHub</h3>
<p>Contribute to the project</p>
</div>
</div>
</div>
<footer class="footer">
<div class="icon-links">
<!-- Simple Icons: GitHub (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('github')" title="GitHub" aria-label="GitHub">
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>
</svg>
</a>
<!-- Simple Icons: Discord (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('discord')" title="Discord" aria-label="Discord">
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/>
</svg>
</a>
<!-- Simple Icons: Telegram (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('telegram')" title="Telegram" aria-label="Telegram">
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/>
</svg>
</a>
<!-- Simple Icons: X / Twitter (CC0) -->
<a class="icon-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('x')" title="X" aria-label="X (Twitter)">
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"/>
</svg>
</a>
</div>
<!-- Tabler: bug (MIT) -->
<a class="report-link" role="link" tabindex="0" onclick="zAppBridge.openShortUrl('report-bug')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 9v-1a3 3 0 0 1 6 0v1"/>
<path d="M8 9h8a6 6 0 0 1 1 3v3a5 5 0 0 1 -10 0v-3a6 6 0 0 1 1 -3"/>
<path d="M3 13l4 0"/>
<path d="M17 13l4 0"/>
<path d="M12 20l0 -6"/>
<path d="M4 19l3.35 -2"/>
<path d="M20 19l-3.35 -2"/>
<path d="M4 7l3.75 2.4"/>
<path d="M20 7l-3.75 2.4"/>
</svg>
<span>Report a problem</span>
</a>
</footer>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script>
new QWebChannel(qt.webChannelTransport, (channel) => {
window.zAppBridge = channel.objects.zAppBridge;
document.getElementById("version").textContent = zAppBridge.AppVersion;
});
document.addEventListener("keydown", (event) => {
const target = event.target;
if (!target.matches("[tabindex='0']")) {
return;
}
// Buttons activate on Enter and Space, links on Enter only.
const isButton = target.getAttribute("role") === "button";
if (event.key === "Enter" || (isButton && event.key === " ")) {
event.preventDefault();
target.click();
}
});
</script>
</body>
</html>
+13 -5
View File
@@ -4,11 +4,19 @@
<file alias="zeal-tray.svg">../../assets/freedesktop/sc-apps-zeal-tray.svg</file>
</qresource>
<qresource prefix="/browser">
<file alias="404.html">resources/browser/404.html</file>
<file alias="assets/css/highlight.css">resources/browser/assets/css/highlight.css</file>
<file alias="assets/css/oat.min.css">resources/browser/assets/css/oat.min.css</file>
<file alias="assets/css/welcome.css">resources/browser/assets/css/welcome.css</file>
<file alias="welcome.html">resources/browser/welcome.html</file>
<file alias="new-tab.html">resources/browser/new-tab.html</file>
<file alias="not-found.html">resources/browser/not-found.html</file>
<file alias="styles/highlight.css">resources/browser/styles/highlight.css</file>
<file alias="styles/manual.css">resources/browser/styles/manual.css</file>
<file alias="styles/pages.css">resources/browser/styles/pages.css</file>
<file alias="styles/prose.css">resources/browser/styles/prose.css</file>
<file alias="styles/tokens.css">resources/browser/styles/tokens.css</file>
<file alias="fonts/GoogleSansCode.woff2">resources/browser/fonts/GoogleSansCode.woff2</file>
<file alias="fonts/GoogleSansCode-Italic.woff2">resources/browser/fonts/GoogleSansCode-Italic.woff2</file>
<file alias="fonts/Inter.woff2">resources/browser/fonts/Inter.woff2</file>
<file alias="fonts/Inter-Italic.woff2">resources/browser/fonts/Inter-Italic.woff2</file>
<file alias="fonts/SourceSerif4.woff2">resources/browser/fonts/SourceSerif4.woff2</file>
<file alias="fonts/SourceSerif4-Italic.woff2">resources/browser/fonts/SourceSerif4-Italic.woff2</file>
</qresource>
<qresource prefix="/icons/tabler">
<file alias="app-window.svg">../../assets/tabler/app-window.svg</file>
+1 -1
View File
@@ -21,7 +21,7 @@ Q_LOGGING_CATEGORY(log, "zeal.browser.settings")
using Qt::Literals::StringLiterals::operator""_L1;
constexpr auto HighlightOnNavigateCssPath = ":/browser/assets/css/highlight.css"_L1;
constexpr auto HighlightOnNavigateCssPath = ":/browser/styles/highlight.css"_L1;
} // namespace
QWebEngineProfile *Settings::m_webProfile = nullptr;
+32 -3
View File
@@ -48,7 +48,10 @@ WebControl::WebControl(QWidget *parent)
m_webView->setToolTip(link);
});
connect(m_webView, &QWebEngineView::titleChanged, this, &WebControl::titleChanged);
connect(m_webView, &QWebEngineView::urlChanged, this, &WebControl::urlChanged);
connect(m_webView, &QWebEngineView::urlChanged, this, [this](const QUrl &url) {
updateWebBridge(url);
emit urlChanged(url);
});
connect(m_webView, &WebView::zoomLevelChanged, this, &WebControl::zoomLevelChanged);
// On a failed load that set no title, relabel the tab to signal the failure.
@@ -148,18 +151,44 @@ void WebControl::setJavaScriptEnabled(bool enabled)
}
void WebControl::setWebBridgeObject(const QString &name, QObject *object)
{
m_webBridgeName = name;
m_webBridgeObject = object;
// Drop any existing channel so updateWebBridge() re-registers with the new object; it
// returns early when a channel is already present on an open qrc page.
if (QWebChannel *oldChannel = m_webView->page()->webChannel()) {
m_webView->page()->setWebChannel(nullptr);
oldChannel->deleteLater();
}
updateWebBridge(m_webView->url());
}
void WebControl::updateWebBridge(const QUrl &url)
{
QWebEnginePage *page = m_webView->page();
if (url.scheme() != QLatin1String("qrc") || m_webBridgeName.isEmpty() || m_webBridgeObject == nullptr) {
if (QWebChannel *oldChannel = page->webChannel()) {
page->setWebChannel(nullptr);
oldChannel->deleteLater();
}
return;
}
if (page->webChannel() != nullptr) {
return;
}
auto *channel = new QWebChannel(page);
channel->registerObject(name, object);
channel->registerObject(m_webBridgeName, m_webBridgeObject);
page->setWebChannel(channel);
}
void WebControl::load(const QUrl &url)
{
m_renderProcessReloadAttempts = 0;
updateWebBridge(url);
m_webView->load(url);
}
+4
View File
@@ -61,8 +61,12 @@ protected:
private:
friend class WebView;
void updateWebBridge(const QUrl &url);
WebView *m_webView = nullptr;
SearchToolBar *m_searchToolBar = nullptr;
QString m_webBridgeName;
QObject *m_webBridgeObject = nullptr;
int m_renderProcessReloadAttempts = 0;
};
+67 -2
View File
@@ -18,6 +18,7 @@
#include <QMessageBox>
#include <QPalette>
#include <QPushButton>
#include <QWebEngineSettings>
namespace Zeal::Browser {
@@ -39,6 +40,26 @@ WebPage::WebPage(QObject *parent)
// TODO: Use a dedicated content appearance signal instead of the catch-all updated().
applyBackgroundColor();
connect(Core::Application::instance()->settings(), &Core::Settings::updated, this, &WebPage::applyBackgroundColor);
// Tabs open on the qrc new-tab page, which themes itself; start with ForceDarkMode off
// so its first render is not inverted. acceptNavigationRequest() sets it per navigated
// URL, and a theme change re-applies it for the current page.
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
settings()->setAttribute(QWebEngineSettings::ForceDarkMode, false);
#endif
connect(Core::Application::instance()->settings(), &Core::Settings::updated, this, [this]() {
applyForceDarkMode(url());
updateInternalPageTheme();
});
// qrc fallback pages (e.g. not-found) can load without the theme fragment that browsertab
// adds to the new-tab URL, so they start in the OS theme. Push Zeal's resolved theme once
// the page is ready; updateInternalPageTheme() no-ops for non-qrc pages.
connect(this, &QWebEnginePage::loadFinished, this, [this](bool ok) {
if (ok) {
updateInternalPageTheme();
}
});
}
void WebPage::applyBackgroundColor()
@@ -46,10 +67,46 @@ void WebPage::applyBackgroundColor()
setBackgroundColor(QApplication::palette().color(QPalette::Window));
}
void WebPage::applyForceDarkMode(const QUrl &pageUrl)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
// qrc pages render their own theme (light, or hand-crafted dark via the data-theme
// attribute); ForceDarkMode exists to darken docset content, so keep it off for them.
const bool isInternal = pageUrl.scheme() == QStringLiteral("qrc");
auto *appSettings = Core::Application::instance()->settings();
settings()->setAttribute(QWebEngineSettings::ForceDarkMode, !isInternal && appSettings->isDarkModeEnabled());
#else
Q_UNUSED(pageUrl)
#endif
}
void WebPage::updateInternalPageTheme()
{
// qrc pages bake their theme in at load (URL fragment + inline script), which does not
// react to a later theme change. Push the resolved theme to the open page so switching
// appearance updates it live, mirroring what the inline script does on load.
if (url().scheme() != QStringLiteral("qrc")) {
return;
}
const QString theme = Core::Application::instance()->settings()->isDarkModeEnabled() ? QStringLiteral("dark")
: QStringLiteral("light");
runJavaScript(QStringLiteral("var r = document.documentElement;"
"r.setAttribute('data-theme', '%1');"
"r.style.colorScheme = '%1';")
.arg(theme));
}
bool WebPage::acceptNavigationRequest(const QUrl &requestUrl, QWebEnginePage::NavigationType type, bool isMainFrame)
{
Q_UNUSED(type)
// Keep ForceDarkMode (which darkens docset content) from inverting Zeal's own qrc
// pages; they theme themselves via the data-theme set from the URL fragment.
if (isMainFrame) {
applyForceDarkMode(requestUrl);
}
// Local elements are always allowed.
if (Core::NetworkAccessManager::isLocalUrl(requestUrl)) {
return true;
@@ -74,10 +131,18 @@ bool WebPage::acceptNavigationRequest(const QUrl &requestUrl, QWebEnginePage::Na
switch (appSettings->externalLinkPolicy) {
case ExternalLinkPolicy::Open:
return true;
case ExternalLinkPolicy::Ask:
return promptForExternalLink(requestUrl);
case ExternalLinkPolicy::Ask: {
// applyForceDarkMode() above ran against the external URL. If the user declines the
// navigation, restore the current qrc page's theme so it is not left inverted.
const bool accepted = promptForExternalLink(requestUrl);
if (!accepted) {
applyForceDarkMode(url());
}
return accepted;
}
case ExternalLinkPolicy::OpenInSystemBrowser:
openInSystemBrowser(requestUrl);
applyForceDarkMode(url());
return false;
}
+2
View File
@@ -27,6 +27,8 @@ protected:
private:
static bool promptForExternalLink(const QUrl &requestUrl);
void applyBackgroundColor();
void applyForceDarkMode(const QUrl &pageUrl);
void updateInternalPageTheme();
};
} // namespace Zeal::Browser
+1 -1
View File
@@ -38,7 +38,7 @@ constexpr auto IndexNameVersion = "0001"_L1; // Current index version
constexpr auto DocumentsPath = "Contents/Resources/Documents/"_L1;
constexpr auto NotFoundPageUrl = "qrc:///browser/404.html"_L1;
constexpr auto NotFoundPageUrl = "qrc:///browser/not-found.html"_L1;
namespace InfoPlist {
constexpr auto CFBundleName = "CFBundleName"_L1;
+11 -2
View File
@@ -12,6 +12,7 @@
#include <browser/webcontrol.h>
#include <browser/webview.h>
#include <core/application.h>
#include <core/settings.h>
#include <registry/docset.h>
#include <registry/docsetregistry.h>
#include <registry/searchquery.h>
@@ -30,7 +31,7 @@ namespace Zeal::WidgetUi {
namespace {
using Qt::Literals::StringLiterals::operator""_L1;
constexpr auto WelcomePageUrl = "qrc:///browser/welcome.html"_L1;
constexpr auto NewTabPageUrl = "qrc:///browser/new-tab.html"_L1;
} // namespace
BrowserTab::BrowserTab(QWidget *parent)
@@ -226,7 +227,15 @@ SearchSidebar *BrowserTab::searchSidebar() const
void BrowserTab::navigateToStartPage()
{
m_webControl->load(QUrl(WelcomePageUrl));
// These pages must follow Zeal's appearance, not the OS. Qt WebEngine resolves
// prefers-color-scheme from the OS for the first renderer (before the app scheme
// propagates), so the page cannot reliably detect the theme on its own. Carry the
// resolved theme in the URL fragment; the page applies it synchronously before its
// styles load (see new-tab.html).
QUrl url(NewTabPageUrl);
const bool dark = Core::Application::instance()->settings()->isDarkModeEnabled();
url.setFragment(dark ? QStringLiteral("dark") : QStringLiteral("light"));
m_webControl->load(url);
}
void BrowserTab::search(const Registry::SearchQuery &query)