Source: starter.js

/* global smartdown */
/* global smartdownBaseURL */
/* global smartdownDefaultHome */
/* global smartdownGistPathPrefix */
/* global smartdownGistHashPrefix */
/* global smartdownResourceURL */
/* global smartdownRawPrefix */
/* global smartdownOutputDivSelector */
/* global smartdownPostLoadMutator */
/* global smartdownMedia */

let currentThemeName = '';

function scrollToSubHash(cardKeySubhash) {
  let scrollToTop = true;

  if (cardKeySubhash) {
    const target = document.getElementById(cardKeySubhash);
    if (target) {
      scrollToTop = false;
      window.setTimeout(() => {
        target.scrollIntoView({
          behavior: 'smooth',
        });
      }, 300);
    }
  }

  if (scrollToTop) {
    window.scrollTo({top: 0, behavior: 'smooth'});

    // document.body.scrollTop = 0; // For Chrome, Safari and Opera
    // document.documentElement.scrollTop = 0; // For IE and Firefox
  }
}


/**
 * A convenient way to initialize Smartdown with common defaults.
 *
 * Although smartdown.configure() can be used directly by certain applications,
 * for many of the common Smartdown examples, this starter.js file can be used
 * to invoke smartdown.configure() with credible default behavior, and the ability
 * to customize this behavior to a reasonable degree.
 *
 * @param {object} [basePrefix=undefined] - Configuration options
 *
 * @example
 * // Use the smartdown/starter.js convenience wrapper to initialize smartdown.
 * // See smartdown/src/SimpleSiteExample/ for usage within an index.html.
 * <script src="lib/starter.js">< /script>
 * <script>
 *   window.smartdownResourceURL = '';
 *   window.smartdownBaseURL = '/';
 *   window.smartdownStarter();
 * < /script>
 */


