better format for gemini articles in WebEngine version

This commit is contained in:
Martin Rotter 2024-12-20 12:43:10 +01:00
parent 3dde4064fb
commit a352b7551e
8 changed files with 106 additions and 22 deletions

View File

@ -11,6 +11,7 @@
<file>scripts/builtin_js/observer.js</file>
<file>scripts/web_ui/rssguard.html</file>
<file>scripts/gemini/style.css</file>
<file>sounds/boing.wav</file>
<file>sounds/rooster.wav</file>

View File

@ -0,0 +1,60 @@
p {
min-height: 0.1em;
margin: 6px 0px;
}
h1, h2, h3, h4, h5, h6, ul, ol, pre {
margin: 9px 0px;
}
pre {
font-family: "Consolas", "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace;
white-space: pre-wrap;
overflow-x: auto;
padding: 8px;
border-radius: 2px;
}
a:link {
text-decoration-line: none;
}
a:hover {
text-decoration-line: underline;
}
blockquote {
font-size: 1.2em;
width: 80%;
margin: 14px auto;
font-family: "Open Sans";
font-style: italic;
color: #555555;
padding: 1em 16px 1em 30px;
border-left: 4px solid #75bff7;
line-height: 1.1;
position: relative;
background: #EDEDED;
}
blockquote::before {
font-family: Arial;
content: "\201C";
color: #7598f7;
font-size: 3em;
position: absolute;
left: 10px;
top: 0px;
}
blockquote::after {
content: '';
}
blockquote span {
display: block;
color: #333333;
font-style: normal;
font-weight: bold;
margin-top: 1em;
}

View File

