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:- 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, []).
 user:body(+Style, +Body)//
Provide the page skin.
   61:- multifile
   62	user:body//2,
   63	plweb:page_title//1,
   64	html_write:html_header_hook/1.   65
   66html_write:html_header_hook(_) :-
   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])).
 page_style(+Style, -Options) is semidet
True if Style is an `object page' and Obj is the object.
  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(_),		   []).
 outer_container(+Content, +Options)//
Display a typical page including headers and footers.
  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).
 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.
  152:- multifile
  153	prolog:doc_page_header//2,
  154	prolog:doc_links//2.  155
  156prolog:doc_page_header(_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		  ]).
 upper_header(+Options)//
Emit the small blue header with Did You Know? and search box
  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			  ])))).
 plweb_search(+Request)
HTTP Handler to search the Prolog website.
  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				      ])).
 searchbox_script(+Tag)//
Emits the script tag for the searchbox
  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|})]).
 tag_line_area//
Emit the Owl logo and tagline area (Robust, mature, free. Prolog for the real world)
  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		 ])).
 title_area(+Style)
  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]).
 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
  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(_).
 swi_logo//
Embed the SWI-Prolog logo.
  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		   ], []))).
 menubar(+Style)// is semidet
Emits a menubar. Style is the page style
  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
  429menu([], _) --> !.
  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
  458submenu(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
  469submenu_label(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
  478menu(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 = '/'		% HOME
  579	    ;	RetURL = ReqURL
  580	    ),
  581	    http_link_to_id(plweb_login_page,
  582			    ['openid.return_to'(RetURL)], LoginURL)
  583	).
 blurb//
Emit the blurb
  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	     |}).
 cta_area//
Emit the Call To Action - the 3 big buttons on homepage
  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://wasm.swi-prolog.org/wasm/shell"
  623	     style="font-size: 60%">
  624	   &#128293; 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		 ])).
 enhanced_search_area//
Emit the large size search area at bottom of home page
  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	[]