22namespace phpdotnet \phd ;
33
44class 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}
0 commit comments