diff options
Diffstat (limited to 'haddock-api/resources/html/js-src')
| -rw-r--r-- | haddock-api/resources/html/js-src/details-helper.ts | 106 | ||||
| -rw-r--r-- | haddock-api/resources/html/js-src/details-helper.tsx | 464 | ||||
| -rw-r--r-- | haddock-api/resources/html/js-src/init.ts | 4 | ||||
| -rw-r--r-- | haddock-api/resources/html/js-src/style-menu.tsx | 177 | 
4 files changed, 559 insertions, 192 deletions
| diff --git a/haddock-api/resources/html/js-src/details-helper.ts b/haddock-api/resources/html/js-src/details-helper.ts deleted file mode 100644 index f13ac905..00000000 --- a/haddock-api/resources/html/js-src/details-helper.ts +++ /dev/null @@ -1,106 +0,0 @@ -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/details-helper.tsx b/haddock-api/resources/html/js-src/details-helper.tsx new file mode 100644 index 00000000..871b5417 --- /dev/null +++ b/haddock-api/resources/html/js-src/details-helper.tsx @@ -0,0 +1,464 @@ +// This file implements the UI and logic for collapsing and expanding +// instance lists ("details"). +// +// A configuration ('GlobalConfig') controlled by the UI is persisted +// in local storage in the user's browser. The configuration includes: +// +// * a global default state ('defaultInstanceState') for all instance +//   lists. The possible values for the global default are "collapsed" +//   and "expanded". +// +// * a global boolean option ('rememberToggles') to remember which +//   specific instance lists are not in the default state (e.g. which +//   instance lists are expanded when the default is "collapsed"). +// +// * a local / per-page record of which specific instance lists are +//   not in the default state, when the global option +//   ('rememberToggles') to remember this info is enabled. +// +// The UI consists of an Instances menu with buttons for expanding and +// collapsing all instance lists in the current module, a checkbox for +// setting the global default state, and a checkbox to enable +// remembering which instance lists are not in the global default +// state. Also, each instance list on each module page has buttons for +// collapsing and expanding. +// +// The logic of the UI is as follows: +// +// * setting the global default state erases any record of which +//   specific instances are in the non-default state, and collapses or +//   expands all instance lists on the current page to be in the +//   global default state. +// +// * changing boolean option for remembering which specific instance +//   lists are not in the default state erases any existing record of +//   which instances are not in the default state across all pages, +//   and updates the record for the current page when the option is +//   set to true. No collapsing or expanding is done. +// +// * toggling the collapse/expand state of a specific instance list +//   causes the state of that specific instance list to be recorded in +//   the persisted configuration iff the new state of that specific +//   instance list is different from the global default state, and the +//   option to remember instance list states is enabled. There are two +//   ways to toggle the collapse/expand state of a specific instance, +//   by clicking its collapse/expand button, and by clicking the +//   "collapse all" or "expand all" button in the Instances menu. +// +// This file also implements an association between elements (with +// class "details-toggle" and "details-toggle-control") that can be +// clicked to expand/collapse <details> elements, and the details +// elements themselves. Note that this covers both <details> elements +// that list instances -- what the above explained UI and logic is +// concerned with -- and details about individual instances themselves +// -- which the above is not concerend with. The association includes +// adding event listeners that change CSS classes back and forth +// between "expander" and "collapser"; these classes determine whether +// an element is adorned with a right arrow ("expander") or a down +// arrow ("collapser"). I don't understand why we don't directly use +// the the HTML <summary> element type to allow the <details> elements +// to be directly clickable. +import preact = require("preact"); + +const { h, Component } = preact; + +enum DefaultState { Closed, Open } + +interface GlobalConfig { +  defaultInstanceState: DefaultState +  rememberToggles: boolean +} + +// Hackage domain-wide config +const globalConfig: GlobalConfig = { +  defaultInstanceState: DefaultState.Open, +  rememberToggles: true, +}; + +class PreferencesButton extends Component<any, any> { +  render(props: { title: string, onClick: () => void }) { +    function onClick(e: Event) { +      e.preventDefault(); +      props.onClick(); +    } +    return <li><a href="#" onClick={onClick}>{props.title}</a></li>; +  } +} + +function addPreferencesButton(action: () => void) { +  const pageMenu = document.querySelector('#page-menu') as HTMLUListElement; +  const dummy = document.createElement('li'); +  pageMenu.insertBefore(dummy, pageMenu.firstChild); +  preact.render(<PreferencesButton onClick={action} title="Instances" />, pageMenu, dummy); +} + +type PreferencesProps = { +  showHideTrigger: (action: () => void) => void +} + +type PreferencesState = { +  isVisible: boolean +} + +class Preferences extends Component<PreferencesProps, PreferencesState> { +  componentWillMount() { +    document.addEventListener('mousedown', this.hide.bind(this)); + +    document.addEventListener('keydown', (e) => { +      if (this.state.isVisible) { +        if (e.key === 'Escape') { +          this.hide(); +        } +      } +    }) +  } + +  hide() { +    this.setState({ isVisible: false }); +  } + +  show() { +    if (!this.state.isVisible) { +      this.setState({ isVisible: true }); +    } +  } + +  toggleVisibility() { +    if (this.state.isVisible) { +      this.hide(); +    } else { +      this.show(); +    } +  } + +  componentDidMount() { +    this.props.showHideTrigger(this.toggleVisibility.bind(this)); +  } + +  render(props: PreferencesProps, state: PreferencesState) { +    const stopPropagation = (e: Event) => { e.stopPropagation(); }; + +    return <div id="preferences" class={state.isVisible ? '' : 'hidden'}> +        <div id="preferences-menu" class="dropdown-menu" onMouseDown={stopPropagation}> +          <PreferencesMenu /> +        </div> +      </div>; +  } +} + +function storeGlobalConfig() { +  const json = JSON.stringify(globalConfig); +  try { +    // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions. +    localStorage.setItem('global', json); +  } catch (e) {} +} + +var globalConfigLoaded: boolean = false; + +function loadGlobalConfig() { +  if (globalConfigLoaded) { return; } +  globalConfigLoaded = true; +  const global = localStorage.getItem('global'); +  if (!global) { return; } +  try { +    const globalConfig_ = JSON.parse(global); +    globalConfig.defaultInstanceState = globalConfig_.defaultInstanceState; +    globalConfig.rememberToggles = globalConfig_.rememberToggles; +  } catch(e) { +    // Gracefully handle errors related to changed config format. +    if (e instanceof SyntaxError || e instanceof TypeError) { +      localStorage.removeItem('global'); +    } else { +      throw e; +    } +  } +} + +function setDefaultInstanceState(s: DefaultState) { +  return (e: Event) => { +    globalConfig.defaultInstanceState = s; +    putInstanceListsInDefaultState(); +    storeGlobalConfig(); +    clearLocalStorage(); +    storeLocalConfig(); +  } +} + +function setRememberToggles(e: Event) { +  const checked: boolean = (e as any).target.checked; +  globalConfig.rememberToggles = checked; +  storeGlobalConfig(); +  clearLocalStorage(); +  storeLocalConfig(); +} + +// Click event consumer for "default collapse" instance menu check box. +function defaultCollapseOnClick(e: Event) { +  const us = document.getElementById('default-collapse-instances') as HTMLInputElement; +  if (us !== null) { +    if (us.checked) { +      setDefaultInstanceState(DefaultState.Closed)(e); +    } else { +      setDefaultInstanceState(DefaultState.Open)(e); +    } +  } +} + +// Instances menu. +function PreferencesMenu() { +  loadGlobalConfig(); +  return <div> +      <div> +        <button type="button" +                onClick={expandAllInstances}> +        Expand All Instances +        </button> +        <button type="button" +                onClick={collapseAllInstances}> +        Collapse All Instances +        </button> +      </div> +      <div> +        <input type="checkbox" +               id="default-collapse-instances" +               name="default-instance-state" +               checked={globalConfig.defaultInstanceState===DefaultState.Closed} +               onClick={defaultCollapseOnClick}></input> + +        <span>Collapse All Instances By Default</span> +      </div> +      <div> +        <input type="checkbox" +               id="remember-toggles" +               name="remember-toggles" +               checked={globalConfig.rememberToggles} +               onClick={setRememberToggles}></input> +        <label for="remember-toggles">Remember Manually Collapsed/Expanded Instances</label> +      </div> +    </div>; +} + +interface HTMLDetailsElement extends HTMLElement { +  open: boolean +} + +interface DetailsInfo { +  element: HTMLDetailsElement +  // Here 'toggles' is the list of all elements of class +  // 'details-toggle-control' that control toggling 'element'. I +  // believe this list is always length zero or one. +  toggles: HTMLElement[] +} + +// Mapping from <details> elements to their info. +const detailsRegistry: { [id: string]: DetailsInfo } = {}; + +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; +} + +// Return true iff instance lists are open by default. +function getDefaultOpenSetting(): boolean { +  return globalConfig.defaultInstanceState == DefaultState.Open; +} + +// Event handler for "toggle" events, which are triggered when a +// <details> element's "open" property changes.  We don't deal with +// any config stuff here, because we only change configs in response +// to mouse clicks. In contrast, for example, this event is triggred +// automatically once for every <details> element when the user clicks +// the "collapse all elements" button. +function onToggleEvent(ev: Event) { +  const element = ev.target as HTMLDetailsElement; +  const id = element.id; +  const info = lookupDetailsRegistry(id); +  const isOpen = info.element.open; +  // Update the CSS of the toggle element users can click on to toggle +  // 'element'. The "collapser" and "expander" classes control what +  // kind of arrow appears next to the 'toggle' element. +  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'); +    } +  } +} + +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, +        toggles: [] // Populated later by 'initCollapseToggles'. +      }; +      el.addEventListener('toggle', onToggleEvent); +    } +  } +} + +// Return the id of the <details> element that the given 'toggle' +// element toggles. +function getDataDetailsId(toggle: Element): string { +  const id = toggle.getAttribute('data-details-id'); +  if (!id) { throw new Error("element with class " + toggle + " has no 'data-details-id' attribute!"); } +  return id; +} + +// Toggle the "open" state of a <details> element when that element's +// toggle element is clicked. +function toggleDetails(toggle: Element) { +  const id = getDataDetailsId(toggle); +  const {element} = lookupDetailsRegistry(id); +  element.open = !element.open; +} + +// Prefix for local keys used with local storage. Idea is that other +// modules could also use local storage with a different prefix and we +// wouldn't step on each other's toes. +// +// NOTE: we're using the browser's "local storage" API via the +// 'localStorage' object to store both "local" (to the current Haddock +// page) and "global" (across all Haddock pages) configuration. Be +// aware of these two different uses of the term "local". +const localStoragePrefix: string = "local-details-config:"; + +// Local storage key for the current page. +function localStorageKey(): string { +  return localStoragePrefix + document.location.pathname; +} + +// Clear all local storage related to instance list configs. +function clearLocalStorage() { +  const keysToDelete: string[] = []; +  for (var i = 0; i < localStorage.length; ++i) { +    const key = localStorage.key(i); +    if (key !== null && key.startsWith(localStoragePrefix)) { +      keysToDelete.push(key); +    } +  } +  keysToDelete.forEach(key => { +    localStorage.removeItem(key); +  }); +} + +// Compute and save the set of instance list ids that aren't in the +// default state. +function storeLocalConfig() { +  if (!globalConfig.rememberToggles) return; +  const instanceListToggles: HTMLElement[] = +    // Restrict to 'details-toggle' elements for "instances" +    // *plural*. These are the toggles that control instance lists and +    // not the list of methods for individual instances. +    Array.prototype.slice.call(document.getElementsByClassName( +      'instances details-toggle details-toggle-control')); +  const nonDefaultInstanceListIds: string[] = []; +  instanceListToggles.forEach(toggle => { +    const id = getDataDetailsId(toggle); +    const details = document.getElementById(id) as HTMLDetailsElement; +    if (details.open != getDefaultOpenSetting()) { +      nonDefaultInstanceListIds.push(id); +    } +  }); + +  const json = JSON.stringify(nonDefaultInstanceListIds); +  try { +    // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions. +    localStorage.setItem(localStorageKey(), json); +  } catch (e) {} +} + +function putInstanceListsInDefaultState() { +  switch (globalConfig.defaultInstanceState) { +    case DefaultState.Closed: _collapseAllInstances(true); break; +    case DefaultState.Open: _collapseAllInstances(false); break; +    default: break; +  } +} + +// Expand and collapse instance lists according to global and local +// config. +function restoreToggled() { +  loadGlobalConfig(); +  putInstanceListsInDefaultState(); +  if (!globalConfig.rememberToggles) { return; } +  const local = localStorage.getItem(localStorageKey()); +  if (!local) { return; } +  try { +    const nonDefaultInstanceListIds: string[] = JSON.parse(local); +    nonDefaultInstanceListIds.forEach(id => { +      const info = lookupDetailsRegistry(id); +      info.element.open = ! getDefaultOpenSetting(); +    }); +  } catch(e) { +    // Gracefully handle errors related to changed config format. +    if (e instanceof SyntaxError || e instanceof TypeError) { +      localStorage.removeItem(localStorageKey()); +    } else { +      throw e; +    } +  } +} + +// Handler for clicking on the "toggle" element that toggles the +// <details> element with id given by the 'data-details-id' property +// of the "toggle" element. +function onToggleClick(ev: MouseEvent) { +  ev.preventDefault(); +  const toggle = ev.currentTarget as HTMLElement; +  toggleDetails(toggle); +  storeLocalConfig(); +} + +// Set event handlers on elements responsible for expanding and +// collapsing <details> elements. +// +// This applies to all 'details-toggle's, not just to to top-level +// 'details-toggle's that control instance lists. +function initCollapseToggles() { +  const toggles: HTMLElement[] = Array.prototype.slice.call(document.getElementsByClassName('details-toggle')); +  toggles.forEach(toggle => { +    const id = getDataDetailsId(toggle); +    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'); +    } +  }); +} + +// Collapse or expand all instances. +function _collapseAllInstances(collapse: boolean) { +  const ilists = document.getElementsByClassName('subs instances'); +  [].forEach.call(ilists, function (ilist : Element) { +    const toggleType = collapse ? 'collapser' : 'expander'; +    const toggle = ilist.getElementsByClassName('instances ' + toggleType)[0]; +    if (toggle) { +      toggleDetails(toggle); +    } +  }); +} + +function collapseAllInstances() { +  _collapseAllInstances(true); +  storeLocalConfig(); +} + +function expandAllInstances() { +  _collapseAllInstances(false); +  storeLocalConfig(); +} + +export function init(showHide?: (action: () => void) => void) { +  gatherDetailsElements(); +  initCollapseToggles(); +  restoreToggled(); +  preact.render( +    <Preferences showHideTrigger={showHide || addPreferencesButton} />, +    document.body +  ); +} diff --git a/haddock-api/resources/html/js-src/init.ts b/haddock-api/resources/html/js-src/init.ts index 877874ae..1bfa8b3c 100644 --- a/haddock-api/resources/html/js-src/init.ts +++ b/haddock-api/resources/html/js-src/init.ts @@ -17,6 +17,6 @@ function onDomReady(callback: () => void) {  onDomReady(() => {    document.body.classList.add('js-enabled');    styleMenu.init(); -  detailsHelper.init();    quickJump.init(); -});
\ No newline at end of file +  detailsHelper.init(); +}); diff --git a/haddock-api/resources/html/js-src/style-menu.tsx b/haddock-api/resources/html/js-src/style-menu.tsx index bab840ca..2eb8344e 100644 --- a/haddock-api/resources/html/js-src/style-menu.tsx +++ b/haddock-api/resources/html/js-src/style-menu.tsx @@ -4,91 +4,14 @@ import {getCookie, setCookie, clearCookie} from "./cookies";  import preact = require("preact");  const { h, Component } = preact; -const rspace = /\s\s+/g, -    rtrim = /^\s+|\s+$/g; - -function spaced(s: string) { return (" " + s + " ").replace(rspace, " "); } -function trim(s: string)   { return s.replace(rtrim, ""); } - -function hasClass(elem: Element, value: string) { -  const className = spaced(elem.className || ""); -  return className.indexOf( " " + value + " " ) >= 0; -} - -function addClass(elem: Element, value: string) { -  const className = spaced(elem.className || ""); -  if ( className.indexOf( " " + value + " " ) < 0 ) { -    elem.className = trim(className + " " + value); -  } -} - -function removeClass(elem: Element, value: string) { -  let className = spaced(elem.className || ""); -  className = className.replace(" " + value + " ", " "); -  elem.className = trim(className); -} - -function toggleClass(elem: Element, valueOn: string, valueOff: string, bool?: boolean): boolean { -  if (bool == null) { bool = ! hasClass(elem, valueOn); } -  if (bool) { -    removeClass(elem, valueOff); -    addClass(elem, valueOn); -  } -  else { -    removeClass(elem, valueOn); -    addClass(elem, valueOff); -  } -  return bool; -} - -function makeClassToggle(valueOn: string, valueOff: string): (elem: Element, bool?: boolean) => boolean { -  return function(elem, bool) { -    return toggleClass(elem, valueOn, valueOff, bool); -  } -} - -const toggleShow = makeClassToggle("show", "hide"); +// Get all of the styles that are available  function styles(): HTMLLinkElement[] {    const es = Array.prototype.slice.call(document.getElementsByTagName("link"));    return es.filter((a: HTMLLinkElement) => a.rel.indexOf("style") != -1 && a.title);  } -class StyleMenuButton extends Component<any, any> { - -  render(props: { stys: string[] }) { -    function action() { -      styleMenu(); -      return false; -    }; - -    return <li><div id='style-menu-holder' onClick={action}> -      <a href='#'>Style ▾</a> -      <ul id='style-menu' class='hide'> -        {props.stys.map((sty) => { -              function action() { -                setActiveStyleSheet(sty); -                return false; -              }; - -              return <li><a href='#' onClick={action}>{sty}</a></li>; -        })} -      </ul> -    </div></li>; -  } - -} - -function addStyleMenu() { -  const stys = styles().map((s) => s.title); -  if (stys.length > 1) { -    const pageMenu = document.querySelector('#page-menu') as HTMLUListElement; -    const dummy = document.createElement('li'); -    pageMenu.appendChild(dummy); -    preact.render(<StyleMenuButton stys={stys} title="Style"/>, pageMenu, dummy); -  } -} - +// Set a style (including setting the cookie)  function setActiveStyleSheet(title: string) {    const as = styles();    let found: null | HTMLLinkElement = null; @@ -110,17 +33,103 @@ function setActiveStyleSheet(title: string) {    }  } +// Reset the style based on the cookie  function resetStyle() {    const s = getCookie("haddock-style");    if (s) setActiveStyleSheet(s);  } -function styleMenu(show?: boolean) { -  const m = document.getElementById('style-menu'); -  if (m) toggleShow(m, show); +class StylesButton extends Component<any, any> { +  render(props: { title: string, onClick: () => void }) { +    function onClick(e: Event) { +      e.preventDefault(); +      props.onClick(); +    } +    return <li><a href="#" onClick={onClick}>{props.title}</a></li>; +  }  } -export function init() { -  addStyleMenu(); +// Add the style menu button +function addStyleMenu(stys: string[], action: () => void) { +  if (stys.length > 1) { +    const pageMenu = document.querySelector('#page-menu') as HTMLUListElement; +    const dummy = document.createElement('li'); +    pageMenu.appendChild(dummy); +    preact.render(<StylesButton onClick={action} title="Styles"/>, pageMenu, dummy); +  } +} + +type StyleProps = { +  styles: string[] +  showHideTrigger: (action: () => void) => void +} + +type StyleState = { +  isVisible: boolean +} + +// Represents the full style dropdown +class Styles extends Component<StyleProps, StyleState> { + +  componentWillMount() { +    document.addEventListener('mousedown', this.hide.bind(this)); + +    document.addEventListener('keydown', (e) => { +      if (this.state.isVisible) { +        if (e.key === 'Escape') { +          this.hide(); +        } +      } +    }) +  } + +  hide() { +    this.setState({ isVisible: false }); +  } + +  show() { +    if (!this.state.isVisible) { +      this.setState({ isVisible: true }); +    } +  } + +  toggleVisibility() { +    if (this.state.isVisible) { +      this.hide(); +    } else { +      this.show(); +    } +  } + +  componentDidMount() { +    this.props.showHideTrigger(this.toggleVisibility.bind(this)); +  } + +  render(props: StyleProps, state: StyleState) { +    const stopPropagation = (e: Event) => { e.stopPropagation(); }; + +    return <div id="style" class={state.isVisible ? '' : 'hidden'}> +        <div id="style-menu" class="dropdown-menu" onMouseDown={stopPropagation}> +          { +            props.styles.map((sty) => +              <button type="button" +                      onClick={(e) => { this.hide(); setActiveStyleSheet(sty) }}> +                {sty} +              </button> +            ) +          } +        </div> +      </div>; +  } +} + + +export function init(showHide?: (action: () => void) => void) { +  const stys = styles().map((s) => s.title); +  const addStylesButton = (action: () => void) => addStyleMenu(stys, action)    resetStyle(); +  preact.render( +    <Styles showHideTrigger={showHide || addStylesButton} styles={stys} />, +    document.body +  );  } | 
