30
31:- module(plweb_page, []). 32:- use_module(footer). 33:- use_module(library(http/html_write)). 34:- use_module(library(http/html_head)). 35:- use_module(library(http/http_path)). 36:- use_module(library(pldoc/doc_search)). 37:- use_module(library(http/js_write)). 38:- use_module(library(http/html_head)). 39:- use_module(library(http/http_wrapper)). 40:- use_module(library(http/http_dispatch)). 41:- use_module(library(http/http_parameters)). 42:- use_module(library(pldoc/doc_html), [object_name//2]). 43:- use_module(library(uri)). 44:- use_module(library(option)). 45
46:- use_module(wiki). 47:- use_module(post). 48:- use_module(openid). 49:- use_module(did_you_know). 50:- use_module(holidays). 51
52:- html_meta
53 outer_container(html, +, ?, ?). 54
55:- http_handler(root(search), plweb_search, []).
61:- multifile
62 user:body//2,
63 plweb:page_title//1,
64 html_write:html_header_hook/1. 65
66html_write:(_) :-
67 format('Content-Security-Policy: frame-ancestors \'none\'~n').
68
69user:body(homepage, Body) --> !,
70 outer_container([ \tag_line_area,
71 \menubar(homepage),
72 \blurb,
73 \cta_area,
74 \enhanced_search_area,
75 Body
76 ], []).
77user:body(Style, Body) -->
78 { page_style(Style, Options), !,
79 functor(Style, ContentClass, _)
80 },
81 outer_container(
82 [ \title_area(Style),
83 \menubar(Style),
84 div(class(breadcrumb), []),
85 div(class(['inner-contents', ContentClass]),
86 div([id(contents), class([contents, ContentClass])],
87 Body))
88 ],
89 Options).
90user:body(Style, Body) -->
91 { Style = forum(_) }, !,
92 outer_container(
93 [ \title_area(Style),
94 \menubar(Style),
95 div(class(breadcrumb), []),
96 Body
97 ],
98 []).
99user:body(plain, Body) --> !,
100 html(body(class(plain), Body)).
101user:body(default, Body) --> !,
102 html(body(class(plain), Body)).
103user:body(Style, _Body) -->
104 html(div('Unknown page style ~q'-[Style])).
110page_style(user(_Action), [show_user(false)]).
111page_style(download(_Dir, _Title), []).
112page_style(dir_index(_Dir, _Title),[]).
113page_style(news(_Which), []).
114page_style(wiki(_Special), []).
115page_style(wiki(Path, _Title), [object(wiki(Path))]).
116page_style(blog(_Special), []).
117page_style(blog(Path, _Title), [object(blog(Path))]).
118page_style(pack(_Action), []).
119page_style(tags(_Action), []).
120page_style(pldoc(object(Obj)), [object(Obj)]) :- !.
121page_style(pldoc(search(For)), [for(For)]) :- !.
122page_style(pldoc(_), []).
123page_style(pack(_Type, _Title), []).
124page_style(git(_), []).
130outer_container(Content, Options) -->
131 html(body(div(class('outer-container'),
132 [ \html_requires(plweb),
133 \html_requires(swipl_css),
134 \shortcut_icons,
135 \upper_header(Options),
136 Content,
137 div([id(dialog),style('display:none;')], []),
138 div(class([footer, newstyle]), \footer(Options)),
139 div(id('tail-end'), &(nbsp)),
140 \page_script(Options)
141 ]))),
142 html_receive(script).
152:- multifile
153 prolog:doc_page_header//2,
154 prolog:doc_links//2. 155
156prolog:(_File, _Options) --> [].
157prolog:doc_links(_Directory, _Options) --> [].
158prolog:doc_file_title(_Title, _File, _Options) --> [].
159
160shortcut_icons -->
161 { http_absolute_location(icons('favicon.ico'), FavIcon, []),
162 http_absolute_location(root('apple-touch-icon.png'), TouchIcon, [])
163 },
164 html_post(head,
165 [ link([ rel('shortcut icon'), href(FavIcon) ]),
166 link([ rel('apple-touch-icon'), href(TouchIcon) ])
167 ]).
173upper_header(Options) -->
174 { http_link_to_id(plweb_search, [], Action),
175 option(for(Search), Options, '')
176 },
177 html(div(id('upper-header'),
178 table(id('upper-header-contents'),
179 tr([ td(id('dyknow-container'),
180 \did_you_know_script('dyknow-container')),
181 td(id('search-container'),
182 [ span(class(lbl), 'Search Documentation:'),
183 form([action(Action),id('search-form')],
184 [ input([ name(for),
185 id(for),
186 value(Search)
187 ], []),
188 input([ id('submit-for'),
189 type(submit),
190 value('Search')
191 ], []),
192 \searchbox_script(for)
193 ])
194 ])
195 ])))).
201plweb_search(Request) :-
202 http_parameters(
203 Request,
204 [ for(For,
205 [ default(''),
206 description('String to search for')
207 ]),
208 in(In,
209 [ oneof([all,app,noapp,man,lib,pack,wiki]),
210 default(noapp),
211 description('Search everying, application only \c
212 or manual only')
213 ]),
214 match(Match,
215 [ oneof([name,summary]),
216 default(summary),
217 description('Match only the name or also the summary')
218 ]),
219 resultFormat(Format,
220 [ oneof(long,summary),
221 default(summary),
222 description('Return full documentation \c
223 or summary-lines')
224 ]),
225 page(Page,
226 [ integer,
227 default(1),
228 description('Page of search results to view')
229 ])
230
231 ]),
232 format(string(Title), 'Prolog search -- ~w', [For]),
233 reply_html_page(pldoc(search(For)),
234 title(Title),
235 \search_reply(For,
236 [ resultFormat(Format),
237 search_in(In),
238 search_match(Match),
239 header(false),
240 private(false),
241 edit(false),
242 page(Page)
243 ])).
250searchbox_script(Tag) -->
251 html([
252 \html_requires(jquery_ui),
253 script(type('text/javascript'), {|javascript(Tag)||
254 $(function() {
255 function htmlEncode(text) {
256 if ( !text ) return "";
257 return document.createElement('a')
258 .appendChild(document.createTextNode(text))
259 .parentNode
260 .innerHTML;
261 }
262 $("#"+Tag).autocomplete({
263 minLength: 1,
264 delay: 0.3,
265 source: "/autocomplete/ac_predicate",
266 focus: function(event,ui) {
267 $("#"+Tag).val(ui.item.label);
268 return false;
269 },
270 select: function(event,ui) {
271 $("#"+Tag).val(ui.item.label);
272 window.location.href = ui.item.href;
273 return false;
274 }
275 })
276 .data("ui-autocomplete")._renderItem = function(ul,item) {
277 var label = String(htmlEncode(item.label)).replace(
278 htmlEncode(this.term),
279 "<span class=\"acmatch\">"+this.term+"</span>");
280 var tag = item.tag ? " <i>["+item.tag+"]</i>" : "";
281 return $("<li>")
282 .append("<a class=\""+item.class+"\">"+label+tag+"</a>")
283 .appendTo(ul)
284 };
285 });
286|})]).
293tag_line_area -->
294 html(div(id('tag-line-area'),
295 [ \swi_logo,
296 span(class(tagline),
297 [ 'Robust, mature, free. ',
298 b('Prolog for the real world.')
299 ])
300 ])).
305title_area(pldoc(file(File, Title))) --> !,
306 { file_base_name(File, Base) },
307 html([ table(id('header-line-area'),
308 tr([ td(id('logo'), \swi_logo),
309 td(class('primary-header'),
310 \page_title(title(Title)))
311 ])),
312 div([ class('file-buttons')
313 ],
314 [ \zoom_button(Base, []),
315 \source_button(Base, [])
316 ])
317 ]).
318title_area(Style) -->
319 html(table(id('header-line-area'),
320 tr([ td(id('logo'), \swi_logo),
321 td(class('primary-header'),
322 \page_title(Style))
323 ]))).
324
325page_title(For) -->
326 plweb:page_title(For), !.
327page_title(pldoc(search(''))) --> !,
328 html('How to use the search box').
329page_title(pldoc(search(For))) --> !,
330 html(['Search results for ', span(class(for), ['"', For, '"'])]).
331page_title(pldoc(object(Obj))) -->
332 object_name(Obj,
333 [ style(title)
334 ]), !.
335page_title(title(Title)) --> !,
336 html(Title).
337page_title(user(login)) --> !,
338 html('Login to www.swi-prolog.org').
339page_title(user(logout)) --> !,
340 html('Logged out from www.swi-prolog.org').
341page_title(user(create_profile)) --> !,
342 html('Create user profile').
343page_title(user(view_profile(UUID))) --> !,
344 { site_user_property(UUID, name(Name)) },
345 html('Profile for user ~w'-[Name]).
346page_title(user(list)) --> !,
347 html('Registered site users').
348page_title(news(fresh)) --> !,
349 html('News').
350page_title(news(all)) --> !,
351 html('News archive').
352page_title(news(Id)) -->
353 { post(Id, title, Title) },
354 html(Title).
355page_title(pack(list)) -->
356 html('Packs (add-ons) for SWI-Prolog').
357page_title(wiki(sandbox)) -->
358 html('PlDoc wiki sandbox').
359page_title(wiki(edit(Action, Location))) -->
360 html([Action, ' wiki page ', Location]).
361page_title(wiki(_Path, Title)) -->
362 html(Title).
363page_title(blog(index)) -->
364 html('SWI-Prolog blog -- index').
365page_title(blog(_Path, Title)) -->
366 html(Title).
367page_title(tags(list)) -->
368 html('Tags').
369page_title(download(_Dir, Title)) -->
370 html(Title).
371page_title(dir_index(_Dir, Title)) -->
372 html(Title).
373page_title(Term) -->
374 html('Title for ~q'-[Term]).
383todays_logo('christmas.png', 'Merry Christmas.') :-
384 todays_holiday(christmas).
385todays_logo('koningsdag.png', 'Kings day in the Netherlands') :-
386 todays_holiday(koningsdag).
387todays_logo('santiklaas.png', 'St. Nicholas\' eve in the Netherlands') :-
388 todays_holiday(santiklaas).
389todays_logo('carnivalswipl.png', 'Carnival in the Netherlands') :-
390 todays_holiday(carnival).
391todays_logo('halloween.png', 'Hoooo.... on Halloween') :-
392 todays_holiday(halloween).
393todays_logo('chinesenewyear.png', 'Chinese New Year') :-
394 todays_holiday(chinese_new_year).
395todays_logo('liberationday.png', 'Liberation Day in the Netherlands') :-
396 todays_holiday(liberation_day).
397todays_logo('swipl.png', 'SWI-Prolog owl logo') :-
398 todays_holiday(_).
404swi_logo -->
405 { todays_logo(File, Alt),
406 http_absolute_location(icons(File), Logo, [])
407 },
408 html(a(href('http://www.swi-prolog.org'),
409 img([ class(owl),
410 src(Logo),
411 alt(Alt),
412 title(Alt)
413 ], []))).
420menubar(Style) -->
421 { menu(Style, Menu) },
422 html_requires(jquery),
423 html_requires(jq('menu.js')),
424 html(div(id(menubar),
425 div(class([menubar, 'fixed-width']),
426 ul(class('menubar-container'),
427 \menu(Menu, 1))))).
428
([], _) --> !.
430menu([H|T], Level) --> !, menu(H, Level), menu(T, Level).
431menu(Label = Link + SubMenu, Level) --> !,
432 submenu(Label, Level, SubMenu, Link).
433menu(Label = SubMenu, Level) -->
434 { is_list(SubMenu)
435 }, !,
436 submenu(Label, Level, SubMenu, -).
437menu(Label = Link, _) -->
438 { atom(Link),
439 uri_is_global(Link), !,
440 http_absolute_location(icons('ext-link.png'), IMG, [])
441 }, !,
442 html(li([ a(href(Link),
443 [ Label,
444 img([ class('ext-link'),
445 src(IMG),
446 alt('External')
447 ])
448 ])
449 ])).
450menu(_Label = (-), _) --> !,
451 [].
452menu(Label = Link, 1) -->
453 { upcase_atom(Label, LABEL) },
454 html(li(a(href(Link), LABEL))).
455menu(Label = Link, _) -->
456 html(li(a(href(Link), Label))).
457
(Label, Level, SubMenu, -) --> !,
459 { SubLevel is Level+1 },
460 html(li([ \submenu_label(Label, Level),
461 ul(\menu(SubMenu, SubLevel))
462 ])).
463submenu(Label, Level, SubMenu, HREF) -->
464 { SubLevel is Level+1 },
465 html(li([ a(href(HREF), \submenu_label(Label, Level)),
466 ul(\menu(SubMenu, SubLevel))
467 ])).
468
(Label, Level) -->
470 { Level =< 1,
471 upcase_atom(Label, LABEL)
472 }, !,
473 html(LABEL).
474submenu_label(Label, _) -->
475 html([Label, span(class(arrow), &('#x25B6'))]).
476
477
(Style,
479 [ 'Home' = '/',
480 'Download' =
481 [ 'SWI-Prolog' = '/Download.html',
482 'Sources/building' = '/build/',
483 'Docker images' = '/Docker.html',
484 'Add-ons' = '/pack/list',
485 'Browse GIT' = 'https://github.com/SWI-Prolog'
486 ],
487 'Documentation' =
488 [ 'Manual' = '/pldoc/refman/',
489 'Packages' = '/pldoc/package/',
490 'FAQ' = '/FAQ/',
491 'Command line' = '/pldoc/man?section=cmdline',
492 'PlDoc' = '/pldoc/package/pldoc.html',
493 'Bluffers' =
494 [ 'Prolog syntax' = '/pldoc/man?section=syntax',
495 'PceEmacs' = '/pldoc/man?section=emacsbluff',
496 'HTML generation' = '/pldoc/man?section=htmlwrite'
497 ],
498 'License' = '/license.html',
499 'Publications' = '/Publications.html',
500 'Rev 7 Extensions' = '/pldoc/man?section=extensions'
501 ],
502 'Tutorials' =
503 [ 'Beginner' =
504 [ 'Getting started' = '/pldoc/man?section=quickstart',
505 'Learn Prolog Now!' = 'http://lpn.swi-prolog.org/',
506 'Simply Logical' = 'http://book.simply-logical.space/',
507 'Debugger' = '/pldoc/man?section=debugoverview',
508 'Development tools' = '/IDE.html'
509 ],
510 'Advanced' =
511 [ 'Modules' = 'http://chiselapp.com/user/ttmrichter/repository/gng/doc/trunk/output/tutorials/swiplmodtut.html',
512 'Grammars (DCGs)' = 'https://www.github.com/Anniepoo/swipldcgtut/blob/master/dcgcourse.adoc',
513 'clp(fd)' = 'https://www.github.com/Anniepoo/swiplclpfd/blob/master/clpfd.adoc',
514 'Printing messages' = 'https://www.github.com/Anniepoo/swiplmessage/blob/master/message.adoc',
515 'PlDoc' = 'http://chiselapp.com/user/ttmrichter/repository/swipldoctut/doc/tip/doc/tutorial.html'
516 ],
517 'Web applications' =
518 [ 'Web applications' = 'https://www.github.com/Anniepoo/swiplwebtut/blob/master/web.adoc',
519 'Let\'s Encrypt!' = 'https://github.com/triska/letswicrypt',
520 'Pengines' = '/pengines/'
521 ],
522 'Semantic web' =
523 [ 'ClioPatria' = 'https://cliopatria.swi-prolog.org/tutorial/',
524 'RDF namespaces' = '/howto/UseRdfMeta.html'
525 ],
526 'Graphics' =
527 [ 'XPCE' = '/download/xpce/doc/coursenotes/coursenotes.pdf',
528 'GUI options' = '/Graphics.html'
529 ],
530 'Machine learning' =
531 [ 'Probabilistic Logic Programming' =
532 'http://cplint.ml.unife.it/'
533 ],
534 'External collections' =
535 [ 'Meta level tutorials' = 'https://www.metalevel.at/prolog'
536 ],
537 'For packagers' =
538 [ 'Linux packages' = '/build/guidelines.html'
539 ]
540 ],
541 'Community' = '/community.html' +
542 [ 'IRC' = 'https://web.libera.chat/?channels=##prolog',
543 'Forum & mailing list'= 'https://swi-prolog.discourse.group',
544 'Blog' = '/blog',
545 'News' = '/news/archive',
546 'Report a bug' = '/bug.html',
547 'Submit a patch' = '/howto/SubmitPatch.html',
548 'Submit an add-on' = '/howto/Pack.html',
549 'Roadmap (on GitHub)' = 'https://github.com/SWI-Prolog/roadmap',
550 'External links' = '/Links.html',
551 'Contributing' = '/contributing.html',
552 'Code of Conduct' = '/Code-of-Conduct.html',
553 'Contributors' = '/Contributors.html',
554 'SWI-Prolog items' = '/loot.html'
555 ],
556 'Commercial' = '/commercial/index.html',
557 'Wiki' =
558 [ LoginLabel = LoginURL,
559 'Edit this page' = EditHREF,
560 'View changes' = '/wiki/changes',
561 'Sandbox' = '/wiki/sandbox',
562 'Wiki help' = '/wiki/',
563 'All tags' = '/list-tags'
564 ]
565 ]) :-
566 http_current_request(Request),
567 memberchk(request_uri(ReqURL), Request),
568 ( functor(Style, wiki, _)
569 -> http_link_to_id(wiki_edit,
570 [location(ReqURL)], EditHREF)
571 ; EditHREF = (-)
572 ),
573 ( site_user_logged_in(_)
574 -> LoginLabel = 'Logout',
575 http_link_to_id(logout, ['openid.return_to'(ReqURL)], LoginURL)
576 ; LoginLabel = 'Login',
577 ( http_link_to_id(logout, [], ReqURL)
578 -> RetURL = '/' 579 ; RetURL = ReqURL
580 ),
581 http_link_to_id(plweb_login_page,
582 ['openid.return_to'(RetURL)], LoginURL)
583 ).
589blurb -->
590 html({|html||
591 <div id="blurb">
592 <div>
593 SWI-Prolog offers a comprehensive free Prolog environment.
594 Since its start in 1987, SWI-Prolog development has been driven
595 by the needs of real world applications. SWI-Prolog is widely
596 used in research and education as well as commercial applications.
597 Join over a million users who have downloaded SWI-Prolog.
598 <a href="/features.html">more ...</a>
599 </div>
600 </div>
601 |}).
607cta_area -->
608 html_post(head,
609 script([ defer(defer), async(async),
610 src('https://buttons.github.io/buttons.js')
611 ], [])),
612 html({|html(_)||
613 <table id='cta-container'>
614 <tr>
615 <td style="text-align:left; vertical-align: top">
616 <a href="Download.html">Download SWI-Prolog</a>
617 <td style="text-align:center; vertical-align: top">
618 <a href="GetStarted.html">Get Started</a>
619 <td style="text-align:right; white-space: nowrap; vertical-align: top">
620 <a href="http://swish.swi-prolog.org">
621 Try SWI-Prolog online (SWISH) </a><br>
622 <a href="http://dev.swi-prolog.org/wasm/shell"
623 style="font-size: 60%">
624 🔥 Try SWI-Prolog in your browser (WASM)</a><br>
625 </tr>
626 </table>
627|}),
628 html(div(id('cta-github'),
629 [ a([ class('github-button'), id('github-star'),
630 href('https://github.com/SWI-Prolog/swipl-devel'),
631 'data-color-scheme'('no-preference: light; \c
632 light: light; dark: dark;'),
633 'data-size'(large),
634 'data-show-count'(true),
635 'aria-label'('Star SWI-Prolog/swipl-devel on GitHub')
636 ], 'Star'),
637 a([ class('github-button'), id('github-sponsor'),
638 href('https://github.com/sponsors/SWI-Prolog'),
639 'data-color-scheme'('no-preference: light; \c
640 light: light; dark: dark;'),
641 'data-size'(large),
642 'data-icon'('octicon-heart'),
643 'data-show-count'(true),
644 'aria-label'('Sponsor @SWI-Prolog on GitHub')
645 ], 'Sponsor')
646 ])).
653enhanced_search_area -->
654 { http_link_to_id(plweb_search, [], Action) },
655 html({|html(Action)||
656 <div id='enhanced-search-container'>
657 <div>
658 <span class='lbl'>SEARCH DOCUMENTATION:</span>
659 <form id="search-form-enhanced" action="Action">
660 <input name="for" type='text' id="forenhanced">
661 <input type="image" src="/icons/go.png" alt='Search'>
662 </form>
663 </div>
664 </div>|}),
665 searchbox_script(forenhanced).
666
667
668page_script(Options) -->
669 { option(object(wiki('commercial/index.md')), Options) },
670 js_script({|javascript||
671function toCollapsible(id)
672{ const c = document.getElementById(id);
673 const elems = [];
674 let divs = [];
675
676 function hlevel(node)
677 { return parseInt(node.tagName.substring(1,2));
678 }
679
680 function expand(ev) {
681 const closed = [];
682 const el = ev.target.parentElement; // The collapsible
683 const ex = el.parentElement.querySelectorAll(':scope > .collapsible:not(.collapsed)');
684 const duration = 200;
685
686 for (const e of ex ) {
687 if ( duration ) {
688 const h = e.clientHeight;
689 e.animate([ { opacity: 1, height: h },
690 { opacity: 0, height: 0 }
691 ],
692 { duration: duration,
693 iterationa: 1
694 });
695 }
696 e.classList.add("collapsed");
697 closed.push(e);
698 }
699 if ( closed.indexOf(el) == -1 ) {
700 const detail = ev.target.nextSibling;
701 el.classList.remove("collapsed");
702 if ( duration ) {
703 const h = detail.clientHeight;
704 detail.animate([ { opacity: 0, height: 0 },
705 { opacity: 1, height: h }
706 ],
707 { duration: 500,
708 iterationa: 1
709 });
710 }
711 }
712 }
713
714 for (const child of c.childNodes) {
715 elems.push(child);
716 }
717
718 for (const child of elems) {
719 if ( /^h[1-4]$/i.test(child.tagName) ) {
720 const level = hlevel(child);
721
722 while ( divs.length > 0 &&
723 divs[0].level >= level ) {
724 divs.shift();
725 }
726
727 const text = child.textContent;
728 if ( /^[QO][:]/.test(text) ) {
729 const div = document.createElement("div");
730 const detail = document.createElement("div");
731
732 if ( /^[O][:]/.test(text) )
733 child.textContent = text.replace(/^O[:] */, "");
734
735 div.classList.add("collapsible", "collapsed");
736 if ( divs.length > 0 )
737 divs[0].div.appendChild(div);
738 else
739 c.insertBefore(div, child);
740 child.classList.add("summary");
741 detail.classList.add("detail");
742 child.addEventListener("click", expand);
743 div.appendChild(child);
744 div.appendChild(detail);
745
746 divs.unshift({level:level, div:detail});
747 }
748 } else if ( divs.length > 0 ) {
749 divs[0].div.appendChild(child);
750 }
751 }
752}
753
754toCollapsible("contents");
755 |}),
756 html({|html||
757<style>
758 .collapsible > .detail > .collapsible {
759 margin-left: 3ex;
760 }
761 .collapsible.collapsed .detail {
762 display: none;
763 }
764 .collapsible.collapsed .summary::before {
765 content: "\0027A4 ";
766 margin-right: 1ex;
767 color: yellow;
768 }
769 .collapsible:not(.collapsed) .summary::before {
770 content: "\002B9F ";
771 margin-right: 1ex;
772 color: yellow;
773 }
774 .collapsible.collapsed .summary:hover {
775 text-decoration: underline;
776 cursor: pointer;
777 }
778 .summary {
779 margin: 0px;
780 }
781 .detail p {
782 margin-top: 0px;
783 }
784 .detail {
785 overflow-y: hidden;
786 }
787</style>
788 |}).
789page_script(_) -->
790 []