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       [ '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 = '/'		% HOME
  581	    ;	RetURL = ReqURL
  582	    ),
  583	    http_link_to_id(plweb_login_page,
  584			    ['openid.return_to'(RetURL)], LoginURL)
  585	).
 blurb//
Emit the blurb
  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	     |}).
 cta_area//
Emit the Call To Action - the 3 big buttons on homepage
  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	   &#128293; Try SWI-Prolog in your browser (WASM)</a><br>
  623      </tr>
  624    </table>
  625|}),
  626	github_actions([star,sponsor]).
 github_actions(+Buttons)// is det
Emit the github star and sponsor buttons.
  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')).
 page_script(+Options)//
Add script for specific pages based on the object displayed. Currently only deals with the commercial page.
  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	[]