View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@cs.vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (C): 2009-2024, VU University Amsterdam
    7			      SWI-Prolog Solutions b.v.
    8
    9    This program is free software; you can redistribute it and/or
   10    modify it under the terms of the GNU General Public License
   11    as published by the Free Software Foundation; either version 2
   12    of the License, or (at your option) any later version.
   13
   14    This program is distributed in the hope that it will be useful,
   15    but WITHOUT ANY WARRANTY; without even the implied warranty of
   16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   17    GNU General Public License for more details.
   18
   19    You should have received a copy of the GNU General Public
   20    License along with this library; if not, write to the Free Software
   21    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   22
   23    As a special exception, if you link this library with other files,
   24    compiled with a Free Software compiler, to produce an executable, this
   25    library does not by itself cause the resulting executable to be covered
   26    by the GNU General Public License. This exception does not however
   27    invalidate any other reasons why the executable file might be covered by
   28    the GNU General Public License.
   29*/
   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, []).
 user:body(+Style, +Body)//
Provide the page skin.
   64:- multifile
   65	user:body//2,
   66	plweb:page_title//1,
   67	html_write:html_header_hook/1.   68
   69html_write:html_header_hook(_) :-
   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])).
 page_style(+Style, -Options) is semidet
True if Style is an `object page' and Obj is the object.
  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(_),		   []).
 outer_container(+Content, +Options)//
Display a typical page including headers and footers.
  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).
 prolog:doc_page_header(+File, +Options)// is det
prolog:doc_links(+Directory, +Options)// is det
prolog:doc_file_title(+Title, +File, +Options)// is det
Called to render the PlDoc page header and link menu. We kill both.
  154:- multifile
  155	prolog:doc_page_header//2,
  156	prolog:doc_links//2.  157
  158prolog:doc_page_header(_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		  ]).
 upper_header(+Options)//
Emit the small blue header with Did You Know? and search box
  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			  ])))).
 plweb_search(+Request)
HTTP Handler to search the Prolog website.
  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				      ])).
 searchbox_script(+Tag)//
Emits the script tag for the searchbox
  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|})]).
 tag_line_area//
Emit the Owl logo and tagline area (Robust, mature, free. Prolog for the real world)
  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		 ])).
 title_area(+Style)
  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]).
 todays_logo(-File:atom, -AltText:atom) is det
succeeds if File is the relative name of the appropriate version of the swi-Prolog logo for the day and AltText is the alt text
  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(_).
 swi_logo//
Embed the SWI-Prolog logo.
  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		   ], []))).
 menubar(+Style)// is semidet
Emits a menubar. Style is the page style
  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
  431menu([], _) --> !.
  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
  460submenu(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
  471submenu_label(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
  480menu(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       [ 'Forum & mailing list'= 'https://swi-prolog.discourse.group',
  545	 'Blog'                = '/blog',
  546	 'News'                = '/news/archive',
  547	 'Report a bug'	       = '/bug.html',
  548	 'Submit a patch'      = '/howto/SubmitPatch.html',
  549	 'Submit an add-on'    = '/howto/Pack.html',
  550	 'Roadmap (on GitHub)' = 'https://github.com/SWI-Prolog/roadmap',
  551	 'External links'      = '/Links.html',
  552	 'Contributing'        = '/contributing.html',
  553	 'Code of Conduct'     = '/Code-of-Conduct.html',
  554	 'Contributors'        = '/Contributors.html',
  555	 'SWI-Prolog items'    = '/loot.html'
  556       ],
  557       'Commercial' = '/commercial/index.html',
  558       'Wiki' =
  559       [ LoginLabel            = LoginURL,
  560	 'Edit this page'      = EditHREF,
  561	 'View changes'	       = '/wiki/changes',
  562	 'Sandbox'             = '/wiki/sandbox',
  563	 'Wiki help'           = '/wiki/',
  564	 'All tags'            = '/list-tags'
  565       ]
  566     ]) :-
  567	http_current_request(Request),
  568	memberchk(request_uri(ReqURL), Request),
  569	(   functor(Style, wiki, _)
  570	->  http_link_to_id(wiki_edit,
  571			    [location(ReqURL)], EditHREF)
  572	;   EditHREF = (-)
  573	),
  574	(   site_user_logged_in(_)
  575	->  LoginLabel = 'Logout',
  576	    http_link_to_id(logout, ['openid.return_to'(ReqURL)], LoginURL)
  577	;   LoginLabel = 'Login',
  578	    (	http_link_to_id(logout, [], ReqURL)
  579	    ->	RetURL = '/'		% HOME
  580	    ;	RetURL = ReqURL
  581	    ),
  582	    http_link_to_id(plweb_login_page,
  583			    ['openid.return_to'(RetURL)], LoginURL)
  584	).
 blurb//
Emit the blurb
  590blurb -->
  591	html({|html||
  592    <div id="blurb">
  593      <div>
  594	 SWI-Prolog offers a comprehensive free Prolog environment.
  595	 Since its start in 1987, SWI-Prolog development has been driven
  596	 by the needs of real world applications. SWI-Prolog is widely
  597	 used in research and education as well as commercial applications.
  598	 Join over a million users who have downloaded SWI-Prolog.
  599	 <a href="/features.html">more ...</a>
  600      </div>
  601    </div>
  602	     |}).
 cta_area//
Emit the Call To Action - the 3 big buttons on homepage
  608cta_area -->
  609	html({|html||
  610    <table id='cta-container'>
  611      <tr>
  612	<td style="text-align:left; vertical-align: top">
  613	   <a href="Download.html">Download SWI-Prolog</a>
  614	<td style="text-align:center; vertical-align: top">
  615	   <a href="GetStarted.html">Get Started</a>
  616	<td style="text-align:right; white-space: nowrap; vertical-align: top">
  617	   <a href="http://swish.swi-prolog.org">
  618	   Try SWI-Prolog online (SWISH) </a><br>
  619	   <a href="http://wasm.swi-prolog.org/wasm/shell"
  620	     style="font-size: 60%">
  621	   &#128293; Try SWI-Prolog in your browser (WASM)</a><br>
  622      </tr>
  623    </table>
  624|}),
  625	github_actions([star,sponsor]).
 github_actions(+Buttons)// is det
Emit the github star and sponsor buttons.
  631github_actions(Which) -->
  632	html_post(head,
  633		  script([ defer(defer), async(async),
  634			   src('https://buttons.github.io/buttons.js')
  635			 ], [])),
  636	html(div(class('github-actions'),
  637		 \sequence(github_action_button, Which))).
  638
  639github_action_button(star) -->
  640	html(a([ class('github-button'), id('github-star'),
  641		 href('https://github.com/SWI-Prolog/swipl-devel'),
  642		 'data-color-scheme'('no-preference: light; \c
  643		 light: light; dark: dark;'),
  644		 'data-size'(large),
  645		 'data-show-count'(true),
  646		 'aria-label'('Star SWI-Prolog/swipl-devel on GitHub')
  647	       ], 'Star')).
  648github_action_button(sponsor) -->
  649	html(a([ class('github-button'), id('github-sponsor'),
  650		 href('https://github.com/sponsors/SWI-Prolog'),
  651		 'data-color-scheme'('no-preference: light; \c
  652		 light: light; dark: dark;'),
  653		 'data-size'(large),
  654		 'data-icon'('octicon-heart'),
  655		 'data-show-count'(true),
  656		 'aria-label'('Sponsor @SWI-Prolog on GitHub')
  657	       ], 'Sponsor')).
 page_script(+Options)//
Add script for specific pages based on the object displayed. Currently only deals with the commercial page.
  664page_script(Options) -->
  665	{ option(object(wiki('commercial/index.md')), Options) },
  666	js_script({|javascript||
  667function toCollapsible(id)
  668{ const c = document.getElementById(id);
  669  const elems = [];
  670  let divs = [];
  671
  672  function hlevel(node)
  673  { return parseInt(node.tagName.substring(1,2));
  674  }
  675
  676  function expand(ev) {
  677    const closed = [];
  678    const el = ev.target.parentElement;  // The collapsible
  679    const ex = el.parentElement.querySelectorAll(':scope > .collapsible:not(.collapsed)');
  680    const duration = 200;
  681
  682    for (const e of ex ) {
  683      if ( duration ) {
  684        const h = e.clientHeight;
  685	e.animate([ { opacity: 1, height: h },
  686		    { opacity: 0, height: 0 }
  687		  ],
  688		  { duration: duration,
  689		    iterationa: 1
  690		  });
  691      }
  692      e.classList.add("collapsed");
  693      closed.push(e);
  694    }
  695    if ( closed.indexOf(el) == -1 ) {
  696      const detail = ev.target.nextSibling;
  697      el.classList.remove("collapsed");
  698      if ( duration ) {
  699	const h = detail.clientHeight;
  700	detail.animate([ { opacity: 0, height: 0 },
  701			 { opacity: 1, height: h }
  702		       ],
  703		       { duration: 500,
  704			 iterationa: 1
  705		       });
  706      }
  707    }
  708  }
  709
  710  for (const child of c.childNodes) {
  711    elems.push(child);
  712  }
  713
  714  for (const child of elems) {
  715    if ( /^h[1-4]$/i.test(child.tagName) ) {
  716      const level = hlevel(child);
  717
  718      while ( divs.length > 0 &&
  719	      divs[0].level >= level ) {
  720	divs.shift();
  721      }
  722
  723      const text = child.textContent;
  724      if ( /^[QO][:]/.test(text) ) {
  725	const div = document.createElement("div");
  726	const detail = document.createElement("div");
  727
  728	if ( /^[O][:]/.test(text) )
  729	  child.textContent = text.replace(/^O[:] */, "");
  730
  731	div.classList.add("collapsible", "collapsed");
  732	if ( divs.length > 0 )
  733	  divs[0].div.appendChild(div);
  734	else
  735	  c.insertBefore(div, child);
  736	child.classList.add("summary");
  737	detail.classList.add("detail");
  738	child.addEventListener("click", expand);
  739	div.appendChild(child);
  740	div.appendChild(detail);
  741
  742	divs.unshift({level:level, div:detail});
  743      }
  744    } else if ( divs.length > 0 ) {
  745      divs[0].div.appendChild(child);
  746    }
  747  }
  748}
  749
  750toCollapsible("contents");
  751		  |}),
  752	html({|html||
  753<style>
  754  .collapsible > .detail > .collapsible {
  755      margin-left: 3ex;
  756  }
  757  .collapsible.collapsed .detail {
  758      display: none;
  759  }
  760  .collapsible.collapsed .summary::before {
  761      content: "\0027A4 ";
  762      margin-right: 1ex;
  763      color: yellow;
  764  }
  765  .collapsible:not(.collapsed) .summary::before {
  766      content: "\002B9F ";
  767      margin-right: 1ex;
  768      color: yellow;
  769  }
  770  .collapsible.collapsed .summary:hover {
  771      text-decoration: underline;
  772      cursor: pointer;
  773  }
  774  .summary {
  775      margin: 0px;
  776  }
  777  .detail p {
  778      margin-top: 0px;
  779  }
  780  .detail {
  781      overflow-y: hidden;
  782  }
  783</style>
  784	     |}).
  785page_script(_) -->
  786	[]