View source with formatted comments or as raw
    1/*  Part of SWISH
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2014-2018, VU University Amsterdam
    7			      CWI, Amsterdam
    8    All rights reserved.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(swish_page,
   37	  [ swish_reply/2,			% +Options, +Request
   38	    swish_reply_resource/1,		% +Request
   39	    swish_page//1,			% +Options
   40
   41	    swish_navbar//1,			% +Options
   42	    swish_content//1,			% +Options
   43
   44	    pengine_logo//1,			% +Options
   45	    swish_logo//1,			% +Options
   46
   47	    swish_resources//0,
   48	    swish_js//0,
   49	    swish_css//0
   50	  ]).   51:- use_module(library(http/http_open)).   52:- use_module(library(http/http_dispatch)).   53:- use_module(library(http/http_parameters)).   54:- use_module(library(http/http_header)).   55:- use_module(library(http/html_write)).   56:- use_module(library(http/js_write)).   57:- use_module(library(http/json)).   58:- use_module(library(http/http_json)).   59:- use_module(library(http/http_path)).   60:- if(exists_source(library(http/http_ssl_plugin))).   61:- use_module(library(http/http_ssl_plugin)).   62:- endif.   63:- use_module(library(debug)).   64:- use_module(library(time)).   65:- use_module(library(lists)).   66:- use_module(library(option)).   67:- use_module(library(uri)).   68:- use_module(library(error)).   69:- use_module(library(http/http_client)).   70
   71:- use_module(config).   72:- use_module(help).   73:- use_module(search).   74:- use_module(chat).   75:- use_module(authenticate).   76:- use_module(pep).   77
   78/** <module> Provide the SWISH application as Prolog HTML component
   79
   80This library provides the SWISH page  and   its  elements as Prolog HTML
   81grammer rules. This allows for server-side   generated  pages to include
   82swish or parts of swish easily into a page.
   83*/
   84
   85http:location(pldoc, swish(pldoc), [priority(100)]).
   86
   87:- http_handler(swish(.), swish_reply([]), [id(swish), prefix]).   88:- http_handler('/sitemap.xml', http_reply_file('sitemap.xml', []),[]).   89:- http_handler('/robots.txt', http_reply_file('robots.txt', []),[]).   90
   91:- multifile
   92	swish_config:logo//1,
   93	swish_config:title//1,
   94	swish_config:source_alias/2,
   95	swish_config:reply_page/1,
   96	swish_config:li_login_button//1.   97
   98%%	swish_reply(+Options, +Request)
   99%
  100%	HTTP handler to reply the  default   SWISH  page.  Processes the
  101%	following parameters:
  102%
  103%	  - code(Code)
  104%	  Use Code as initial code. Code is either an HTTP url or
  105%	  - url(URL)
  106%	  Download code from URL.  As code(URL), but makes the browser
  107%	  download the source rather than the server.
  108%	  - background(Code)
  109%	  Similar to Code, but not displayed in the editor.
  110%	  - examples(Code)
  111%	  Provide examples. Each example starts with ?- at the beginning
  112%	  of a line.
  113%	  - q(Query)
  114%	  Use Query as the initial query.
  115%	  - show_beware(Boolean)
  116%	  Control showing the _beware limited edition_ warning.
  117%	  - preserve_state(Boolean)
  118%	  If `true`, save state on unload and restore old state on load.
  119
  120swish_reply(Options, Request) :-
  121	(   option(identity(_), Options)
  122	->  Options2 = Options
  123	;   authenticate(Request, Auth),
  124	    Options2 = [identity(Auth)|Options]
  125	),
  126	swish_reply2(Options2, Request).
  127
  128swish_reply2(Options, Request) :-
  129	option(method(Method), Request),
  130	Method \== get, Method \== head, !,
  131	swish_rest_reply(Method, Request, Options).
  132swish_reply2(_, Request) :-
  133	swish_reply_resource(Request), !.
  134swish_reply2(Options, Request) :-
  135	swish_reply_config(Request, Options), !.
  136swish_reply2(SwishOptions, Request) :-
  137	Params = [ code(_,	  [optional(true)]),
  138		   url(_,	  [optional(true)]),
  139		   label(_,	  [optional(true)]),
  140		   show_beware(_, [optional(true)]),
  141		   background(_,  [optional(true)]),
  142		   examples(_,    [optional(true)]),
  143		   q(_,           [optional(true)]),
  144		   format(_,      [oneof([swish,raw,json]), default(swish)])
  145		 ],
  146	http_parameters(Request, Params),
  147	params_options(Params, Options0),
  148	add_show_beware(Options0, Options1),
  149	add_preserve_state(Options1, Options2),
  150	merge_options(Options2, SwishOptions, Options3),
  151	source_option(Request, Options3, Options4),
  152	option(format(Format), Options4),
  153	swish_reply3(Format, Options4).
  154
  155swish_reply3(raw, Options) :-
  156	option(code(Code), Options), !,
  157	format('Content-type: text/x-prolog~n~n'),
  158	format('~s', [Code]).
  159swish_reply3(json, Options) :-
  160	option(code(Code), Options), !,
  161	option(meta(Meta), Options, _{}),
  162	option(chat_count(Count), Options, 0),
  163	reply_json_dict(json{data:Code, meta:Meta, chats:_{total:Count}}).
  164swish_reply3(_, Options) :-
  165	swish_config:reply_page(Options), !.
  166swish_reply3(_, Options) :-
  167	reply_html_page(
  168	    swish(main),
  169	    \swish_title(Options),
  170	    \swish_page(Options)).
  171
  172params_options([], []).
  173params_options([H0|T0], [H|T]) :-
  174	arg(1, H0, Value), nonvar(Value), !,
  175	functor(H0, Name, _),
  176	H =.. [Name,Value],
  177	params_options(T0, T).
  178params_options([_|T0], T) :-
  179	params_options(T0, T).
  180
  181%!	add_show_beware(+Options0, -Option) is det.
  182%
  183%	Add show_beware(false) when called with code, query or examples.
  184%	These are dedicated calls that do not justify this message.
  185
  186add_show_beware(Options0, Options) :-
  187	implicit_no_show_beware(Options0), !,
  188	Options = [show_beware(false)|Options0].
  189add_show_beware(Options, Options).
  190
  191implicit_no_show_beware(Options) :-
  192	option(show_beware(_), Options), !,
  193	fail.
  194implicit_no_show_beware(Options) :-
  195	\+ option(format(swish), Options), !,
  196	fail.
  197implicit_no_show_beware(Options) :-
  198	option(code(_), Options).
  199implicit_no_show_beware(Options) :-
  200	option(q(_), Options).
  201implicit_no_show_beware(Options) :-
  202	option(examples(_), Options).
  203implicit_no_show_beware(Options) :-
  204	option(background(_), Options).
  205
  206%!	add_preserve_state(+Options0, -Option) is det.
  207%
  208%	Add preserve_state(false) when called with code.
  209
  210add_preserve_state(Options0, Options) :-
  211	option(preserve_state(_), Options0), !,
  212	Options = Options0.
  213add_preserve_state(Options0, Options) :-
  214	option(code(_), Options0), !,
  215	Options = [preserve_state(false)|Options0].
  216add_preserve_state(Options, Options).
  217
  218
  219%%	source_option(+Request, +Options0, -Options)
  220%
  221%	If the data was requested  as   '/Alias/File',  reply using file
  222%	Alias(File).
  223
  224source_option(_Request, Options0, Options) :-
  225	option(code(Code), Options0),
  226	option(format(swish), Options0), !,
  227	(   uri_is_global(Code)
  228	->  Options = [url(Code),st_type(external)|Options0]
  229	;   Options = Options0
  230	).
  231source_option(_Request, Options0, Options) :-
  232	option(url(_), Options0),
  233	option(format(swish), Options0), !,
  234	Options = [st_type(external),download(browser)|Options0].
  235source_option(Request, Options0, Options) :-
  236	source_file(Request, File, Options0), !,
  237	option(path(Path), Request),
  238	(   source_data(File, String, Options1)
  239	->  append([ [code(String), url(Path), st_type(filesys)],
  240		     Options1,
  241		     Options0
  242		   ], Options)
  243	;   http_404([], Request)
  244	).
  245source_option(_, Options, Options).
  246
  247%%	source_file(+Request, -File, +Options) is semidet.
  248%
  249%	File is the file associated with a SWISH request.  A file is
  250%	associated if _path_info_ is provided.  If the file does not
  251%	exist, an HTTP 404 exception is returned.  Options:
  252%
  253%	  - alias(-Alias)
  254%	    Get the swish_config:source_alias/2 Alias name that
  255%	    was used to find File.
  256
  257source_file(Request, File, Options) :-
  258	option(path_info(PathInfo), Request), !,
  259	PathInfo \== 'index.html',
  260	(   path_info_file(PathInfo, File, Options)
  261	->  true
  262	;   http_404([], Request)
  263	).
  264
  265path_info_file(PathInfo, Path, Options) :-
  266	sub_atom(PathInfo, B, _, A, /),
  267	sub_atom(PathInfo, 0, B, _, Alias),
  268	sub_atom(PathInfo, _, A, 0, File),
  269	catch(swish_config:source_alias(Alias, AliasOptions), E,
  270	      (print_message(warning, E), fail)),
  271	Spec =.. [Alias,File],
  272	http_safe_file(Spec, []),
  273	absolute_file_name(Spec, Path,
  274			   [ access(read),
  275			     file_errors(fail)
  276			   ]),
  277	confirm_access(Path, AliasOptions), !,
  278	option(alias(Alias), Options, _).
  279
  280source_data(Path, Code, [title(Title), type(Ext), meta(Meta)]) :-
  281	setup_call_cleanup(
  282	    open(Path, read, In, [encoding(utf8)]),
  283	    read_string(In, _, Code),
  284	    close(In)),
  285	source_metadata(Path, Code, Meta),
  286	file_base_name(Path, File),
  287	file_name_extension(Title, Ext, File).
  288
  289%%	source_metadata(+Path, +Code, -Meta:dict) is det.
  290%
  291%	Obtain meta information about a local  source file. Defined meta
  292%	info is:
  293%
  294%	  - last_modified:Time
  295%	  Last modified stamp of the file.  Always present.
  296%	  - loaded:true
  297%	  Present of the file is a loaded source file
  298%	  - modified_since_loaded:true
  299%	  Present if the file loaded, has been edited, but not
  300%	  yet reloaded.
  301
  302source_metadata(Path, Code, Meta) :-
  303	findall(Name-Value, source_metadata(Path, Code, Name, Value), Pairs),
  304	dict_pairs(Meta, meta, Pairs).
  305
  306source_metadata(Path, _Code, path, Path).
  307source_metadata(Path, _Code, last_modified, Modified) :-
  308	time_file(Path, Modified).
  309source_metadata(Path, _Code, loaded, true) :-
  310	source_file(Path).
  311source_metadata(Path, _Code, modified_since_loaded, true) :-
  312	source_file_property(Path, modified(ModifiedWhenLoaded)),
  313	time_file(Path, Modified),
  314	ModifiedWhenLoaded \== Modified.
  315source_metadata(Path, _Code, module, Module) :-
  316	file_name_extension(_, Ext, Path),
  317	user:prolog_file_type(Ext, prolog),
  318	xref_public_list(Path, _, [module(Module)]).
  319
  320confirm_access(Path, Options) :-
  321	option(if(Condition), Options), !,
  322	must_be(oneof([loaded]), Condition),
  323	eval_condition(Condition, Path).
  324confirm_access(_, _).
  325
  326eval_condition(loaded, Path) :-
  327	source_file(Path).
  328
  329%%	swish_reply_resource(+Request) is semidet.
  330%
  331%	Serve /swish/Resource files.
  332
  333swish_reply_resource(Request) :-
  334	option(path_info(Info), Request),
  335	resource_prefix(Prefix),
  336	sub_atom(Info, 0, _, _, Prefix), !,
  337	http_reply_file(swish_web(Info), [], Request).
  338
  339resource_prefix('css/').
  340resource_prefix('help/').
  341resource_prefix('form/').
  342resource_prefix('icons/').
  343resource_prefix('js/').
  344resource_prefix('node_modules/').
  345
  346%%	swish_page(+Options)//
  347%
  348%	Generate the entire SWISH default page.
  349
  350swish_page(Options) -->
  351	swish_navbar(Options),
  352	swish_content(Options).
  353
  354%%	swish_navbar(+Options)//
  355%
  356%	Generate the swish navigation bar.
  357
  358swish_navbar(Options) -->
  359	swish_resources,
  360	html(div([id('navbarhelp'),style('height:40px;margin: 10px 5px;text-align:center')], %;line-height: 40px')],
  361	[div([class('container'),style('display: flex; height: 100px;')],[
  362	  div([style('width: 5%;')],[
  363	    a([href('https://ml.unife.it'),target('_blank')],
  364	      [img([src('/icons/logo-unife.png'),height(40)])])]),
  365	  div([style('flex-grow 1;')],[span([],[span([style('color:maroon')],['cplint on ']),
  366        span([style('color:darkblue')],['SWI']),
  367        span([style('color:maroon')],['SH']),
  368        ' is a web application for probabilistic logic programming',
  369        &(nbsp), &(nbsp),
  370        a([id('about')],['About']),
  371        &(nbsp), &(nbsp),
  372        a([href('http://friguzzi.github.io/cplint/'),target('_blank')],['Help']),
  373				&(nbsp), &(nbsp),
  374				a([href('http://friguzzi.github.io/liftcover/'),target('_blank')],['LIFTCOVER-Help']),
  375				&(nbsp), &(nbsp),
  376				a([href('http://arnaudfadja.github.io/phil/'),target('_blank')],['PHIL-Help']),
  377										&(nbsp), &(nbsp),
  378	a([href('http://friguzzi.github.io/pascal/'),target('_blank')],['PASCAL-Help']),
  379        &(nbsp), &(nbsp),
  380        a([href('/help/credits.html'),target('_blank')],['Credits']),
  381		&(nbsp), &(nbsp),
  382        a([id('dismisslink'),href('')],['Dismiss']),
  383p(['Latest: ',
  384a([href('/e/liftcover/liftcover_examples.swinb')],['Threads and Python in LIFTCOVER']),', ',
  385a([href('/e/course.swinb')],['course']),', ',
  386a([href('/e/phil_examples.swinb')],['PHIL examples']),', ',
  387a([href('http://ml.unife.it/plp-book/'),target('_blank')],["book"])
  388])])
  389       ])])]))
  390        ,
  391
  392	html(nav([ class([navbar, 'navbar-default']),
  393		   role(navigation)
  394		 ],
  395		 [ div(class('navbar-header'),
  396		       [ \collapsed_button,
  397			 \swish_logos(Options)
  398		       ]),
  399		   div([ class([collapse, 'navbar-collapse']),
  400			 id(navbar)
  401		       ],
  402		       [ ul([class([nav, 'navbar-nav', menubar])], []),
  403			 ul([class([nav, 'navbar-nav', 'navbar-right'])],
  404			    [ li(\notifications(Options)),
  405			      li(\search_box(Options)),
  406			      \li_login_button(Options),
  407			      li(\broadcast_bell(Options)),
  408			      li(\updates(Options))
  409			    ])
  410		       ])
  411		 ])).
  412
  413li_login_button(Options) -->
  414	swish_config:li_login_button(Options).
  415li_login_button(_Options) -->
  416	[].
  417
  418collapsed_button -->
  419	html(button([type(button),
  420		     class('navbar-toggle'),
  421		     'data-toggle'(collapse),
  422		     'data-target'('#navbar')
  423		    ],
  424		    [ span(class('sr-only'), 'Toggle navigation'),
  425		      span(class('icon-bar'), []),
  426		      span(class('icon-bar'), []),
  427		      span(class('icon-bar'), [])
  428		    ])).
  429
  430updates(_Options) -->
  431	html([ a(id('swish-updates'), []) ]).
  432
  433
  434		 /*******************************
  435		 *	      BRANDING		*
  436		 *******************************/
  437
  438%!	swish_title(+Options)// is det.
  439%
  440%	Emit the HTML header options dealing with the title and shortcut
  441%	icons.  This can be hooked using swish_config:title//1.
  442
  443swish_title(Options) -->
  444	swish_config:title(Options), !.
  445swish_title(_Options) -->
  446	html([ title('cplint on SWISH -- Probabilistic Logic Programming'),
  447	      link([ rel('shortcut icon'),
  448		     href('/icons/favicon.ico')
  449		   ]),
  450	      link([ rel('apple-touch-icon'),
  451		     href('/icons/cplint-touch-icon.png')
  452		   ]),
  453              meta([name('msvalidate.01'),
  454                content('A9C78799EC9EDC7CE041CB7CD8E2D76E')])
  455	    ]).
  456
  457%!	swish_logos(+Options)// is det.
  458%
  459%	Emit the navbar branding logos at   the  top-left. Can be hooked
  460%	using swish_config:swish_logos//1.
  461
  462swish_logos(Options) -->
  463	swish_config:logo(Options), !.
  464swish_logos(Options) -->
  465	pengine_logo(Options),
  466	swish_logo(Options).
  467
  468%!	swish_config:logo(+Options)// is semidet.
  469%
  470%	Hook  to  include  the  top-left    logos.   The  default  calls
  471%	pengine_logo//1 and swish_logo//1.  The   implementation  should
  472%	emit     zero     or      more       <a>      elements.      See
  473%	`config_available/branding.pl` for an example.
  474
  475%!	pengine_logo(+Options)// is det.
  476%!	swish_logo(+Options)// is det.
  477%
  478%	Emit an <a> element that provides a   link to Pengines and SWISH
  479%	on this server. These may be called from swish_config:logo//1 to
  480%	include the default logos.
  481
  482pengine_logo(_Options) -->
  483	{ http_absolute_location(root(.), HREF, [])
  484	},
  485	html(a([href(HREF), class('pengine-logo')], &(nbsp))).
  486swish_logo(_Options) -->
  487	{ http_absolute_location(swish(.), HREF, [])
  488	},
  489	html(a([href(HREF), class('swish-logo')], &(nbsp))).
  490
  491
  492		 /*******************************
  493		 *	     CONTENT		*
  494		 *******************************/
  495
  496%%	swish_content(+Options)//
  497%
  498%	Generate the SWISH editor, Prolog output  area and query editor.
  499%	Options processed:
  500%
  501%	  - source(HREF)
  502%	  Load initial source from HREF
  503%	  - chat_count(Count)
  504%	  Indicate the presense of Count chat messages
  505
  506swish_content(Options) -->
  507	{ document_type(Type, Options)
  508	},
  509	swish_resources,
  510	swish_config_hash(Options),
  511	swish_options(Options),
  512	html(div([id(content), class([container, 'tile-top'])],
  513		 [ div([class([tile, horizontal]), 'data-split'('50%')],
  514		       [ div([ class([editors, tabbed])
  515			     ],
  516			     [ \source(Type, Options),
  517			       \notebooks(Type, Options)
  518			     ]),
  519			 div([class([tile, vertical]), 'data-split'('70%')],
  520			     [ div(class('prolog-runners'), []),
  521			       div(class('prolog-query'), \query(Options))
  522			     ])
  523		       ]),
  524		   \background(Options),
  525		   \examples(Options)
  526		 ])).
  527
  528
  529%%	swish_config_hash(+Options)//
  530%
  531%	Set `window.swish.config_hash` to a  hash   that  represents the
  532%	current configuration. This is used by   config.js  to cache the
  533%	configuration in the browser's local store.
  534
  535swish_config_hash(Options) -->
  536	{ swish_config_hash(Hash, Options) },
  537	js_script({|javascript(Hash)||
  538		   window.swish = window.swish||{};
  539		   window.swish.config_hash = Hash;
  540		   |}).
  541
  542
  543%!	swish_options(+Options)//
  544%
  545%	Emit additional options. This is  similar   to  config,  but the
  546%	config object is big and stable   for a particular SWISH server.
  547%	The options are set per session.
  548
  549swish_options(Options) -->
  550	js_script({|javascript||
  551		   window.swish = window.swish||{};
  552		   window.swish.option = window.swish.option||{};
  553		  |}),
  554	swish_options([show_beware, preserve_state], Options).
  555
  556swish_options([], _) --> [].
  557swish_options([H|T], Options) -->
  558	swish_option(H, Options),
  559	swish_options(T, Options).
  560
  561swish_option(Name, Options) -->
  562	{ Opt =.. [Name,Val],
  563	  option(Opt, Options),
  564	  JSVal = @(Val)
  565	}, !,
  566	js_script({|javascript(Name, JSVal)||
  567		   window.swish.option[Name] = JSVal;
  568		   |}).
  569swish_option(_, _) -->
  570	[].
  571
  572%%	source(+Type, +Options)//
  573%
  574%	Associate the source with the SWISH   page. The source itself is
  575%	stored  in  the  textarea  from  which  CodeMirror  is  created.
  576%	Options:
  577%
  578%	  - code(+String)
  579%	  Initial code of the source editor
  580%	  - file(+File)
  581%	  If present and code(String) is present, also associate the
  582%	  editor with the given file.  See storage.pl.
  583%	  - url(+URL)
  584%	  as file(File), but used if the data is loaded from an
  585%	  alias/file path.
  586%	  - title(+Title)
  587%	  Defines the title used for the tab.
  588
  589source(pl, Options) -->
  590	{ (   option(code(Spec), Options)
  591	  ;   option(download(browser), Options)
  592          ),
  593          !,
  594          download_source(Spec, Source, Options),
  595	  phrase(source_data_attrs(Options), Extra),
  596          option(label(Label), Options, 'Program')
  597	},
  598	html(div([ class(['prolog-editor']),
  599		   'data-label'(Label)
  600		 ],
  601		 [ textarea([ class([source,prolog]),
  602			      style('display:none')
  603			    | Extra
  604			    ],
  605			    Source)
  606		 ])).
  607source(_, _) --> [].
  608
  609source_data_attrs(Options) -->
  610	(source_file_data(Options) -> [] ; []),
  611	(source_url_data(Options) -> [] ; []),
  612	(source_download_data(Options) -> [] ; []),
  613	(source_title_data(Options) -> [] ; []),
  614	(source_meta_data(Options) -> [] ; []),
  615	(source_st_type_data(Options) -> [] ; []),
  616	(source_chat_data(Options) -> [] ; []).
  617
  618source_file_data(Options) -->
  619	{ option(file(File), Options) },
  620	['data-file'(File)].
  621source_url_data(Options) -->
  622	{ option(url(URL), Options) },
  623	['data-url'(URL)].
  624source_download_data(Options) -->
  625	{ option(download(Who), Options) },
  626	['data-download'(Who)].
  627source_title_data(Options) -->
  628	{ option(title(File), Options) },
  629	['data-title'(File)].
  630source_st_type_data(Options) -->
  631	{ option(st_type(Type), Options) },
  632	['data-st_type'(Type)].
  633source_meta_data(Options) -->
  634	{ option(meta(Meta), Options), !,
  635	  atom_json_dict(Text, Meta, [])
  636	},
  637	['data-meta'(Text)].
  638source_chat_data(Options) -->
  639	{ option(chat_count(Count), Options),
  640	  atom_json_term(JSON, _{count:Count}, [as(string)])
  641	},
  642	['data-chats'(JSON)].
  643
  644%%	background(+Options)//
  645%
  646%	Associate  the  background  program  (if  any).  The  background
  647%	program is not displayed in  the  editor,   but  is  sent to the
  648%	pengine for execution.
  649
  650background(Options) -->
  651	{ option(background(Spec), Options), !,
  652	  download_source(Spec, Source, Options)
  653	},
  654	html(textarea([ class([source,prolog,background]),
  655			style('display:none')
  656		      ],
  657		      Source)).
  658background(_) --> [].
  659
  660
  661examples(Options) -->
  662	{ option(examples(Examples), Options), !
  663	},
  664	html(textarea([ class([examples,prolog]),
  665			style('display:none')
  666		      ],
  667		      Examples)).
  668examples(_) --> [].
  669
  670
  671query(Options) -->
  672	{ option(q(Query), Options)
  673	}, !,
  674	html(textarea([ class([query,prolog]),
  675			style('display:none')
  676		      ],
  677		      Query)).
  678query(_) --> [].
  679
  680%%	notebooks(+Type, +Options)//
  681%
  682%	We have opened a notebook. Embed the notebook data in the
  683%	left-pane tab area.
  684
  685notebooks(swinb, Options) -->
  686	{ option(code(Spec), Options),
  687	  download_source(Spec, NoteBookText, Options),
  688	  phrase(source_data_attrs(Options), Extra)
  689	},
  690	html(div([ class('notebook'),
  691		   'data-label'('Notebook')		% Use file?
  692		 ],
  693		 [ pre([ class('notebook-data'),
  694			 style('display:none')
  695		       | Extra
  696		       ],
  697		       NoteBookText)
  698		 ])).
  699notebooks(_, _) --> [].
  700
  701%%	download_source(+HREF, -Source, +Options) is det.
  702%
  703%	Download source from a URL.  Options processed:
  704%
  705%	  - timeout(+Seconds)
  706%	    Max time to wait for reading the source.  Default
  707%	    is 10 seconds.
  708%	  - max_length(+Chars)
  709%	    Maximum lenght of the content.  Default is 1 million.
  710%	  - encoding(+Encoding)
  711%	    Encoding used to interpret the text.  Default is UTF-8.
  712%
  713%	@bug: Should try to interpret the encoding from the HTTP
  714%	      header.
  715
  716download_source(_HREF, Source, Options) :-
  717	option(download(browser), Options),
  718	!,
  719        Source = "".
  720download_source(HREF, Source, Options) :-
  721	uri_is_global(HREF), !,
  722	download_href(HREF, Source, Options).
  723download_source(Source0, Source, Options) :-
  724	option(max_length(MaxLen), Options, 1_000_000),
  725	string_length(Source0, Len),
  726	(   Len =< MaxLen
  727	->  Source = Source0
  728	;   format(string(Source),
  729		   '% ERROR: Content too long (max ~D)~n', [MaxLen])
  730	).
  731
  732download_href(HREF, Source, Options) :-
  733	option(timeout(TMO), Options, 10),
  734	option(max_length(MaxLen), Options, 1_000_000),
  735	catch(call_with_time_limit(
  736		  TMO,
  737		  setup_call_cleanup(
  738		      http_open(HREF, In,
  739				[ cert_verify_hook(cert_accept_any)
  740				]),
  741		      read_source(In, MaxLen, Source, Options),
  742		      close(In))),
  743	      E, load_error(E, Source)).
  744
  745read_source(In, MaxLen, Source, Options) :-
  746	option(encoding(Enc), Options, utf8),
  747	set_stream(In, encoding(Enc)),
  748	ReadMax is MaxLen + 1,
  749	read_string(In, ReadMax, Source0),
  750	string_length(Source0, Len),
  751	(   Len =< MaxLen
  752	->  Source = Source0
  753	;   format(string(Source),
  754		   ' % ERROR: Content too long (max ~D)~n', [MaxLen])
  755	).
  756
  757load_error(E, Source) :-
  758	message_to_string(E, String),
  759	format(string(Source), '% ERROR: ~s~n', [String]).
  760
  761%%	document_type(-Type, +Options) is det.
  762%
  763%	Determine the type of document.
  764%
  765%	@arg Type is one of `swinb` or `pl`
  766
  767document_type(Type, Options) :-
  768	(   option(type(Type0), Options)
  769	->  Type = Type0
  770	;   option(meta(Meta), Options),
  771	    file_name_extension(_, Type0, Meta.name),
  772	    Type0 \== ''
  773	->  Type = Type0
  774	;   option(st_type(external), Options),
  775	    option(url(URL), Options),
  776	    file_name_extension(_, Ext, URL),
  777	    ext_type(Ext, Type)
  778	->  true
  779	;   Type = pl
  780	).
  781
  782ext_type(swinb, swinb).
  783
  784
  785		 /*******************************
  786		 *	     RESOURCES		*
  787		 *******************************/
  788
  789%%	swish_resources//
  790%
  791%	Include  SWISH  CSS  and   JavaScript.    This   does   not  use
  792%	html_require//1  because  we  need  to   include  the  JS  using
  793%	RequireJS, which requires a non-standard script element.
  794
  795swish_resources -->
  796	swish_css,
  797	swish_js.
  798
  799swish_js  --> html_post(head, \include_swish_js).
  800swish_css --> html_post(head, \include_swish_css).
  801
  802include_swish_js -->
  803	html(script([],[
  804      '(function(i,s,o,g,r,a,m){i[''GoogleAnalyticsObject'']=r;i[r]=i[r]||function(){
  805       (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  806        m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  807        })(window,document,''script'',''//www.google-analytics.com/analytics.js'',''ga'');
  808
  809        ga(''create'', ''UA-16202613-9'', ''auto'');
  810        ga(''send'', ''pageview'');'])),
  811        html(\['<!-- Global site tag (gtag.js) - Google Analytics -->
  812<script async src="https://www.googletagmanager.com/gtag/js?id=G-GL8L9W5NE7"></script>
  813<script>
  814  window.dataLayer = window.dataLayer || [];
  815  function gtag(){dataLayer.push(arguments);}
  816  gtag(''js'', new Date());
  817
  818  gtag(''config'', ''G-GL8L9W5NE7'');
  819</script>']),
  820        { swish_resource(js, JS),
  821	  swish_resource(rjs, RJS),
  822	  http_absolute_location(swish(js/JS), SwishJS, []),
  823	  http_absolute_location(swish(RJS),   SwishRJS, [])
  824	},
  825	rjs_timeout(JS),
  826	html(script([ src(SwishRJS),
  827		      'data-main'(SwishJS)
  828		    ], [])).
  829
  830rjs_timeout('swish-min') --> !,
  831	js_script({|javascript||
  832// Override RequireJS timeout, until main file is loaded.
  833window.require = { waitSeconds: 0 };
  834		  |}).
  835rjs_timeout(_) --> [].
  836
  837
  838include_swish_css -->
  839	{ swish_resource(css, CSS),
  840	  http_absolute_location(swish(css/CSS), SwishCSS, [])
  841	},
  842	html(link([ rel(stylesheet),
  843		    href(SwishCSS)
  844		  ])).
  845
  846swish_resource(Type, ID) :-
  847	alt(Type, ID, File),
  848	(   File == (-)
  849	;   absolute_file_name(File, _P, [file_errors(fail), access(read)])
  850	), !.
  851
  852alt(js,  'swish-min',     swish_web('js/swish-min.js')) :-
  853	\+ debugging(nominified).
  854alt(js,  'swish',         swish_web('js/swish.js')).
  855alt(css, 'swish-min.css', swish_web('css/swish-min.css')) :-
  856	\+ debugging(nominified).
  857alt(css, 'swish.css',     swish_web('css/swish.css')).
  858alt(rjs, 'js/require.js', swish_web('js/require.js')) :-
  859	\+ debugging(nominified).
  860alt(rjs, 'node_modules/requirejs/require.js', -).
  861
  862
  863		 /*******************************
  864		 *	       REST		*
  865		 *******************************/
  866
  867%%	swish_rest_reply(+Method, +Request, +Options) is det.
  868%
  869%	Handle non-GET requests.  Such requests may be used to modify
  870%	source code.
  871
  872swish_rest_reply(put, Request, Options) :-
  873	merge_options(Options, [alias(_)], Options1),
  874	source_file(Request, File, Options1), !,
  875	option(content_type(String), Request),
  876	http_parse_header_value(content_type, String, Type),
  877	read_data(Type, Request, Data, Meta),
  878	authorized(file(update(File,Meta)), Options1),
  879	setup_call_cleanup(
  880	    open(File, write, Out, [encoding(utf8)]),
  881	    format(Out, '~s', [Data]),
  882	    close(Out)),
  883	reply_json_dict(true).
  884
  885read_data(media(Type,_), Request, Data, Meta) :-
  886	http_json:json_type(Type), !,
  887	http_read_json_dict(Request, Dict),
  888	del_dict(data, Dict, Data, Meta).
  889read_data(media(text/_,_), Request, Data, _{}) :-
  890	http_read_data(Request, Data,
  891		       [ to(string),
  892			 input_encoding(utf8)
  893		       ])