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