( function () { // eslint-disable-line strict 'use strict'; // eslint-disable-line strict /* global monaco, require, lessonEditorSettings */ const { fixSourceLinks, fixJSForCodeSite, extraHTMLParsing, runOnResize, lessonSettings, } = lessonEditorSettings; const lessonHelperScriptRE = /' ) || getHTMLPart( inlineModuleScriptRE, obj, '' ); 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 kScript = 'script'; const scripts = []; html = html.replace( externalScriptRE, function ( p0, p1, p2, type, p3, p4 ) { p1 = p1 || ''; scripts.push( `${p1}<${kScript} ${p2}${safeStr( type )}src="${p3}"${p4}>` ); return ''; } ); const prefix = getPrefix( url ); const rootPrefix = getRootPrefix( url ); function addCorrectPrefix( href ) { return ( href.startsWith( '/' ) ) ? `${rootPrefix}${href}` : removeDotDotSlash( ( `${prefix}/${href}` ).replace( /\/.\//g, '/' ) ); } function addPrefix( url ) { return url.indexOf( '://' ) < 0 && ! url.startsWith( 'data:' ) && url[ 0 ] !== '?' ? removeDotDotSlash( addCorrectPrefix( url ) ) : url; } const importMapRE = /type\s*=["']importmap["']/; const dataScripts = []; html = html.replace( dataScriptRE, function ( p0, blockComments, scriptTagAttrs, content ) { blockComments = blockComments || ''; if ( importMapRE.test( scriptTagAttrs ) ) { const imap = JSON.parse( content ); const imports = imap.imports; if ( imports ) { for ( const [ k, url ] of Object.entries( imports ) ) { if ( url.indexOf( '://' ) < 0 && ! url.startsWith( 'data:' ) ) { imports[ k ] = addPrefix( url ); } } } content = JSON.stringify( imap, null, '\t' ); } dataScripts.push( `${blockComments}<${kScript} ${scriptTagAttrs}>${content}` ); 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; } 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(); 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 )}; import '${dirname( scriptInfo.fqURL )}/resources/webgl-debug-helper.js'; import '${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 dname = dirname( g.url ); // HACK! for webgl-2d-vs... those examples are not in /webgl they're in /webgl/resources // We basically assume url is https://foo/base/example.html so there will be 4 slashes // If the path is longer than then we need '../' to back up so prefix works below const prefix = dname; //`${dname}${dname.split('/').slice(4).map(() => '/..').join('')}`; 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.search( // ); g.rootScriptInfo.numLinesBeforeScript = ( source.substring( 0, scriptNdx ).match( /\n/g ) || [] ).length; const blob = new Blob( [ source ], { type: 'text/html' } ); // This seems hacky. We are combining html/css/js into one html blob but we already made // a blob for the JS so let's replace that blob. That means it will get auto-released when script blobs // are regenerated. It also means error reporting will work const blobUrl = URL.createObjectURL( blob ); URL.revokeObjectURL( g.rootScriptInfo.blobUrl ); g.rootScriptInfo.blobUrl = blobUrl; return blobUrl; } function getSourcesFromEditor() { for ( const partTypeInfo of Object.values( htmlParts ) ) { for ( const source of partTypeInfo.sources ) { source.source = source.editor.getValue(); // hack: shouldn't store this twice. Also see other comment, // should consolidate so scriptInfo is used for css and html if ( source.scriptInfo ) { source.scriptInfo.source = source.source; } } } } function getSourceBlobFromEditor() { getSourcesFromEditor(); return getSourceBlob( { html: htmlParts.html.sources[ 0 ].source, css: htmlParts.css.sources[ 0 ].source, js: htmlParts.js.sources[ 0 ].source, } ); } function getSourceBlobFromOrig() { return getSourceBlob( { html: htmlParts.html.sources[ 0 ].source, css: htmlParts.css.sources[ 0 ].source, js: htmlParts.js.sources[ 0 ].source, } ); } function dirname( path ) { const ndx = path.lastIndexOf( '/' ); return path.substring( 0, ndx ); } function basename( path ) { const ndx = path.lastIndexOf( '/' ); return path.substring( ndx + 1 ); } function resize() { forEachHTMLPart( function ( info ) { info.editors.forEach( ( editorInfo ) => { editorInfo.editor.layout(); } ); } ); } function getScripts( scriptInfo ) { ++ blobGeneration; function getScriptsImpl( scriptInfo ) { const scripts = []; if ( scriptInfo.blobGenerationId !== blobGeneration ) { scriptInfo.blobGenerationId = blobGeneration; scripts.push( ...scriptInfo.deps.map( getScriptsImpl ).flat() ); let text = scriptInfo.source; scriptInfo.deps.forEach( ( depScriptInfo ) => { text = text.split( depScriptInfo.fqURL ).join( `worker-${basename( depScriptInfo.fqURL )}` ); } ); scripts.push( { name: `worker-${basename( scriptInfo.fqURL )}`, text, } ); } return scripts; } return getScriptsImpl( scriptInfo ); } function makeScriptsForWorkers( scriptInfo ) { const scripts = getScripts( scriptInfo ); if ( scripts.length === 1 ) { return { js: scripts[ 0 ].text, html: '', }; } // scripts[last] = main script // scripts[last - 1] = worker const mainScriptInfo = scripts[ scripts.length - 1 ]; const workerScriptInfo = scripts[ scripts.length - 2 ]; const workerName = workerScriptInfo.name; mainScriptInfo.text = mainScriptInfo.text.split( `'${workerName}'` ).join( 'getWorkerBlob()' ); const html = scripts.map( ( nameText ) => { const { name, text } = nameText; return `\n`; } ).join( '\n' ); const init = ` // ------ // Creates Blobs for the Scripts so things can be self contained for snippets/JSFiddle/Codepen // even though they are using workers // (function() { 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}); } window.getWorkerBlob = function() { return idsToUrls.pop().url; }; import(window.getWorkerBlob()); }()); `; return { js: init, html, }; } async function fixHTMLForCodeSite( html ) { html = html.replace( lessonHelperScriptRE, '' ); html = html.replace( webglDebugHelperScriptRE, '' ); return html; } async function openInCodepen() { const comment = `// ${g.title} // from ${g.url} `; getSourcesFromEditor(); const scripts = makeScriptsForWorkers( g.rootScriptInfo ); const code = await fixJSForCodeSite( scripts.js ); const html = await fixHTMLForCodeSite( htmlParts.html.sources[ 0 ].source ); const pen = { title: g.title, description: 'from: ' + g.url, tags: lessonEditorSettings.tags, editors: '101', html: scripts.html + html, css: htmlParts.css.sources[ 0 ].source, js: comment + code, }; 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 ); } async function openInJSFiddle() { const comment = `// ${g.title} // from ${g.url} `; getSourcesFromEditor(); const scripts = makeScriptsForWorkers( g.rootScriptInfo ); const code = await fixJSForCodeSite( scripts.js ); const html = await fixHTMLForCodeSite( htmlParts.html.sources[ 0 ].source ); const elem = document.createElement( 'div' ); elem.innerHTML = ` `; elem.querySelector( 'input[name=html]' ).value = scripts.html + html; elem.querySelector( 'input[name=css]' ).value = htmlParts.css.sources[ 0 ].source; elem.querySelector( 'input[name=js]' ).value = comment + code; elem.querySelector( 'input[name=title]' ).value = g.title; window.frameElement.ownerDocument.body.appendChild( elem ); elem.querySelector( 'form' ).submit(); window.frameElement.ownerDocument.body.removeChild( elem ); } async function openInJSGist() { const comment = `// ${g.title} // from ${g.url} `; getSourcesFromEditor(); const scripts = makeScriptsForWorkers( g.rootScriptInfo ); const code = await fixJSForCodeSite( scripts.js ); const html = await fixHTMLForCodeSite( htmlParts.html.sources[ 0 ].source ); const gist = { name: g.title, settings: {}, files: [ { name: 'index.html', content: scripts.html + html, }, { name: 'index.css', content: htmlParts.css.sources[ 0 ].source, }, { name: 'index.js', content: comment + code, }, ], }; window.open( 'https://jsgist.org/?newGist=true', '_blank' ); const send = ( e ) => { e.source.postMessage( { type: 'newGist', data: gist }, '*' ); }; window.addEventListener( 'message', send, { once: true } ); } /* console.log(); h1 { color: red; }

foo

*/ function indent4( s ) { return s.split( '\n' ).map( s => ` ${s}` ).join( '\n' ); } async function openInStackOverflow() { const comment = `// ${g.title} // from ${g.url} `; getSourcesFromEditor(); const scripts = makeScriptsForWorkers( g.rootScriptInfo ); const code = await fixJSForCodeSite( scripts.js ); const html = await fixHTMLForCodeSite( htmlParts.html.sources[ 0 ].source ); const mainHTML = scripts.html + html; const mainJS = comment + code; const mainCSS = htmlParts.css.sources[ 0 ].source; const asModule = /\bimport\b/.test( mainJS ); // Three.js wants us to use modules but Stack Overflow doesn't support them const text = asModule ? ` ${indent4( mainCSS )} ${indent4( mainHTML )} ` : ` ${indent4( mainJS )} ${indent4( mainCSS )} ${indent4( mainHTML )} `; const dialogElem = document.querySelector( '.copy-dialog' ); dialogElem.style.display = ''; const copyAreaElem = dialogElem.querySelector( '.copy-area' ); copyAreaElem.textContent = text; const linkElem = dialogElem.querySelector( 'a' ); const tags = lessonEditorSettings.tags.filter( f => ! f.endsWith( '.org' ) ).join( ' ' ); linkElem.href = `https://stackoverflow.com/questions/ask?&tags=javascript ${tags}`; } function htmlTemplate( s ) { return ` ${s.title} ${s.body} ${s.script.startsWith( '<' ) ? s.script : ` `} `; } // ---vvv--- // Copyright (c) 2013 Pieroxy // This work is free. You can redistribute it and/or modify it // under the terms of the WTFPL, Version 2 // For more information see LICENSE.txt or http://www.wtfpl.net/ // // For more information, the home page: // http://pieroxy.net/blog/pages/lz-string/testing.html // // LZ-based compression algorithm, version 1.4.4 // // Modified: // private property const keyStrBase64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; function compressToBase64( input ) { if ( input === null ) { return ''; } const res = _compress( input, 6, function ( a ) { return keyStrBase64.charAt( a ); } ); switch ( res.length % 4 ) { // To produce valid Base64 default: // When could this happen ? case 0 : return res; case 1 : return res + '==='; case 2 : return res + '=='; case 3 : return res + '='; } } function _compress( uncompressed, bitsPerChar, getCharFromInt ) { let i; let value; const context_dictionary = {}; const context_dictionaryToCreate = {}; let context_c = ''; let context_wc = ''; let context_w = ''; let context_enlargeIn = 2; // Compensate for the first entry which should not count let context_dictSize = 3; let context_numBits = 2; const context_data = []; let context_data_val = 0; let context_data_position = 0; let ii; for ( ii = 0; ii < uncompressed.length; ii += 1 ) { context_c = uncompressed.charAt( ii ); if ( ! Object.prototype.hasOwnProperty.call( context_dictionary, context_c ) ) { context_dictionary[ context_c ] = context_dictSize ++; context_dictionaryToCreate[ context_c ] = true; } context_wc = context_w + context_c; if ( Object.prototype.hasOwnProperty.call( context_dictionary, context_wc ) ) { context_w = context_wc; } else { if ( Object.prototype.hasOwnProperty.call( context_dictionaryToCreate, context_w ) ) { if ( context_w.charCodeAt( 0 ) < 256 ) { for ( i = 0; i < context_numBits; i ++ ) { context_data_val = ( context_data_val << 1 ); if ( context_data_position === bitsPerChar - 1 ) { context_data_position = 0; context_data.push( getCharFromInt( context_data_val ) ); context_data_val = 0; } else { context_data_position ++; } } value = context_w.charCodeAt( 0 ); for ( i = 0; i < 8; i ++ ) { context_data_val = ( context_data_val << 1 ) | ( value & 1 ); if ( context_data_position === bitsPerChar - 1 ) { context_data_position = 0; context_data.push( getCharFromInt( context_data_val ) ); context_data_val = 0; } else { context_data_position ++; } value = value >> 1; } } else { value = 1; for ( i = 0; i < context_numBits; i ++ ) { context_data_val = ( context_data_val << 1 ) | value; if ( context_data_position === bitsPerChar - 1 ) { context_data_position = 0; context_data.push( getCharFromInt( context_data_val ) ); context_data_val = 0; } else { context_data_position ++; } value = 0; } value = context_w.charCodeAt( 0 ); for ( i = 0; i < 16; i ++ ) { context_data_val = ( context_data_val << 1 ) | ( value & 1 ); if ( context_data_position === bitsPerChar - 1 ) { context_data_position = 0; context_data.push( getCharFromInt( context_data_val ) ); context_data_val = 0; } else { context_data_position ++; } value = value >> 1; } } context_enlargeIn --; if ( context_enlargeIn === 0 ) { context_enlargeIn = Math.pow( 2, context_numBits ); context_numBits ++; } delete context_dictionaryToCreate[ context_w ]; } else { value = context_dictionary[ context_w ]; for ( i = 0; i < context_numBits; i ++ ) { context_data_val = ( context_data_val << 1 ) | ( value & 1 ); if ( context_data_position === bitsPerChar - 1 ) { context_data_position = 0; context_data.push( getCharFromInt( context_data_val ) ); context_data_val = 0; } else { context_data_position ++; } value = value >> 1; } } context_enlargeIn --; if ( context_enlargeIn === 0 ) { context_enlargeIn = Math.pow( 2, context_numBits ); context_numBits ++; } // Add wc to the dictionary. context_dictionary[ context_wc ] = context_dictSize ++; context_w = String( context_c ); } } // Output the code for w. if ( context_w !== '' ) { if ( Object.prototype.hasOwnProperty.call( context_dictionaryToCreate, context_w ) ) { if ( context_w.charCodeAt( 0 ) < 256 ) { for ( i = 0; i < context_numBits; i ++ ) { context_data_val = ( context_data_val << 1 ); if ( context_data_position === bitsPerChar - 1 ) { context_data_position = 0; context_data.push( getCharFromInt( context_data_val ) ); context_data_val = 0; } else { context_data_position ++; } } value = context_w.charCodeAt( 0 ); for ( i = 0; i < 8; i ++ ) { context_data_val = ( context_data_val << 1 ) | ( value & 1 ); if ( context_data_position === bitsPerChar - 1 ) { context_data_position = 0; context_data.push( getCharFromInt( context_data_val ) ); context_data_val = 0; } else { context_data_position ++; } value = value >> 1; } } else { value = 1; for ( i = 0; i < context_numBits; i ++ ) { context_data_val = ( context_data_val << 1 ) | value; if ( context_data_position === bitsPerChar - 1 ) { context_data_position = 0; context_data.push( getCharFromInt( context_data_val ) ); context_data_val = 0; } else { context_data_position ++; } value = 0; } value = context_w.charCodeAt( 0 ); for ( i = 0; i < 16; i ++ ) { context_data_val = ( context_data_val << 1 ) | ( value & 1 ); if ( context_data_position === bitsPerChar - 1 ) { context_data_position = 0; context_data.push( getCharFromInt( context_data_val ) ); context_data_val = 0; } else { context_data_position ++; } value = value >> 1; } } context_enlargeIn --; if ( context_enlargeIn === 0 ) { context_enlargeIn = Math.pow( 2, context_numBits ); context_numBits ++; } delete context_dictionaryToCreate[ context_w ]; } else { value = context_dictionary[ context_w ]; for ( i = 0; i < context_numBits; i ++ ) { context_data_val = ( context_data_val << 1 ) | ( value & 1 ); if ( context_data_position === bitsPerChar - 1 ) { context_data_position = 0; context_data.push( getCharFromInt( context_data_val ) ); context_data_val = 0; } else { context_data_position ++; } value = value >> 1; } } context_enlargeIn --; if ( context_enlargeIn === 0 ) { context_numBits ++; } } // Mark the end of the stream value = 2; for ( i = 0; i < context_numBits; i ++ ) { context_data_val = ( context_data_val << 1 ) | ( value & 1 ); if ( context_data_position === bitsPerChar - 1 ) { context_data_position = 0; context_data.push( getCharFromInt( context_data_val ) ); context_data_val = 0; } else { context_data_position ++; } value = value >> 1; } // Flush the last char for ( ;; ) { context_data_val = ( context_data_val << 1 ); if ( context_data_position === bitsPerChar - 1 ) { context_data.push( getCharFromInt( context_data_val ) ); break; } else { context_data_position ++; } } return context_data.join( '' ); } function compress( input ) { return compressToBase64( input ) .replace( /\+/g, '-' ) // Convert '+' to '-' .replace( /\//g, '_' ) // Convert '/' to '_' .replace( /=+$/, '' ); // Remove ending '=' } function getParameters( parameters ) { return compress( JSON.stringify( parameters ) ); } // -- ^^^ --- async function openInCodeSandbox() { const comment = `// ${g.title} // from ${g.url} `; getSourcesFromEditor(); const scripts = getScripts( g.rootScriptInfo ); const mainScript = scripts.pop(); const code = await fixJSForCodeSite( mainScript.text ); const html = await fixHTMLForCodeSite( htmlParts.html.sources[ 0 ].source ); const names = scripts.map( s => s.name ); const files = scripts.reduce( ( files, { name, text: content } ) => { files[ name ] = { content }; return files; }, { 'index.html': { content: htmlTemplate( { body: html, css: htmlParts.css.sources[ 0 ].source, title: g.title, script: comment + code, } ), }, 'sandbox.config.json': { content: '{\n "template": "static"\n}\n', }, 'package.json': { content: JSON.stringify( { 'name': 'static', 'version': '1.0.0', 'description': 'This is a static template with no bundling', 'main': 'index.html', 'scripts': { 'start': 'serve', 'build': 'echo This is a static template, there is no bundler or bundling involved!', }, 'license': 'MIT', 'devDependencies': { 'serve': '^11.2.0', }, }, null, 2 ), }, } ); for ( const file of Object.values( files ) ) { for ( const name of names ) { file.content = file.content.split( name ).join( `./${name}` ); } } const parameters = getParameters( { files } ); const elem = document.createElement( 'div' ); elem.innerHTML = ` `; elem.querySelector( 'input[name=parameters]' ).value = parameters; window.frameElement.ownerDocument.body.appendChild( elem ); elem.querySelector( 'form' ).submit(); window.frameElement.ownerDocument.body.removeChild( elem ); } /* async function openInStackBlitz() { const comment = `// ${g.title} // from ${g.url} `; getSourcesFromEditor(); const scripts = getScripts(g.rootScriptInfo); const code = await fixJSForCodeSite(scripts.js); const html = await fixHTMLForCodeSite(htmlParts.html.sources[0].source); const mainScript = scripts.pop(); const names = scripts.map(s => s.name); const files = scripts.reduce((files, {name, text: content}) => { files[name] = {content}; return files; }, { 'index.html': { content: htmlTemplate({ body: html, css: htmlParts.css.sources[0].source, title: g.title, script: '', }), }, 'index.js': { content: comment + code, }, // "tsconfig.json": { // content: JSON.stringify({ // "compilerOptions": { // "target": "esnext" // } // }, null, 2), // }, 'package.json': { content: JSON.stringify({ 'name': 'js', 'version': '0.0.0', 'private': true, 'dependencies': {} }, null, 2), } }); const elem = document.createElement('div'); elem.innerHTML = ` `; const form = elem.querySelector('form'); for (const [name, file] of Object.entries(files)) { for (const name of names) { file.content = file.content.split(name).join(`./${name}`); } const input = document.createElement('input'); input.type = 'hidden'; input.name = `project[files][${name}]`; input.value = file.content; form.appendChild(input); } window.frameElement.ownerDocument.body.appendChild(elem); form.submit(); window.frameElement.ownerDocument.body.removeChild(elem); } */ document.querySelectorAll( '.dialog' ).forEach( dialogElem => { dialogElem.addEventListener( 'click', function ( e ) { if ( e.target === this ) { this.style.display = 'none'; } } ); dialogElem.addEventListener( 'keydown', function ( e ) { console.log( e.keyCode ); if ( e.keyCode === 27 ) { this.style.display = 'none'; } } ); } ); const exportDialogElem = document.querySelector( '.export' ); function openExport() { exportDialogElem.style.display = ''; exportDialogElem.firstElementChild.focus(); } function closeExport( fn ) { return () => { exportDialogElem.style.display = 'none'; fn(); }; } document.querySelector( '.button-export' ).addEventListener( 'click', openExport ); 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', closeExport( openInCodepen ) ); document.querySelector( '.button-jsfiddle' ).addEventListener( 'click', closeExport( openInJSFiddle ) ); document.querySelector( '.button-jsgist' ).addEventListener( 'click', closeExport( openInJSGist ) ); document.querySelector( '.button-stackoverflow' ).addEventListener( 'click', closeExport( openInStackOverflow ) ); document.querySelector( '.button-codesandbox' ).addEventListener( 'click', closeExport( openInCodeSandbox ) ); //document.querySelector('.button-stackblitz').addEventListener('click', openInStackBlitz); 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 >= 1000 ) { 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() { g.setPosition = false; const url = getSourceBlobFromEditor(); // g.iframe.src = url; // work around firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1828286 g.iframe.contentWindow.location.replace(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': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.34.1/min/vs' } } ); require( [ 'vs/editor/editor.main' ], main ); } } start(); }() );