app = $app;
// Init config
$this->config = \Yggverse\Yoda\Model\File::getConfig()->app->tab->page;
// Init DNS memory
$this->dns = new \Yggverse\Yoda\Model\Memory();
// Init history
$this->history = new \Yggverse\Yoda\Model\History();
// Compose header
$this->header = new \GtkBox(
\GtkOrientation::HORIZONTAL
);
$this->header->set_margin_top(
$this->config->header->margin
);
$this->header->set_margin_bottom(
$this->config->header->margin
);
$this->header->set_margin_start(
$this->config->header->margin
);
$this->header->set_margin_end(
$this->config->header->margin
);
$this->header->set_spacing(
$this->config->header->margin
);
// Home button
$this->home = \GtkButton::new_with_label(
$this->config->header->button->home->label
);
$this->home->connect(
'released',
function ($entry)
{
$this->history->reset();
$this->open(
$this->config->header->button->home->url
);
}
);
if ($this->config->header->button->home->visible)
{
$this->header->add(
$this->home
);
}
// Back button
$this->back = \GtkButton::new_with_label(
$this->config->header->button->back->label
);
$this->back->set_sensitive(
false
);
$this->back->connect(
'released',
function ($entry)
{
$this->open(
$this->history->goBack(),
false
);
}
);
// Forward button
$this->forward = \GtkButton::new_with_label(
$this->config->header->button->forward->label
);
$this->forward->set_sensitive(
false
);
$this->forward->connect(
'released',
function ($entry)
{
$this->open(
$this->history->goForward(),
false
);
}
);
/// Group buttons
if ($this->config->header->button->back->visible || $this->config->header->button->forward->visible)
{
$buttonGroup = new \GtkButtonBox(
\GtkOrientation::HORIZONTAL
);
$buttonGroup->set_layout(
\GtkButtonBoxStyle::EXPAND
);
if ($this->config->header->button->back->visible)
{
$buttonGroup->add(
$this->back
);
}
if ($this->config->header->button->forward->visible)
{
$buttonGroup->add(
$this->forward
);
}
$this->header->add(
$buttonGroup
);
}
// Address field
$this->address = new \GtkEntry();
$this->address->set_placeholder_text(
$this->config->header->address->placeholder
);
$this->address->set_max_length(
$this->config->header->address->length->max
);
$this->address->connect(
'activate',
function ($entry)
{
$this->open(
$entry->get_text()
);
}
);
$this->header->pack_start(
$this->address,
true,
true,
0
);
// Go button
$this->go = \GtkButton::new_with_label(
$this->config->header->button->go->label
);
$this->go->connect(
'released',
function ($entry)
{
$this->open(
$this->address->get_text()
);
}
);
if ($this->config->header->button->go->visible)
{
$this->header->add(
$this->go
);
}
// Compose body
$this->content = new \GtkLabel();
$this->content->set_use_markup(
true
);
$this->content->set_selectable(
true
);
$this->content->set_line_wrap(
true
);
$this->content->set_xalign(
0
);
$this->content->set_yalign(
0
);
// Init scrolled container
$this->container = new \GtkScrolledWindow();
$this->container->add(
$this->content
);
$this->body = new \GtkBox(
\GtkOrientation::VERTICAL
);
$this->body->set_margin_start(
$this->config->body->margin
);
$this->body->pack_start(
$this->container,
true,
true,
0
);
// Compose footer
$this->footer = new \GtkBox(
\GtkOrientation::HORIZONTAL
);
$this->footer->set_margin_top(
$this->config->footer->margin
);
$this->footer->set_margin_bottom(
$this->config->footer->margin
);
$this->footer->set_margin_start(
$this->config->footer->margin
);
$this->footer->set_margin_end(
$this->config->footer->margin
);
$this->footer->set_spacing(
$this->config->footer->margin
);
$this->status = new \GtkLabel();
$this->footer->add(
$this->status
);
// Compose page
$this->box = new \GtkBox(
\GtkOrientation::VERTICAL
);
$this->box->add(
$this->header
);
$this->box->pack_start(
$this->body,
true,
true,
0
);
$this->box->add(
$this->footer
);
}
public function open(
string $url,
bool $history = true
): void
{
// Update address field by requested
$this->address->set_text(
$url
);
// Update history pool
if ($history)
{
$this->history->add(
$url
);
}
// Update home button sensibility on match requested
$this->home->set_sensitive(
!($url == $this->config->header->button->home->url)
);
// Update back button sensibility
$this->back->set_sensitive(
(bool) $this->history->getBack()
);
// Update forward button sensibility
$this->forward->set_sensitive(
(bool) $this->history->getForward()
);
// Open current address
switch (true)
{
case str_starts_with($url, 'gemini://'):
$this->_gemini(
$url
);
break;
default:
$this->_yoda(
$url
);
}
}
private function _gemini(string $url): void
{
// Track response time
$start = microtime(true);
// Init custom resolver
$host = null;
if ($this->config->resolver->enabled)
{
$address = new \Yggverse\Net\Address(
$url
);
$name = $address->getHost();
if (!$host = $this->dns->get($name))
{
$resolve = new \Yggverse\Net\Resolve(
$this->config->resolver->request->record,
$this->config->resolver->request->host,
$this->config->resolver->request->timeout,
$this->config->resolver->result->shuffle
);
$resolved = $resolve->address(
$address
);
if ($resolved)
{
$host = $resolved->getHost();
$this->dns->set(
$name,
$host
);
}
}
}
$request = new \Yggverse\Gemini\Client\Request(
$url,
$host
);
$raw = $request->getResponse();
$end = microtime(true);
$response = new \Yggverse\Gemini\Client\Response(
$raw
);
$this->content->set_markup(
$this->_gemtext(
$response->getBody()
)
);
$body = new \Yggverse\Gemini\Gemtext\Body(
$response->getBody()
);
if ($h1 = $body->getH1())
{
$this->app->window->set_title(
sprintf(
'%s - %s',
empty($h1[0]) ? $address->getHost() : $h1[0],
$this->app->config->title
)
);
// @TODO update tab title
}
$this->status->set_markup(
str_replace( // Custom macros mask from config.json
[
'{NAVIGATION_ADDRESS}',
'{TIME_C}',
'{RESPONSE_META}',
'{RESPONSE_LENGTH}',
'{RESPONSE_SECONDS}'
],
[
urlencode(
$url
),
date(
'c'
),
$response->getMeta() ? $response->getMeta() : $response->getCode(),
number_format(
mb_strlen(
$raw
)
),
round(
$end - $start, 2
)
],
$this->config->footer->status->open->complete
)
);
}
private function _yoda(
string $url
): void
{
// Load local page
if (!$data = \Yggverse\Yoda\Model\Page::get(str_replace('yoda://', '', $url)))
{
$data = \Yggverse\Yoda\Model\Page::get('Oops');
}
$this->content->set_markup(
$this->_gemtext(
$data
)
);
// Parse gemtext
$body = new \Yggverse\Gemini\Gemtext\Body(
$data
);
if ($h1 = $body->getH1())
{
$this->app->window->set_title(
$h1[0]
);
// @TODO update tab title
}
}
private function _gemtext(
string $gemtext
): string
{
// Format body
$body = new \Yggverse\Gemini\Gemtext\Body(
$gemtext
);
$lines = $body->getLines();
$escaped = [];
/// Format H1
foreach ($body->getH1() as $index => $h1)
{
$lines[$index] = sprintf(
'%s',
htmlentities(
$h1
)
);
$escaped[] = $index;
}
/// Format H2
foreach ($body->getH2() as $index => $h2)
{
$lines[$index] = sprintf(
'%s',
htmlentities(
$h2
)
);
$escaped[] = $index;
}
/// Format H3
foreach ($body->getH3() as $index => $h3)
{
$lines[$index] = sprintf(
'%s',
htmlentities(
$h3
)
);
$escaped[] = $index;
}
/// Escape entities
foreach ($lines as $index => $line)
{
if (!in_array($index, $escaped))
{
$lines[$index] = htmlentities(
$line
);
}
}
// @TODO links, code, escape entities
return implode(
PHP_EOL,
$lines
);
}
}