/* globals marked, unfetch, ES6Promise, Promise */ // eslint-disable-line no-redeclare if (!window.Promise) { window.Promise = ES6Promise; } if (!window.fetch) { window.fetch = unfetch; } onunhandledrejection = function(e) { throw e.reason; }; var $loadingElem = document.querySelector('#loading'); var $mainElem = document.querySelector('#main'); var $markdownElem = document.querySelector('#markdown'); var $markedVerElem = document.querySelector('#markedVersion'); var $commitVerElem = document.querySelector('#commitVersion'); var $markedVer = document.querySelector('#markedCdn'); var $optionsElem = document.querySelector('#options'); var $outputTypeElem = document.querySelector('#outputType'); var $inputTypeElem = document.querySelector('#inputType'); var $responseTimeElem = document.querySelector('#responseTime'); var $previewElem = document.querySelector('#preview'); var $previewIframe = document.querySelector('#preview iframe'); var $permalinkElem = document.querySelector('#permalink'); var $clearElem = document.querySelector('#clear'); var $htmlElem = document.querySelector('#html'); var $lexerElem = document.querySelector('#lexer'); var $panes = document.querySelectorAll('.pane'); var $inputPanes = document.querySelectorAll('.inputPane'); var lastInput = ''; var inputDirty = true; var $activeOutputElem = null; var search = searchToObject(); var markedVersions = { master: 'https://cdn.jsdelivr.net/gh/markedjs/marked/lib/marked.js' }; var markedVersionCache = {}; var delayTime = 1; var checkChangeTimeout = null; var markedWorker; $previewIframe.addEventListener('load', handleIframeLoad); $outputTypeElem.addEventListener('change', handleOutputChange, false); $inputTypeElem.addEventListener('change', handleInputChange, false); $markedVerElem.addEventListener('change', handleVersionChange, false); $markdownElem.addEventListener('change', handleInput, false); $markdownElem.addEventListener('keyup', handleInput, false); $markdownElem.addEventListener('keypress', handleInput, false); $markdownElem.addEventListener('keydown', handleInput, false); $optionsElem.addEventListener('change', handleInput, false); $optionsElem.addEventListener('keyup', handleInput, false); $optionsElem.addEventListener('keypress', handleInput, false); $optionsElem.addEventListener('keydown', handleInput, false); $commitVerElem.style.display = 'none'; $commitVerElem.addEventListener('keypress', handleAddVersion, false); $clearElem.addEventListener('click', handleClearClick, false); Promise.all([ setInitialQuickref(), setInitialOutputType(), setInitialText(), setInitialVersion() .then(setInitialOptions) ]).then(function() { handleInputChange(); handleOutputChange(); checkForChanges(); setScrollPercent(0); $loadingElem.style.display = 'none'; $mainElem.style.display = 'block'; }); function setInitialText() { if ('text' in search) { $markdownElem.value = search.text; } else { return fetch('./initial.md') .then(function(res) { return res.text(); }) .then(function(text) { if ($markdownElem.value === '') { $markdownElem.value = text; } }); } } function setInitialQuickref() { return fetch('./quickref.md') .then(function(res) { return res.text(); }) .then(function(text) { document.querySelector('#quickref').value = text; }); } function setInitialVersion() { return fetch('https://data.jsdelivr.com/v1/package/npm/marked') .then(function(res) { return res.json(); }) .then(function(json) { for (var i = 0; i < json.versions.length; i++) { var ver = json.versions[i]; markedVersions[ver] = 'https://cdn.jsdelivr.net/npm/marked@' + ver + '/lib/marked.js'; var opt = document.createElement('option'); opt.textContent = ver; opt.value = ver; $markedVerElem.appendChild(opt); } }) .then(function() { return fetch('https://api.github.com/repos/markedjs/marked/commits') .then(function(res) { return res.json(); }) .then(function(json) { markedVersions.master = 'https://cdn.jsdelivr.net/gh/markedjs/marked@' + json[0].sha + '/lib/marked.js'; }) .catch(function() { // do nothing // uses url without commit }); }) .then(function() { if (search.version) { if (markedVersions[search.version]) { return search.version; } else { var match = search.version.match(/^(\w+):(.+)$/); if (match) { switch (match[1]) { case 'commit': addCommitVersion(search.version, match[2].substring(0, 7), match[2]); return search.version; case 'pr': return getPrCommit(match[2]) .then(function(commit) { if (!commit) { return 'master'; } addCommitVersion(search.version, 'PR #' + match[2], commit); return search.version; }); } } } } return 'master'; }) .then(function(version) { $markedVerElem.value = version; }) .then(updateVersion); } function setInitialOptions() { if ('options' in search) { $optionsElem.value = search.options; } else { setDefaultOptions(); } } function setInitialOutputType() { if (search.outputType) { $outputTypeElem.value = search.outputType; } } function handleIframeLoad() { lastInput = ''; inputDirty = true; } function handleInput() { inputDirty = true; }; function handleVersionChange() { if ($markedVerElem.value === 'commit' || $markedVerElem.value === 'pr') { $commitVerElem.style.display = ''; } else { $commitVerElem.style.display = 'none'; updateVersion(); } } function handleClearClick() { $markdownElem.value = ''; $markedVerElem.value = 'master'; $commitVerElem.style.display = 'none'; updateVersion().then(setDefaultOptions); } function handleAddVersion(e) { if (e.which === 13) { switch ($markedVerElem.value) { case 'commit': var commit = $commitVerElem.value.toLowerCase(); if (!commit.match(/^[0-9a-f]{40}$/)) { alert('That is not a valid commit'); return; } addCommitVersion('commit:' + commit, commit.substring(0, 7), commit); $markedVerElem.value = 'commit:' + commit; $commitVerElem.style.display = 'none'; $commitVerElem.value = ''; updateVersion(); break; case 'pr': $commitVerElem.disabled = true; var pr = $commitVerElem.value.replace(/\D/g, ''); getPrCommit(pr) .then(function(commit) { $commitVerElem.disabled = false; if (!commit) { alert('That is not a valid PR'); return; } addCommitVersion('pr:' + pr, 'PR #' + pr, commit); $markedVerElem.value = 'pr:' + pr; $commitVerElem.style.display = 'none'; $commitVerElem.value = ''; updateVersion(); }); } } } function handleInputChange() { handleChange($inputPanes, $inputTypeElem.value); } function handleOutputChange() { $activeOutputElem = handleChange($panes, $outputTypeElem.value); updateLink(); } function handleChange(panes, visiblePane) { var active = null; for (var i = 0; i < panes.length; i++) { if (panes[i].id === visiblePane) { panes[i].style.display = ''; active = panes[i]; } else { panes[i].style.display = 'none'; } } return active; }; function addCommitVersion(value, text, commit) { if (markedVersions[value]) { return; } markedVersions[value] = 'https://cdn.jsdelivr.net/gh/markedjs/marked@' + commit + '/lib/marked.js'; var opt = document.createElement('option'); opt.textContent = text; opt.value = value; $markedVerElem.insertBefore(opt, $markedVerElem.firstChild); } function getPrCommit(pr) { return fetch('https://api.github.com/repos/markedjs/marked/pulls/' + pr + '/commits') .then(function(res) { return res.json(); }) .then(function(json) { return json[json.length - 1].sha; }).catch(function() { // return undefined }); } function setDefaultOptions() { if (window.Worker) { messageWorker({ task: 'defaults', version: markedVersions[$markedVerElem.value] }); } else { var defaults = marked.getDefaults(); setOptions(defaults); } } function setOptions(opts) { $optionsElem.value = JSON.stringify( opts, function(key, value) { if (value && typeof value === 'object' && Object.getPrototypeOf(value) !== Object.prototype) { return undefined; } return value; }, ' '); } function searchToObject() { // modified from https://stackoverflow.com/a/7090123/806777 var pairs = location.search.slice(1).split('&'); var obj = {}; for (var i = 0; i < pairs.length; i++) { if (pairs[i] === '') { continue; } var pair = pairs[i].split('='); obj[decodeURIComponent(pair.shift())] = decodeURIComponent(pair.join('=')); } return obj; } function jsonString(input) { var output = (input + '') .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') .replace(/\t/g, '\\t') .replace(/\f/g, '\\f') .replace(/[\\"']/g, '\\$&') .replace(/\u0000/g, '\\0'); return '"' + output + '"'; }; function getScrollSize() { var e = $activeOutputElem; return e.scrollHeight - e.clientHeight; }; function getScrollPercent() { var size = getScrollSize(); if (size <= 0) { return 1; } return $activeOutputElem.scrollTop / size; }; function setScrollPercent(percent) { $activeOutputElem.scrollTop = percent * getScrollSize(); }; function updateLink() { var outputType = ''; if ($outputTypeElem.value !== 'preview') { outputType = 'outputType=' + $outputTypeElem.value + '&'; } $permalinkElem.href = '?' + outputType + 'text=' + encodeURIComponent($markdownElem.value) + '&options=' + encodeURIComponent($optionsElem.value) + '&version=' + encodeURIComponent($markedVerElem.value); history.replaceState('', document.title, $permalinkElem.href); } function updateVersion() { if (window.Worker) { handleInput(); return Promise.resolve(); } var promise; if (markedVersionCache[$markedVerElem.value]) { promise = Promise.resolve(markedVersionCache[$markedVerElem.value]); } else { promise = fetch(markedVersions[$markedVerElem.value]) .then(function(res) { return res.text(); }) .then(function(text) { markedVersionCache[$markedVerElem.value] = text; return text; }); } return promise.then(function(text) { var script = document.createElement('script'); script.textContent = text; $markedVer.parentNode.replaceChild(script, $markedVer); $markedVer = script; }).then(handleInput); } function checkForChanges() { if (inputDirty && $markedVerElem.value !== 'commit' && $markedVerElem.value !== 'pr' && (typeof marked !== 'undefined' || window.Worker)) { inputDirty = false; updateLink(); var options = {}; var optionsString = $optionsElem.value || '{}'; try { var newOptions = JSON.parse(optionsString); options = newOptions; $optionsElem.classList.remove('error'); } catch (err) { $optionsElem.classList.add('error'); } var version = markedVersions[$markedVerElem.value]; var markdown = $markdownElem.value; var hash = version + markdown + optionsString; if (lastInput !== hash) { lastInput = hash; if (window.Worker) { delayTime = 100; messageWorker({ task: 'parse', version: version, markdown: markdown, options: options }); } else { var startTime = new Date(); var lexed = marked.lexer(markdown, options); var lexedList = []; for (var i = 0; i < lexed.length; i++) { var lexedLine = []; for (var j in lexed[i]) { lexedLine.push(j + ':' + jsonString(lexed[i][j])); } lexedList.push('{' + lexedLine.join(', ') + '}'); } var parsed = marked.parser(lexed, options); var scrollPercent = getScrollPercent(); setParsed(parsed, lexedList.join('\n')); setScrollPercent(scrollPercent); var endTime = new Date(); delayTime = endTime - startTime; setResponseTime(delayTime); if (delayTime < 50) { delayTime = 50; } else if (delayTime > 500) { delayTime = 1000; } } } } checkChangeTimeout = window.setTimeout(checkForChanges, delayTime); }; function setResponseTime(ms) { var amount = ms; var suffix = 'ms'; if (ms > 1000 * 60 * 60) { amount = 'Too Long'; suffix = ''; } else if (ms > 1000 * 60) { amount = '>' + Math.floor(ms / (1000 * 60)); suffix = 'm'; } else if (ms > 1000) { amount = '>' + Math.floor(ms / 1000); suffix = 's'; } $responseTimeElem.textContent = amount + suffix; } function setParsed(parsed, lexed) { try { $previewIframe.contentDocument.body.innerHTML = parsed; } catch (ex) {} $htmlElem.value = parsed; $lexerElem.value = lexed; } function messageWorker(message) { if (!markedWorker || markedWorker.working) { if (markedWorker) { clearTimeout(markedWorker.timeout); markedWorker.terminate(); } markedWorker = new Worker('worker.js'); markedWorker.onmessage = function(e) { clearTimeout(markedWorker.timeout); markedWorker.working = false; switch (e.data.task) { case 'defaults': setOptions(e.data.defaults); break; case 'parse': $previewElem.classList.remove('error'); $htmlElem.classList.remove('error'); $lexerElem.classList.remove('error'); var scrollPercent = getScrollPercent(); setParsed(e.data.parsed, e.data.lexed); setScrollPercent(scrollPercent); setResponseTime(e.data.time); break; } clearTimeout(checkChangeTimeout); delayTime = 10; checkForChanges(); }; markedWorker.onerror = markedWorker.onmessageerror = function(err) { clearTimeout(markedWorker.timeout); var error = 'There was an error in the Worker'; if (err) { if (err.message) { error = err.message; } else { error = err; } } error = error.replace(/^Uncaught Error: /, ''); $previewElem.classList.add('error'); $htmlElem.classList.add('error'); $lexerElem.classList.add('error'); setParsed(error, error); setScrollPercent(0); }; } if (message.task !== 'defaults') { markedWorker.working = true; workerTimeout(0); } markedWorker.postMessage(message); } function workerTimeout(seconds) { markedWorker.timeout = setTimeout(function() { seconds++; markedWorker.onerror('Marked has taken longer than ' + seconds + ' second' + (seconds > 1 ? 's' : '') + ' to respond...'); workerTimeout(seconds); }, 1000); }