@ -15,11 +15,11 @@
#include <QTimer>
Downloader::Downloader(QObject* parent)
: QObject(parent), m_geminiClient(new GeminiClient(this)), m_activeReply(nullptr),
m_downloadManager(new SilentNetworkAccessManager(this)), m_timer(new QTimer(this)), m_inputData(QByteArray()),
m_inputMultipartData(nullptr), m_targetProtected(false), m_targetUsername(QString()), m_targetPassword(QString()),
m_lastOutputData({}), m_lastOutputError(QNetworkReply::NetworkError::NoError), m_lastHttpStatusCode(0),
m_lastHeaders({}) {
: QObject(parent), m_geminiClient(new GeminiClient(this)), m_geminiParser(GeminiParser(false)),
m_activeReply(nullptr), m_downloadManager(new SilentNetworkAccessManager(this)), m_timer(new QTimer(this)),
m_inputData(QByteArray()), m_inputMultipartData(nullptr), m_targetProtected(false), m_targetUsername(QString()),
m_targetPassword(QString()), m_lastOutputData({}), m_lastOutputError(QNetworkReply::NetworkError::NoError),
m_lastHttpStatusCode(0), m_lastHeaders({}) {
m_timer->setInterval(DOWNLOAD_TIMEOUT);
m_timer->setSingleShot(true);
@ -50,7 +50,7 @@ void Downloader::geminiFinished(const QByteArray& data, const QString& mime) {
m_lastOutputMultipartData = {};
if (mime.startsWith(QSL(GEMINI_MIME_TYPE))) {
m_lastOutputData = GeminiParser().geminiToHtml(data).toUtf8();
m_lastOutputData = m_geminiParser.geminiToHtml(data).toUtf8();
}
else {
m_lastOutputData = data;

View File

@ -5,6 +5,7 @@
#include "definitions/definitions.h"
#include "network-web/gemini/geminiclient.h"
#include "network-web/gemini/geminiparser.h"
#include "network-web/httpresponse.h"
#include <QHttpMultiPart>
@ -105,6 +106,7 @@ class Downloader : public QObject {
private:
GeminiClient* m_geminiClient;
GeminiParser m_geminiParser;
QNetworkReply* m_activeReply;
QScopedPointer<SilentNetworkAccessManager> m_downloadManager;
QTimer* m_timer;

View File

@ -12,12 +12,14 @@ QString GeminiParser::geminiToHtml(const QByteArray& gemini_data) {
QStringList lines = gemini_hypertext.split(QL1C('\n'));
mode = State::Normal;
static QRegularExpression exp_link(R"(^=>\s+([^\s]+)(?:\s+(\w.+))?$)");
static QRegularExpression exp_link(R"(^=>\s+([^\s]+)(?:\s+(\S.+))?$)");
static QRegularExpression exp_heading(R"(^(#{1,6})\s+(.+)$)");
static QRegularExpression exp_list(R"(^\*\s(.+)$)");
static QRegularExpression exp_quote(R"((?:^>$|^>\s?(.+)$))");
static QRegularExpression exp_pre(R"(^```.*$)");
static QString rich_style = QString::fromUtf8(IOFactory::readFile(QSL(":/scripts/gemini/style.css")));
QRegularExpressionMatch mtch;
QString title;
@ -69,13 +71,17 @@ QString GeminiParser::geminiToHtml(const QByteArray& gemini_data) {
html += endBlock(State::Normal);
// IOFactory::writeFile("aa", html.toUtf8());
return QSL("<html>"
"<head><title>%1</title></head>"
return QSL("<!DOCTYPE html>"
"<html>"
"<head>"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<meta charset=utf-8>"
"<title>%1</title>"
"<style>%3</style>"
"</head>"
"<body>%2</body>"
"</html>")
.arg(title, html);
.arg(title, html, m_richHtml ? rich_style : QString());
}
QString GeminiParser::beginBlock(State new_mode) {
@ -87,11 +93,12 @@ QString GeminiParser::beginBlock(State new_mode) {
return "<ul>\n";
case State::Quote:
return "<div align=\"center\" style=\""
return QSL("<%1 style=\""
"background-color: #E1E5EE;"
"font-style: italic;"
"margin-left: 20px;"
"margin-right: 20px;\">\n";
"margin-right: 20px;\">\n")
.arg(m_richHtml ? QSL("blockquote") : QSL("div"));
case State::Pre:
return "<pre style=\"background-color: #E1E5EE;\">\n";
@ -111,7 +118,7 @@ QString GeminiParser::endBlock(State new_mode) {
break;
case State::Quote:
to_return = "</div>\n";
to_return = QSL("</%1>\n").arg(m_richHtml ? QSL("blockquote") : QSL("div"));
break;
case State::Pre:
@ -125,6 +132,8 @@ QString GeminiParser::endBlock(State new_mode) {
return to_return;
}
GeminiParser::GeminiParser(bool rich_html) : m_richHtml(rich_html) {}
QString GeminiParser::parseLink(const QRegularExpressionMatch& mtch) const {
QString link = mtch.captured(1);
QString name = mtch.captured(2);
@ -146,8 +155,10 @@ QString GeminiParser::parseHeading(const QRegularExpressionMatch& mtch, QString*
QString GeminiParser::parseQuote(const QRegularExpressionMatch& mtch) const {
QString text = mtch.captured(1);
QString element = m_richHtml ? QSL("p") : QSL("div");
return QSL("<div>%1</div>\n").arg(text.isEmpty() ? QString() : QSL("&#8220;%1&#8221;").arg(text));
return QSL("<%2>%1</%2>\n")
.arg(text.simplified().isEmpty() ? QString() : (m_richHtml ? text : QSL("&#8220;%1&#8221;").arg(text)), element);
}
QString GeminiParser::parseList(const QRegularExpressionMatch& mtch) const {

View File

@ -8,6 +8,8 @@
class GeminiParser {
public:
explicit GeminiParser(bool rich_html);
QString geminiToHtml(const QByteArray& gemini_data);
private:
@ -37,6 +39,7 @@ class GeminiParser {
private:
State mode;
bool m_richHtml;
};
#endif // GEMINIPARSER_H

View File

@ -8,7 +8,8 @@
#include <QBuffer>
GeminiSchemeHandler::GeminiSchemeHandler(QObject* parent) : QWebEngineUrlSchemeHandler(parent) {}
GeminiSchemeHandler::GeminiSchemeHandler(QObject* parent)
: QWebEngineUrlSchemeHandler(parent), m_geminiParser(GeminiParser(true)) {}
void GeminiSchemeHandler::requestStarted(QWebEngineUrlRequestJob* request) {
GeminiClient* gemini_client = new GeminiClient(this);
@ -45,9 +46,13 @@ void GeminiSchemeHandler::onCompleted(const QByteArray& data, const QString& mim
buf->open(QBuffer::ReadWrite);
if (mime.startsWith(QSL(GEMINI_MIME_TYPE))) {
// IOFactory::writeFile("a", data);
QString htmlized_gemini = m_geminiParser.geminiToHtml(data);
buf->write(htmlized_gemini.toUtf8());
#if !defined(NDEBUG)
IOFactory::writeFile("aa.html", htmlized_gemini.toUtf8());
#endif
buf->write(GeminiParser().geminiToHtml(data).toUtf8());
target_mime = QSL("text/html");
}
else {

View File

@ -4,6 +4,7 @@
#define GEMINISCHEMEHANDLER_H
#include "network-web/gemini/geminiclient.h"
#include "network-web/gemini/geminiparser.h"
#include <QWebEngineUrlRequestJob>
#include <QWebEngineUrlSchemeHandler>
@ -23,6 +24,7 @@ class GeminiSchemeHandler : public QWebEngineUrlSchemeHandler {
private:
QHash<QWebEngineUrlRequestJob*, GeminiClient*> m_jobs;
GeminiParser m_geminiParser;
};
#endif // GEMINISCHEMEHANDLER_H