(function() { // eslint-disable-line strict
'use strict'; // eslint-disable-line strict
/* global monaco, require, lessonEditorSettings */
const {
fixSourceLinks,
fixJSForCodeSite,
extraHTMLParsing,
runOnResize,
lessonSettings,
} = lessonEditorSettings;
const lessonHelperScriptRE = /');
html = obj.html;
const fqURL = getFQUrl(url);
/** @type Object */
const scriptInfos = {};
g.rootScriptInfo = {
fqURL,
deps: [],
source: rootScript,
};
scriptInfos[fqURL] = g.rootScriptInfo;
const {text} = await getWorkerScripts(rootScript, fqURL, scriptInfos);
g.rootScriptInfo.source = text;
g.scriptInfos = scriptInfos;
for (const [fqURL, scriptInfo] of Object.entries(scriptInfos)) {
addSource('js', basename(fqURL), scriptInfo.source, scriptInfo);
}
const tm = titleRE.exec(html);
if (tm) {
g.title = tm[1];
}
const scripts = [];
html = html.replace(externalScriptRE, function(p0, p1, p2) {
p1 = p1 || '';
scripts.push(`${p1}`);
return '';
});
const dataScripts = [];
html = html.replace(dataScriptRE, function(p0, p1, p2, p3) {
p1 = p1 || '';
dataScripts.push(`${p1}`);
return '';
});
htmlParts.html.sources[0].source += dataScripts.join('\n');
htmlParts.html.sources[0].source += scripts.join('\n');
// add style section if there is non
if (html.indexOf('${css}') < 0) {
html = html.replace('', '\n');
}
// add hackedparams section.
// We need a way to pass parameters to a blob. Normally they'd be passed as
// query params but that only works in Firefox >:(
html = html.replace('', '\n');
html = extraHTMLParsing(html, htmlParts);
let links = '';
html = html.replace(cssLinkRE, function(p0, p1) {
if (isCSSLinkRE.test(p1)) {
const m = hrefRE.exec(p1);
if (m) {
links += `@import url("${m[1]}");\n`;
}
return '';
} else {
return p0;
}
});
htmlParts.css.sources[0].source = links + htmlParts.css.sources[0].source;
g.html = html;
}
function cantGetHTML(e) { // eslint-disable-line
console.log(e); // eslint-disable-line
console.log("TODO: don't run editor if can't get HTML"); // eslint-disable-line
}
async function main() {
const query = getQuery();
g.url = getFQUrl(query.url);
g.query = getSearch(g.url);
let html;
try {
html = await getHTML(query.url);
} catch (err) {
console.log(err); // eslint-disable-line
return;
}
await parseHTML(query.url, html);
setupEditor(query.url);
if (query.startPane) {
const button = document.querySelector('.button-' + query.startPane);
toggleSourcePane(button);
}
}
function getJavaScriptBlob(source) {
const blob = new Blob([source], {type: 'application/javascript'});
return URL.createObjectURL(blob);
}
let blobGeneration = 0;
function makeBlobURLsForSources(scriptInfo) {
++blobGeneration;
function makeBlobURLForSourcesImpl(scriptInfo) {
if (scriptInfo.blobGenerationId !== blobGeneration) {
scriptInfo.blobGenerationId = blobGeneration;
if (scriptInfo.blobUrl) {
URL.revokeObjectURL(scriptInfo.blobUrl);
}
scriptInfo.deps.forEach(makeBlobURLForSourcesImpl);
let text = scriptInfo.source;
scriptInfo.deps.forEach((depScriptInfo) => {
text = text.split(depScriptInfo.fqURL).join(depScriptInfo.blobUrl);
});
scriptInfo.numLinesBeforeScript = 0;
if (scriptInfo.isWorker) {
const extra = `self.lessonSettings = ${JSON.stringify(lessonSettings)};
importScripts('${dirname(scriptInfo.fqURL)}/resources/lessons-worker-helper.js')`;
scriptInfo.numLinesBeforeScript = extra.split('\n').length;
text = `${extra}\n${text}`;
}
scriptInfo.blobUrl = getJavaScriptBlob(text);
scriptInfo.munged = text;
}
}
makeBlobURLForSourcesImpl(scriptInfo);
}
function getSourceBlob(htmlParts) {
g.rootScriptInfo.source = htmlParts.js;
makeBlobURLsForSources(g.rootScriptInfo);
const prefix = dirname(g.url);
let source = g.html;
source = source.replace('${hackedParams}', JSON.stringify(g.query));
source = source.replace('${html}', htmlParts.html);
source = source.replace('${css}', htmlParts.css);
source = source.replace('${js}', g.rootScriptInfo.munged); //htmlParts.js);
source = source.replace('', `
`);
source = source.replace('', `
`);
const scriptNdx = source.indexOf('`;
}).join('\n');
const init = `
// ------
// Creates Blobs for the Worker Scripts so things can be self contained for snippets/JSFiddle/Codepen
//
function getWorkerBlob() {
const idsToUrls = [];
const scriptElements = [...document.querySelectorAll('script[type=x-worker]')];
for (const scriptElement of scriptElements) {
let text = scriptElement.text;
for (const {id, url} of idsToUrls) {
text = text.split(id).join(url);
}
const blob = new Blob([text], {type: 'application/javascript'});
const url = URL.createObjectURL(blob);
const id = scriptElement.id;
idsToUrls.push({id, url});
}
return idsToUrls.pop().url;
}
`;
return {
js: mainScript.split(`'${workerName}'`).join('getWorkerBlob()') + init,
html,
};
}
function openInCodepen() {
const comment = `// ${g.title}
// from ${g.url}
`;
getSourcesFromEditor();
const scripts = makeScriptsForWorkers(g.rootScriptInfo);
const pen = {
title : g.title,
description : 'from: ' + g.url,
tags : lessonEditorSettings.tags,
editors : '101',
html : scripts.html + htmlParts.html.sources[0].source.replace(lessonHelperScriptRE, ''),
css : htmlParts.css.sources[0].source,
js : comment + fixJSForCodeSite(scripts.js),
};
const elem = document.createElement('div');
elem.innerHTML = `
"
`;
elem.querySelector('input[name=data]').value = JSON.stringify(pen);
window.frameElement.ownerDocument.body.appendChild(elem);
elem.querySelector('form').submit();
window.frameElement.ownerDocument.body.removeChild(elem);
}
function openInJSFiddle() {
const comment = `// ${g.title}
// from ${g.url}
`;
getSourcesFromEditor();
const scripts = makeScriptsForWorkers(g.rootScriptInfo);
const elem = document.createElement('div');
elem.innerHTML = `
`;
elem.querySelector('input[name=html]').value = scripts.html + htmlParts.html.sources[0].source.replace(lessonHelperScriptRE, '');
elem.querySelector('input[name=css]').value = htmlParts.css.sources[0].source;
elem.querySelector('input[name=js]').value = comment + fixJSForCodeSite(scripts.js);
elem.querySelector('input[name=title]').value = g.title;
window.frameElement.ownerDocument.body.appendChild(elem);
elem.querySelector('form').submit();
window.frameElement.ownerDocument.body.removeChild(elem);
}
function selectFile(info, ndx, fileDivs) {
if (info.editors.length <= 1) {
return;
}
info.editors.forEach((editorInfo, i) => {
const selected = i === ndx;
editorInfo.div.style.display = selected ? '' : 'none';
editorInfo.editor.layout();
addRemoveClass(fileDivs.children[i], 'fileSelected', selected);
});
}
function showEditorSubPane(type, ndx) {
const info = htmlParts[type];
selectFile(info, ndx, info.files);
}
function setupEditor() {
forEachHTMLPart(function(info, ndx, name) {
info.pane = document.querySelector('.panes>.' + name);
info.code = info.pane.querySelector('.code');
info.files = info.pane.querySelector('.files');
info.editors = info.sources.map((sourceInfo, ndx) => {
if (info.sources.length > 1) {
const div = document.createElement('div');
div.textContent = basename(sourceInfo.name);
info.files.appendChild(div);
div.addEventListener('click', () => {
selectFile(info, ndx, info.files);
});
}
const div = document.createElement('div');
info.code.appendChild(div);
const editor = runEditor(div, sourceInfo.source, info.language);
sourceInfo.editor = editor;
return {
div,
editor,
};
});
info.button = document.querySelector('.button-' + name);
info.button.addEventListener('click', function() {
toggleSourcePane(info.button);
runIfNeeded();
});
});
g.fullscreen = document.querySelector('.button-fullscreen');
g.fullscreen.addEventListener('click', toggleFullscreen);
g.run = document.querySelector('.button-run');
g.run.addEventListener('click', run);
g.iframe = document.querySelector('.result>iframe');
g.other = document.querySelector('.panes .other');
document.querySelector('.button-codepen').addEventListener('click', openInCodepen);
document.querySelector('.button-jsfiddle').addEventListener('click', openInJSFiddle);
g.result = document.querySelector('.panes .result');
g.resultButton = document.querySelector('.button-result');
g.resultButton.addEventListener('click', function() {
toggleResultPane();
runIfNeeded();
});
g.result.style.display = 'none';
toggleResultPane();
if (window.innerWidth > 1200) {
toggleSourcePane(htmlParts.js.button);
}
window.addEventListener('resize', resize);
showEditorSubPane('js', 0);
showOtherIfAllPanesOff();
document.querySelector('.other .loading').style.display = 'none';
resize();
run();
}
function toggleFullscreen() {
try {
toggleIFrameFullscreen(window);
resize();
runIfNeeded();
} catch (e) {
console.error(e); // eslint-disable-line
}
}
function runIfNeeded() {
if (runOnResize) {
run();
}
}
function run(options) {
g.setPosition = false;
const url = getSourceBlobFromEditor(options);
g.iframe.src = url;
}
function addClass(elem, className) {
const parts = elem.className.split(' ');
if (parts.indexOf(className) < 0) {
elem.className = elem.className + ' ' + className;
}
}
function removeClass(elem, className) {
const parts = elem.className.split(' ');
const numParts = parts.length;
for (;;) {
const ndx = parts.indexOf(className);
if (ndx < 0) {
break;
}
parts.splice(ndx, 1);
}
if (parts.length !== numParts) {
elem.className = parts.join(' ');
return true;
}
return false;
}
function toggleClass(elem, className) {
if (removeClass(elem, className)) {
return false;
} else {
addClass(elem, className);
return true;
}
}
function toggleIFrameFullscreen(childWindow) {
const frame = childWindow.frameElement;
if (frame) {
const isFullScreen = toggleClass(frame, 'fullscreen');
frame.ownerDocument.body.style.overflow = isFullScreen ? 'hidden' : '';
}
}
function addRemoveClass(elem, className, add) {
if (add) {
addClass(elem, className);
} else {
removeClass(elem, className);
}
}
function toggleSourcePane(pressedButton) {
forEachHTMLPart(function(info) {
const pressed = pressedButton === info.button;
if (pressed && !info.showing) {
addClass(info.button, 'show');
info.pane.style.display = 'flex';
info.showing = true;
} else {
removeClass(info.button, 'show');
info.pane.style.display = 'none';
info.showing = false;
}
});
showOtherIfAllPanesOff();
resize();
}
function showingResultPane() {
return g.result.style.display !== 'none';
}
function toggleResultPane() {
const showing = showingResultPane();
g.result.style.display = showing ? 'none' : 'block';
addRemoveClass(g.resultButton, 'show', !showing);
showOtherIfAllPanesOff();
resize();
}
function showOtherIfAllPanesOff() {
let paneOn = showingResultPane();
forEachHTMLPart(function(info) {
paneOn = paneOn || info.showing;
});
g.other.style.display = paneOn ? 'none' : 'block';
}
// seems like we should probably store a map
function getEditorNdxByBlobUrl(type, url) {
return htmlParts[type].sources.findIndex(source => source.scriptInfo.blobUrl === url);
}
function getActualLineNumberAndMoveTo(url, lineNo, colNo) {
let origUrl = url;
let actualLineNo = lineNo;
const scriptInfo = Object.values(g.scriptInfos).find(scriptInfo => scriptInfo.blobUrl === url);
if (scriptInfo) {
actualLineNo = lineNo - scriptInfo.numLinesBeforeScript;
origUrl = basename(scriptInfo.fqURL);
if (!g.setPosition) {
// Only set the first position
g.setPosition = true;
const editorNdx = getEditorNdxByBlobUrl('js', url);
if (editorNdx >= 0) {
showEditorSubPane('js', editorNdx);
const editor = htmlParts.js.editors[editorNdx].editor;
editor.setPosition({
lineNumber: actualLineNo,
column: colNo,
});
editor.revealLineInCenterIfOutsideViewport(actualLineNo);
editor.focus();
}
}
}
return {origUrl, actualLineNo};
}
window.getActualLineNumberAndMoveTo = getActualLineNumberAndMoveTo;
function runEditor(parent, source, language) {
return monaco.editor.create(parent, {
value: source,
language: language,
//lineNumbers: false,
theme: 'vs-dark',
disableTranslate3d: true,
// model: null,
scrollBeyondLastLine: false,
minimap: { enabled: false },
});
}
async function runAsBlob() {
const query = getQuery();
g.url = getFQUrl(query.url);
g.query = getSearch(g.url);
let html;
try {
html = await getHTML(query.url);
} catch (err) {
console.log(err); // eslint-disable-line
return;
}
await parseHTML(query.url, html);
window.location.href = getSourceBlobFromOrig();
}
function applySubstitutions() {
[...document.querySelectorAll('[data-subst]')].forEach((elem) => {
elem.dataset.subst.split('&').forEach((pair) => {
const [attr, key] = pair.split('|');
elem[attr] = lessonEditorSettings[key];
});
});
}
function start() {
const parentQuery = getQuery(window.parent.location.search);
const isSmallish = window.navigator.userAgent.match(/Android|iPhone|iPod|Windows Phone/i);
const isEdge = window.navigator.userAgent.match(/Edge/i);
if (isEdge || isSmallish || parentQuery.editor === 'false') {
runAsBlob();
// var url = query.url;
// window.location.href = url;
} else {
applySubstitutions();
require.config({ paths: { 'vs': '/monaco-editor/min/vs' }});
require(['vs/editor/editor.main'], main);
}
}
start();
}());