Skip to content

Commit 5b4703a

Browse files
authored
Chunked XHTML with JS search (#204)
* Chunked XHTML with JS search * Improve prev/next nav alignment/sizing with differing content * Refactor to merge changes into Chunked XHTML directly; New search indexes pushed up to Web (allowing search.js changes to be pushed to web-php, avoiding duplication) * Add search access key hint * Update tests * Exclude search from CHM format * Chunked XHTML with JS search (don't use the localStorage cache for local docs)
1 parent 264c65b commit 5b4703a

File tree

4 files changed

+687
-45
lines changed

4 files changed

+687
-45
lines changed

phpdotnet/phd/Package/PHP/CHM.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,4 +449,14 @@ public function format_link($open, $name, $attrs, $props) {
449449
$link = preg_replace($search, $replacement, $link);
450450
return $link;
451451
}
452+
453+
protected function headerNav($id): string
454+
{
455+
return '';
456+
}
457+
458+
protected function footerSearch(): string
459+
{
460+
return '';
461+
}
452462
}

phpdotnet/phd/Package/PHP/ChunkedXHTML.php

Lines changed: 240 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
namespace phpdotnet\phd;
33

44
class Package_PHP_ChunkedXHTML extends Package_PHP_Web {
5+
protected array $js = [];
6+
57
public function __construct(
68
Config $config,
79
OutputHandler $outputHandler
@@ -15,37 +17,69 @@ public function __destruct() {
1517
parent::__destruct();
1618
}
1719

18-
public function header($id) {
19-
$title = Format::getLongDescription($id);
20-
static $cssLinks = null;
21-
if ($cssLinks === null) {
22-
$cssLinks = $this->createCSSLinks();
23-
}
24-
$header = <<<HEADER
25-
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
26-
<html>
27-
<head>
28-
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
29-
<title>$title</title>
30-
{$cssLinks}
31-
</head>
32-
<body class="docs">
33-
HEADER;
34-
$nextLink = $prevLink = $upLink = '';
20+
protected function headerNav($id): string
21+
{
22+
// https://feathericons.com search
23+
$searchIcon = <<<SVG
24+
<svg
25+
xmlns="http://www.w3.org/2000/svg"
26+
aria-hidden="true"
27+
width="24"
28+
viewBox="0 0 24 24"
29+
fill="none"
30+
stroke="currentColor"
31+
stroke-width="2"
32+
stroke-linecap="round"
33+
stroke-linejoin="round"
34+
>
35+
<circle cx="11" cy="11" r="8"></circle>
36+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
37+
</svg>
38+
SVG;
39+
40+
$nextLink = $prevLink = '<li class="navbar__item"></li>';
3541
if ($prevId = Format::getPrevious($id)) {
3642
$prev = array(
3743
"href" => $this->getFilename($prevId) . $this->getExt(),
3844
"desc" => $this->getShortDescription($prevId),
3945
);
40-
$prevLink = "<li style=\"float: left;\"><a href=\"{$prev["href"]}\"{$prev["desc"]}</a></li>";
46+
$prevLink = "<li class=\"navbar__item prev\"><a href=\"{$prev["href"]}\" class=\"navbar__link\"{$prev["desc"]}</a></li>";
4147
}
4248
if ($nextId = Format::getNext($id)) {
4349
$next = array(
4450
"href" => $this->getFilename($nextId) . $this->getExt(),
4551
"desc" => $this->getShortDescription($nextId),
4652
);
47-
$nextLink = "<li style=\"float: right;\"><a href=\"{$next["href"]}\">{$next["desc"]} »</a></li>";
53+
$nextLink = "<li class=\"navbar__item next\"><a href=\"{$next["href"]}\" class=\"navbar__link\">{$next["desc"]} »</a></li>";
4854
}
55+
56+
return <<<HTML
57+
<div class="navbar navbar-fixed-top">
58+
<div class="navbar__inner clearfix">
59+
<ul class="navbar__local">
60+
{$prevLink}
61+
<li class="navbar__item search">
62+
<!-- Desktop encanced search -->
63+
<button
64+
id="navbar__search-button"
65+
class="navbar__search-button"
66+
hidden
67+
>
68+
$searchIcon
69+
Search
70+
</button>
71+
</li>
72+
{$nextLink}
73+
</ul>
74+
</div>
75+
</div>
76+
HTML;
77+
}
78+
79+
protected function headerCrumbs($id): string
80+
{
81+
$title = Format::getLongDescription($id);
82+
$upLink = '';
4983
if ($parentId = Format::getParent($id)) {
5084
$up = array(
5185
"href" => $this->getFilename($parentId) . $this->getExt(),
@@ -56,31 +90,196 @@ public function header($id) {
5690
}
5791
}
5892

59-
$nav = <<<NAV
60-
<div class="navbar navbar-fixed-top">
61-
<div class="navbar-inner clearfix">
62-
<ul class="nav" style="width: 100%">
63-
{$prevLink}
64-
{$nextLink}
65-
</ul>
66-
</div>
67-
</div>
68-
<div id="breadcrumbs" class="clearfix">
69-
<ul class="breadcrumbs-container">
70-
<li><a href="index.html">PHP Manual</a></li>
71-
{$upLink}
72-
<li>{$title}</li>
73-
</ul>
74-
</div>
75-
<div id="layout">
76-
<div id="layout-content">
77-
NAV;
78-
$header .= $nav;
79-
return $header;
93+
return <<<HTML
94+
<div id="breadcrumbs" class="clearfix">
95+
<ul class="breadcrumbs-container">
96+
<li><a href="index.html">PHP Manual</a></li>
97+
{$upLink}
98+
<li>{$title}</li>
99+
</ul>
100+
</div>
101+
HTML;
102+
}
103+
104+
public function header($id) {
105+
$title = Format::getLongDescription($id);
106+
static $cssLinks = null;
107+
if ($cssLinks === null) {
108+
$cssLinks = $this->createCSSLinks();
109+
}
110+
$header = <<<HTML
111+
<!DOCTYPE HTML>
112+
<html>
113+
<head>
114+
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
115+
<style>
116+
.navbar__local {
117+
flex-grow: 1;
118+
display: grid;
119+
grid-auto-flow: column;
120+
grid-auto-columns: 1fr min-content 1fr;
121+
margin: 0;
122+
height: 100%;
123+
124+
.navbar__item {
125+
align-content: center;
126+
}
127+
.navbar__link {
128+
display: inline;
129+
}
130+
.search {
131+
text-align: center;
132+
}
133+
.next {
134+
text-align: right;
135+
}
136+
}
137+
</style>
138+
<title>$title</title>
139+
{$cssLinks}
140+
</head>
141+
<body class="docs">
142+
HTML;
143+
144+
$contentOpen = <<<HTML
145+
<div id="layout">
146+
<div id="layout-content">
147+
HTML;
148+
149+
return $header . $this->headerNav($id) . $this->headerCrumbs($id) . $contentOpen;
150+
}
151+
152+
protected function footerSearch(): string
153+
{
154+
$this->fetchJS();
155+
156+
$footer = '';
157+
$footer .= '<script type="text/javascript" src="search-combined.js"></script>';
158+
foreach ($this->js as $jsFile) {
159+
$footer .= '<script type="text/javascript" src="' . $jsFile . '"></script>';
160+
}
161+
162+
$footer .= <<<HTML
163+
<div id="search-modal__backdrop" class="search-modal__backdrop">
164+
<div
165+
role="dialog"
166+
aria-label="Search modal"
167+
id="search-modal"
168+
class="search-modal"
169+
>
170+
<div class="search-modal__header">
171+
<div class="search-modal__form">
172+
<div class="search-modal__input-icon">
173+
<!-- https://feathericons.com search -->
174+
<svg xmlns="http://www.w3.org/2000/svg"
175+
aria-hidden="true"
176+
width="24"
177+
viewBox="0 0 24 24"
178+
fill="none"
179+
stroke="currentColor"
180+
stroke-width="2"
181+
stroke-linecap="round"
182+
stroke-linejoin="round"
183+
>
184+
<circle cx="11" cy="11" r="8"></circle>
185+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
186+
</svg>
187+
</div>
188+
<input
189+
type="search"
190+
id="search-modal__input"
191+
class="search-modal__input"
192+
placeholder="Search docs"
193+
aria-label="Search docs"
194+
/>
195+
</div>
196+
197+
<button aria-label="Close" class="search-modal__close">
198+
<!-- https://pictogrammers.com/library/mdi/icon/close/ -->
199+
<svg
200+
xmlns="http://www.w3.org/2000/svg"
201+
aria-hidden="true"
202+
width="24"
203+
viewBox="0 0 24 24"
204+
>
205+
<path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"/>
206+
</svg>
207+
</button>
208+
</div>
209+
<div
210+
role="listbox"
211+
aria-label="Search results"
212+
id="search-modal__results"
213+
class="search-modal__results"
214+
></div>
215+
<div class="search-modal__helper-text">
216+
<div>
217+
<kbd>↑</kbd> and <kbd>↓</kbd> to navigate •
218+
<kbd>Enter</kbd> to select •
219+
<kbd>Esc</kbd> to close • <kbd>/</kbd> to open
220+
</div>
221+
</div>
222+
</div>
223+
</div>
224+
<script type="text/javascript">
225+
document.addEventListener("DOMContentLoaded", function() {
226+
/*{{{Search Modal*/
227+
const language = 'local';
228+
initSearchModal();
229+
initPHPSearch(language).then((searchCallback) => {
230+
initSearchUI({language, searchCallback, limit: 30});
231+
});
232+
/*}}}*/
233+
});
234+
</script>
235+
HTML;
236+
237+
return $footer;
238+
}
239+
240+
private function mkdir(string $dir): void
241+
{
242+
if (file_exists($dir)) {
243+
if (!is_dir($dir)) {
244+
trigger_error("The specified directory is a file: {$dir}", E_USER_ERROR);
245+
}
246+
return;
247+
}
248+
if (!mkdir($dir, 0777, true)) {
249+
trigger_error("Can't create the specified directory: {$dir}", E_USER_ERROR);
250+
}
251+
}
252+
253+
private function fetchJS(): void
254+
{
255+
if ($this->js !== []) {
256+
return;
257+
}
258+
$outputDir = $this->getOutputDir();
259+
if (!$outputDir) {
260+
$outputDir = $this->config->outputDir;
261+
}
262+
$jsDir = 'js/';
263+
$outputDir .= '/' . $jsDir;
264+
$this->mkdir($outputDir);
265+
266+
$files = [
267+
'https://www.php.net/js/ext/FuzzySearch.min.js',
268+
__DIR__ . '/search.js',
269+
];
270+
foreach ($files as $sourceFile) {
271+
$basename = basename($sourceFile);
272+
$dest = md5(substr($sourceFile, 0, -strlen($basename))) . '-' . $basename;
273+
if (! @copy($sourceFile, $outputDir . $dest)) {
274+
trigger_error(vsprintf('Impossible to copy the %s file.', [$sourceFile]), E_USER_WARNING);
275+
continue;
276+
}
277+
$this->js[] = $jsDir . $dest;
278+
}
80279
}
81280

82281
public function footer($id)
83282
{
84-
return '</div></div></body></html>';
283+
return '</div></div>' . $this->footerSearch() . '</body></html>';
85284
}
86285
}

phpdotnet/phd/Package/PHP/Web.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -337,10 +337,6 @@ private function processCombinedJsonIndex(): array
337337
$nameParts = explode('::', $index['sdesc']);
338338
$methodName = array_pop($nameParts);
339339

340-
if (str_contains('wrapper', $index['filename'])) {
341-
print "Combined index: adding " . $index['filename'] . " :: " . $index['sdesc'] . "\n";
342-
}
343-
344340
$type = 'General';
345341
switch ($index['element']) {
346342
case "phpdoc:varentry":

0 commit comments

Comments
 (0)