|  | // Copyright (c) 2016, Compiler Explorer Authors | 
|  | // All rights reserved. | 
|  | // | 
|  | // Redistribution and use in source and binary forms, with or without | 
|  | // modification, are permitted provided that the following conditions are met: | 
|  | // | 
|  | //     * Redistributions of source code must retain the above copyright notice, | 
|  | //       this list of conditions and the following disclaimer. | 
|  | //     * Redistributions in binary form must reproduce the above copyright | 
|  | //       notice, this list of conditions and the following disclaimer in the | 
|  | //       documentation and/or other materials provided with the distribution. | 
|  | // | 
|  | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
|  | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|  | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
|  | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | 
|  | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | 
|  | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | 
|  | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | 
|  | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | 
|  | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 
|  | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 
|  | // POSSIBILITY OF SUCH DAMAGE. | 
|  |  | 
|  | // setup analytics before anything else so we can capture any future errors in sentry | 
|  | import {ga as analytics} from './analytics'; | 
|  |  | 
|  | import 'whatwg-fetch'; | 
|  | import 'popper.js'; // eslint-disable-line requirejs/no-js-extension | 
|  | import 'bootstrap'; | 
|  |  | 
|  | import $ from 'jquery'; | 
|  | import _ from 'underscore'; | 
|  |  | 
|  | import GoldenLayout from 'golden-layout'; | 
|  | import JsCookie from 'js-cookie'; | 
|  | import clipboard from 'clipboard'; | 
|  | import * as Sentry from '@sentry/browser'; | 
|  |  | 
|  | // We re-assign this | 
|  | let jsCookie = JsCookie; | 
|  |  | 
|  | import {Sharing} from './sharing'; | 
|  | import * as Components from './components'; | 
|  | import * as url from './url'; | 
|  | import {Hub} from './hub'; | 
|  | import {Settings, SiteSettings} from './settings'; | 
|  | import * as local from './local'; | 
|  | import {Alert} from './widgets/alert'; | 
|  | import * as themer from './themes'; | 
|  | import * as motd from './motd'; | 
|  | import {SimpleCook} from './widgets/simplecook'; | 
|  | import {HistoryWidget} from './widgets/history-widget'; | 
|  | import * as History from './history'; | 
|  | import {Presentation} from './presentation'; | 
|  | import {setupSiteTemplateWidgetButton} from './widgets/site-templates-widget'; | 
|  | import {options} from './options'; | 
|  | import {unwrap} from './assert'; | 
|  |  | 
|  | import {Language, LanguageKey} from '../types/languages.interfaces'; | 
|  | import {CompilerExplorerOptions} from './global'; | 
|  | import {ComponentConfig, EmptyCompilerState, StateWithId, StateWithLanguage} from './components.interfaces'; | 
|  |  | 
|  | import * as utils from '../lib/common-utils'; | 
|  |  | 
|  | const logos = require.context('../views/resources/logos', false, /\.(png|svg)$/); | 
|  |  | 
|  | const siteTemplateScreenshots = require.context('../views/resources/template_screenshots', false, /\.png$/); | 
|  |  | 
|  | if (!window.PRODUCTION && !options.embedded) { | 
|  | require('./tests/_all'); | 
|  | } | 
|  |  | 
|  | //css | 
|  | require('bootstrap/dist/css/bootstrap.min.css'); | 
|  | require('golden-layout/src/css/goldenlayout-base.css'); | 
|  | require('tom-select/dist/css/tom-select.bootstrap4.css'); | 
|  | require('./colours.scss'); | 
|  | require('./styles/explorer.scss'); | 
|  |  | 
|  | // Check to see if the current unload is a UI reset. | 
|  | // Forgive me the global usage here | 
|  | let hasUIBeenReset = false; | 
|  | const simpleCooks = new SimpleCook(); | 
|  | const historyWidget = new HistoryWidget(); | 
|  |  | 
|  | const policyDocuments = { | 
|  | cookies: require('./generated/cookies.pug').default, | 
|  | privacy: require('./generated/privacy.pug').default, | 
|  | }; | 
|  |  | 
|  | function setupSettings(hub: Hub) { | 
|  | const eventHub = hub.layout.eventHub; | 
|  | const defaultSettings = { | 
|  | defaultLanguage: hub.defaultLangId, | 
|  | }; | 
|  | let currentSettings: SiteSettings = JSON.parse(local.get('settings', 'null')) || defaultSettings; | 
|  |  | 
|  | function onChange(newSettings: SiteSettings) { | 
|  | if (currentSettings.theme !== newSettings.theme) { | 
|  | analytics.proxy('send', { | 
|  | hitType: 'event', | 
|  | eventCategory: 'ThemeChange', | 
|  | eventAction: newSettings.theme, | 
|  | }); | 
|  | } | 
|  | if (currentSettings.colourScheme !== newSettings.colourScheme) { | 
|  | analytics.proxy('send', { | 
|  | hitType: 'event', | 
|  | eventCategory: 'ColourSchemeChange', | 
|  | eventAction: newSettings.colourScheme, | 
|  | }); | 
|  | } | 
|  | $('#settings').find('.editorsFFont').css('font-family', newSettings.editorsFFont); | 
|  | currentSettings = newSettings; | 
|  | local.set('settings', JSON.stringify(newSettings)); | 
|  | eventHub.emit('settingsChange', newSettings); | 
|  | } | 
|  |  | 
|  | new themer.Themer(eventHub, currentSettings); | 
|  |  | 
|  | eventHub.on('requestSettings', () => { | 
|  | eventHub.emit('settingsChange', currentSettings); | 
|  | }); | 
|  |  | 
|  | const SettingsObject = new Settings(hub, $('#settings'), currentSettings, onChange, hub.subdomainLangId); | 
|  | eventHub.on('modifySettings', (newSettings: Partial<SiteSettings>) => { | 
|  | SettingsObject.setSettings(_.extend(currentSettings, newSettings)); | 
|  | }); | 
|  | return currentSettings; | 
|  | } | 
|  |  | 
|  | function hasCookieConsented(options: CompilerExplorerOptions) { | 
|  | return jsCookie.get(options.policies.cookies.key) === policyDocuments.cookies.hash; | 
|  | } | 
|  |  | 
|  | function isMobileViewer() { | 
|  | return window.compilerExplorerOptions.mobileViewer; | 
|  | } | 
|  |  | 
|  | function calcLocaleChangedDate(policyModal: JQuery) { | 
|  | const timestamp = policyModal.find('#changed-date'); | 
|  | timestamp.text(new Date(unwrap(timestamp.attr('datetime'))).toLocaleString()); | 
|  | } | 
|  |  | 
|  | function setupButtons(options: CompilerExplorerOptions, hub: Hub) { | 
|  | const eventHub = hub.createEventHub(); | 
|  | const alertSystem = new Alert(); | 
|  |  | 
|  | // I'd like for this to be the only function used, but it gets messy to pass the callback function around, | 
|  | // so we instead trigger a click here when we want it to open with this effect. Sorry! | 
|  | if (options.policies.privacy.enabled) { | 
|  | $('#privacy').on('click', (event, data) => { | 
|  | const modal = alertSystem.alert( | 
|  | data && data.title ? data.title : 'Privacy policy', | 
|  | policyDocuments.privacy.text | 
|  | ); | 
|  | calcLocaleChangedDate(modal); | 
|  | // I can't remember why this check is here as it seems superfluous | 
|  | if (options.policies.privacy.enabled) { | 
|  | jsCookie.set(options.policies.privacy.key, policyDocuments.privacy.hash, { | 
|  | expires: 365, | 
|  | sameSite: 'strict', | 
|  | }); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | if (options.policies.cookies.enabled) { | 
|  | const getCookieTitle = () => { | 
|  | return ( | 
|  | 'Cookies & related technologies policy<br><p>Current consent status: <span style="color:' + | 
|  | (hasCookieConsented(options) ? 'green' : 'red') + | 
|  | '">' + | 
|  | (hasCookieConsented(options) ? 'Granted' : 'Denied') + | 
|  | '</span></p>' | 
|  | ); | 
|  | }; | 
|  | $('#cookies').on('click', () => { | 
|  | const modal = alertSystem.ask(getCookieTitle(), policyDocuments.cookies.text, { | 
|  | yes: () => { | 
|  | simpleCooks.callDoConsent.apply(simpleCooks); | 
|  | }, | 
|  | yesHtml: 'Consent', | 
|  | no: () => { | 
|  | simpleCooks.callDontConsent.apply(simpleCooks); | 
|  | }, | 
|  | noHtml: 'Do NOT consent', | 
|  | }); | 
|  | calcLocaleChangedDate(modal); | 
|  | }); | 
|  | } | 
|  |  | 
|  | $('#ui-reset').on('click', () => { | 
|  | local.remove('gl'); | 
|  | hasUIBeenReset = true; | 
|  | window.history.replaceState(null, '', window.httpRoot); | 
|  | window.location.reload(); | 
|  | }); | 
|  |  | 
|  | $('#ui-duplicate').on('click', () => { | 
|  | window.open('/', '_blank'); | 
|  | }); | 
|  |  | 
|  | $('#changes').on('click', () => { | 
|  | // TODO(jeremy-rifkin): Fix types | 
|  | alertSystem.alert('Changelog', $(require('./generated/changelog.pug').default.text) as any); | 
|  | }); | 
|  |  | 
|  | $.get(window.location.origin + window.httpRoot + 'bits/icons.html') | 
|  | .done(data => { | 
|  | $('#ces .ces-icons').html(data); | 
|  | }) | 
|  | .fail(err => { | 
|  | Sentry.captureException(err); | 
|  | }); | 
|  |  | 
|  | $('#ces').on('click', () => { | 
|  | $.get(window.location.origin + window.httpRoot + 'bits/sponsors.html') | 
|  | .done(data => { | 
|  | alertSystem.alert('Compiler Explorer Sponsors', data); | 
|  | analytics.proxy('send', { | 
|  | hitType: 'event', | 
|  | eventCategory: 'Sponsors', | 
|  | eventAction: 'open', | 
|  | }); | 
|  | }) | 
|  | .fail(err => { | 
|  | const result = err.responseText || JSON.stringify(err); | 
|  | alertSystem.alert( | 
|  | 'Compiler Explorer Sponsors', | 
|  | '<div>Unable to fetch sponsors:</div><div>' + result + '</div>' | 
|  | ); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | $('#ui-history').on('click', () => { | 
|  | historyWidget.run(data => { | 
|  | local.set('gl', JSON.stringify(data.config)); | 
|  | hasUIBeenReset = true; | 
|  | window.history.replaceState(null, '', window.httpRoot); | 
|  | window.location.reload(); | 
|  | }); | 
|  |  | 
|  | $('#history').modal(); | 
|  | }); | 
|  |  | 
|  | $('#ui-apply-default-font-scale').on('click', () => { | 
|  | const defaultFontScale = Settings.getStoredSettings().defaultFontScale; | 
|  | if (defaultFontScale !== undefined) { | 
|  | eventHub.emit('broadcastFontScale', defaultFontScale); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | function configFromEmbedded(embeddedUrl: string) { | 
|  | // Old-style link? | 
|  | let params; | 
|  | try { | 
|  | params = url.unrisonify(embeddedUrl); | 
|  | } catch (e) { | 
|  | document.write( | 
|  | '<div style="padding: 10px; background: #fa564e; color: black;">' + | 
|  | "An error was encountered while decoding the URL for this embed. Make sure the URL hasn't been " + | 
|  | 'truncated, otherwise if you believe your URL is valid please let us know on ' + | 
|  | '<a href="https://github.com/compiler-explorer/compiler-explorer/issues" style="color: black;">' + | 
|  | 'our github' + | 
|  | '</a>.' + | 
|  | '</div>' | 
|  | ); | 
|  | throw new Error('Embed url decode error'); | 
|  | } | 
|  | if (params && params.source && params.compiler) { | 
|  | const filters = Object.fromEntries(((params.filters as string) || '').split(',').map(o => [o, true])); | 
|  | // TODO(jeremy-rifkin): Fix types | 
|  | return { | 
|  | content: [ | 
|  | { | 
|  | type: 'row', | 
|  | content: [ | 
|  | Components.getEditorWith(1, params.source, filters as any), | 
|  | Components.getCompilerWith(1, filters as any, params.options, params.compiler), | 
|  | ], | 
|  | }, | 
|  | ], | 
|  | }; | 
|  | } else { | 
|  | return url.deserialiseState(embeddedUrl); | 
|  | } | 
|  | } | 
|  |  | 
|  | // TODO(jeremy-rifkin): Unsure of the type, just typing enough for `content` at the moment | 
|  | function fixBugsInConfig(config: Record<string, any> & {content?: any[]}) { | 
|  | if (config.activeItemIndex && config.activeItemIndex >= unwrap(config.content).length) { | 
|  | config.activeItemIndex = unwrap(config.content).length - 1; | 
|  | } | 
|  |  | 
|  | if (config.content) { | 
|  | for (const item of config.content) { | 
|  | fixBugsInConfig(item); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | type ConfigType = { | 
|  | settings: { | 
|  | showPopoutIcon: boolean; | 
|  | }; | 
|  | content: { | 
|  | type: string; | 
|  | content: (ComponentConfig<Partial<StateWithId & StateWithLanguage>> | ComponentConfig<EmptyCompilerState>)[]; | 
|  | }[]; | 
|  | }; | 
|  |  | 
|  | function findConfig(defaultConfig: ConfigType, options: CompilerExplorerOptions) { | 
|  | let config; | 
|  | if (!options.embedded) { | 
|  | if (options.slides) { | 
|  | const presentation = new Presentation(unwrap(window.compilerExplorerOptions.slides).length); | 
|  | const currentSlide = presentation.currentSlide; | 
|  | if (currentSlide < options.slides.length) { | 
|  | config = options.slides[currentSlide]; | 
|  | } else { | 
|  | presentation.currentSlide = 0; | 
|  | config = options.slides[0]; | 
|  | } | 
|  | if ( | 
|  | isMobileViewer() && | 
|  | window.compilerExplorerOptions.slides && | 
|  | window.compilerExplorerOptions.slides.length > 1 | 
|  | ) { | 
|  | $('#share').remove(); | 
|  | $('.ui-presentation-control').removeClass('d-none'); | 
|  | $('.ui-presentation-first').on('click', presentation.first.bind(presentation)); | 
|  | $('.ui-presentation-prev').on('click', presentation.previous.bind(presentation)); | 
|  | $('.ui-presentation-next').on('click', presentation.next.bind(presentation)); | 
|  | } | 
|  | } else { | 
|  | if (options.config) { | 
|  | config = options.config; | 
|  | } else { | 
|  | try { | 
|  | config = url.deserialiseState(window.location.hash.substring(1)); | 
|  | } catch (e) { | 
|  | // #3518 Alert the user that the url is invalid | 
|  | const alertSystem = new Alert(); | 
|  | alertSystem.alert( | 
|  | 'Decode Error', | 
|  | 'An error was encountered while decoding the URL, the last locally saved configuration will ' + | 
|  | "be used if present.<br/><br/>Make sure the URL you're using hasn't been truncated, " + | 
|  | 'otherwise if you believe your URL is valid please let us know on ' + | 
|  | '<a href="https://github.com/compiler-explorer/compiler-explorer/issues">our github</a>.' | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (config) { | 
|  | // replace anything in the default config with that from the hash | 
|  | config = _.extend(defaultConfig, config); | 
|  | } | 
|  | if (!config) { | 
|  | const savedState = local.get('gl', null); | 
|  | config = savedState !== null ? JSON.parse(savedState) : defaultConfig; | 
|  | } | 
|  | } | 
|  | } else { | 
|  | config = _.extend( | 
|  | defaultConfig, | 
|  | { | 
|  | settings: { | 
|  | showMaximiseIcon: false, | 
|  | showCloseIcon: false, | 
|  | hasHeaders: false, | 
|  | }, | 
|  | }, | 
|  | configFromEmbedded(window.location.hash.substring(1)) | 
|  | ); | 
|  | } | 
|  |  | 
|  | removeOrphanedMaximisedItemFromConfig(config); | 
|  | fixBugsInConfig(config); | 
|  |  | 
|  | return config; | 
|  | } | 
|  |  | 
|  | function initializeResetLayoutLink() { | 
|  | const currentUrl = document.URL; | 
|  | if (currentUrl.includes('/z/')) { | 
|  | $('#ui-brokenlink').attr('href', currentUrl.replace('/z/', '/resetlayout/')).show(); | 
|  | } else { | 
|  | $('#ui-brokenlink').hide(); | 
|  | } | 
|  | } | 
|  |  | 
|  | function initPolicies(options: CompilerExplorerOptions) { | 
|  | if (options.policies.privacy.enabled) { | 
|  | if (jsCookie.get(options.policies.privacy.key) == null) { | 
|  | $('#privacy').trigger('click', { | 
|  | title: 'New Privacy Policy. Please take a moment to read it', | 
|  | }); | 
|  | } else if (policyDocuments.privacy.hash !== jsCookie.get(options.policies.privacy.key)) { | 
|  | // When the user has already accepted the privacy, just show a pretty notification. | 
|  | const ppolicyBellNotification = $('#policyBellNotification'); | 
|  | const pprivacyBellNotification = $('#privacyBellNotification'); | 
|  | const pcookiesBellNotification = $('#cookiesBellNotification'); | 
|  | ppolicyBellNotification.removeClass('d-none'); | 
|  | pprivacyBellNotification.removeClass('d-none'); | 
|  | $('#privacy').on('click', () => { | 
|  | // Only hide if the other policy does not also have a bell | 
|  | if (pcookiesBellNotification.hasClass('d-none')) { | 
|  | ppolicyBellNotification.addClass('d-none'); | 
|  | } | 
|  | pprivacyBellNotification.addClass('d-none'); | 
|  | }); | 
|  | } | 
|  | } | 
|  | simpleCooks.setOnDoConsent(() => { | 
|  | jsCookie.set(options.policies.cookies.key, policyDocuments.cookies.hash, { | 
|  | expires: 365, | 
|  | sameSite: 'strict', | 
|  | }); | 
|  | analytics.toggle(true); | 
|  | }); | 
|  | simpleCooks.setOnDontConsent(() => { | 
|  | analytics.toggle(false); | 
|  | jsCookie.set(options.policies.cookies.key, '', { | 
|  | sameSite: 'strict', | 
|  | }); | 
|  | }); | 
|  | simpleCooks.setOnHide(() => { | 
|  | const spolicyBellNotification = $('#policyBellNotification'); | 
|  | const sprivacyBellNotification = $('#privacyBellNotification'); | 
|  | const scookiesBellNotification = $('#cookiesBellNotification'); | 
|  | // Only hide if the other policy does not also have a bell | 
|  | if (sprivacyBellNotification.hasClass('d-none')) { | 
|  | spolicyBellNotification.addClass('d-none'); | 
|  | } | 
|  | scookiesBellNotification.addClass('d-none'); | 
|  | $(window).trigger('resize'); | 
|  | }); | 
|  | // '' means no consent. Hash match means consent of old. Null means new user! | 
|  | const storedCookieConsent = jsCookie.get(options.policies.cookies.key); | 
|  | if (options.policies.cookies.enabled) { | 
|  | if (storedCookieConsent !== '' && policyDocuments.cookies.hash !== storedCookieConsent) { | 
|  | simpleCooks.show(); | 
|  | const cpolicyBellNotification = $('#policyBellNotification'); | 
|  | const cprivacyBellNotification = $('#privacyBellNotification'); | 
|  | const ccookiesBellNotification = $('#cookiesBellNotification'); | 
|  | cpolicyBellNotification.removeClass('d-none'); | 
|  | ccookiesBellNotification.removeClass('d-none'); | 
|  | $('#cookies').on('click', () => { | 
|  | if (cprivacyBellNotification.hasClass('d-none')) { | 
|  | cpolicyBellNotification.addClass('d-none'); | 
|  | } | 
|  | ccookiesBellNotification.addClass('d-none'); | 
|  | }); | 
|  | } else if (hasCookieConsented(options)) { | 
|  | analytics.initialise(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * this nonsense works around a bug in goldenlayout where a config can be generated | 
|  | * that contains a flag indicating there is a maximized item which does not correspond | 
|  | * to any items that actually exist in the config. | 
|  | * | 
|  | * See https://github.com/compiler-explorer/compiler-explorer/issues/2056 | 
|  | */ | 
|  | function removeOrphanedMaximisedItemFromConfig(config) { | 
|  | // nothing to do if the maximised item id is not set | 
|  | if (config.maximisedItemId !== '__glMaximised') return; | 
|  |  | 
|  | let found = false as boolean; | 
|  |  | 
|  | function impl(component) { | 
|  | if (component.id === '__glMaximised') { | 
|  | found = true; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (component.content) { | 
|  | for (let i = 0; i < component.content.length; i++) { | 
|  | impl(component.content[i]); | 
|  | if (found) return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | impl(config); | 
|  |  | 
|  | if (!found) { | 
|  | config.maximisedItemId = null; | 
|  | } | 
|  | } | 
|  |  | 
|  | function setupLanguageLogos(languages: Partial<Record<LanguageKey, Language>>) { | 
|  | for (const lang of Object.values(languages)) { | 
|  | try { | 
|  | if (lang.logoUrl !== null) { | 
|  | lang.logoData = logos('./' + lang.logoUrl); | 
|  | if (lang.logoUrlDark !== null) { | 
|  | lang.logoDataDark = logos('./' + lang.logoUrlDark); | 
|  | } | 
|  | } | 
|  | } catch (ignored) { | 
|  | lang.logoData = ''; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | function earlyGetDefaultLangSetting() { | 
|  | return Settings.getStoredSettings().defaultLanguage; | 
|  | } | 
|  |  | 
|  | function getDefaultLangId(subLangId: LanguageKey | undefined, options: CompilerExplorerOptions) { | 
|  | let defaultLangId = subLangId; | 
|  | if (!defaultLangId) { | 
|  | const defaultLangSetting = earlyGetDefaultLangSetting(); | 
|  | if (defaultLangSetting && defaultLangSetting in options.languages) { | 
|  | defaultLangId = defaultLangSetting; | 
|  | } else if ('c++' in options.languages) { | 
|  | defaultLangId = 'c++'; | 
|  | } else { | 
|  | defaultLangId = utils.keys(options.languages)[0]; | 
|  | } | 
|  | } | 
|  | return defaultLangId; | 
|  | } | 
|  |  | 
|  | // eslint-disable-next-line max-statements | 
|  | function start() { | 
|  | initializeResetLayoutLink(); | 
|  | setupSiteTemplateWidgetButton(siteTemplateScreenshots); | 
|  |  | 
|  | const hostnameParts = window.location.hostname.split('.'); | 
|  | let subLangId: LanguageKey | undefined = undefined; | 
|  | // Only set the subdomain lang id if it makes sense to do so | 
|  | if (hostnameParts.length > 0) { | 
|  | const subdomainPart = hostnameParts[0]; | 
|  | const langBySubdomain = Object.values(options.languages).find( | 
|  | lang => lang.id === subdomainPart || lang.alias.includes(subdomainPart) | 
|  | ); | 
|  | if (langBySubdomain) { | 
|  | subLangId = langBySubdomain.id; | 
|  | } | 
|  | } | 
|  |  | 
|  | const defaultLangId = getDefaultLangId(subLangId, options); | 
|  |  | 
|  | setupLanguageLogos(options.languages); | 
|  |  | 
|  | // Cookie domains are matched as a RE against the window location. This allows a flexible | 
|  | // way that works across multiple domains (e.g. godbolt.org and compiler-explorer.com). | 
|  | // We allow this to be configurable so that (for example), gcc.godbolt.org and d.godbolt.org | 
|  | // share the same cookie domain for some settings. | 
|  | const cookieDomain = new RegExp(options.cookieDomainRe).exec(window.location.hostname); | 
|  | if (cookieDomain && cookieDomain[0]) { | 
|  | jsCookie = jsCookie.withAttributes({domain: cookieDomain[0]}); | 
|  | } | 
|  |  | 
|  | const defaultConfig = { | 
|  | settings: {showPopoutIcon: false}, | 
|  | content: [ | 
|  | { | 
|  | type: 'row', | 
|  | content: [Components.getEditor(1, defaultLangId), Components.getCompiler(1, defaultLangId)], | 
|  | }, | 
|  | ], | 
|  | }; | 
|  |  | 
|  | $(window).on('hashchange', () => { | 
|  | // punt on hash events and just reload the page if there's a hash | 
|  | if (window.location.hash.substring(1)) window.location.reload(); | 
|  | }); | 
|  |  | 
|  | // Which buttons act as a linkable popup | 
|  | const linkablePopups = ['#ces', '#sponsors', '#changes', '#cookies', '#setting', '#privacy']; | 
|  | let hashPart = linkablePopups.includes(window.location.hash) ? window.location.hash : null; | 
|  | if (hashPart) { | 
|  | window.location.hash = ''; | 
|  | // Handle the time we renamed sponsors to ces to work around issues with blockers. | 
|  | if (hashPart === '#sponsors') hashPart = '#ces'; | 
|  | } | 
|  |  | 
|  | const config = findConfig(defaultConfig, options); | 
|  |  | 
|  | const root = $('#root'); | 
|  |  | 
|  | let layout; | 
|  | let hub; | 
|  | try { | 
|  | layout = new GoldenLayout(config, root); | 
|  | hub = new Hub(layout, subLangId, defaultLangId); | 
|  | } catch (e) { | 
|  | Sentry.captureException(e); | 
|  |  | 
|  | if (document.URL.includes('/z/')) { | 
|  | document.location = document.URL.replace('/z/', '/resetlayout/'); | 
|  | } | 
|  |  | 
|  | layout = new GoldenLayout(defaultConfig, root); | 
|  | hub = new Hub(layout, subLangId, defaultLangId); | 
|  | } | 
|  |  | 
|  | if (hub.hasTree()) { | 
|  | $('#add-tree').prop('disabled', true); | 
|  | } | 
|  |  | 
|  | function sizeRoot() { | 
|  | const height = unwrap($(window).height()) - root.position().top - ($('#simplecook:visible').height() || 0); | 
|  | root.height(height); | 
|  | layout.updateSize(); | 
|  | } | 
|  |  | 
|  | $(window) | 
|  | .on('resize', sizeRoot) | 
|  | .on('beforeunload', () => { | 
|  | // Only preserve state in localStorage in non-embedded mode. | 
|  | const shouldSave = !window.hasUIBeenReset && !hasUIBeenReset; | 
|  | if (!options.embedded && !isMobileViewer() && shouldSave) { | 
|  | local.set('gl', JSON.stringify(layout.toConfig())); | 
|  | } | 
|  | }); | 
|  |  | 
|  | new clipboard('.btn.clippy'); | 
|  |  | 
|  | const settings = setupSettings(hub); | 
|  |  | 
|  | // We assume no consent for embed users | 
|  | if (!options.embedded) { | 
|  | setupButtons(options, hub); | 
|  | } | 
|  |  | 
|  | const addDropdown = $('#addDropdown'); | 
|  |  | 
|  | function setupAdd<C>(thing: JQuery, func: () => ComponentConfig<C>) { | 
|  | layout.createDragSource(thing, func)._dragListener.on('dragStart', () => { | 
|  | addDropdown.dropdown('toggle'); | 
|  | }); | 
|  |  | 
|  | thing.on('click', () => { | 
|  | if (hub.hasTree()) { | 
|  | hub.addInEditorStackIfPossible(func()); | 
|  | } else { | 
|  | hub.addAtRoot(func()); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | setupAdd($('#add-editor'), () => { | 
|  | return Components.getEditor(); | 
|  | }); | 
|  | setupAdd($('#add-diff'), () => { | 
|  | return Components.getDiffView(); | 
|  | }); | 
|  | setupAdd($('#add-tree'), () => { | 
|  | $('#add-tree').prop('disabled', true); | 
|  | return Components.getTree(); | 
|  | }); | 
|  |  | 
|  | if (hashPart) { | 
|  | const element = $(hashPart); | 
|  | element.trigger('click'); | 
|  | } | 
|  | initPolicies(options); | 
|  |  | 
|  | // Skip some steps if using embedded mode | 
|  | if (!options.embedded) { | 
|  | // Only fetch MOTD when not embedded. | 
|  | motd.initialise( | 
|  | options.motdUrl, | 
|  | $('#motd'), | 
|  | subLangId ?? '', | 
|  | settings.enableCommunityAds, | 
|  | data => { | 
|  | const sendMotd = () => { | 
|  | hub.layout.eventHub.emit('motd', data); | 
|  | }; | 
|  | hub.layout.eventHub.on('requestMotd', sendMotd); | 
|  | sendMotd(); | 
|  | }, | 
|  | () => { | 
|  | hub.layout.eventHub.emit('modifySettings', { | 
|  | enableCommunityAds: false, | 
|  | }); | 
|  | } | 
|  | ); | 
|  |  | 
|  | // Don't try to update Version tree link | 
|  | const release = window.compilerExplorerOptions.gitReleaseCommit; | 
|  | let versionLink = 'https://github.com/compiler-explorer/compiler-explorer/'; | 
|  | if (release) { | 
|  | versionLink += 'tree/' + release; | 
|  | } | 
|  | $('#version-tree').prop('href', versionLink); | 
|  | } | 
|  |  | 
|  | if (options.hideEditorToolbars) { | 
|  | $('[name="editor-btn-toolbar"]').addClass('d-none'); | 
|  | } | 
|  |  | 
|  | window.onSponsorClick = (sponsorUrl: string) => { | 
|  | analytics.proxy('send', { | 
|  | hitType: 'event', | 
|  | eventCategory: 'Sponsors', | 
|  | eventAction: 'click', | 
|  | eventLabel: sponsorUrl, | 
|  | transport: 'beacon', | 
|  | }); | 
|  | window.open(sponsorUrl); | 
|  | }; | 
|  |  | 
|  | if (options.pageloadUrl) { | 
|  | setTimeout(() => { | 
|  | const visibleIcons = $('.ces-icon:visible') | 
|  | .map((_, value) => value.dataset.statsid) | 
|  | .get() | 
|  | .join(','); | 
|  | $.post(options.pageloadUrl + '?icons=' + encodeURIComponent(visibleIcons)); | 
|  | }, 5000); | 
|  | } | 
|  |  | 
|  | sizeRoot(); | 
|  |  | 
|  | History.trackHistory(layout); | 
|  | new Sharing(layout); | 
|  | } | 
|  |  | 
|  | $(start); |