aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralexbiehl <alex.biehl@gmail.com>2017-08-27 18:50:16 +0200
committeralexbiehl <alex.biehl@gmail.com>2017-08-27 18:50:16 +0200
commit705dc006595db165e1cb244485cf47bcae02e2d0 (patch)
treee65650d436af87bfd84b4ae65668d14490b98476
parent3a09040a16fb574254d4dc095047ed7b0b7beb19 (diff)
Content search for haddock html doc
-rw-r--r--haddock-api/resources/html/fuse.js9
-rw-r--r--haddock-api/resources/html/index.js381
-rw-r--r--haddock-api/resources/html/preact.js408
-rw-r--r--haddock-api/src/Haddock/Backends/Xhtml.hs6
-rw-r--r--haddock-api/src/Haddock/Utils.hs10
5 files changed, 813 insertions, 1 deletions
diff --git a/haddock-api/resources/html/fuse.js b/haddock-api/resources/html/fuse.js
new file mode 100644
index 00000000..67fabe43
--- /dev/null
+++ b/haddock-api/resources/html/fuse.js
@@ -0,0 +1,9 @@
+/*!
+ * Fuse.js v3.0.4 - Lightweight fuzzy-search (http://fusejs.io)
+ *
+ * Copyright (c) 2012-2017 Kirollos Risk (http://kiro.me)
+ * All Rights Reserved. Apache Software License 2.0
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("Fuse",[],t):"object"==typeof exports?exports.Fuse=t():e.Fuse=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=8)}([function(e,t,n){"use strict";e.exports=function(e){return"[object Array]"===Object.prototype.toString.call(e)}},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),i=n(5),s=n(7),a=n(4),c=function(){function e(t,n){var o=n.location,i=void 0===o?0:o,s=n.distance,c=void 0===s?100:s,h=n.threshold,l=void 0===h?.6:h,u=n.maxPatternLength,f=void 0===u?32:u,v=n.isCaseSensitive,d=void 0!==v&&v,p=n.tokenSeparator,g=void 0===p?/ +/g:p,m=n.findAllMatches,y=void 0!==m&&m,k=n.minMatchCharLength,x=void 0===k?1:k;r(this,e),this.options={location:i,distance:c,threshold:l,maxPatternLength:f,isCaseSensitive:d,tokenSeparator:g,findAllMatches:y,minMatchCharLength:x},this.pattern=this.options.isCaseSensitive?t:t.toLowerCase(),this.pattern.length<=f&&(this.patternAlphabet=a(this.pattern))}return o(e,[{key:"search",value:function(e){if(this.options.isCaseSensitive||(e=e.toLowerCase()),this.pattern===e)return{isMatch:!0,score:0,matchedIndices:[[0,e.length-1]]};var t=this.options,n=t.maxPatternLength,r=t.tokenSeparator;if(this.pattern.length>n)return i(e,this.pattern,r);var o=this.options,a=o.location,c=o.distance,h=o.threshold,l=o.findAllMatches,u=o.minMatchCharLength;return s(e,this.pattern,this.patternAlphabet,{location:a,distance:c,threshold:h,findAllMatches:l,minMatchCharLength:u})}}]),e}();e.exports=c},function(e,t,n){"use strict";var r=n(0),o=function e(t,n,o){if(n){var i=n.indexOf("."),s=n,a=null;-1!==i&&(s=n.slice(0,i),a=n.slice(i+1));var c=t[s];if(null!==c&&void 0!==c)if(a||"string"!=typeof c&&"number"!=typeof c)if(r(c))for(var h=0,l=c.length;h<l;h+=1)e(c[h],a,o);else a&&e(c,a,o);else o.push(c)}else o.push(t);return o};e.exports=function(e,t){return o(e,t,[])}},function(e,t,n){"use strict";e.exports=function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1,n=[],r=-1,o=-1,i=0,s=e.length;i<s;i+=1){var a=e[i];a&&-1===r?r=i:a||-1===r||(o=i-1,o-r+1>=t&&n.push([r,o]),r=-1)}return e[i-1]&&i-r>=t&&n.push([r,i-1]),n}},function(e,t,n){"use strict";e.exports=function(e){for(var t={},n=e.length,r=0;r<n;r+=1)t[e.charAt(r)]=0;for(var o=0;o<n;o+=1)t[e.charAt(o)]|=1<<n-o-1;return t}},function(e,t,n){"use strict";e.exports=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:/ +/g,r=e.match(new RegExp(t.replace(n,"|"))),o=!!r,i=[];if(o)for(var s=0,a=r.length;s<a;s+=1){var c=r[s];i.push([e.indexOf(c),c.length-1])}return{score:o?.5:1,isMatch:o,matchedIndices:i}}},function(e,t,n){"use strict";e.exports=function(e,t){var n=t.errors,r=void 0===n?0:n,o=t.currentLocation,i=void 0===o?0:o,s=t.expectedLocation,a=void 0===s?0:s,c=t.distance,h=void 0===c?100:c,l=r/e.length,u=Math.abs(a-i);return h?l+u/h:u?1:l}},function(e,t,n){"use strict";var r=n(6),o=n(3);e.exports=function(e,t,n,i){for(var s=i.location,a=void 0===s?0:s,c=i.distance,h=void 0===c?100:c,l=i.threshold,u=void 0===l?.6:l,f=i.findAllMatches,v=void 0!==f&&f,d=i.minMatchCharLength,p=void 0===d?1:d,g=a,m=e.length,y=u,k=e.indexOf(t,g),x=t.length,S=[],M=0;M<m;M+=1)S[M]=0;if(-1!=k){var b=r(t,{errors:0,currentLocation:k,expectedLocation:g,distance:h});if(y=Math.min(b,y),-1!=(k=e.lastIndexOf(t,g+x))){var _=r(t,{errors:0,currentLocation:k,expectedLocation:g,distance:h});y=Math.min(_,y)}}k=-1;for(var L=[],w=1,C=x+m,A=1<<x-1,F=0;F<x;F+=1){for(var O=0,P=C;O<P;){r(t,{errors:F,currentLocation:g+P,expectedLocation:g,distance:h})<=y?O=P:C=P,P=Math.floor((C-O)/2+O)}C=P;var j=Math.max(1,g-P+1),z=v?m:Math.min(g+P,m)+x,I=Array(z+2);I[z+1]=(1<<F)-1;for(var T=z;T>=j;T-=1){var E=T-1,K=n[e.charAt(E)];if(K&&(S[E]=1),I[T]=(I[T+1]<<1|1)&K,0!==F&&(I[T]|=(L[T+1]|L[T])<<1|1|L[T+1]),I[T]&A&&(w=r(t,{errors:F,currentLocation:E,expectedLocation:g,distance:h}))<=y){if(y=w,(k=E)<=g)break;j=Math.max(1,2*g-k)}}if(r(t,{errors:F+1,currentLocation:g,expectedLocation:g,distance:h})>y)break;L=I}return{isMatch:k>=0,score:0===w?.001:w,matchedIndices:o(S,p)}}},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),i=n(1),s=n(2),a=n(0),c=function(){function e(t,n){var o=n.location,i=void 0===o?0:o,a=n.distance,c=void 0===a?100:a,h=n.threshold,l=void 0===h?.6:h,u=n.maxPatternLength,f=void 0===u?32:u,v=n.caseSensitive,d=void 0!==v&&v,p=n.tokenSeparator,g=void 0===p?/ +/g:p,m=n.findAllMatches,y=void 0!==m&&m,k=n.minMatchCharLength,x=void 0===k?1:k,S=n.id,M=void 0===S?null:S,b=n.keys,_=void 0===b?[]:b,L=n.shouldSort,w=void 0===L||L,C=n.getFn,A=void 0===C?s:C,F=n.sortFn,O=void 0===F?function(e,t){return e.score-t.score}:F,P=n.tokenize,j=void 0!==P&&P,z=n.matchAllTokens,I=void 0!==z&&z,T=n.includeMatches,E=void 0!==T&&T,K=n.includeScore,R=void 0!==K&&K,q=n.verbose,B=void 0!==q&&q;r(this,e),this.options={location:i,distance:c,threshold:l,maxPatternLength:f,isCaseSensitive:d,tokenSeparator:g,findAllMatches:y,minMatchCharLength:x,id:M,keys:_,includeMatches:E,includeScore:R,shouldSort:w,getFn:A,sortFn:O,verbose:B,tokenize:j,matchAllTokens:I},this.setCollection(t)}return o(e,[{key:"setCollection",value:function(e){return this.list=e,e}},{key:"search",value:function(e){this._log('---------\nSearch pattern: "'+e+'"');var t=this._prepareSearchers(e),n=t.tokenSearchers,r=t.fullSearcher,o=this._search(n,r),i=o.weights,s=o.results;return this._computeScore(i,s),this.options.shouldSort&&this._sort(s),this._format(s)}},{key:"_prepareSearchers",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=[];if(this.options.tokenize)for(var n=e.split(this.options.tokenSeparator),r=0,o=n.length;r<o;r+=1)t.push(new i(n[r],this.options));return{tokenSearchers:t,fullSearcher:new i(e,this.options)}}},{key:"_search",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments[1],n=this.list,r={},o=[];if("string"==typeof n[0]){for(var i=0,s=n.length;i<s;i+=1)this._analyze({key:"",value:n[i],record:i,index:i},{resultMap:r,results:o,tokenSearchers:e,fullSearcher:t});return{weights:null,results:o}}for(var a={},c=0,h=n.length;c<h;c+=1)for(var l=n[c],u=0,f=this.options.keys.length;u<f;u+=1){var v=this.options.keys[u];if("string"!=typeof v){if(a[v.name]={weight:1-v.weight||1},v.weight<=0||v.weight>1)throw new Error("Key weight has to be > 0 and <= 1");v=v.name}else a[v]={weight:1};this._analyze({key:v,value:this.options.getFn(l,v),record:l,index:c},{resultMap:r,results:o,tokenSearchers:e,fullSearcher:t})}return{weights:a,results:o}}},{key:"_analyze",value:function(e,t){var n=e.key,r=e.value,o=e.record,i=e.index,s=t.tokenSearchers,c=void 0===s?[]:s,h=t.fullSearcher,l=void 0===h?[]:h,u=t.resultMap,f=void 0===u?{}:u,v=t.results,d=void 0===v?[]:v;if(void 0!==r&&null!==r){var p=!1,g=-1,m=0;if("string"==typeof r){this._log("\nKey: "+(""===n?"-":n));var y=l.search(r);if(this._log('Full text: "'+r+'", score: '+y.score),this.options.tokenize){for(var k=r.split(this.options.tokenSeparator),x=[],S=0;S<c.length;S+=1){var M=c[S];this._log('\nPattern: "'+M.pattern+'"');for(var b=!1,_=0;_<k.length;_+=1){var L=k[_],w=M.search(L),C={};w.isMatch?(C[L]=w.score,p=!0,b=!0,x.push(w.score)):(C[L]=1,this.options.matchAllTokens||x.push(1)),this._log('Token: "'+L+'", score: '+C[L])}b&&(m+=1)}g=x[0];for(var A=x.length,F=1;F<A;F+=1)g+=x[F];g/=A,this._log("Token score average:",g)}var O=y.score;g>-1&&(O=(O+g)/2),this._log("Score average:",O);var P=!this.options.tokenize||!this.options.matchAllTokens||m>=c.length;if(this._log("\nCheck Matches: "+P),(p||y.isMatch)&&P){var j=f[i];j?j.output.push({key:n,score:O,matchedIndices:y.matchedIndices}):(f[i]={item:o,output:[{key:n,score:O,matchedIndices:y.matchedIndices}]},d.push(f[i]))}}else if(a(r))for(var z=0,I=r.length;z<I;z+=1)this._analyze({key:n,value:r[z],record:o,index:i},{resultMap:f,results:d,tokenSearchers:c,fullSearcher:l})}}},{key:"_computeScore",value:function(e,t){this._log("\n\nComputing score:\n");for(var n=0,r=t.length;n<r;n+=1){for(var o=t[n].output,i=o.length,s=0,a=1,c=0;c<i;c+=1){var h=o[c].score,l=e?e[o[c].key].weight:1,u=h*l;1!==l?a=Math.min(a,u):(o[c].nScore=u,s+=u)}t[n].score=1===a?s/i:a,this._log(t[n])}}},{key:"_sort",value:function(e){this._log("\n\nSorting...."),e.sort(this.options.sortFn)}},{key:"_format",value:function(e){var t=[];this._log("\n\nOutput:\n\n",e);var n=[];this.options.includeMatches&&n.push(function(e,t){var n=e.output;t.matches=[];for(var r=0,o=n.length;r<o;r+=1){var i=n[r],s={indices:i.matchedIndices};i.key&&(s.key=i.key),t.matches.push(s)}}),this.options.includeScore&&n.push(function(e,t){t.score=e.score});for(var r=0,o=e.length;r<o;r+=1){var i=e[r];if(this.options.id&&(i.item=this.options.getFn(i.item,this.options.id)[0]),n.length){for(var s={item:i.item},a=0,c=n.length;a<c;a+=1)n[a](i,s);t.push(s)}else t.push(i.item)}return t}},{key:"_log",value:function(){if(this.options.verbose){var e;(e=console).log.apply(e,arguments)}}}]),e}();e.exports=c}])}); \ No newline at end of file
diff --git a/haddock-api/resources/html/index.js b/haddock-api/resources/html/index.js
new file mode 100644
index 00000000..9123f8ae
--- /dev/null
+++ b/haddock-api/resources/html/index.js
@@ -0,0 +1,381 @@
+// alias preact's hyperscript reviver since it's referenced a lot:
+var h = preact.h;
+
+function createClass(obj) {
+ // sub-class Component:
+ function F(){ preact.Component.call(this); }
+ var p = F.prototype = new preact.Component;
+ // copy our skeleton into the prototype:
+ for (var i in obj) {
+ if (i === 'getDefaultProps' && typeof obj.getDefaultProps === 'function') {
+ F.defaultProps = obj.getDefaultProps() || {};
+ } else {
+ p[i] = obj[i];
+ }
+ }
+ // restore constructor:
+ return p.constructor = F;
+}
+
+function loadJSON(path, success, error) {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function()
+ {
+ if (xhr.readyState === XMLHttpRequest.DONE) {
+ if (xhr.status === 200) {
+ if (success)
+ success(JSON.parse(xhr.responseText));
+ } else {
+ if (error)
+ error(xhr);
+ }
+ }
+ };
+ xhr.open("GET", path, true);
+ xhr.send();
+}
+
+// -------------------------------------------------------------------------- //
+
+var PageMenuButton = createClass({
+
+ render: function(props) {
+ function onClick(e) {
+ e.preventDefault();
+ props.onClick();
+ }
+
+ return h('li', null,
+ h('a', { href: '#', onClick: onClick }, props.title)
+ );
+ }
+
+});
+
+function addSearchPageMenuButton(action) {
+ var pageMenu = document.querySelector('#package-header ul.links');
+ var dummy = document.createElement('li');
+ pageMenu.insertBefore(dummy, pageMenu.firstChild);
+ preact.render(h(PageMenuButton, { onClick: action, title: "Search" }), pageMenu, dummy);
+}
+
+// -------------------------------------------------------------------------- //
+
+function take(n, arr) {
+ if (arr.length <= n) { return arr; }
+ return arr.slice(0, n);
+}
+
+var App = createClass({
+ componentWillMount: function() {
+ var self = this;
+ self.setState({
+ searchString: '',
+ isVisible: false,
+ expanded: {},
+ activeLinkIndex: -1,
+ moduleResults: []
+ });
+ loadJSON("doc-index.json", function(data) {
+ self.setState({
+ fuse: new Fuse(data, {
+ threshold: 0.4,
+ caseSensitive: true,
+ includeScore: true,
+ keys: ["name"]
+ }),
+ moduleResults: []
+ });
+ }, function (err) {
+ if (console) {
+ console.error("could not load 'doc-index.json' for searching", err);
+ }
+ self.setState({ failedLoading: true });
+ });
+
+ document.addEventListener('mousedown', this.hide.bind(this));
+
+ document.addEventListener('keydown', function(e) {
+ if (self.state.isVisible) {
+ if (e.key === 'Escape') {
+ self.hide();
+ } else if (e.key === 'ArrowUp' || (e.key === 'k' && e.ctrlKey)) {
+ e.preventDefault();
+ self.navigateLinks(-1);
+ } else if (e.key === 'ArrowDown' || (e.key === 'j' && e.ctrlKey)) {
+ e.preventDefault();
+ self.navigateLinks(+1);
+ } else if (e.key === 'Enter' && self.state.activeLinkIndex >= 0) {
+ self.followActiveLink();
+ }
+ }
+
+ if (e.key === 's' && e.target.tagName.toLowerCase() !== 'input') {
+ e.preventDefault();
+ self.show();
+ }
+ })
+ },
+
+ hide: function() {
+ this.setState({ isVisible: false });
+ },
+
+ show: function() {
+ if (!this.state.isVisible) {
+ this.focusPlease = true;
+ this.setState({ isVisible: true, activeLinkIndex: -1 });
+ }
+ },
+
+ toggleVisibility: function() {
+ if (this.state.isVisible) {
+ this.hide();
+ } else {
+ this.show();
+ }
+ },
+
+ navigateLinks: function(change) {
+ var newActiveLinkIndex = Math.max(-1, Math.min(this.linkIndex-1, this.state.activeLinkIndex + change));
+ this.navigatedByKeyboard = true;
+ this.setState({ activeLinkIndex: newActiveLinkIndex });
+ },
+
+ followActiveLink: function() {
+ if (!this.activeLinkAction) { return; }
+ this.activeLinkAction();
+ },
+
+ updateResults: function() {
+ var searchString = this.input.value;
+ var results = this.state.fuse.search(searchString)
+
+ var resultsByModule = {};
+
+ results.forEach(function(result) {
+ var moduleName = result.item.module;
+ var resultsInModule = resultsByModule[moduleName] || (resultsByModule[moduleName] = []);
+ resultsInModule.push(result);
+ });
+
+ var moduleResults = [];
+ for (var moduleName in resultsByModule) {
+ var items = resultsByModule[moduleName];
+ var sumOfInverseScores = 0;
+ items.forEach(function(item) { sumOfInverseScores += 1/item.score; });
+ moduleResults.push({ module: moduleName, totalScore: 1/sumOfInverseScores, items: items });
+ }
+
+ moduleResults.sort(function(a, b) { return a.totalScore - b.totalScore; });
+
+ this.setState({ searchString: searchString, isVisible: true, moduleResults: moduleResults });
+ },
+
+ componentDidUpdate: function() {
+ if (this.state.isVisible && this.activeLink && this.navigatedByKeyboard) {
+ var rect = this.activeLink.getClientRects()[0];
+ var searchResultsTop = this.searchResults.getClientRects()[0].top;
+ if (rect.bottom > window.innerHeight) {
+ this.searchResults.scrollTop += rect.bottom - window.innerHeight + 80;
+ } else if (rect.top < searchResultsTop) {
+ this.searchResults.scrollTop -= searchResultsTop - rect.top + 80;
+ }
+ }
+ if (this.focusPlease && this.input) {
+ this.input.focus();
+ }
+ this.navigatedByKeyboard = false;
+ this.focusPlease = false;
+ },
+
+ componentDidMount: function() {
+ addSearchPageMenuButton(this.toggleVisibility.bind(this));
+ },
+
+ render: function(props, state) {
+ if (state.failedLoading) { return null; }
+
+ var self = this;
+ this.linkIndex = 0;
+
+ var stopPropagation = function(e) { e.stopPropagation(); };
+
+ var onMouseOver = function(e) {
+ var target = e.target;
+ while (target) {
+ if (typeof target.hasAttribute == 'function' && target.hasAttribute('data-link-index')) {
+ var linkIndex = parseInt(target.getAttribute('data-link-index'), 10);
+ this.setState({ activeLinkIndex: linkIndex });
+ break;
+ }
+ target = target.parentNode;
+ }
+ }.bind(this);
+
+ var items = take(10, state.moduleResults).map(this.renderResultsInModule.bind(this));
+
+ return (
+ h('div', { id: 'search', class: state.isVisible ? '' : 'hidden' },
+ h('div', { id: 'search-form', onMouseDown: stopPropagation },
+ h('input', {
+ placeholder: "Search in package by name",
+ ref: function(input) { self.input = input; },
+ onFocus: this.show.bind(this),
+ onClick: this.show.bind(this),
+ onInput: this.updateResults.bind(this)
+ }),
+ ),
+ h('div', {
+ id: 'search-results',
+ ref: function(el) { self.searchResults = el; },
+ onMouseDown: stopPropagation, onMouseOver: onMouseOver
+ },
+ state.searchString === ''
+ ? [h(IntroMsg), h(KeyboardShortcuts)]
+ : items.length == 0
+ ? h(NoResultsMsg, { searchString: state.searchString })
+ : h('ul', null, items)
+ )
+ )
+ );
+ },
+
+ renderResultsInModule: function(resultsInModule) {
+ var items = resultsInModule.items;
+ var moduleName = resultsInModule.module;
+ var showAll = this.state.expanded[moduleName] || items.length <= 10;
+ var visibleItems = showAll ? items : take(8, items);
+
+ var expand = function() {
+ var newExpanded = Object.assign({}, this.state.expanded);
+ newExpanded[moduleName] = true;
+ this.setState({ expanded: newExpanded });
+ }.bind(this);
+
+ var renderItem = function(item) {
+ return h('li', { class: 'search-result' },
+ this.navigationLink(item.link, {},
+ h(DocHtml, { html: item.display_html })
+ )
+ );
+ }.bind(this);
+
+ return h('li', { class: 'search-module' },
+ h('h4', null, moduleName),
+ h('ul', null,
+ visibleItems.map(function(item) { return renderItem(item.item); }),
+ showAll
+ ? null
+ : h('li', { class: 'more-results' },
+ this.actionLink(expand, {}, "show " + (items.length - visibleItems.length) + " more results from this module")
+ )
+ ),
+ )
+ },
+
+ navigationLink: function(href, attrs) {
+ var fullAttrs = Object.assign({ href: href, onClick: this.hide.bind(this) }, attrs);
+ var action = function() { window.location.href = href; this.hide(); }.bind(this);
+ var args = [fullAttrs, action].concat(Array.prototype.slice.call(arguments, 2));
+ return this.menuLink.apply(this, args);
+ },
+
+ actionLink: function(callback, attrs) {
+ var onClick = function(e) { e.preventDefault(); callback(); }
+ var fullAttrs = Object.assign({ href: '#', onClick: onClick }, attrs);
+ var args = [fullAttrs, callback].concat(Array.prototype.slice.call(arguments, 2));
+ return this.menuLink.apply(this, args);
+ },
+
+ menuLink: function(attrs, action) {
+ var children = Array.prototype.slice.call(arguments, 2);
+ var linkIndex = this.linkIndex;
+ if (linkIndex === this.state.activeLinkIndex) {
+ attrs['class'] = (attrs['class'] ? attrs['class'] + ' ' : '') + 'active-link';
+ attrs.ref = function (link) { if (link) this.activeLink = link; }.bind(this);
+ this.activeLinkAction = action;
+ }
+ var newAttrs = Object.assign({ 'data-link-index': linkIndex }, attrs);
+ var args = ['a', newAttrs].concat(children);
+ this.linkIndex += 1;
+ return h.apply(null, args);
+ }
+
+});
+
+var DocHtml = createClass({
+
+ shouldComponentUpdate: function(newProps) {
+ return this.props.html !== newProps.html;
+ },
+
+ render: function(props) {
+ return h('div', {dangerouslySetInnerHTML: {__html: props.html}});
+ }
+
+});
+
+var KeyboardShortcuts = function() {
+ return h('table', { class: 'keyboard-shortcuts' },
+ h('tr', null,
+ h('th', null, "Key"),
+ h('th', null, "Shortcut")
+ ),
+ h('tr', null,
+ h('td', null, h('span', { class: 'key' }, "s")),
+ h('td', null, "Open this search box")
+ ),
+ h('tr', null,
+ h('td', null, h('span', { class: 'key' }, "esc")),
+ h('td', null, "Close this search box")
+ ),
+ h('tr', null,
+ h('td', null,
+ h('span', { class: 'key' }, "↓"), ", ",
+ h('span', { class: 'key' }, "ctrl"), "+",
+ h('span', { class: 'key' }, "j")
+ ),
+ h('td', null, "Move down in search results")
+ ),
+ h('tr', null,
+ h('td', null,
+ h('span', { class: 'key' }, "↑"), ", ",
+ h('span', { class: 'key' }, "ctrl"), "+",
+ h('span', { class: 'key' }, "k")
+ ),
+ h('td', null, "Move up in search results")
+ ),
+ h('tr', null,
+ h('td', null, h('span', { class: 'key' }, "↵")),
+ h('td', null, "Go to active search result")
+ ),
+ );
+};
+
+var IntroMsg = function() {
+ return h('p', null,
+ "You can find any exported type, constructor, class, function or pattern defined in this package by (approximate) name.",
+ );
+};
+
+var NoResultsMsg = function(props) {
+ var messages = [
+ h('p', null,
+ "Your search for '" + props.searchString + "' produced the following list of results: ",
+ h('code', null, '[]'),
+ "."
+ ),
+ h('p', null,
+ h('code', null, 'Nothing'),
+ " matches your query for '" + props.searchString + "'.",
+ ),
+ h('p', null,
+ h('code', null, 'Left "no matches for \'' + props.searchString + '\'" :: Either String (NonEmpty SearchResult)'),
+ )
+ ];
+
+ return messages[(props.searchString || 'a').charCodeAt(0) % messages.length];
+};
+
+preact.render(h(App), document.body); \ No newline at end of file
diff --git a/haddock-api/resources/html/preact.js b/haddock-api/resources/html/preact.js
new file mode 100644
index 00000000..efa3c0db
--- /dev/null
+++ b/haddock-api/resources/html/preact.js
@@ -0,0 +1,408 @@
+!function() {
+ 'use strict';
+ function VNode() {}
+ function h(nodeName, attributes) {
+ var lastSimple, child, simple, i, children = EMPTY_CHILDREN;
+ for (i = arguments.length; i-- > 2; ) stack.push(arguments[i]);
+ if (attributes && null != attributes.children) {
+ if (!stack.length) stack.push(attributes.children);
+ delete attributes.children;
+ }
+ while (stack.length) if ((child = stack.pop()) && void 0 !== child.pop) for (i = child.length; i--; ) stack.push(child[i]); else {
+ if ('boolean' == typeof child) child = null;
+ if (simple = 'function' != typeof nodeName) if (null == child) child = ''; else if ('number' == typeof child) child = String(child); else if ('string' != typeof child) simple = !1;
+ if (simple && lastSimple) children[children.length - 1] += child; else if (children === EMPTY_CHILDREN) children = [ child ]; else children.push(child);
+ lastSimple = simple;
+ }
+ var p = new VNode();
+ p.nodeName = nodeName;
+ p.children = children;
+ p.attributes = null == attributes ? void 0 : attributes;
+ p.key = null == attributes ? void 0 : attributes.key;
+ if (void 0 !== options.vnode) options.vnode(p);
+ return p;
+ }
+ function extend(obj, props) {
+ for (var i in props) obj[i] = props[i];
+ return obj;
+ }
+ function cloneElement(vnode, props) {
+ return h(vnode.nodeName, extend(extend({}, vnode.attributes), props), arguments.length > 2 ? [].slice.call(arguments, 2) : vnode.children);
+ }
+ function enqueueRender(component) {
+ if (!component.__d && (component.__d = !0) && 1 == items.push(component)) (options.debounceRendering || defer)(rerender);
+ }
+ function rerender() {
+ var p, list = items;
+ items = [];
+ while (p = list.pop()) if (p.__d) renderComponent(p);
+ }
+ function isSameNodeType(node, vnode, hydrating) {
+ if ('string' == typeof vnode || 'number' == typeof vnode) return void 0 !== node.splitText;
+ if ('string' == typeof vnode.nodeName) return !node._componentConstructor && isNamedNode(node, vnode.nodeName); else return hydrating || node._componentConstructor === vnode.nodeName;
+ }
+ function isNamedNode(node, nodeName) {
+ return node.__n === nodeName || node.nodeName.toLowerCase() === nodeName.toLowerCase();
+ }
+ function getNodeProps(vnode) {
+ var props = extend({}, vnode.attributes);
+ props.children = vnode.children;
+ var defaultProps = vnode.nodeName.defaultProps;
+ if (void 0 !== defaultProps) for (var i in defaultProps) if (void 0 === props[i]) props[i] = defaultProps[i];
+ return props;
+ }
+ function createNode(nodeName, isSvg) {
+ var node = isSvg ? document.createElementNS('http://www.w3.org/2000/svg', nodeName) : document.createElement(nodeName);
+ node.__n = nodeName;
+ return node;
+ }
+ function removeNode(node) {
+ var parentNode = node.parentNode;
+ if (parentNode) parentNode.removeChild(node);
+ }
+ function setAccessor(node, name, old, value, isSvg) {
+ if ('className' === name) name = 'class';
+ if ('key' === name) ; else if ('ref' === name) {
+ if (old) old(null);
+ if (value) value(node);
+ } else if ('class' === name && !isSvg) node.className = value || ''; else if ('style' === name) {
+ if (!value || 'string' == typeof value || 'string' == typeof old) node.style.cssText = value || '';
+ if (value && 'object' == typeof value) {
+ if ('string' != typeof old) for (var i in old) if (!(i in value)) node.style[i] = '';
+ for (var i in value) node.style[i] = 'number' == typeof value[i] && !1 === IS_NON_DIMENSIONAL.test(i) ? value[i] + 'px' : value[i];
+ }
+ } else if ('dangerouslySetInnerHTML' === name) {
+ if (value) node.innerHTML = value.__html || '';
+ } else if ('o' == name[0] && 'n' == name[1]) {
+ var useCapture = name !== (name = name.replace(/Capture$/, ''));
+ name = name.toLowerCase().substring(2);
+ if (value) {
+ if (!old) node.addEventListener(name, eventProxy, useCapture);
+ } else node.removeEventListener(name, eventProxy, useCapture);
+ (node.__l || (node.__l = {}))[name] = value;
+ } else if ('list' !== name && 'type' !== name && !isSvg && name in node) {
+ setProperty(node, name, null == value ? '' : value);
+ if (null == value || !1 === value) node.removeAttribute(name);
+ } else {
+ var ns = isSvg && name !== (name = name.replace(/^xlink\:?/, ''));
+ if (null == value || !1 === value) if (ns) node.removeAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase()); else node.removeAttribute(name); else if ('function' != typeof value) if (ns) node.setAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase(), value); else node.setAttribute(name, value);
+ }
+ }
+ function setProperty(node, name, value) {
+ try {
+ node[name] = value;
+ } catch (e) {}
+ }
+ function eventProxy(e) {
+ return this.__l[e.type](options.event && options.event(e) || e);
+ }
+ function flushMounts() {
+ var c;
+ while (c = mounts.pop()) {
+ if (options.afterMount) options.afterMount(c);
+ if (c.componentDidMount) c.componentDidMount();
+ }
+ }
+ function diff(dom, vnode, context, mountAll, parent, componentRoot) {
+ if (!diffLevel++) {
+ isSvgMode = null != parent && void 0 !== parent.ownerSVGElement;
+ hydrating = null != dom && !('__preactattr_' in dom);
+ }
+ var ret = idiff(dom, vnode, context, mountAll, componentRoot);
+ if (parent && ret.parentNode !== parent) parent.appendChild(ret);
+ if (!--diffLevel) {
+ hydrating = !1;
+ if (!componentRoot) flushMounts();
+ }
+ return ret;
+ }
+ function idiff(dom, vnode, context, mountAll, componentRoot) {
+ var out = dom, prevSvgMode = isSvgMode;
+ if (null == vnode || 'boolean' == typeof vnode) vnode = '';
+ if ('string' == typeof vnode || 'number' == typeof vnode) {
+ if (dom && void 0 !== dom.splitText && dom.parentNode && (!dom._component || componentRoot)) {
+ if (dom.nodeValue != vnode) dom.nodeValue = vnode;
+ } else {
+ out = document.createTextNode(vnode);
+ if (dom) {
+ if (dom.parentNode) dom.parentNode.replaceChild(out, dom);
+ recollectNodeTree(dom, !0);
+ }
+ }
+ out.__preactattr_ = !0;
+ return out;
+ }
+ var vnodeName = vnode.nodeName;
+ if ('function' == typeof vnodeName) return buildComponentFromVNode(dom, vnode, context, mountAll);
+ isSvgMode = 'svg' === vnodeName ? !0 : 'foreignObject' === vnodeName ? !1 : isSvgMode;
+ vnodeName = String(vnodeName);
+ if (!dom || !isNamedNode(dom, vnodeName)) {
+ out = createNode(vnodeName, isSvgMode);
+ if (dom) {
+ while (dom.firstChild) out.appendChild(dom.firstChild);
+ if (dom.parentNode) dom.parentNode.replaceChild(out, dom);
+ recollectNodeTree(dom, !0);
+ }
+ }
+ var fc = out.firstChild, props = out.__preactattr_, vchildren = vnode.children;
+ if (null == props) {
+ props = out.__preactattr_ = {};
+ for (var a = out.attributes, i = a.length; i--; ) props[a[i].name] = a[i].value;
+ }
+ if (!hydrating && vchildren && 1 === vchildren.length && 'string' == typeof vchildren[0] && null != fc && void 0 !== fc.splitText && null == fc.nextSibling) {
+ if (fc.nodeValue != vchildren[0]) fc.nodeValue = vchildren[0];
+ } else if (vchildren && vchildren.length || null != fc) innerDiffNode(out, vchildren, context, mountAll, hydrating || null != props.dangerouslySetInnerHTML);
+ diffAttributes(out, vnode.attributes, props);
+ isSvgMode = prevSvgMode;
+ return out;
+ }
+ function innerDiffNode(dom, vchildren, context, mountAll, isHydrating) {
+ var j, c, f, vchild, child, originalChildren = dom.childNodes, children = [], keyed = {}, keyedLen = 0, min = 0, len = originalChildren.length, childrenLen = 0, vlen = vchildren ? vchildren.length : 0;
+ if (0 !== len) for (var i = 0; i < len; i++) {
+ var _child = originalChildren[i], props = _child.__preactattr_, key = vlen && props ? _child._component ? _child._component.__k : props.key : null;
+ if (null != key) {
+ keyedLen++;
+ keyed[key] = _child;
+ } else if (props || (void 0 !== _child.splitText ? isHydrating ? _child.nodeValue.trim() : !0 : isHydrating)) children[childrenLen++] = _child;
+ }
+ if (0 !== vlen) for (var i = 0; i < vlen; i++) {
+ vchild = vchildren[i];
+ child = null;
+ var key = vchild.key;
+ if (null != key) {
+ if (keyedLen && void 0 !== keyed[key]) {
+ child = keyed[key];
+ keyed[key] = void 0;
+ keyedLen--;
+ }
+ } else if (!child && min < childrenLen) for (j = min; j < childrenLen; j++) if (void 0 !== children[j] && isSameNodeType(c = children[j], vchild, isHydrating)) {
+ child = c;
+ children[j] = void 0;
+ if (j === childrenLen - 1) childrenLen--;
+ if (j === min) min++;
+ break;
+ }
+ child = idiff(child, vchild, context, mountAll);
+ f = originalChildren[i];
+ if (child && child !== dom && child !== f) if (null == f) dom.appendChild(child); else if (child === f.nextSibling) removeNode(f); else dom.insertBefore(child, f);
+ }
+ if (keyedLen) for (var i in keyed) if (void 0 !== keyed[i]) recollectNodeTree(keyed[i], !1);
+ while (min <= childrenLen) if (void 0 !== (child = children[childrenLen--])) recollectNodeTree(child, !1);
+ }
+ function recollectNodeTree(node, unmountOnly) {
+ var component = node._component;
+ if (component) unmountComponent(component); else {
+ if (null != node.__preactattr_ && node.__preactattr_.ref) node.__preactattr_.ref(null);
+ if (!1 === unmountOnly || null == node.__preactattr_) removeNode(node);
+ removeChildren(node);
+ }
+ }
+ function removeChildren(node) {
+ node = node.lastChild;
+ while (node) {
+ var next = node.previousSibling;
+ recollectNodeTree(node, !0);
+ node = next;
+ }
+ }
+ function diffAttributes(dom, attrs, old) {
+ var name;
+ for (name in old) if ((!attrs || null == attrs[name]) && null != old[name]) setAccessor(dom, name, old[name], old[name] = void 0, isSvgMode);
+ for (name in attrs) if (!('children' === name || 'innerHTML' === name || name in old && attrs[name] === ('value' === name || 'checked' === name ? dom[name] : old[name]))) setAccessor(dom, name, old[name], old[name] = attrs[name], isSvgMode);
+ }
+ function collectComponent(component) {
+ var name = component.constructor.name;
+ (components[name] || (components[name] = [])).push(component);
+ }
+ function createComponent(Ctor, props, context) {
+ var inst, list = components[Ctor.name];
+ if (Ctor.prototype && Ctor.prototype.render) {
+ inst = new Ctor(props, context);
+ Component.call(inst, props, context);
+ } else {
+ inst = new Component(props, context);
+ inst.constructor = Ctor;
+ inst.render = doRender;
+ }
+ if (list) for (var i = list.length; i--; ) if (list[i].constructor === Ctor) {
+ inst.__b = list[i].__b;
+ list.splice(i, 1);
+ break;
+ }
+ return inst;
+ }
+ function doRender(props, state, context) {
+ return this.constructor(props, context);
+ }
+ function setComponentProps(component, props, opts, context, mountAll) {
+ if (!component.__x) {
+ component.__x = !0;
+ if (component.__r = props.ref) delete props.ref;
+ if (component.__k = props.key) delete props.key;
+ if (!component.base || mountAll) {
+ if (component.componentWillMount) component.componentWillMount();
+ } else if (component.componentWillReceiveProps) component.componentWillReceiveProps(props, context);
+ if (context && context !== component.context) {
+ if (!component.__c) component.__c = component.context;
+ component.context = context;
+ }
+ if (!component.__p) component.__p = component.props;
+ component.props = props;
+ component.__x = !1;
+ if (0 !== opts) if (1 === opts || !1 !== options.syncComponentUpdates || !component.base) renderComponent(component, 1, mountAll); else enqueueRender(component);
+ if (component.__r) component.__r(component);
+ }
+ }
+ function renderComponent(component, opts, mountAll, isChild) {
+ if (!component.__x) {
+ var rendered, inst, cbase, props = component.props, state = component.state, context = component.context, previousProps = component.__p || props, previousState = component.__s || state, previousContext = component.__c || context, isUpdate = component.base, nextBase = component.__b, initialBase = isUpdate || nextBase, initialChildComponent = component._component, skip = !1;
+ if (isUpdate) {
+ component.props = previousProps;
+ component.state = previousState;
+ component.context = previousContext;
+ if (2 !== opts && component.shouldComponentUpdate && !1 === component.shouldComponentUpdate(props, state, context)) skip = !0; else if (component.componentWillUpdate) component.componentWillUpdate(props, state, context);
+ component.props = props;
+ component.state = state;
+ component.context = context;
+ }
+ component.__p = component.__s = component.__c = component.__b = null;
+ component.__d = !1;
+ if (!skip) {
+ rendered = component.render(props, state, context);
+ if (component.getChildContext) context = extend(extend({}, context), component.getChildContext());
+ var toUnmount, base, childComponent = rendered && rendered.nodeName;
+ if ('function' == typeof childComponent) {
+ var childProps = getNodeProps(rendered);
+ inst = initialChildComponent;
+ if (inst && inst.constructor === childComponent && childProps.key == inst.__k) setComponentProps(inst, childProps, 1, context, !1); else {
+ toUnmount = inst;
+ component._component = inst = createComponent(childComponent, childProps, context);
+ inst.__b = inst.__b || nextBase;
+ inst.__u = component;
+ setComponentProps(inst, childProps, 0, context, !1);
+ renderComponent(inst, 1, mountAll, !0);
+ }
+ base = inst.base;
+ } else {
+ cbase = initialBase;
+ toUnmount = initialChildComponent;
+ if (toUnmount) cbase = component._component = null;
+ if (initialBase || 1 === opts) {
+ if (cbase) cbase._component = null;
+ base = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, !0);
+ }
+ }
+ if (initialBase && base !== initialBase && inst !== initialChildComponent) {
+ var baseParent = initialBase.parentNode;
+ if (baseParent && base !== baseParent) {
+ baseParent.replaceChild(base, initialBase);
+ if (!toUnmount) {
+ initialBase._component = null;
+ recollectNodeTree(initialBase, !1);
+ }
+ }
+ }
+ if (toUnmount) unmountComponent(toUnmount);
+ component.base = base;
+ if (base && !isChild) {
+ var componentRef = component, t = component;
+ while (t = t.__u) (componentRef = t).base = base;
+ base._component = componentRef;
+ base._componentConstructor = componentRef.constructor;
+ }
+ }
+ if (!isUpdate || mountAll) mounts.unshift(component); else if (!skip) {
+ if (component.componentDidUpdate) component.componentDidUpdate(previousProps, previousState, previousContext);
+ if (options.afterUpdate) options.afterUpdate(component);
+ }
+ if (null != component.__h) while (component.__h.length) component.__h.pop().call(component);
+ if (!diffLevel && !isChild) flushMounts();
+ }
+ }
+ function buildComponentFromVNode(dom, vnode, context, mountAll) {
+ var c = dom && dom._component, originalComponent = c, oldDom = dom, isDirectOwner = c && dom._componentConstructor === vnode.nodeName, isOwner = isDirectOwner, props = getNodeProps(vnode);
+ while (c && !isOwner && (c = c.__u)) isOwner = c.constructor === vnode.nodeName;
+ if (c && isOwner && (!mountAll || c._component)) {
+ setComponentProps(c, props, 3, context, mountAll);
+ dom = c.base;
+ } else {
+ if (originalComponent && !isDirectOwner) {
+ unmountComponent(originalComponent);
+ dom = oldDom = null;
+ }
+ c = createComponent(vnode.nodeName, props, context);
+ if (dom && !c.__b) {
+ c.__b = dom;
+ oldDom = null;
+ }
+ setComponentProps(c, props, 1, context, mountAll);
+ dom = c.base;
+ if (oldDom && dom !== oldDom) {
+ oldDom._component = null;
+ recollectNodeTree(oldDom, !1);
+ }
+ }
+ return dom;
+ }
+ function unmountComponent(component) {
+ if (options.beforeUnmount) options.beforeUnmount(component);
+ var base = component.base;
+ component.__x = !0;
+ if (component.componentWillUnmount) component.componentWillUnmount();
+ component.base = null;
+ var inner = component._component;
+ if (inner) unmountComponent(inner); else if (base) {
+ if (base.__preactattr_ && base.__preactattr_.ref) base.__preactattr_.ref(null);
+ component.__b = base;
+ removeNode(base);
+ collectComponent(component);
+ removeChildren(base);
+ }
+ if (component.__r) component.__r(null);
+ }
+ function Component(props, context) {
+ this.__d = !0;
+ this.context = context;
+ this.props = props;
+ this.state = this.state || {};
+ }
+ function render(vnode, parent, merge) {
+ return diff(merge, vnode, {}, !1, parent, !1);
+ }
+ var options = {};
+ var stack = [];
+ var EMPTY_CHILDREN = [];
+ var defer = 'function' == typeof Promise ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;
+ var IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i;
+ var items = [];
+ var mounts = [];
+ var diffLevel = 0;
+ var isSvgMode = !1;
+ var hydrating = !1;
+ var components = {};
+ extend(Component.prototype, {
+ setState: function(state, callback) {
+ var s = this.state;
+ if (!this.__s) this.__s = extend({}, s);
+ extend(s, 'function' == typeof state ? state(s, this.props) : state);
+ if (callback) (this.__h = this.__h || []).push(callback);
+ enqueueRender(this);
+ },
+ forceUpdate: function(callback) {
+ if (callback) (this.__h = this.__h || []).push(callback);
+ renderComponent(this, 2);
+ },
+ render: function() {}
+ });
+ var preact = {
+ h: h,
+ createElement: h,
+ cloneElement: cloneElement,
+ Component: Component,
+ render: render,
+ rerender: rerender,
+ options: options
+ };
+ if ('undefined' != typeof module) module.exports = preact; else self.preact = preact;
+}();
+//# sourceMappingURL=preact.js.map \ No newline at end of file
diff --git a/haddock-api/src/Haddock/Backends/Xhtml.hs b/haddock-api/src/Haddock/Backends/Xhtml.hs
index 78bd0262..e1bbee7b 100644
--- a/haddock-api/src/Haddock/Backends/Xhtml.hs
+++ b/haddock-api/src/Haddock/Backends/Xhtml.hs
@@ -108,6 +108,9 @@ copyHtmlBits odir libdir themes = do
copyLibFile f = copyFile (joinPath [libhtmldir, f]) (joinPath [odir, f])
mapM_ copyCssFile (cssFiles themes)
copyLibFile jsFile
+ copyLibFile jsFuseFile
+ copyLibFile jsIndexFile
+ copyLibFile jsPreactFile
return ()
@@ -118,6 +121,9 @@ headHtml docTitle themes mathjax_url =
thetitle << docTitle,
styleSheet themes,
script ! [src jsFile, thetype "text/javascript"] << noHtml,
+ script ! [src jsPreactFile, thetype "text/javascript"] << noHtml,
+ script ! [src jsFuseFile, thetype "text/javascript"] << noHtml,
+ script ! [src jsIndexFile, thetype "text/javascript"] << noHtml,
script ! [src mjUrl, thetype "text/javascript"] << noHtml,
script ! [thetype "text/javascript"]
-- NB: Within XHTML, the content of script tags needs to be
diff --git a/haddock-api/src/Haddock/Utils.hs b/haddock-api/src/Haddock/Utils.hs
index 200cd00a..53d69418 100644
--- a/haddock-api/src/Haddock/Utils.hs
+++ b/haddock-api/src/Haddock/Utils.hs
@@ -23,7 +23,7 @@ module Haddock.Utils (
contentsHtmlFile, indexHtmlFile, indexJsonFile,
moduleIndexFrameName, mainFrameName, synopsisFrameName,
subIndexHtmlFile,
- jsFile,
+ jsFile, jsFuseFile, jsIndexFile, jsPreactFile,
-- * Anchor and URL utilities
moduleNameUrl, moduleNameUrl', moduleUrl,
@@ -328,6 +328,14 @@ makeAnchorId (f:r) = escape isAlpha f ++ concatMap (escape isLegal) r
jsFile :: String
jsFile = "haddock-util.js"
+jsIndexFile :: String
+jsIndexFile = "index.js"
+
+jsFuseFile :: String
+jsFuseFile = "fuse.js"
+
+jsPreactFile :: String
+jsPreactFile = "preact.js"
-------------------------------------------------------------------------------
-- * Misc.