marked/docs/demo/demo.js
Tony Brix 7d95a91093
fix: Clean up files in repo (#2963)
BREAKING CHANGE:

- remove built files from git repo.
  - If you need to use the latest version of marked on the web you can use a cdn to get marked.min.js from npm:
  - `https://cdn.jsdelivr.net/npm/marked/marked.min.js`
2023-09-09 17:51:42 -06:00

397 lines
10 KiB
JavaScript

onunhandledrejection = (e) => {
throw e.reason;
};
const $loadingElem = document.querySelector('#loading');
const $mainElem = document.querySelector('#main');
const $markdownElem = document.querySelector('#markdown');
const $markedVerElem = document.querySelector('#markedVersion');
const $optionsElem = document.querySelector('#options');
const $outputTypeElem = document.querySelector('#outputType');
const $inputTypeElem = document.querySelector('#inputType');
const $responseTimeElem = document.querySelector('#responseTime');
const $previewElem = document.querySelector('#preview');
const $previewIframe = document.querySelector('#preview iframe');
const $permalinkElem = document.querySelector('#permalink');
const $clearElem = document.querySelector('#clear');
const $htmlElem = document.querySelector('#html');
const $lexerElem = document.querySelector('#lexer');
const $panes = document.querySelectorAll('.pane');
const $inputPanes = document.querySelectorAll('.inputPane');
let lastInput = '';
let inputDirty = true;
let $activeOutputElem = null;
let latestVersion = 'master';
const search = searchToObject();
const markedVersions = {
master: '../'
};
let delayTime = 1;
let checkChangeTimeout = null;
let 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);
$clearElem.addEventListener('click', handleClearClick, false);
Promise.all([
setInitialQuickref(),
setInitialOutputType(),
setInitialText(),
setInitialVersion()
.then(setInitialOptions)
]).then(() => {
handleInputChange();
handleOutputChange();
checkForChanges();
setScrollPercent(0);
$loadingElem.style.display = 'none';
$mainElem.style.display = 'block';
}).catch(() => {
$loadingElem.classList.add('loadingError');
$loadingElem.textContent = 'Failed to load marked. Refresh the page to try again.';
});
function setInitialText() {
if ('text' in search) {
$markdownElem.value = search.text;
} else {
return fetch('./initial.md')
.then(res => res.text())
.then(text => {
if ($markdownElem.value === '') {
$markdownElem.value = text;
}
});
}
}
function setInitialQuickref() {
return fetch('./quickref.md')
.then(res => res.text())
.then(text => {
document.querySelector('#quickref').value = text;
});
}
function setInitialVersion() {
return fetch('https://data.jsdelivr.com/v1/package/npm/marked')
.then(res => res.json())
.then(json => {
for (const ver of json.versions) {
markedVersions[ver] = 'https://cdn.jsdelivr.net/npm/marked@' + ver;
const opt = document.createElement('option');
opt.textContent = ver;
opt.value = ver;
$markedVerElem.appendChild(opt);
}
if (location.host === 'marked.js.org') {
latestVersion = json.tags.latest;
} else {
$markedVerElem.querySelector('option[value="master"]').textContent = 'This Build';
}
if (search.version && markedVersions[search.version]) {
$markedVerElem.value = search.version;
return;
}
$markedVerElem.value = latestVersion;
})
.then(updateVersion);
}
function setInitialOptions() {
if ('options' in search) {
$optionsElem.value = search.options;
} else {
return setDefaultOptions();
}
}
function setInitialOutputType() {
if (search.outputType) {
$outputTypeElem.value = search.outputType;
}
}
function handleIframeLoad() {
lastInput = '';
inputDirty = true;
}
function handleInput() {
inputDirty = true;
}
function handleVersionChange() {
updateVersion();
}
function handleClearClick() {
$markdownElem.value = '';
$markedVerElem.value = latestVersion;
updateVersion();
setDefaultOptions();
}
function handleInputChange() {
handleChange($inputPanes, $inputTypeElem.value);
}
function handleOutputChange() {
$activeOutputElem = handleChange($panes, $outputTypeElem.value);
updateLink();
}
function handleChange(panes, visiblePane) {
let active = null;
for (let 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 setDefaultOptions() {
return messageWorker({
task: 'defaults',
version: markedVersions[$markedVerElem.value]
});
}
function setOptions(opts) {
$optionsElem.value = JSON.stringify(
opts,
(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
const pairs = location.search.slice(1).split('&');
const obj = {};
for (let i = 0; i < pairs.length; i++) {
if (pairs[i] === '') {
continue;
}
const pair = pairs[i].split('=');
obj[decodeURIComponent(pair.shift())] = decodeURIComponent(pair.join('='));
}
return obj;
}
function getScrollSize() {
if (!$activeOutputElem) {
return 0;
}
const e = $activeOutputElem;
return e.scrollHeight - e.clientHeight;
}
function getScrollPercent() {
if (!$activeOutputElem) {
return 1;
}
const size = getScrollSize();
if (size <= 0) {
return 1;
}
return $activeOutputElem.scrollTop / size;
}
function setScrollPercent(percent) {
if ($activeOutputElem) {
$activeOutputElem.scrollTop = percent * getScrollSize();
}
}
function updateLink() {
let 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() {
handleInput();
}
function checkForChanges() {
if (inputDirty && $markedVerElem.value !== 'pr') {
inputDirty = false;
updateLink();
let options = {};
const optionsString = $optionsElem.value || '{}';
try {
const newOptions = JSON.parse(optionsString);
options = newOptions;
$optionsElem.classList.remove('error');
} catch (err) {
$optionsElem.classList.add('error');
}
const version = markedVersions[$markedVerElem.value];
const markdown = $markdownElem.value;
const hash = version + markdown + optionsString;
if (lastInput !== hash) {
lastInput = hash;
delayTime = 100;
messageWorker({
task: 'parse',
version,
markdown,
options
});
}
}
checkChangeTimeout = window.setTimeout(checkForChanges, delayTime);
}
function setResponseTime(ms) {
let amount = ms;
let 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;
$responseTimeElem.animate([
{ transform: 'scale(1.2)' },
{ transform: 'scale(1)' }
], 200);
}
function setParsed(parsed, lexed) {
try {
$previewIframe.contentDocument.body.innerHTML = parsed;
} catch (ex) {}
$htmlElem.value = parsed;
$lexerElem.value = lexed;
}
const workerPromises = {};
function messageWorker(message) {
if (!markedWorker || markedWorker.working) {
if (markedWorker) {
clearTimeout(markedWorker.timeout);
markedWorker.terminate();
}
markedWorker = new Worker('worker.js');
markedWorker.onmessage = (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');
const scrollPercent = getScrollPercent();
setParsed(e.data.parsed, e.data.lexed);
setScrollPercent(scrollPercent);
setResponseTime(e.data.time);
break;
}
}
clearTimeout(checkChangeTimeout);
delayTime = 10;
checkForChanges();
workerPromises[e.data.id]();
delete workerPromises[e.data.id];
};
markedWorker.onerror = markedWorker.onmessageerror = (err) => {
clearTimeout(markedWorker.timeout);
let 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);
}
return new Promise(resolve => {
message.id = uniqueWorkerMessageId();
workerPromises[message.id] = resolve;
markedWorker.postMessage(message);
});
}
function uniqueWorkerMessageId() {
let id;
do {
id = Math.random().toString(36);
} while (id in workerPromises);
return id;
}
function workerTimeout(seconds) {
markedWorker.timeout = setTimeout(() => {
seconds++;
markedWorker.onerror('Marked has taken longer than ' + seconds + ' second' + (seconds > 1 ? 's' : '') + ' to respond...');
workerTimeout(seconds);
}, 1000);
}