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