12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973 |
- ( function () { // eslint-disable-line strict
- 'use strict'; // eslint-disable-line strict
- /* global monaco, require, lessonEditorSettings */
- const {
- fixSourceLinks,
- fixJSForCodeSite,
- extraHTMLParsing,
- runOnResize,
- lessonSettings,
- } = lessonEditorSettings;
- const lessonHelperScriptRE = /<script src="[^"]+lessons-helper\.js"><\/script>/;
- const webglDebugHelperScriptRE = /<script src="[^"]+webgl-debug-helper\.js"><\/script>/;
- function getQuery( s ) {
- s = s === undefined ? window.location.search : s;
- if ( s[ 0 ] === '?' ) {
- s = s.substring( 1 );
- }
- const query = {};
- s.split( '&' ).forEach( function ( pair ) {
- const parts = pair.split( '=' ).map( decodeURIComponent );
- query[ parts[ 0 ] ] = parts[ 1 ];
- } );
- return query;
- }
- function getSearch( url ) {
- // yea I know this is not perfect but whatever
- const s = url.indexOf( '?' );
- return s < 0 ? {} : getQuery( url.substring( s ) );
- }
- function getFQUrl( path, baseUrl ) {
- const url = new URL( path, baseUrl || window.location.href );
- return url.href;
- }
- async function getHTML( url ) {
- const req = await fetch( url );
- return await req.text();
- }
- function getPrefix( url ) {
- const u = new URL( url, window.location.href );
- const prefix = u.origin + dirname( u.pathname );
- return prefix;
- }
- function fixCSSLinks( url, source ) {
- const cssUrlRE1 = /(url\(')(.*?)('\))/g;
- const cssUrlRE2 = /(url\()(.*?)(\))/g;
- const prefix = getPrefix( url );
- function addPrefix( url ) {
- return url.indexOf( '://' ) < 0 && ! url.startsWith( 'data:' ) ? `${prefix}/${url}` : url;
- }
- function makeFQ( match, prefix, url, suffix ) {
- return `${prefix}${addPrefix( url )}${suffix}`;
- }
- source = source.replace( cssUrlRE1, makeFQ );
- source = source.replace( cssUrlRE2, makeFQ );
- return source;
- }
- /**
- * @typedef {Object} Globals
- * @property {SourceInfo} rootScriptInfo
- * @property {Object<string, SourceInfo} scriptInfos
- */
- /** @type {Globals} */
- const g = {
- html: '',
- };
- /**
- * This is what's in the sources array
- * @typedef {Object} SourceInfo
- * @property {string} source The source text (html, css, js)
- * @property {string} name The filename or "main page"
- * @property {ScriptInfo} scriptInfo The associated ScriptInfo
- * @property {string} fqURL ??
- * @property {Editor} editor in instance of Monaco editor
- *
- */
- /**
- * @typedef {Object} EditorInfo
- * @property {HTMLElement} div The div holding the monaco editor
- * @property {Editor} editor an instance of a monaco editor
- */
- /**
- * What's under each language
- * @typedef {Object} HTMLPart
- * @property {string} language Name of language
- * @property {SourceInfo} sources array of SourceInfos. Usually 1 for HTML, 1 for CSS, N for JS
- * @property {HTMLElement} pane the pane for these editors
- * @property {HTMLElement} code the div holding the files
- * @property {HTMLElement} files the div holding the divs holding the monaco editors
- * @property {HTMLElement} button the element to click to show this pane
- * @property {EditorInfo} editors
- */
- /** @type {Object<string, HTMLPart>} */
- const htmlParts = {
- js: {
- language: 'javascript',
- sources: [],
- },
- css: {
- language: 'css',
- sources: [],
- },
- html: {
- language: 'html',
- sources: [],
- },
- };
- function getRootPrefix( url ) {
- const u = new URL( url, window.location.href );
- return u.origin;
- }
- function removeDotDotSlash( href ) {
- // assumes a well formed URL. In other words: 'https://..//foo.html" is a bad URL and this code would fail.
- const url = new URL( href, window.location.href );
- const parts = url.pathname.split( '/' );
- for ( ;; ) {
- const dotDotNdx = parts.indexOf( '..' );
- if ( dotDotNdx < 0 ) {
- break;
- }
- parts.splice( dotDotNdx - 1, 2 );
- }
- url.pathname = parts.join( '/' );
- return url.toString();
- }
- function forEachHTMLPart( fn ) {
- Object.keys( htmlParts ).forEach( function ( name, ndx ) {
- const info = htmlParts[ name ];
- fn( info, ndx, name );
- } );
- }
- function getHTMLPart( re, obj, tag ) {
- let part = '';
- obj.html = obj.html.replace( re, function ( p0, p1 ) {
- part = p1;
- return tag;
- } );
- return part.replace( /\s*/, '' );
- }
- // doesn't handle multi-line comments or comments with { or } in them
- function formatCSS( css ) {
- let indent = '';
- return css.split( '\n' ).map( ( line ) => {
- let currIndent = indent;
- if ( line.includes( '{' ) ) {
- indent = indent + ' ';
- } else if ( line.includes( '}' ) ) {
- indent = indent.substring( 0, indent.length - 2 );
- currIndent = indent;
- }
- return `${currIndent}${line.trim()}`;
- } ).join( '\n' );
- }
- async function getScript( url, scriptInfos ) {
- // check it's an example script, not some other lib
- if ( ! scriptInfos[ url ].source ) {
- const source = await getHTML( url );
- const fixedSource = fixSourceLinks( url, source );
- const { text } = await getWorkerScripts( fixedSource, url, scriptInfos );
- scriptInfos[ url ].source = text;
- }
- }
- /**
- * @typedef {Object} ScriptInfo
- * @property {string} fqURL The original fully qualified URL
- * @property {ScriptInfo[]} deps Array of other ScriptInfos this is script dependant on
- * @property {boolean} isWorker True if this script came from `new Worker('someurl')` vs `import` or `importScripts`
- * @property {string} blobUrl The blobUrl for this script if one has been made
- * @property {number} blobGenerationId Used to not visit things twice while recursing.
- * @property {string} source The source as extracted. Updated from editor by getSourcesFromEditor
- * @property {string} munged The source after urls have been replaced with blob urls etc... (the text send to new Blob)
- */
- async function getWorkerScripts( text, baseUrl, scriptInfos = {} ) {
- const parentScriptInfo = scriptInfos[ baseUrl ];
- const workerRE = /(new\s+Worker\s*\(\s*)('|")(.*?)('|")/g;
- const importScriptsRE = /(importScripts\s*\(\s*)('|")(.*?)('|")/g;
- const importRE = /(import.*?)(?!'three')('|")(.*?)('|")/g;
- const newScripts = [];
- const slashRE = /\/manual\/examples\/[^/]+$/;
- function replaceWithUUID( match, prefix, quote, url ) {
- const fqURL = getFQUrl( url, baseUrl );
- if ( ! slashRE.test( fqURL ) ) {
- return match.toString();
- }
- if ( ! scriptInfos[ url ] ) {
- scriptInfos[ fqURL ] = {
- fqURL,
- deps: [],
- isWorker: prefix.indexOf( 'Worker' ) >= 0,
- };
- newScripts.push( fqURL );
- }
- parentScriptInfo.deps.push( scriptInfos[ fqURL ] );
- return `${prefix}${quote}${fqURL}${quote}`;
- }
- function replaceWithUUIDModule( match, prefix, quote, url ) {
- // modules are either relative, fully qualified, or a module name
- // Skip it if it's a module name
- return ( url.startsWith( '.' ) || url.includes( '://' ) )
- ? replaceWithUUID( match, prefix, quote, url )
- : match.toString();
- }
- text = text.replace( workerRE, replaceWithUUID );
- text = text.replace( importScriptsRE, replaceWithUUID );
- text = text.replace( importRE, replaceWithUUIDModule );
- await Promise.all( newScripts.map( ( url ) => {
- return getScript( url, scriptInfos );
- } ) );
- return { text, scriptInfos };
- }
- // hack: scriptInfo is undefined for html and css
- // should try to include html and css in scriptInfos
- function addSource( type, name, source, scriptInfo ) {
- htmlParts[ type ].sources.push( { source, name, scriptInfo } );
- }
- function safeStr( s ) {
- return s === undefined ? '' : s;
- }
- async function parseHTML( url, html ) {
- html = fixSourceLinks( url, html );
- html = html.replace( /<div class="description">[^]*?<\/div>/, '' );
- const styleRE = /<style>([^]*?)<\/style>/i;
- const titleRE = /<title>([^]*?)<\/title>/i;
- const bodyRE = /<body>([^]*?)<\/body>/i;
- const inlineScriptRE = /<script>([^]*?)<\/script>/i;
- const inlineModuleScriptRE = /<script type="module">([^]*?)<\/script>/i;
- const externalScriptRE = /(<!--(?:(?!-->)[\s\S])*?-->\n){0,1}<script\s+([^>]*?)(type="module"\s+)?src\s*=\s*"(.*?)"(.*?)>\s*<\/script>/ig;
- const dataScriptRE = /(<!--(?:(?!-->)[\s\S])*?-->\n){0,1}<script([^>]*?type="(?!module).*?".*?)>([^]*?)<\/script>/ig;
- const cssLinkRE = /<link ([^>]+?)>/g;
- const isCSSLinkRE = /type="text\/css"|rel="stylesheet"/;
- const hrefRE = /href="([^"]+)"/;
- const obj = { html: html };
- addSource( 'css', 'css', formatCSS( fixCSSLinks( url, getHTMLPart( styleRE, obj, '<style>\n${css}</style>' ) ) ) );
- addSource( 'html', 'html', getHTMLPart( bodyRE, obj, '<body>${html}</body>' ) );
- const rootScript = getHTMLPart( inlineScriptRE, obj, '<script>${js}</script>' ) ||
- getHTMLPart( inlineModuleScriptRE, obj, '<script type="module">${js}</script>' );
- html = obj.html;
- const fqURL = getFQUrl( url );
- /** @type Object<string, SourceInfo> */
- 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}></${kScript}>` );
- 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}</${kScript}>` );
- 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( '</head>', '<style>\n${css}</style>\n</head>' );
- }
- // 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( '</head>', '<script id="hackedparams">window.hackedParams = ${hackedParams}\n</script>\n</head>' );
- 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( '<head>', `<head>
- <link rel="stylesheet" href="${prefix}/resources/lesson-helper.css" type="text/css">
- <script match="false">self.lessonSettings = ${JSON.stringify( lessonSettings )}</script>` );
- source = source.replace( '</head>', `<script src="${prefix}/resources/webgl-debug-helper.js"></script>
- <script src="${prefix}/resources/lessons-helper.js"></script>
- </head>` );
- const scriptNdx = source.search( /<script(\s+type="module"\s*)?>/ );
- 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 `<script id="${name}" type="x-worker">\n${text}\n</script>\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 = `
- <form method="POST" target="_blank" action="https://codepen.io/pen/define" class="hidden">'
- <input type="hidden" name="data">
- <input type="submit" />
- "</form>"
- `;
- 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 = `
- <form method="POST" target="_black" action="https://jsfiddle.net/api/mdn/" class="hidden">
- <input type="hidden" name="html" />
- <input type="hidden" name="css" />
- <input type="hidden" name="js" />
- <input type="hidden" name="title" />
- <input type="hidden" name="wrap" value="b" />
- <input type="submit" />
- </form>
- `;
- 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 } );
- }
- /*
- <!-- begin snippet: js hide: false console: true babel: false -->
- <!-- language: lang-js -->
- console.log();
- <!-- language: lang-css -->
- h1 { color: red; }
- <!-- language: lang-html -->
- <h1>foo</h1>
- <!-- end snippet -->
- */
- 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
- ? `
- <!-- begin snippet: js hide: false console: true babel: false -->
- <!-- language: lang-js -->
- <!-- language: lang-css -->
- ${indent4( mainCSS )}
- <!-- language: lang-html -->
- ${indent4( mainHTML )}
- <script type="module">
- ${indent4( mainJS )}
- </script>
- <!-- end snippet -->
- `
- : `
- <!-- begin snippet: js hide: false console: true babel: false -->
- <!-- language: lang-js -->
- ${indent4( mainJS )}
- <!-- language: lang-css -->
- ${indent4( mainCSS )}
- <!-- language: lang-html -->
- ${indent4( mainHTML )}
- <!-- end snippet -->
- `;
- 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 `<!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
- <title>${s.title}</title>
- <style>
- ${s.css}
- </style>
- </head>
- <body>
- ${s.body}
- </body>
- ${s.script.startsWith( '<' )
- ? s.script
- : `
- <script type="module">
- ${s.script}
- </script>
- `}
- </html>`;
- }
- // ---vvv---
- // Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
- // 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 = `
- <form action="https://codesandbox.io/api/v1/sandboxes/define" method="POST" target="_blank" class="hidden">
- <input type="hidden" name="parameters" />
- <input type="submit" />
- </form>
- `;
- 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: '<script src="index.js" type="module"></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 = `
- <form action="https://stackblitz.com/run" method="POST" target="_blank" class="hidden">
- <input type="hidden" name="project[description]" value="${g.title}">
- <input type="hidden" name="project[dependencies]" value="{}">
- <input type="hidden" name="project[template]" value="javascript">
- <input type="hidden" name="project[settings]" value="{}">
- <input type="submit" />
- </form>
- `;
- 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();
- }() );
|