diff options
Diffstat (limited to 'haddock-api/resources/html/js-src')
-rw-r--r-- | haddock-api/resources/html/js-src/cookies.ts | 20 | ||||
-rw-r--r-- | haddock-api/resources/html/js-src/details-helper.ts | 106 | ||||
-rw-r--r-- | haddock-api/resources/html/js-src/init.ts | 9 | ||||
-rw-r--r-- | haddock-api/resources/html/js-src/quick-jump.tsx | 29 | ||||
-rw-r--r-- | haddock-api/resources/html/js-src/style-menu.ts (renamed from haddock-api/resources/html/js-src/haddock-util.ts) | 75 |
5 files changed, 161 insertions, 78 deletions
diff --git a/haddock-api/resources/html/js-src/cookies.ts b/haddock-api/resources/html/js-src/cookies.ts new file mode 100644 index 00000000..3b68d6d8 --- /dev/null +++ b/haddock-api/resources/html/js-src/cookies.ts @@ -0,0 +1,20 @@ +export function setCookie(name: string, value: string) { + document.cookie = name + "=" + encodeURIComponent(value) + ";path=/;"; +} + +export function clearCookie(name: string) { + document.cookie = name + "=;path=/;expires=Thu, 01-Jan-1970 00:00:01 GMT;"; +} + +export function getCookie(name: string) { + const nameEQ = name + "="; + const ca = document.cookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) { + return decodeURIComponent(c.substring(nameEQ.length,c.length)); + } + } + return null; +}
\ No newline at end of file diff --git a/haddock-api/resources/html/js-src/details-helper.ts b/haddock-api/resources/html/js-src/details-helper.ts new file mode 100644 index 00000000..f13ac905 --- /dev/null +++ b/haddock-api/resources/html/js-src/details-helper.ts @@ -0,0 +1,106 @@ +import {getCookie} from "./cookies"; + +interface HTMLDetailsElement extends HTMLElement { + open: boolean +} + +interface DetailsInfo { + element: HTMLDetailsElement + openByDefault: boolean + toggles: HTMLElement[] +} + +// Global state +const detailsRegistry: { [id: string]: DetailsInfo } = {}; +const toggled: { [id: string]: true } = {}; /* stores which <details> are not in their default state */ + +function lookupDetailsRegistry(id: string): DetailsInfo { + const info = detailsRegistry[id]; + if (info == undefined) { throw new Error(`could not find <details> element with id '${id}'`); } + return info; +} + +function onDetailsToggle(ev: Event) { + const element = ev.target as HTMLDetailsElement; + const id = element.id; + const info = lookupDetailsRegistry(id); + const isOpen = info.element.open; + for (const toggle of info.toggles) { + if (toggle.classList.contains('details-toggle-control')) { + toggle.classList.add(isOpen ? 'collapser' : 'expander'); + toggle.classList.remove(isOpen ? 'expander' : 'collapser'); + } + } + if (element.open == info.openByDefault) { + delete toggled[id]; + } else { + toggled[id] = true; + } + rememberToggled(); +} + +function gatherDetailsElements() { + const els: HTMLDetailsElement[] = Array.prototype.slice.call(document.getElementsByTagName('details')); + for (const el of els) { + if (typeof el.id == "string" && el.id.length > 0) { + detailsRegistry[el.id] = { + element: el, + openByDefault: !!el.open, + toggles: [] // added later + }; + el.addEventListener('toggle', onDetailsToggle); + } + } +} + +function toggleDetails(id: string) { + const {element} = lookupDetailsRegistry(id); + element.open = !element.open; +} + +function rememberToggled() { + const sections: string[] = Object.keys(toggled); + // cookie specific to this page; don't use setCookie which sets path=/ + document.cookie = "toggled=" + encodeURIComponent(sections.join('+')); +} + +function restoreToggled() { + const cookie = getCookie("toggled"); + if (!cookie) { return; } + const ids = cookie.split('+'); + for (const id of ids) { + const info = detailsRegistry[id]; + toggled[id] = true; + if (info) { + info.element.open = !info.element.open; + } + } +} + +function onToggleClick(ev: MouseEvent) { + ev.preventDefault(); + const toggle = ev.currentTarget as HTMLElement; + const id = toggle.getAttribute('data-details-id'); + if (!id) { throw new Error("element with class 'details-toggle' has no 'data-details-id' attribute!"); } + toggleDetails(id); +} + +function initCollapseToggles() { + const toggles: HTMLElement[] = Array.prototype.slice.call(document.getElementsByClassName('details-toggle')); + toggles.forEach(toggle => { + const id = toggle.getAttribute('data-details-id'); + if (!id) { throw new Error("element with class 'details-toggle' has no 'data-details-id' attribute!"); } + const info = lookupDetailsRegistry(id); + info.toggles.push(toggle); + toggle.addEventListener('click', onToggleClick); + if (toggle.classList.contains('details-toggle-control')) { + toggle.classList.add(info.element.open ? 'collapser' : 'expander'); + } + }); +} + +export function init() { + gatherDetailsElements(); + restoreToggled(); + initCollapseToggles(); +}
\ No newline at end of file diff --git a/haddock-api/resources/html/js-src/init.ts b/haddock-api/resources/html/js-src/init.ts index 0619dfc3..877874ae 100644 --- a/haddock-api/resources/html/js-src/init.ts +++ b/haddock-api/resources/html/js-src/init.ts @@ -1,4 +1,5 @@ -import * as util from "./haddock-util"; +import * as styleMenu from "./style-menu"; +import * as detailsHelper from "./details-helper"; import * as quickJump from "./quick-jump"; function onDomReady(callback: () => void) { @@ -14,8 +15,8 @@ function onDomReady(callback: () => void) { } onDomReady(() => { - util.addStyleMenu(); - util.resetStyle(); - util.restoreCollapsed(); + document.body.classList.add('js-enabled'); + styleMenu.init(); + detailsHelper.init(); quickJump.init(); });
\ No newline at end of file diff --git a/haddock-api/resources/html/js-src/quick-jump.tsx b/haddock-api/resources/html/js-src/quick-jump.tsx index a2bcdb64..97ac14af 100644 --- a/haddock-api/resources/html/js-src/quick-jump.tsx +++ b/haddock-api/resources/html/js-src/quick-jump.tsx @@ -3,10 +3,6 @@ import preact = require("preact"); const { h, Component } = preact; -declare interface ObjectConstructor { - assign(target: any, ...sources: any[]): any; -} - type DocItem = { display_html: string name: string @@ -19,8 +15,13 @@ function loadJSON(path: string, success: (json: DocItem[]) => void, error: (xhr: xhr.onreadystatechange = () => { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { - if (success) - success(JSON.parse(xhr.responseText)); + if (success) { + try { + success(JSON.parse(xhr.responseText)); + } catch (exc) { + error(xhr); + } + } } else { if (error) { error(xhr); } } @@ -102,7 +103,7 @@ class QuickJump extends Component<QuickJumpProps, QuickJumpState> { loadJSON(this.props.baseUrl + "/doc-index.json", (data) => { this.setState({ fuse: new Fuse(data, { - threshold: 0.4, + threshold: 0.25, caseSensitive: true, includeScore: true, tokenize: true, @@ -218,7 +219,19 @@ class QuickJump extends Component<QuickJumpProps, QuickJumpState> { } render(props: any, state: QuickJumpState) { - if (state.failedLoading) { return null; } + if (state.failedLoading) { + const usingFileProtocol = window.location.protocol == 'file:'; + return <div id="search" class={state.isVisible ? '' : 'hidden'}> + <div id="search-results"> + <p class="error">Failed to load file 'doc-index.json' containing definitions in this package.</p> + {usingFileProtocol ? <p class="error"> + To use quick jump, load this page with HTTP (from a local static file web server) instead of using the <code>file://</code> protocol. + (For security reasons, it is not possible to fetch auxiliary files using JS in a HTML page opened with <code>file://</code>.) + </p> : [] + } + </div> + </div>; + } this.linkIndex = 0; diff --git a/haddock-api/resources/html/js-src/haddock-util.ts b/haddock-api/resources/html/js-src/style-menu.ts index 257ceb6a..3911655b 100644 --- a/haddock-api/resources/html/js-src/haddock-util.ts +++ b/haddock-api/resources/html/js-src/style-menu.ts @@ -1,5 +1,7 @@ // Haddock JavaScript utilities +import {getCookie, setCookie, clearCookie} from "./cookies"; + const rspace = /\s\s+/g, rtrim = /^\s+|\s+$/g; @@ -37,7 +39,6 @@ function toggleClass(elem: Element, valueOn: string, valueOff: string, bool?: bo return bool; } - function makeClassToggle(valueOn: string, valueOff: string): (elem: Element, bool?: boolean) => boolean { return function(elem, bool) { return toggleClass(elem, valueOn, valueOff, bool); @@ -45,69 +46,6 @@ function makeClassToggle(valueOn: string, valueOff: string): (elem: Element, boo } const toggleShow = makeClassToggle("show", "hide"); -const toggleCollapser = makeClassToggle("collapser", "expander"); - -function toggleSection(id: string): boolean { - const b = toggleShow(document.getElementById("section." + id) as Element); - toggleCollapser(document.getElementById("control." + id) as Element, b); - rememberCollapsed(id); - return b; -} - -// TODO: get rid of global variables -if (typeof window !== 'undefined') { - (window as any).toggleSection = toggleSection; -} - -const collapsed: { [id: string]: boolean } = {}; -function rememberCollapsed(id: string) { - if(collapsed[id]) - delete collapsed[id] - else - collapsed[id] = true; - - const sections: string[] = []; - for(let i in collapsed) { - if(collapsed.hasOwnProperty(i)) - sections.push(i); - } - // cookie specific to this page; don't use setCookie which sets path=/ - document.cookie = "collapsed=" + encodeURIComponent(sections.join('+')); -} - -export function restoreCollapsed() { - const cookie = getCookie("collapsed"); - if(!cookie) - return; - - const ids = cookie.split('+'); - for(const i in ids) - { - if(document.getElementById("section." + ids[i])) - toggleSection(ids[i]); - } -} - -function setCookie(name: string, value: string) { - document.cookie = name + "=" + encodeURIComponent(value) + ";path=/;"; -} - -function clearCookie(name: string) { - document.cookie = name + "=;path=/;expires=Thu, 01-Jan-1970 00:00:01 GMT;"; -} - -function getCookie(name: string) { - const nameEQ = name + "="; - const ca = document.cookie.split(';'); - for (let i = 0; i < ca.length; i++) { - let c = ca[i]; - while (c.charAt(0)==' ') c = c.substring(1,c.length); - if (c.indexOf(nameEQ) == 0) { - return decodeURIComponent(c.substring(nameEQ.length,c.length)); - } - } - return null; -} function addMenuItem(html: string) { const menu = document.getElementById("page-menu"); @@ -123,7 +61,7 @@ function styles(): HTMLLinkElement[] { return es.filter((a: HTMLLinkElement) => a.rel.indexOf("style") != -1 && a.title); } -export function addStyleMenu() { +function addStyleMenu() { const as = styles(); let btns = ""; as.forEach((a) => { @@ -162,7 +100,7 @@ function setActiveStyleSheet(title: string) { styleMenu(false); } -export function resetStyle() { +function resetStyle() { const s = getCookie("haddock-style"); if (s) setActiveStyleSheet(s); } @@ -170,4 +108,9 @@ export function resetStyle() { function styleMenu(show?: boolean) { const m = document.getElementById('style-menu'); if (m) toggleShow(m, show); +} + +export function init() { + addStyleMenu(); + resetStyle(); }
\ No newline at end of file |