UNPKG

smartdown

Version:

A library for translating, rendering and interacting with Smartdown documents. Smartdown is an extension of Markdown that provides richer media support and reactive programming capability.

546 lines (473 loc) 17.6 kB
/* 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;