marked/docs/demo/demo.js

569 lines
16 KiB
JavaScript
Raw Normal View History

2019-07-02 16:28:32 -05:00
/* globals marked, unfetch, ES6Promise, Promise */ // eslint-disable-line no-redeclare
2018-03-26 10:36:47 -05:00
if (!window.Promise) {
window.Promise = ES6Promise;
}
if (!window.fetch) {
window.fetch = unfetch;
}
2019-04-26 20:58:04 -05:00
onunhandledrejection = function(e) {
2019-02-13 12:16:39 -06:00
throw e.reason;
};
2019-02-14 11:25:01 -06:00
var $loadingElem = document.querySelector('#loading');
var $mainElem = document.querySelector('#main');
2018-10-18 00:19:48 -07:00
var $markdownElem = document.querySelector('#markdown');
2019-02-08 14:11:09 -06:00
var $markedVerElem = document.querySelector('#markedVersion');
2019-02-14 00:08:33 -06:00
var $commitVerElem = document.querySelector('#commitVersion');
var $markedVer = document.querySelector('#markedCdn');
2018-10-17 15:11:53 -07:00
var $optionsElem = document.querySelector('#options');
2018-03-26 10:36:47 -05:00
var $outputTypeElem = document.querySelector('#outputType');
2018-10-18 00:19:48 -07:00
var $inputTypeElem = document.querySelector('#inputType');
2019-02-13 14:18:15 -06:00
var $responseTimeElem = document.querySelector('#responseTime');
2019-02-13 12:16:39 -06:00
var $previewElem = document.querySelector('#preview');
2018-06-24 01:46:25 -05:00
var $previewIframe = document.querySelector('#preview iframe');
2018-03-28 11:24:14 -05:00
var $permalinkElem = document.querySelector('#permalink');
var $clearElem = document.querySelector('#clear');
2018-03-26 10:36:47 -05:00
var $htmlElem = document.querySelector('#html');
var $lexerElem = document.querySelector('#lexer');
var $panes = document.querySelectorAll('.pane');
2018-10-18 00:19:48 -07:00
var $inputPanes = document.querySelectorAll('.inputPane');
2019-02-13 12:16:39 -06:00
var lastInput = '';
2018-03-26 10:36:47 -05:00
var inputDirty = true;
2018-10-18 00:19:48 -07:00
var $activeOutputElem = null;
2018-04-03 11:21:25 -05:00
var search = searchToObject();
2019-02-08 14:11:09 -06:00
var markedVersions = {
master: 'https://cdn.jsdelivr.net/gh/markedjs/marked/marked.min.js'
2019-02-08 14:11:09 -06:00
};
var markedVersionCache = {};
2019-02-14 11:14:15 -06:00
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);
2019-02-08 14:11:09 -06:00
2019-02-14 10:43:03 -06:00
$commitVerElem.style.display = 'none';
2019-02-14 11:14:15 -06:00
$commitVerElem.addEventListener('keypress', handleAddVersion, false);
$clearElem.addEventListener('click', handleClearClick, false);
Promise.all([
setInitialQuickref(),
2019-02-14 11:18:47 -06:00
setInitialOutputType(),
2019-02-14 11:14:15 -06:00
setInitialText(),
setInitialVersion()
.then(setInitialOptions)
2019-04-26 20:58:04 -05:00
]).then(function() {
2019-02-14 11:18:47 -06:00
handleInputChange();
handleOutputChange();
2019-02-14 11:14:15 -06:00
checkForChanges();
setScrollPercent(0);
2019-02-14 11:25:01 -06:00
$loadingElem.style.display = 'none';
$mainElem.style.display = 'block';
2018-09-23 20:56:38 -04:00
});
2018-06-24 01:46:25 -05:00
2019-04-26 20:58:04 -05:00
function setInitialText() {
2019-02-14 11:52:14 -06:00
if ('text' in search) {
2019-02-14 11:14:15 -06:00
$markdownElem.value = search.text;
} else {
return fetch('./initial.md')
2019-04-26 20:58:04 -05:00
.then(function(res) { return res.text(); })
.then(function(text) {
2019-02-14 11:14:15 -06:00
if ($markdownElem.value === '') {
$markdownElem.value = text;
}
});
}
}
2019-04-26 20:58:04 -05:00
function setInitialQuickref() {
2019-02-14 11:14:15 -06:00
return fetch('./quickref.md')
2019-04-26 20:58:04 -05:00
.then(function(res) { return res.text(); })
.then(function(text) {
2019-02-14 11:14:15 -06:00
document.querySelector('#quickref').value = text;
2018-03-26 10:36:47 -05:00
});
}
2019-04-26 20:58:04 -05:00
function setInitialVersion() {
2019-02-14 11:14:15 -06:00
return fetch('https://data.jsdelivr.com/v1/package/npm/marked')
2019-04-26 20:58:04 -05:00
.then(function(res) {
2019-02-14 11:14:15 -06:00
return res.json();
})
2019-04-26 20:58:04 -05:00
.then(function(json) {
2019-02-14 11:14:15 -06:00
for (var i = 0; i < json.versions.length; i++) {
var ver = json.versions[i];
markedVersions[ver] = 'https://cdn.jsdelivr.net/npm/marked@' + ver + '/marked.min.js';
2019-02-14 11:14:15 -06:00
var opt = document.createElement('option');
opt.textContent = ver;
opt.value = ver;
$markedVerElem.appendChild(opt);
}
})
2019-04-26 20:58:04 -05:00
.then(function() {
2019-04-01 09:08:42 -05:00
return fetch('https://api.github.com/repos/markedjs/marked/commits')
2019-04-26 20:58:04 -05:00
.then(function(res) {
2019-04-01 09:08:42 -05:00
return res.json();
})
2019-04-26 20:58:04 -05:00
.then(function(json) {
markedVersions.master = 'https://cdn.jsdelivr.net/gh/markedjs/marked@' + json[0].sha + '/marked.min.js';
2019-04-01 09:08:42 -05:00
})
2019-04-26 20:58:04 -05:00
.catch(function() {
2019-04-01 09:08:42 -05:00
// do nothing
// uses url without commit
});
})
2019-04-26 20:58:04 -05:00
.then(function() {
2019-02-14 11:14:15 -06:00
if (search.version) {
2019-04-03 16:47:30 -05:00
if (markedVersions[search.version]) {
return search.version;
} else {
2019-02-14 11:14:15 -06:00
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])
2019-04-26 20:58:04 -05:00
.then(function(commit) {
2019-02-14 11:14:15 -06:00
if (!commit) {
return 'master';
}
addCommitVersion(search.version, 'PR #' + match[2], commit);
return search.version;
});
}
2019-02-14 10:43:03 -06:00
}
}
2019-02-13 18:24:44 -06:00
}
2019-02-08 14:11:09 -06:00
2019-02-14 11:14:15 -06:00
return 'master';
})
2019-04-26 20:58:04 -05:00
.then(function(version) {
2019-02-14 11:14:15 -06:00
$markedVerElem.value = version;
})
.then(updateVersion);
2018-04-03 11:21:25 -05:00
}
2019-04-26 20:58:04 -05:00
function setInitialOptions() {
2019-02-14 11:52:14 -06:00
if ('options' in search) {
2019-02-14 11:14:15 -06:00
$optionsElem.value = search.options;
} else {
return setDefaultOptions();
2019-02-14 00:08:33 -06:00
}
}
2019-04-26 20:58:04 -05:00
function setInitialOutputType() {
2019-02-14 11:14:15 -06:00
if (search.outputType) {
$outputTypeElem.value = search.outputType;
}
2018-10-18 00:19:48 -07:00
}
2018-04-03 11:21:25 -05:00
2019-04-26 20:58:04 -05:00
function handleIframeLoad() {
2019-02-14 11:14:15 -06:00
lastInput = '';
inputDirty = true;
2018-10-18 00:19:48 -07:00
}
2019-04-26 20:58:04 -05:00
function handleInput() {
2019-02-14 11:14:15 -06:00
inputDirty = true;
2020-04-01 21:08:44 -05:00
}
2018-03-26 10:36:47 -05:00
2019-04-26 20:58:04 -05:00
function handleVersionChange() {
2019-02-14 10:43:03 -06:00
if ($markedVerElem.value === 'commit' || $markedVerElem.value === 'pr') {
2019-02-14 00:08:33 -06:00
$commitVerElem.style.display = '';
} else {
$commitVerElem.style.display = 'none';
updateVersion();
}
2019-02-14 11:14:15 -06:00
}
2018-03-26 10:36:47 -05:00
2019-04-26 20:58:04 -05:00
function handleClearClick() {
2019-02-14 11:14:15 -06:00
$markdownElem.value = '';
$markedVerElem.value = 'master';
$commitVerElem.style.display = 'none';
updateVersion().then(setDefaultOptions);
}
2018-10-17 15:11:53 -07:00
2019-04-26 20:58:04 -05:00
function handleAddVersion(e) {
2019-02-14 00:08:33 -06:00
if (e.which === 13) {
2019-02-14 10:43:03 -06:00
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)
2019-04-26 20:58:04 -05:00
.then(function(commit) {
2019-02-14 10:43:03 -06:00
$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();
});
2019-02-14 00:08:33 -06:00
}
}
2019-02-14 11:14:15 -06:00
}
2019-02-14 00:08:33 -06:00
2019-04-26 20:58:04 -05:00
function handleInputChange() {
2019-02-14 11:14:15 -06:00
handleChange($inputPanes, $inputTypeElem.value);
}
2019-04-26 20:58:04 -05:00
function handleOutputChange() {
2019-02-14 11:14:15 -06:00
$activeOutputElem = handleChange($panes, $outputTypeElem.value);
updateLink();
}
2019-04-26 20:58:04 -05:00
function handleChange(panes, visiblePane) {
2019-02-14 11:14:15 -06:00
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;
2020-04-01 21:08:44 -05:00
}
2019-02-14 11:14:15 -06:00
2019-04-26 20:58:04 -05:00
function addCommitVersion(value, text, commit) {
2019-02-14 11:14:15 -06:00
if (markedVersions[value]) {
return;
}
markedVersions[value] = 'https://cdn.jsdelivr.net/gh/markedjs/marked@' + commit + '/marked.min.js';
2019-02-14 11:14:15 -06:00
var opt = document.createElement('option');
opt.textContent = text;
opt.value = value;
$markedVerElem.insertBefore(opt, $markedVerElem.firstChild);
}
2018-03-28 11:24:14 -05:00
2019-04-26 20:58:04 -05:00
function getPrCommit(pr) {
2019-02-14 10:43:03 -06:00
return fetch('https://api.github.com/repos/markedjs/marked/pulls/' + pr + '/commits')
2019-04-26 20:58:04 -05:00
.then(function(res) {
2019-02-14 10:43:03 -06:00
return res.json();
})
2019-04-26 20:58:04 -05:00
.then(function(json) {
2019-02-14 10:43:03 -06:00
return json[json.length - 1].sha;
2019-04-26 20:58:04 -05:00
}).catch(function() {
2019-02-14 10:43:03 -06:00
// return undefined
});
}
2019-04-26 20:58:04 -05:00
function setDefaultOptions() {
2019-02-14 09:19:14 -06:00
if (window.Worker) {
return messageWorker({
2019-02-13 12:16:39 -06:00
task: 'defaults',
2019-02-14 10:43:03 -06:00
version: markedVersions[$markedVerElem.value]
});
2019-02-13 12:16:39 -06:00
} else {
var defaults = marked.getDefaults();
setOptions(defaults);
}
}
2019-04-26 20:58:04 -05:00
function setOptions(opts) {
2019-02-13 12:16:39 -06:00
$optionsElem.value = JSON.stringify(
opts,
2019-04-26 20:58:04 -05:00
function(key, value) {
2019-02-13 12:16:39 -06:00
if (value && typeof value === 'object' && Object.getPrototypeOf(value) !== Object.prototype) {
return undefined;
}
return value;
}, ' ');
}
2019-04-26 20:58:04 -05:00
function searchToObject() {
2018-04-03 11:21:25 -05:00
// 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;
}
2020-11-06 08:34:31 -06:00
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
function jsonString(input, level) {
level = level || 0;
if (isArray(input)) {
if (input.length === 0) {
return '[]';
}
var items = [],
i;
if (!isArray(input[0]) && typeof input[0] === 'object' && input[0] !== null) {
for (i = 0; i < input.length; i++) {
items.push(' '.repeat(2 * level) + jsonString(input[i], level + 1));
2020-11-06 08:34:31 -06:00
}
return '[\n' + items.join('\n') + '\n]';
}
for (i = 0; i < input.length; i++) {
items.push(jsonString(input[i], level));
}
return '[' + items.join(', ') + ']';
} else if (typeof input === 'object' && input !== null) {
var props = [];
for (var prop in input) {
props.push(prop + ':' + jsonString(input[prop], level));
}
return '{' + props.join(', ') + '}';
} else {
return JSON.stringify(input);
}
2020-04-01 21:08:44 -05:00
}
2018-03-26 10:36:47 -05:00
2019-04-26 20:58:04 -05:00
function getScrollSize() {
2018-10-18 00:19:48 -07:00
var e = $activeOutputElem;
2018-03-26 10:36:47 -05:00
return e.scrollHeight - e.clientHeight;
2020-04-01 21:08:44 -05:00
}
2019-02-14 11:14:15 -06:00
2019-04-26 20:58:04 -05:00
function getScrollPercent() {
2018-03-26 10:36:47 -05:00
var size = getScrollSize();
if (size <= 0) {
return 1;
}
2018-10-18 00:19:48 -07:00
return $activeOutputElem.scrollTop / size;
2020-04-01 21:08:44 -05:00
}
2019-02-14 11:14:15 -06:00
2019-04-26 20:58:04 -05:00
function setScrollPercent(percent) {
2018-10-18 00:19:48 -07:00
$activeOutputElem.scrollTop = percent * getScrollSize();
2020-04-01 21:08:44 -05:00
}
2018-03-26 10:36:47 -05:00
2019-04-26 20:58:04 -05:00
function updateLink() {
2018-04-03 11:21:25 -05:00
var outputType = '';
if ($outputTypeElem.value !== 'preview') {
outputType = 'outputType=' + $outputTypeElem.value + '&';
}
2018-10-18 00:19:48 -07:00
$permalinkElem.href = '?' + outputType + 'text=' + encodeURIComponent($markdownElem.value)
2019-02-08 14:11:09 -06:00
+ '&options=' + encodeURIComponent($optionsElem.value)
+ '&version=' + encodeURIComponent($markedVerElem.value);
2018-04-03 11:21:25 -05:00
history.replaceState('', document.title, $permalinkElem.href);
}
2019-04-26 20:58:04 -05:00
function updateVersion() {
2019-02-14 09:19:14 -06:00
if (window.Worker) {
2019-02-13 12:16:39 -06:00
handleInput();
return Promise.resolve();
}
2019-02-08 14:11:09 -06:00
var promise;
2019-02-14 11:14:15 -06:00
if (markedVersionCache[$markedVerElem.value]) {
2019-02-08 14:11:09 -06:00
promise = Promise.resolve(markedVersionCache[$markedVerElem.value]);
} else {
promise = fetch(markedVersions[$markedVerElem.value])
2019-04-26 20:58:04 -05:00
.then(function(res) { return res.text(); })
.then(function(text) {
2019-02-08 14:11:09 -06:00
markedVersionCache[$markedVerElem.value] = text;
return text;
});
}
2019-04-26 20:58:04 -05:00
return promise.then(function(text) {
2019-02-08 14:11:09 -06:00
var script = document.createElement('script');
script.textContent = text;
$markedVer.parentNode.replaceChild(script, $markedVer);
$markedVer = script;
}).then(handleInput);
}
2019-04-26 20:58:04 -05:00
function checkForChanges() {
2019-02-14 10:43:03 -06:00
if (inputDirty && $markedVerElem.value !== 'commit' && $markedVerElem.value !== 'pr' && (typeof marked !== 'undefined' || window.Worker)) {
2018-03-26 10:36:47 -05:00
inputDirty = false;
2018-03-28 11:24:14 -05:00
2018-04-03 11:21:25 -05:00
updateLink();
2018-03-28 11:24:14 -05:00
2019-02-13 12:16:39 -06:00
var options = {};
var optionsString = $optionsElem.value || '{}';
2018-10-17 15:11:53 -07:00
try {
2018-10-17 15:27:25 -07:00
var newOptions = JSON.parse(optionsString);
options = newOptions;
2019-02-13 12:16:39 -06:00
$optionsElem.classList.remove('error');
2018-10-17 15:27:25 -07:00
} catch (err) {
2019-02-13 12:16:39 -06:00
$optionsElem.classList.add('error');
2018-10-17 15:11:53 -07:00
}
2019-02-13 12:16:39 -06:00
var version = markedVersions[$markedVerElem.value];
var markdown = $markdownElem.value;
var hash = version + markdown + optionsString;
if (lastInput !== hash) {
lastInput = hash;
2019-02-14 09:19:14 -06:00
if (window.Worker) {
2019-02-13 12:16:39 -06:00
delayTime = 100;
messageWorker({
task: 'parse',
version: version,
markdown: markdown,
options: options
});
} else {
var startTime = new Date();
var lexed = marked.lexer(markdown, options);
2020-11-06 08:34:31 -06:00
var lexedList = jsonString(lexed);
2019-02-13 12:16:39 -06:00
var parsed = marked.parser(lexed, options);
2020-11-06 08:34:31 -06:00
var endTime = new Date();
$previewElem.classList.remove('error');
$htmlElem.classList.remove('error');
$lexerElem.classList.remove('error');
2019-02-13 12:16:39 -06:00
var scrollPercent = getScrollPercent();
2020-11-06 08:34:31 -06:00
setParsed(parsed, lexedList);
2019-02-13 12:16:39 -06:00
setScrollPercent(scrollPercent);
delayTime = endTime - startTime;
2019-02-13 14:18:15 -06:00
setResponseTime(delayTime);
2019-02-13 12:16:39 -06:00
if (delayTime < 50) {
delayTime = 50;
} else if (delayTime > 500) {
delayTime = 1000;
}
2018-03-26 10:36:47 -05:00
}
2018-06-24 01:46:25 -05:00
}
2019-02-13 12:16:39 -06:00
}
checkChangeTimeout = window.setTimeout(checkForChanges, delayTime);
2020-04-01 21:08:44 -05:00
}
2018-03-26 10:36:47 -05:00
2019-04-26 20:58:04 -05:00
function setResponseTime(ms) {
2019-02-13 14:18:15 -06:00
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;
}
2019-04-26 20:58:04 -05:00
function setParsed(parsed, lexed) {
2019-02-14 09:19:14 -06:00
try {
2019-02-13 12:16:39 -06:00
$previewIframe.contentDocument.body.innerHTML = parsed;
2019-02-14 09:19:14 -06:00
} catch (ex) {}
2019-02-13 12:16:39 -06:00
$htmlElem.value = parsed;
$lexerElem.value = lexed;
}
2018-03-26 10:36:47 -05:00
var workerPromises = {};
2019-04-26 20:58:04 -05:00
function messageWorker(message) {
2019-02-13 12:16:39 -06:00
if (!markedWorker || markedWorker.working) {
if (markedWorker) {
clearTimeout(markedWorker.timeout);
markedWorker.terminate();
2018-03-26 10:36:47 -05:00
}
2019-02-13 12:16:39 -06:00
markedWorker = new Worker('worker.js');
2019-04-26 20:58:04 -05:00
markedWorker.onmessage = function(e) {
2019-02-13 12:16:39 -06:00
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);
2019-02-13 14:18:15 -06:00
setResponseTime(e.data.time);
2019-02-13 12:16:39 -06:00
break;
}
clearTimeout(checkChangeTimeout);
delayTime = 10;
checkForChanges();
workerPromises[e.data.id]();
delete workerPromises[e.data.id];
2019-02-13 12:16:39 -06:00
};
2019-04-26 20:58:04 -05:00
markedWorker.onerror = markedWorker.onmessageerror = function(err) {
2019-02-13 12:16:39 -06:00
clearTimeout(markedWorker.timeout);
var error = 'There was an error in the Worker';
if (err) {
if (err.message) {
error = err.message;
} else {
error = err;
}
}
2019-02-14 00:08:33 -06:00
error = error.replace(/^Uncaught Error: /, '');
2019-02-13 12:16:39 -06:00
$previewElem.classList.add('error');
$htmlElem.classList.add('error');
$lexerElem.classList.add('error');
setParsed(error, error);
setScrollPercent(0);
};
2018-03-26 10:36:47 -05:00
}
2019-02-14 00:08:33 -06:00
if (message.task !== 'defaults') {
markedWorker.working = true;
workerTimeout(0);
}
return new Promise(function(resolve) {
message.id = uniqueWorkerMessageId();
workerPromises[message.id] = resolve;
markedWorker.postMessage(message);
});
}
function uniqueWorkerMessageId() {
var id;
do {
id = Math.random().toString(36);
} while (id in workerPromises);
return id;
2019-02-13 14:18:15 -06:00
}
2019-04-26 20:58:04 -05:00
function workerTimeout(seconds) {
markedWorker.timeout = setTimeout(function() {
2019-02-13 14:18:15 -06:00
seconds++;
markedWorker.onerror('Marked has taken longer than ' + seconds + ' second' + (seconds > 1 ? 's' : '') + ' to respond...');
workerTimeout(seconds);
2019-02-13 12:16:39 -06:00
}, 1000);
}