|  | // 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. | 
|  |  | 
|  | 'use strict'; | 
|  | var $ = require('jquery'); | 
|  | var _ = require('underscore'); | 
|  | var options = require('./options'); | 
|  | var Components = require('./components'); | 
|  | var url = require('./url'); | 
|  | var ga = require('./analytics'); | 
|  |  | 
|  | var shareServices = { | 
|  | twitter: { | 
|  | embedValid: false, | 
|  | logoClass: 'fab fa-twitter', | 
|  | cssClass: 'share-twitter', | 
|  | getLink: function (title, url) { | 
|  | return 'https://twitter.com/intent/tweet?text=' + | 
|  | encodeURIComponent(title) + '&url=' + encodeURIComponent(url) + '&via=CompileExplore'; | 
|  | }, | 
|  | text: 'Tweet', | 
|  | }, | 
|  | reddit: { | 
|  | embedValid: false, | 
|  | logoClass: 'fab fa-reddit', | 
|  | cssClass: 'share-reddit', | 
|  | getLink: function (title, url) { | 
|  | return 'http://www.reddit.com/submit?url=' + | 
|  | encodeURIComponent(url) + '&title=' + encodeURIComponent(title); | 
|  | }, | 
|  | text: 'Share on Reddit', | 
|  | }, | 
|  | }; | 
|  |  | 
|  | function configFromEmbedded(embeddedUrl) { | 
|  | // Old-style link? | 
|  | var params; | 
|  | try { | 
|  | params = url.unrisonify(embeddedUrl); | 
|  | } catch (e) { | 
|  | // Ignore this, it's not a problem | 
|  | } | 
|  | if (params && params.source && params.compiler) { | 
|  | var filters = _.chain((params.filters || '').split(',')) | 
|  | .map(function (o) { | 
|  | return [o, true]; | 
|  | }) | 
|  | .object() | 
|  | .value(); | 
|  | return { | 
|  | content: [ | 
|  | { | 
|  | type: 'row', | 
|  | content: [ | 
|  | Components.getEditorWith(1, params.source, filters), | 
|  | Components.getCompilerWith(1, filters, params.options, params.compiler), | 
|  | ], | 
|  | }, | 
|  | ], | 
|  | }; | 
|  | } else { | 
|  | return url.deserialiseState(embeddedUrl); | 
|  | } | 
|  | } | 
|  |  | 
|  | function updateShares(container, url) { | 
|  | var baseTemplate = $('#share-item'); | 
|  | _.each(shareServices, function (service, serviceName) { | 
|  | var newElement = baseTemplate.children('a.share-item').clone(); | 
|  | if (service.logoClass) { | 
|  | newElement.prepend($('<span>') | 
|  | .addClass('dropdown-icon') | 
|  | .addClass(service.logoClass) | 
|  | .prop('title', serviceName) | 
|  | ); | 
|  | } | 
|  | if (service.text) { | 
|  | newElement.children('span.share-item-text') | 
|  | .text(service.text); | 
|  | } | 
|  | newElement | 
|  | .prop('href', service.getLink('Compiler Explorer', url)) | 
|  | .addClass(service.cssClass) | 
|  | .toggleClass('share-no-embeddable', !service.embedValid) | 
|  | .appendTo(container); | 
|  | }); | 
|  | } | 
|  |  | 
|  | function initShareButton(getLink, layout, noteNewState) { | 
|  | var baseUrl = window.location.protocol + '//' + window.location.hostname; | 
|  | var html = $('.template .urls').html(); | 
|  | var currentNode = null; | 
|  | // Explicit because webstorm gets confused about the type of this variable. | 
|  | /*** | 
|  | * Current URL bind | 
|  | * @type {string} | 
|  | */ | 
|  | var currentBind = ''; | 
|  | var title = getLink.prop('title'); // preserve before popover/tooltip breaks it | 
|  |  | 
|  | getLink.popover({ | 
|  | container: 'body', | 
|  | content: html, | 
|  | html: true, | 
|  | placement: 'bottom', | 
|  | trigger: 'manual', | 
|  | sanitize: false, | 
|  | }).click(function () { | 
|  | getLink.popover('toggle'); | 
|  | }).on('inserted.bs.popover', function () { | 
|  | ga.proxy('send', { | 
|  | hitType: 'event', | 
|  | eventCategory: 'OpenModalPane', | 
|  | eventAction: 'Sharing', | 
|  | }); | 
|  | var popoverElement = $($(this).data('bs.popover').tip); | 
|  | var socialSharingElements = popoverElement.find('.socialsharing'); | 
|  | var root = $('.urls-container:visible'); | 
|  | var label = root.find('.current'); | 
|  | var permalink = $('.permalink'); | 
|  | var urls = {}; | 
|  | if (!currentNode) currentNode = $(root.find('.sources button')[0]); | 
|  | if (!currentBind) currentBind = currentNode.data().bind; | 
|  |  | 
|  | function setCurrent(node) { | 
|  | currentNode = node; | 
|  | currentBind = node.data().bind; | 
|  | } | 
|  |  | 
|  | function setSocialSharing(element, sharedUrl) { | 
|  | if (options.sharingEnabled) { | 
|  | updateShares(element, sharedUrl); | 
|  | // Disable the links for every share item which does not support embed html as links | 
|  | if (currentBind !== 'Full' && currentBind !== 'Short') { | 
|  | element.children('.share-no-embeddable') | 
|  | .addClass('share-disabled') | 
|  | .prop('title', 'Embed links are not supported in this service') | 
|  | .on('click', false); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | function onUpdate(socialSharing, config, bind, result) { | 
|  | if (result.updateState) { | 
|  | noteNewState(config, result.extra); | 
|  | } | 
|  | label.text(bind); | 
|  | permalink.val(result.url); | 
|  | setSocialSharing(socialSharing, result.url); | 
|  | } | 
|  |  | 
|  | function getEmbeddedCacheLinkId() { | 
|  | if ($('#shareembedlink input:checked').length === 0) return 'Embed'; | 
|  |  | 
|  | return 'Embed|' + $('#shareembedlink input:checked').map(function () { | 
|  | return $(this).prop('class'); | 
|  | }) | 
|  | .get() | 
|  | .join(); | 
|  | } | 
|  |  | 
|  | function update() { | 
|  | var socialSharing = socialSharingElements; | 
|  | socialSharing.empty(); | 
|  | if (!currentBind) return; | 
|  | permalink.prop('disabled', false); | 
|  | var config = layout.toConfig(); | 
|  | var cacheLinkId = currentBind; | 
|  | if (currentBind === 'Embed') { | 
|  | cacheLinkId = getEmbeddedCacheLinkId(); | 
|  | } | 
|  | if (!urls[cacheLinkId]) { | 
|  | label.text(currentNode.text()); | 
|  | permalink.val(''); | 
|  | getLinks(config, currentBind, function (error, newUrl, extra, updateState) { | 
|  | if (error || !newUrl) { | 
|  | permalink.prop('disabled', true); | 
|  | permalink.val(error || 'Error providing URL'); | 
|  | } else { | 
|  | urls[cacheLinkId] = { | 
|  | updateState: updateState, | 
|  | extra: extra, | 
|  | url: newUrl, | 
|  | }; | 
|  | onUpdate(socialSharing, config, currentBind, urls[cacheLinkId]); | 
|  | } | 
|  | }); | 
|  | } else { | 
|  | onUpdate(socialSharing, config, currentBind, urls[cacheLinkId]); | 
|  | } | 
|  | } | 
|  |  | 
|  | root.find('.sources button').on('click', function () { | 
|  | setCurrent($(this)); | 
|  | update(); | 
|  | }); | 
|  |  | 
|  | var embeddedButton = $('.shareembed'); | 
|  | embeddedButton.on('click', function () { | 
|  | setCurrent(embeddedButton); | 
|  | update(); | 
|  | getLink.popover('hide'); | 
|  | }); | 
|  |  | 
|  | $('#embedsettings input').off('click').on('click', function () { | 
|  | setCurrent(embeddedButton); | 
|  | update(); | 
|  | }); | 
|  |  | 
|  | update(); | 
|  | }).prop('title', title); | 
|  |  | 
|  | // Dismiss the popover on escape. | 
|  | $(document).on('keyup.editable', function (e) { | 
|  | if (e.which === 27) { | 
|  | getLink.popover('hide'); | 
|  | } | 
|  | }); | 
|  |  | 
|  | // Dismiss on any click that isn't either in the opening element, inside | 
|  | // the popover or on any alert | 
|  | $(document).on('mouseup', function (e) { | 
|  | var target = $(e.target); | 
|  | if (!target.is(getLink) && getLink.has(target).length === 0 && target.closest('.popover').length === 0) | 
|  | getLink.popover('hide'); | 
|  | }); | 
|  |  | 
|  | // Opens the popup if asked to by the editor | 
|  | layout.eventHub.on('displaySharingPopover', function () { | 
|  | getLink.popover('show'); | 
|  | }); | 
|  |  | 
|  | if (options.sharingEnabled) { | 
|  | updateShares($('#socialshare'), baseUrl); | 
|  | } | 
|  | } | 
|  |  | 
|  | function getEmbeddedUrl(config, root, readOnly, extraOptions) { | 
|  | var location = window.location.origin + root; | 
|  | var path = ''; | 
|  | var parameters = ''; | 
|  |  | 
|  | _.forEach(extraOptions, function (value, key) { | 
|  | if (parameters === '') { | 
|  | parameters = '?'; | 
|  | } else { | 
|  | parameters += '&'; | 
|  | } | 
|  |  | 
|  | parameters += key + '=' + value; | 
|  | }); | 
|  |  | 
|  | if (readOnly) { | 
|  | path = 'embed-ro' + parameters + '#'; | 
|  | } else { | 
|  | path = 'e' + parameters + '#'; | 
|  | } | 
|  |  | 
|  | return location + path + url.serialiseState(config); | 
|  | } | 
|  |  | 
|  | function getEmbeddedHtml(config, root, isReadOnly, extraOptions) { | 
|  | return '<iframe width="800px" height="200px" src="' + | 
|  | getEmbeddedUrl(config, root, isReadOnly, extraOptions) + '"></iframe>'; | 
|  | } | 
|  |  | 
|  | function getShortLink(config, root, done) { | 
|  | var useExternalShortener = options.urlShortenService !== 'default'; | 
|  | var data = JSON.stringify({ | 
|  | config: useExternalShortener | 
|  | ? url.serialiseState(config) | 
|  | : config, | 
|  | }); | 
|  | $.ajax({ | 
|  | type: 'POST', | 
|  | url: window.location.origin + root + 'shortener', | 
|  | dataType: 'json',  // Expected | 
|  | contentType: 'application/json',  // Sent | 
|  | data: data, | 
|  | success: _.bind(function (result) { | 
|  | var pushState = useExternalShortener ? null : result.url; | 
|  | done(null, result.url, pushState, true); | 
|  | }, this), | 
|  | error: _.bind(function (err) { | 
|  | // Notify the user that we ran into trouble? | 
|  | done(err.statusText, null, false); | 
|  | }, this), | 
|  | cache: true, | 
|  | }); | 
|  | } | 
|  |  | 
|  | function getLinks(config, currentBind, done) { | 
|  | var root = window.httpRoot; | 
|  | switch (currentBind) { | 
|  | case 'Short': | 
|  | getShortLink(config, root, done); | 
|  | return; | 
|  | case 'Full': | 
|  | done(null, window.location.origin + root + '#' + url.serialiseState(config), false); | 
|  | return; | 
|  | default: | 
|  | if (currentBind.substr(0, 5) === 'Embed') { | 
|  | var options = {}; | 
|  | $('#shareembedlink input:checked').each(function () { | 
|  | options[$(this).prop('class')] = true; | 
|  | }); | 
|  | done(null, getEmbeddedHtml(config, root, false, options), false); | 
|  | return; | 
|  | } | 
|  | // Hmmm | 
|  | done('Unknown link type', null); | 
|  | } | 
|  | } | 
|  |  | 
|  | module.exports = { | 
|  | initShareButton: initShareButton, | 
|  | configFromEmbedded: configFromEmbedded, | 
|  | }; |