function starter(basePrefix, doneHandler) {
  let defaultHome = 'Home';
  let baseURL = 'https://unpkg.com/smartdown/dist/';
  let resourceURL = baseURL + 'lib/resources/';
  let rawPrefix = window.location.origin + window.location.pathname;
  let gistPathPrefix = '';
  let gistHashPrefix = 'gist/';
  let outputDivSelector = '#smartdown-output';
  let postLoadMutator = null;
  let media = {
    cloud: '/gallery/resources/cloud.jpg',
    badge: '/gallery/resources/badge.svg',
    hypercube: '/gallery/resources/Hypercube.svg',
    StalactiteStalagmite: '/gallery/resources/StalactiteStalagmite.svg',
    church: '/gallery/resources/church.svg',
    lighthouse: '/gallery/resources/lighthouse.svg',
    barn: '/gallery/resources/barn.svg',
    'medieval-gate': '/gallery/resources/medieval-gate.svg'
  };
  let multiparts = {};
  let rootHash = '';
  let gistOrg = '';
  let gistID = '';


  if (typeof smartdownBaseURL === 'string') {
    baseURL = smartdownBaseURL;
  }
  if (typeof smartdownResourceURL === 'string') {
    resourceURL = smartdownResourceURL;
  }
  if (typeof smartdownDefaultHome === 'string') {
    defaultHome = smartdownDefaultHome;
  }
  if (typeof smartdownGistPathPrefix === 'string') {
    gistPathPrefix = smartdownGistPathPrefix;
  }
  if (typeof smartdownGistHashPrefix === 'string') {
    gistHashPrefix = smartdownGistHashPrefix;
  }
  if (typeof smartdownRawPrefix === 'string') {
    rawPrefix = smartdownRawPrefix;
  }
  if (typeof smartdownOutputDivSelector === 'string') {
    outputDivSelector = smartdownOutputDivSelector;
  }
  if (typeof smartdownPostLoadMutator === 'function') {
    postLoadMutator = smartdownPostLoadMutator;
  }

  if (typeof smartdownMedia === 'object') {
    media = Object.assign(media, smartdownMedia);
  }

  let lastLoadedRawPrefix = rawPrefix;

  /* Common code above between inline/blocks helpers */

  function cardLoaded(sourceText, cardKey, cardURL, outputDivId, cardKeySubhash) {
    // console.log('cardLoaded', cardURL, cardKey, cardKeySubhash);
    if (postLoadMutator) {
      sourceText = postLoadMutator(sourceText, cardKey, cardURL, defaultHome);
    }
    multiparts = smartdown.partitionMultipart(sourceText);
    const output = document.getElementById(outputDivId);
    rootHash = '#' + cardKey;
    if (lastLoadedRawPrefix !== rawPrefix) {
      rootHash = '#' + cardURL;
      // console.log('rootHash', rootHash);
    }

    let search = window.location.search;
    if (currentThemeName !== '') {
      search = `?theme=${currentThemeName}`;
    }

    let defaultPart = '_default_';
    if (cardKey.indexOf('http:') !== 0 &&
        cardKey.indexOf('https:') !== 0 &&
        cardKey.indexOf(':') >= 0) {
      const keyParts = cardKey.split(':');
      defaultPart = keyParts[keyParts.length - 1];
    }

    // console.log(sourceText);
    // console.log('defaultPart', defaultPart);
    // console.log(JSON.stringify(multiparts, null, 2));
    smartdown.setHome(multiparts[defaultPart], output, function() {
      let newHash = '#' + cardKey;

      if (cardKeySubhash && cardKeySubhash !== 'undefined') {
        newHash += '#' + cardKeySubhash;
      }

      // console.log('newHash ignored', newHash, cardKeySubhash, window.location.pathname);
      window.history.pushState({}, '', window.location.pathname + newHash + search);
      scrollToSubHash(cardKeySubhash);

      if (!output.id) {
        output.id = 'smartdown-output-' + String(Math.random()).slice(2);
      }
      smartdown.startAutoplay(output);
    });
  }

  function loadAsyncCard(cardKey, cardURL, outputDivId, cardKeySubhash) {
    // console.log('loadAsyncCard', cardKey, cardURL, cardKeySubhash, rootHash, outputDivId);

    const oReq = new XMLHttpRequest();
    oReq.addEventListener('load', function() {
      cardLoaded(this.responseText, cardKey, cardURL, outputDivId, cardKeySubhash);
    });
    oReq.open('GET', cardURL);
    oReq.send();
  }


  function relativeCardLoader(cardKeyWithHash, outputDivId) {
    // console.log('relativeCardLoader', rootHash, cardKeyWithHash);

    let cardKey = null;
    let cardKeySubhash = null;
    if (cardKeyWithHash.indexOf('#') !== 0) {
      cardKeyWithHash = '#' + cardKeyWithHash;
    }

    const cardKeyHashParts = cardKeyWithHash.split('#');

    if (cardKeyHashParts.length > 1) {
      cardKey = cardKeyHashParts[1];
      cardKeySubhash = cardKeyHashParts[2];
      if (cardKeySubhash) {
        cardKeySubhash = cardKeySubhash.split('?')[0];
      }

      if (rootHash !== '' && ('#' + cardKey) === rootHash) {
        window.history.replaceState({}, '', window.location.pathname + cardKeyWithHash);
        scrollToSubHash(cardKeySubhash);

        return;
      }
      // else
      // {
      //   // console.log('empty roothash', cardKeyWithHash, cardKey, rootHash, cardKeySubhash);
      // }
    }
    else {
      console.log('... illformatted cardKeyWithHash', cardKeyWithHash);
    }

    // console.log('# cardKey', cardKey);
    // console.log('# lastLoadedRawPrefix', lastLoadedRawPrefix);
    // console.log('# gistPathPrefix', gistPathPrefix);
    // console.log('# gistHashPrefix', gistHashPrefix);
    // console.log('# window.location.pathname', window.location.pathname);
    // const re = '^/?(' + gistPathPrefix + ')?' + gistHashPrefix + '([^/]+)/([^/]+)(/(\\w*))?$';
    const re = '^/?(' + gistPathPrefix + ')?' + gistHashPrefix + '([^/]+)/([^/]+)(/(\\w*))?$';
    // const re = `^/?(${gistPathPrefix})?${gistHashPrefix}([^/]+)/([^/]+)(/(\\w*))?$`;
    const gistRE = new RegExp(re, 'g');
    const match = gistRE.exec(cardKey);
    if (match) {
      // console.log('#re match', cardKey, re, window.location.pathname, match);
      gistOrg = match[2];
      gistID = match[3];
      const newCardKey = match[5] || 'Home';
      // console.log('cardKey', cardKey, gistOrg, gistID, newCardKey);
      cardKey = newCardKey;
    }

    const part = multiparts[cardKey];
    if (part) {
      const output = document.querySelectorAll(outputDivSelector)[0];
      smartdown.setHome(part, output, function() {
        if (!output.id) {
          output.id = 'smartdown-output-' + String(Math.random()).slice(2);
        }
        smartdown.startAutoplay(output);
      });
    }
    else if (cardKey.indexOf('http') === 0) {
      gistOrg = '';
      gistID = '';
      const endOfPath = cardKey.lastIndexOf('/');
      if (endOfPath > 0) {
        lastLoadedRawPrefix = cardKey.slice(0, endOfPath + 1);
      }
      loadAsyncCard(cardKey, cardKey, outputDivId, cardKeySubhash);
    }
    else if (cardKey.indexOf('/') === 0) {
      gistOrg = '';
      gistID = '';
      lastLoadedRawPrefix = rawPrefix;
      // console.log('...lastLoadedRawPrefix2', lastLoadedRawPrefix);
      loadAsyncCard(cardKey, cardKey, outputDivId);
    }
    else if (gistOrg !== '' && gistID !== '') {
      const gistAPIBase = 'https://api.github.com/gists/' + gistID;
      // console.log('gistAPIBase', gistAPIBase);

      const oReq = new XMLHttpRequest();
      oReq.addEventListener('load', function() {
        const gistResponse = JSON.parse(this.responseText);
        // console.log('gist Response', gistResponse);
        const gistFile = gistResponse.files[cardKey + '.md'];
        if (!gistFile) {
          console.log('Unable to locate Gist for "', cardKey, '" ', gistAPIBase);
        }
        else {
          // console.log('gistFile', gistFile);
          const gistFileURL = gistFile.raw_url;
          cardKey = gistHashPrefix + gistOrg + '/' + gistID + '/' + cardKey;
          lastLoadedRawPrefix = 'https://gist.githubusercontent.com/' + gistOrg + '/' + gistID + '/raw/';

          loadAsyncCard(cardKey, gistFileURL, outputDivId, cardKeySubhash);
        }
      });
      oReq.open('GET', gistAPIBase);
      oReq.send();
    }
    else if (cardKey.indexOf(':') >= 0) {
      const keyParts = cardKey.split(':');
      const loadableKey = keyParts.slice(0, -1).join(':');
      const suffix = (loadableKey === '') ? '' : (loadableKey + '.md');
      const cardURL = lastLoadedRawPrefix + suffix;
      loadAsyncCard(cardKey, cardURL, outputDivId, cardKeySubhash);
    }
    else {
      gistOrg = '';
      gistID = '';
      const suffix = cardKey.endsWith('.md') ? '' : '.md';
      let cardURL = lastLoadedRawPrefix + cardKey + suffix;

      // Hack for Solid
      if (lastLoadedRawPrefix.endsWith('/public/smartdown/')) {
        const parts = lastLoadedRawPrefix.split('/');
        const minusLastPart = parts.slice(0, -2).join('/');
        cardURL = minusLastPart + '/' + cardKey + suffix;
      }

      loadAsyncCard(cardKey, cardURL, outputDivId, cardKeySubhash);
    }
  }

  function updateTheme(newThemeName) {
    const container = document.getElementById('smartdown-outer-container');
    if (container) {
      if (newThemeName !== '') {
        [...container.classList].forEach((c) => {
          if (c.indexOf('smartdown-theme-') === 0) {
            // console.log('updateTheme: Removing class ', c);
            container.classList.remove(c);
          }
        });
        container.classList.add(`smartdown-theme-${newThemeName}`);
      }
    }
  }

  function loadHome(baseHash) {
    let hash = window.location.hash;
    if (baseHash) {
      const hashElements = hash.split('/');
      const baseHashElements = baseHash.split('/');

      hash = baseHash;
      if (baseHashElements.length === 4 &&
          hashElements.length === 4) {
        baseHashElements[3] = hashElements[3];
        hash = baseHashElements.join('/');
      }
    }

    // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
    // https://localhost:4000/#Astronomy?theme=dark?theme=dark


    // console.log(searchParams.has('topic'));               // true
    // console.log(searchParams.get('topic') === "api");     // true
    // console.log(searchParams.getAll('topic'));            // ["api"]
    // console.log(searchParams.get('foo') === null);        // true
    // console.log(searchParams.append('topic', 'webdev'));

    let args = '';
    const argsPos = hash.indexOf('?');
    if (argsPos >= 0) {
      args = hash.slice(argsPos + 1);
      hash = hash.slice(0, argsPos);

      const extraArgPos = args.indexOf('?');

      if (extraArgPos >= 0) {
        args = args.slice(0, extraArgPos);
      }

      const argsParams = new URLSearchParams(args);
      currentThemeName = argsParams.get('theme') || '';
    }

    if (currentThemeName === '') {
      const search = window.location.search;
      const searchParams = new URLSearchParams(search);
      currentThemeName = searchParams.get('theme') || '';
    }

    updateTheme(currentThemeName);

    if (hash === '') {
      hash = defaultHome;
    }

    relativeCardLoader(hash, document.querySelectorAll(outputDivSelector)[0].id);
  }

  const calcHandlers = smartdown.defaultCalcHandlers;
  const replace = rawPrefix;
  function gistPrefix() {
    let result = lastLoadedRawPrefix;
    let hash = window.location.hash;
    // const args = '';
    const argsPos = hash.indexOf('?');
    if (argsPos >= 0) {
      // args = hash.slice(argsPos);
      hash = hash.slice(0, argsPos);
      // console.log('gistPrefix hashargs', hash, args, window.location.search);
    }

    if (gistPathPrefix.length > 0 && window.location.pathname.endsWith(gistPathPrefix)) {
      const re = '^/?(' + gistPathPrefix + ')?' + gistHashPrefix + '([^/]+)/([^/]+)(/(\\w*))?$';
      const gistRE = new RegExp(re, 'g');
      const match = gistRE.exec(hash);
      if (match) {
        const matchGistOrg = match[2].replace('#', '');
        const matchGistID = match[3];
        result = 'https://gist.githubusercontent.com/' + matchGistOrg + '/' + matchGistID + '/raw/';
      }
    }
    else if (gistHashPrefix.length > 0 && hash.indexOf('#' + gistHashPrefix) === 0) {
      const re = '^#' + gistHashPrefix + '([^/]+)/([^/]+)(/(\\w*))?$';
      const gistRE = new RegExp(re, 'g');
      const match = gistRE.exec(hash);
      if (match) {
        const matchGistOrg = match[1];
        const matchGistID = match[2];
        result = 'https://gist.githubusercontent.com/' + matchGistOrg + '/' + matchGistID + '/raw/';
      }
    }
    else if (hash.indexOf('#https://gist.githubusercontent.com/') === 0) {
      const re = '^#https://gist.githubusercontent.com/([^/]+)/([^/]+)/.*$';
      const gistRE = new RegExp(re, 'g');
      const match = gistRE.exec(hash);
      if (match) {
        const matchGistOrg = match[1];
        const matchGistID = match[2];
        result = 'https://gist.githubusercontent.com/' + matchGistOrg + '/' + matchGistID + '/raw/';
      }
    }

    // console.log('gistPrefix', result, lastLoadedRawPrefix, hash);

    return result;
  }


  const linkRules = [
    {
      prefix: '/block/',
      replace: gistPrefix
    },
    {
      prefix: 'block/',
      replace: gistPrefix
    },
    {
      prefix: 'assets/',
      replace: replace + 'assets/'
    },
    {
      prefix: '/assets/',
      replace: replace + 'assets/'
    },
    {
      prefix: 'content/',
      replace: replace + 'content/'
    },
    {
      prefix: '/content/',
      replace: replace + 'content/'
    },
    {
      prefix: '/gallery/resources/',
      replace: resourceURL === '' ? '/gallery/resources/' : resourceURL
    },
    {
      prefix: '/gallery/DataElements.csv',
      replace: baseURL === '/smartdown/' ? '/smartdown/gallery/DataElements.csv' : '/gallery/DataElements.csv'
    },
    {
      prefix: '/resources/',
      replace: resourceURL === '' ? '/resources/' : resourceURL
    },
  ];

  function locationHashChanged(event) {
    // console.log('#locationHashChanged', event, window.location.href, window.location.hash, JSON.stringify(window.location.search), window.location.pathname, rootHash);

    const oldURL = event.oldURL;
    const oldHashPos = oldURL.indexOf('#');
    const oldSearchPos = oldURL.indexOf('?');
    let oldSearch = '';
    if (oldSearchPos >= 0) {
      if (oldHashPos > oldSearchPos) {
        oldSearch = oldURL.slice(oldSearchPos, oldHashPos - 1);
      }
      else {
        oldSearch = oldURL.slice(oldSearchPos);
      }

      // console.log('oldURL', oldURL, oldSearch, oldHashPos, oldSearchPos);
    }

    let hash = window.location.hash;
    let args = '';
    const argsPos = hash.indexOf('?');
    if (argsPos >= 0) {
      args = hash.slice(argsPos + 1);
      hash = hash.slice(0, argsPos);
      if (args.indexOf('theme=') === 0) {
        currentThemeName = args.slice('theme='.length);
        updateTheme(currentThemeName);
      }
    }

    if (rootHash === hash) {
      console.log('...locationHashChanged INHIBIT', window.location.hash, hash, rootHash);
      scrollToSubHash();
    }
    else {
      let cardKey = hash.slice(1);

      if (cardKey === '') {
        cardKey = defaultHome;
      }
      else if (cardKey.indexOf('/') === -1) {
        gistOrg = '';
        gistID = '';
        cardKey = '#' + cardKey;
      }

      let cardKeySubhash = null;
      const cardKeyHashParts = cardKey.split('#');

      if (cardKeyHashParts.length > 1) {
        cardKey = cardKeyHashParts[1];
        cardKeySubhash = cardKeyHashParts[2];

        if (cardKey === '') {
          cardKey = rootHash;
        }

        cardKey = cardKey + '#' + cardKeySubhash + oldSearch;
      }

      relativeCardLoader(cardKey, document.querySelectorAll(outputDivSelector)[0].id);
      event.preventDefault();
      event.stopImmediatePropagation();
    }

    return false;
  }

  window.onhashchange = locationHashChanged;
  // window.onpopstate = locationHashChanged;

  function loadHomeDefault() {
    loadHome(basePrefix);
  }

  if (!doneHandler) {
    doneHandler = loadHomeDefault;
  }
  // console.log(JSON.stringify(media, null, 2));
  // console.log(JSON.stringify(linkRules, null, 2));
  smartdown.initialize(media, baseURL, doneHandler, relativeCardLoader, calcHandlers, linkRules);
}

window.smartdownStarter = starter;