<commit_before>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
/*
 * Dependencies
 *
 * /dev/lib/jquery/plugins/jquery.json.js (toJSON)
 * /dev/lib/jquery/plugins/jqmodal.sakai-edited.js
 * /dev/lib/misc/trimpath.template.js (TrimpathTemplates)
 * /dev/lib/jquery/plugins/jquery.autoSuggest.sakai-edited.js (autoSuggest)
 */
/*global $ */

// Namespaces
require(["jquery", "sakai/sakai.api.core"], function($, sakai){

    /**
     * @name sakai_global.newsharecontent
     *
     * @class newsharecontent
     *
     * @description
     * Content Share widget
     *
     * @version 0.0.1
     * @param {String} tuid Unique id of the widget
     * @param {Boolean} showSettings Show the settings of the widget or not
     */
    sakai_global.newsharecontent = function(tuid, showSettings){

        /////////////////////////////
        // CONFIGURATION VARIABLES //
        /////////////////////////////

        // Containers
        var $newsharecontentContainer = $("#newsharecontent_widget");
        var $newsharecontentMessageContainer = $("#newsharecontent_message_container");

        // Elements
        var $newsharecontentLinkURL = $("#newsharecontent_linkurl");
        var $newsharecontentSharelist = $("#newsharecontent_sharelist");
        var $newsharecontentMessage = $("#newsharecontent_message");
        var $newsharecontentSendButton = $("#sharecontent_send_button");
        var newsharecontentListItem = ".as-selection-item";
        var newsharecontentShareListContainer = "#newsharecontent_sharelist_container";
        var $newsharecontentMessageToggle = $('label.toggletext',$newsharecontentContainer);
        var $newsharecontentMessageArrow = $('#newsharecontent_message_arrow');
        var $newsharecontentHeading = $('#newsharecontent_heading');

        // Classes
        var newsharecontentRequiredClass = "newsharecontent_required";

        // Content object
        var contentObj = {};

        ///////////////
        // RENDERING //
        ///////////////

        var fillShareData = function(hash){
            $newsharecontentLinkURL.val(contentObj.shareUrl);
            var shareData = {
                "filename": "\"" + contentObj.data["sakai:pooled-content-file-name"] + "\"",
                "path": contentObj.shareUrl,
                "user": sakai.data.me.profile.basic.elements.firstName.value
            };

            $newsharecontentMessage.val(sakai.api.Util.TemplateRenderer("newsharecontent_share_message_template", shareData));

            if (hash) {
                hash.w.show();
            }
            var tbx = $('#toolbox');
            if(tbx.find('a').length===0){
                var svcs = {facebook: 'Facebook', twitter: 'Twitter', delicious:'Delicious', stumbleupon: 'StumbleUpon', blogger:'Blogger', wordpress:'Wordpress', google:'Google', expanded: 'More'};
                for (var s in svcs) {
                    tbx.append('');
                }
                addthis.toolbox("#toolbox");
            }
        };

        var resetWidget = function(hash){
            $newsharecontentMessage.removeClass(newsharecontentRequiredClass);
            $(newsharecontentShareListContainer).removeClass(newsharecontentRequiredClass);
            sakai.api.Util.AutoSuggest.reset($newsharecontentSharelist);
            hash.w.hide();
        };

        ///////////
        // SHARE //
        ///////////

        var getSelectedList = function() {
            var list = $("#as-values-" + tuid).val();
            // this value is a comma-delimited list
            // split it and get rid of any empty values in the array
            list = list.split(",");
            var removed = 0;
            $(list).each(function(i, val) {

               if (val === "") {
                   list.splice(i - removed, 1);
                   removed += 1;
               }
            });

            // Create list to show in the notification
            var toAddNames = [];
            $("#newsharecontent_container .as-selection-item").each(function(){
                // In IE 7  is returned and in firefox
                toAddNames.push($(this).html().split(/<\/[aA]?>/g)[1]);
            });
            var returnValue = {"list":list, "toAddNames":toAddNames};

            return returnValue;
        };

        var createActivity = function(activityMessage){
            var activityData = {
                "sakai:activityMessage": activityMessage
            };
            sakai.api.Activity.createActivity("/p/" + contentObj.data["_path"], "content", "default", activityData, function(){
                $(window).trigger("load.content_profile.sakai", function(){
                    $(window).trigger("render.entity.sakai", ["content", contentObj]);
                });
            });
        };

        var doShare = function(event,userlist,message,contentobj){
            var userList = userlist || getSelectedList();
            var messageText = message || $.trim($newsharecontentMessage.val());
            contentObj = contentobj || contentObj;
            $newsharecontentMessage.removeClass(newsharecontentRequiredClass);
            $(newsharecontentShareListContainer).removeClass(newsharecontentRequiredClass);
            if (userList && userList.list && userList.list.length && messageText && contentObj && contentObj.data) {
                var toAddList = userList.list.slice();
                userList.list = toAddList;
                if (toAddList.length) {
                    sakai.api.Communication.sendMessage(userList.list, sakai.data.me, sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "I_WANT_TO_SHARE") + " \"" + contentObj.data["sakai:pooled-content-file-name"] + "\"", messageText, "message", false, false, true, "shared_content");
                    sakai.api.Content.addToLibrary(contentObj.data["_path"], toAddList);
                    sakai.api.Util.notification.show(false, sakai.api.Security.saneHTML($("#newsharecontent_users_added_text").text()) + " " + userList.toAddNames.join(", "), "");
                    createActivity("__MSG__ADDED_A_MEMBER__");
                    $newsharecontentContainer.jqmHide();
                }
            } else {
                if (!messageText) {
                    $newsharecontentMessage.addClass(newsharecontentRequiredClass);
                    sakai.api.Util.notification.show(sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "NO_MESSAGE_PROVIDED"), sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "A_MESSAGE_SHOULD_BE_PROVIDED_TO_SHARE"));
                }
                if (!userList.list.length) {
                    $(newsharecontentShareListContainer).addClass(newsharecontentRequiredClass);
                    sakai.api.Util.notification.show(sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "NO_USERS_SELECTED"), sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "NO_USERS_TO_SHARE_FILE_WITH"));
                }
                if (!contentObj || !contentObj.data) {
                    $(newsharecontentShareListContainer).addClass(newsharecontentRequiredClass);
                    sakai.api.Util.notification.show(sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "AN_ERROR_OCCURRED"), sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "AN_ERROR_OCCURRED_FULL_MESSAGE"));
                }
            }
        };

        //////////////
        // BINDINGS //
        //////////////

        var addBinding = function(){
            $newsharecontentContainer.jqm({
                modal: false,
                overlay: 0,
                toTop: true,
                zIndex: 3000,
                onShow: fillShareData,
                onHide: resetWidget
            });

            $('.share_trigger_click').live('click',function(){
                var contentId = $(this).data("entityid");
                var $this = $(this);
                $newsharecontentContainer.css({'top':$this.offset().top + $this.height() - 5,'left':$this.offset().left + $this.width() / 2 - 125});
                sakai.api.Server.loadJSON("/p/" + contentId + ".json", function(success, data){
                    if (success) {
                        contentObj = {
                            "data": data,
                            "shareUrl": sakai.config.SakaiDomain + "/content#p=" + data["_path"] + "/" + encodeURI(data["sakai:pooled-content-file-name"])
                        };
                        if (window["addthis"]) {
                            $newsharecontentContainer.jqmShow();
                        }
                    }
                });
            });

            $newsharecontentSendButton.unbind("click", doShare);
            $newsharecontentSendButton.bind("click", doShare);
        };

        $newsharecontentMessageToggle.add($newsharecontentMessageArrow).bind('click',function(){
            $newsharecontentMessageArrow.toggleClass('arrow_down');
            $newsharecontentMessageContainer.stop(true, true).slideToggle();
        });

        sakai.api.Util.hideOnClickOut(".newsharecontent_dialog", ".share_trigger_click", function(){
            $newsharecontentContainer.jqmHide();
        });

        $(window).bind("finished.sharecontent.sakai",doShare);

        ////////////////////
        // INITIALIZATION //
        ////////////////////

        var init = function(){
            addBinding();
            var ajaxcache = $.ajaxSettings.cache;
            $.ajaxSettings.cache = true;
            $.getScript('http://s7.addthis.com/js/250/addthis_widget.js?%23pubid=' + sakai.widgets.newsharecontent.defaultConfiguration.newsharecontent.addThisAccountId + '&domready=1');
            $.ajaxSettings.cache = ajaxcache;
            sakai.api.Util.AutoSuggest.setup($newsharecontentSharelist, {"asHtmlID": tuid});
        };

        init();
    };

    sakai.api.Widgets.widgetLoader.informOnLoad("newsharecontent");
});
<commit_msg>SAKIII-3563 Use url beginning with // so that browsers will automagically insert the correct scheme<commit_after>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
/*
 * Dependencies
 *
 * /dev/lib/jquery/plugins/jquery.json.js (toJSON)
 * /dev/lib/jquery/plugins/jqmodal.sakai-edited.js
 * /dev/lib/misc/trimpath.template.js (TrimpathTemplates)
 * /dev/lib/jquery/plugins/jquery.autoSuggest.sakai-edited.js (autoSuggest)
 */
/*global $ */

// Namespaces
require(["jquery", "sakai/sakai.api.core"], function($, sakai){

    /**
     * @name sakai_global.newsharecontent
     *
     * @class newsharecontent
     *
     * @description
     * Content Share widget
     *
     * @version 0.0.1
     * @param {String} tuid Unique id of the widget
     * @param {Boolean} showSettings Show the settings of the widget or not
     */
    sakai_global.newsharecontent = function(tuid, showSettings){

        /////////////////////////////
        // CONFIGURATION VARIABLES //
        /////////////////////////////

        // Containers
        var $newsharecontentContainer = $("#newsharecontent_widget");
        var $newsharecontentMessageContainer = $("#newsharecontent_message_container");

        // Elements
        var $newsharecontentLinkURL = $("#newsharecontent_linkurl");
        var $newsharecontentSharelist = $("#newsharecontent_sharelist");
        var $newsharecontentMessage = $("#newsharecontent_message");
        var $newsharecontentSendButton = $("#sharecontent_send_button");
        var newsharecontentListItem = ".as-selection-item";
        var newsharecontentShareListContainer = "#newsharecontent_sharelist_container";
        var $newsharecontentMessageToggle = $('label.toggletext',$newsharecontentContainer);
        var $newsharecontentMessageArrow = $('#newsharecontent_message_arrow');
        var $newsharecontentHeading = $('#newsharecontent_heading');

        // Classes
        var newsharecontentRequiredClass = "newsharecontent_required";

        // Content object
        var contentObj = {};

        ///////////////
        // RENDERING //
        ///////////////

        var fillShareData = function(hash){
            $newsharecontentLinkURL.val(contentObj.shareUrl);
            var shareData = {
                "filename": "\"" + contentObj.data["sakai:pooled-content-file-name"] + "\"",
                "path": contentObj.shareUrl,
                "user": sakai.data.me.profile.basic.elements.firstName.value
            };

            $newsharecontentMessage.val(sakai.api.Util.TemplateRenderer("newsharecontent_share_message_template", shareData));

            if (hash) {
                hash.w.show();
            }
            var tbx = $('#toolbox');
            if(tbx.find('a').length===0){
                var svcs = {facebook: 'Facebook', twitter: 'Twitter', delicious:'Delicious', stumbleupon: 'StumbleUpon', blogger:'Blogger', wordpress:'Wordpress', google:'Google', expanded: 'More'};
                for (var s in svcs) {
                    tbx.append('');
                }
                addthis.toolbox("#toolbox");
            }
        };

        var resetWidget = function(hash){
            $newsharecontentMessage.removeClass(newsharecontentRequiredClass);
            $(newsharecontentShareListContainer).removeClass(newsharecontentRequiredClass);
            sakai.api.Util.AutoSuggest.reset($newsharecontentSharelist);
            hash.w.hide();
        };

        ///////////
        // SHARE //
        ///////////

        var getSelectedList = function() {
            var list = $("#as-values-" + tuid).val();
            // this value is a comma-delimited list
            // split it and get rid of any empty values in the array
            list = list.split(",");
            var removed = 0;
            $(list).each(function(i, val) {

               if (val === "") {
                   list.splice(i - removed, 1);
                   removed += 1;
               }
            });

            // Create list to show in the notification
            var toAddNames = [];
            $("#newsharecontent_container .as-selection-item").each(function(){
                // In IE 7  is returned and in firefox
                toAddNames.push($(this).html().split(/<\/[aA]?>/g)[1]);
            });
            var returnValue = {"list":list, "toAddNames":toAddNames};

            return returnValue;
        };

        var createActivity = function(activityMessage){
            var activityData = {
                "sakai:activityMessage": activityMessage
            };
            sakai.api.Activity.createActivity("/p/" + contentObj.data["_path"], "content", "default", activityData, function(){
                $(window).trigger("load.content_profile.sakai", function(){
                    $(window).trigger("render.entity.sakai", ["content", contentObj]);
                });
            });
        };

        var doShare = function(event,userlist,message,contentobj){
            var userList = userlist || getSelectedList();
            var messageText = message || $.trim($newsharecontentMessage.val());
            contentObj = contentobj || contentObj;
            $newsharecontentMessage.removeClass(newsharecontentRequiredClass);
            $(newsharecontentShareListContainer).removeClass(newsharecontentRequiredClass);
            if (userList && userList.list && userList.list.length && messageText && contentObj && contentObj.data) {
                var toAddList = userList.list.slice();
                userList.list = toAddList;
                if (toAddList.length) {
                    sakai.api.Communication.sendMessage(userList.list, sakai.data.me, sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "I_WANT_TO_SHARE") + " \"" + contentObj.data["sakai:pooled-content-file-name"] + "\"", messageText, "message", false, false, true, "shared_content");
                    sakai.api.Content.addToLibrary(contentObj.data["_path"], toAddList);
                    sakai.api.Util.notification.show(false, sakai.api.Security.saneHTML($("#newsharecontent_users_added_text").text()) + " " + userList.toAddNames.join(", "), "");
                    createActivity("__MSG__ADDED_A_MEMBER__");
                    $newsharecontentContainer.jqmHide();
                }
            } else {
                if (!messageText) {
                    $newsharecontentMessage.addClass(newsharecontentRequiredClass);
                    sakai.api.Util.notification.show(sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "NO_MESSAGE_PROVIDED"), sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "A_MESSAGE_SHOULD_BE_PROVIDED_TO_SHARE"));
                }
                if (!userList.list.length) {
                    $(newsharecontentShareListContainer).addClass(newsharecontentRequiredClass);
                    sakai.api.Util.notification.show(sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "NO_USERS_SELECTED"), sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "NO_USERS_TO_SHARE_FILE_WITH"));
                }
                if (!contentObj || !contentObj.data) {
                    $(newsharecontentShareListContainer).addClass(newsharecontentRequiredClass);
                    sakai.api.Util.notification.show(sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "AN_ERROR_OCCURRED"), sakai.api.i18n.Widgets.getValueForKey("newsharecontent", "", "AN_ERROR_OCCURRED_FULL_MESSAGE"));
                }
            }
        };

        //////////////
        // BINDINGS //
        //////////////

        var addBinding = function(){
            $newsharecontentContainer.jqm({
                modal: false,
                overlay: 0,
                toTop: true,
                zIndex: 3000,
                onShow: fillShareData,
                onHide: resetWidget
            });

            $('.share_trigger_click').live('click',function(){
                var contentId = $(this).data("entityid");
                var $this = $(this);
                $newsharecontentContainer.css({'top':$this.offset().top + $this.height() - 5,'left':$this.offset().left + $this.width() / 2 - 125});
                sakai.api.Server.loadJSON("/p/" + contentId + ".json", function(success, data){
                    if (success) {
                        contentObj = {
                            "data": data,
                            "shareUrl": sakai.config.SakaiDomain + "/content#p=" + data["_path"] + "/" + encodeURI(data["sakai:pooled-content-file-name"])
                        };
                        if (window["addthis"]) {
                            $newsharecontentContainer.jqmShow();
                        }
                    }
                });
            });

            $newsharecontentSendButton.unbind("click", doShare);
            $newsharecontentSendButton.bind("click", doShare);
        };

        $newsharecontentMessageToggle.add($newsharecontentMessageArrow).bind('click',function(){
            $newsharecontentMessageArrow.toggleClass('arrow_down');
            $newsharecontentMessageContainer.stop(true, true).slideToggle();
        });

        sakai.api.Util.hideOnClickOut(".newsharecontent_dialog", ".share_trigger_click", function(){
            $newsharecontentContainer.jqmHide();
        });

        $(window).bind("finished.sharecontent.sakai",doShare);

        ////////////////////
        // INITIALIZATION //
        ////////////////////

        var init = function(){
            addBinding();
            var ajaxcache = $.ajaxSettings.cache;
            $.ajaxSettings.cache = true;
            // the peculiar form of the addthis url is not a typo! see http://paulirish.com/2010/the-protocol-relative-url/
            $.getScript('//s7.addthis.com/js/250/addthis_widget.js?%23pubid=' + sakai.widgets.newsharecontent.defaultConfiguration.newsharecontent.addThisAccountId + '&domready=1');
            $.ajaxSettings.cache = ajaxcache;
            sakai.api.Util.AutoSuggest.setup($newsharecontentSharelist, {"asHtmlID": tuid});
        };

        init();
    };

    sakai.api.Widgets.widgetLoader.informOnLoad("newsharecontent");
});
<|endoftext|>


<commit_before>/**
 * Analytics Tracking Exclusion switches component.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * WordPress dependencies
 */
import { useCallback } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { STORE_NAME } from '../../datastore/constants';
import Switch from '../../../../components/Switch';
import Row from '../../../../material-components/layout/Row';
import Cell from '../../../../material-components/layout/Cell';
const { useSelect, useDispatch } = Data;

const TRACKING_LOGGED_IN_USERS = 'loggedinUsers';
const TRACKING_CONTENT_CREATORS = 'contentCreators';

export const trackingExclusionLabels = {
	[ TRACKING_LOGGED_IN_USERS ]: __( 'All logged-in users', 'google-site-kit' ),
	[ TRACKING_CONTENT_CREATORS ]: __( 'Users that can write posts', 'google-site-kit' ),
};

export default function TrackingExclusionSwitches() {
	const trackingDisabled = useSelect( ( select ) => select( STORE_NAME ).getTrackingDisabled() );
	const { setTrackingDisabled } = useDispatch( STORE_NAME );

	let message;
	if ( trackingDisabled && trackingDisabled.includes( TRACKING_LOGGED_IN_USERS ) ) {
		message = __( 'All logged-in users will be excluded from Analytics tracking.', 'google-site-kit' );
	} else if ( trackingDisabled && trackingDisabled.includes( TRACKING_CONTENT_CREATORS ) ) {
		message = __( 'Users that can write posts will be excluded from Analytics tracking.', 'google-site-kit' );
	} else {
		message = __( 'All logged-in users will be included in Analytics tracking.', 'google-site-kit' );
	}

	const updateTrackingDisabled = useCallback( ( users, exclude ) => {
		let trackingDisabledArray;
		if ( exclude ) {
			trackingDisabledArray = trackingDisabled.concat( users );
		} else {
			trackingDisabledArray = trackingDisabled.filter( ( item ) => item !== users );
		}
		setTrackingDisabled( trackingDisabledArray );
	}, [ trackingDisabled, setTrackingDisabled ] );

	const onChangeTrackContentCreators = useCallback( ( event ) => {
		const { checked: exclude } = event.target;
		updateTrackingDisabled( TRACKING_CONTENT_CREATORS, exclude );
	}, [ updateTrackingDisabled ] );

	const onChangeTrackLoggedInUsers = useCallback( ( event ) => {
		const { checked: exclude } = event.target;
		updateTrackingDisabled( TRACKING_LOGGED_IN_USERS, exclude );
	}, [ updateTrackingDisabled ] );

	if ( ! Array.isArray( trackingDisabled ) ) {
		return null;
	}

	return (
		<fieldset className="googlesitekit-analytics-trackingdisabled">
			<legend className="googlesitekit-setup-module__text">
				{ __( 'Exclude from Analytics', 'google-site-kit' ) }
			</legend>

			<Row>
				<Cell>
					<Switch
						label={ trackingExclusionLabels[ TRACKING_LOGGED_IN_USERS ] }
						checked={ trackingDisabled.includes( TRACKING_LOGGED_IN_USERS ) }
						onClick={ onChangeTrackLoggedInUsers }
						hideLabel={ false }
					/>
				</Cell>

				{ ! trackingDisabled.includes( TRACKING_LOGGED_IN_USERS ) &&
				(
					<Cell>
						<Switch
							label={ trackingExclusionLabels[ TRACKING_CONTENT_CREATORS ] }
							checked={ trackingDisabled.includes( TRACKING_CONTENT_CREATORS ) }
							onClick={ onChangeTrackContentCreators }
							hideLabel={ false }
						/>
					</Cell>
				) }
			</Row>

{ message }

		</fieldset>
	);
}
<commit_msg>Code review feedback.<commit_after>/**
 * Analytics Tracking Exclusion switches component.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * WordPress dependencies
 */
import { useCallback } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { STORE_NAME } from '../../datastore/constants';
import Switch from '../../../../components/Switch';
import Row from '../../../../material-components/layout/Row';
import Cell from '../../../../material-components/layout/Cell';
const { useSelect, useDispatch } = Data;

const TRACKING_LOGGED_IN_USERS = 'loggedinUsers';
const TRACKING_CONTENT_CREATORS = 'contentCreators';

export const trackingExclusionLabels = {
	[ TRACKING_LOGGED_IN_USERS ]: __( 'All logged-in users', 'google-site-kit' ),
	[ TRACKING_CONTENT_CREATORS ]: __( 'Users that can write posts', 'google-site-kit' ),
};

export default function TrackingExclusionSwitches() {
	const trackingDisabled = useSelect( ( select ) => select( STORE_NAME ).getTrackingDisabled() );
	const { setTrackingDisabled } = useDispatch( STORE_NAME );

	let message;
	if ( trackingDisabled && trackingDisabled.includes( TRACKING_LOGGED_IN_USERS ) ) {
		message = __( 'All logged-in users will be excluded from Analytics tracking.', 'google-site-kit' );
	} else if ( trackingDisabled && trackingDisabled.includes( TRACKING_CONTENT_CREATORS ) ) {
		message = __( 'Users that can write posts will be excluded from Analytics tracking.', 'google-site-kit' );
	} else {
		message = __( 'All logged-in users will be included in Analytics tracking.', 'google-site-kit' );
	}

	const updateTrackingDisabled = useCallback( ( users, exclude ) => {
		const trackingDisabledArray = exclude
			? trackingDisabled.concat( users )
			: trackingDisabled.filter( ( item ) => item !== users );

		setTrackingDisabled( trackingDisabledArray );
	}, [ trackingDisabled, setTrackingDisabled ] );

	const onChangeTrackContentCreators = useCallback( ( event ) => {
		const { checked: exclude } = event.target;
		updateTrackingDisabled( TRACKING_CONTENT_CREATORS, exclude );
	}, [ updateTrackingDisabled ] );

	const onChangeTrackLoggedInUsers = useCallback( ( event ) => {
		const { checked: exclude } = event.target;
		updateTrackingDisabled( TRACKING_LOGGED_IN_USERS, exclude );
	}, [ updateTrackingDisabled ] );

	if ( ! Array.isArray( trackingDisabled ) ) {
		return null;
	}

	return (
		<fieldset className="googlesitekit-analytics-trackingdisabled">
			<legend className="googlesitekit-setup-module__text">
				{ __( 'Exclude from Analytics', 'google-site-kit' ) }
			</legend>

			<Row>
				<Cell>
					<Switch
						label={ trackingExclusionLabels[ TRACKING_LOGGED_IN_USERS ] }
						checked={ trackingDisabled.includes( TRACKING_LOGGED_IN_USERS ) }
						onClick={ onChangeTrackLoggedInUsers }
						hideLabel={ false }
					/>
				</Cell>

				{ ! trackingDisabled.includes( TRACKING_LOGGED_IN_USERS ) && (
					<Cell>
						<Switch
							label={ trackingExclusionLabels[ TRACKING_CONTENT_CREATORS ] }
							checked={ trackingDisabled.includes( TRACKING_CONTENT_CREATORS ) }
							onClick={ onChangeTrackContentCreators }
							hideLabel={ false }
						/>
					</Cell>
				) }
			</Row>

{ message }

		</fieldset>
	);
}
<|endoftext|>


<commit_before>dojo.require("dojox.cometd");
dojo.require("dojox.cometd.timestamp");
dojo.require("dojox.cometd.ack");
dojo.require("dojox.cometd.reload");

var room = {
    _lastUser: null,
    _username: null,
    _connected: false,
    _disconnecting: false,
    _chatSubscription: null,
    _membersSubscription: null,

    _init: function()
    {
        dojo.removeClass("join", "hidden");
        dojo.addClass("joined", "hidden");
        dojo.byId('username').focus();

        dojo.query("#username").attr({
            "autocomplete": "off"
        }).onkeyup(function(e)
        {
            if (e.keyCode == dojo.keys.ENTER)
            {
                room.join(dojo.byId('username').value);
            }
        });

        dojo.query("#joinButton").onclick(function(e)
        {
            room.join(dojo.byId('username').value);
        });

        dojo.query("#phrase").attr({
            "autocomplete": "off"
        }).onkeyup(function(e)
        {
            if (e.keyCode == dojo.keys.ENTER)
            {
                room.chat();
            }
        });

        dojo.query("#sendButton").onclick(function(e)
        {
            room.chat();
        });

        dojo.query("#leaveButton").onclick(room, "leave");

        // Check if there was a saved application state
        var stateCookie = org.cometd.COOKIE?org.cometd.COOKIE.get('org.cometd.demo.state'):null;
        var state = stateCookie ? org.cometd.JSON.fromJSON(stateCookie) : null;
        // Restore the state, if present
        if (state)
        {
            dojo.byId('username').value=state.username;
            setTimeout(function()
            {
                // This will perform the handshake
                room.join(state.username);
            }, 0);
        }
    },

    join: function(name)
    {
        room._disconnecting = false;

        if (name == null || name.length == 0)
        {
            alert('Please enter a username');
            return;
        }

        dojox.cometd.ackEnabled = dojo.query("#ackEnabled").attr("checked");

        dojox.cometd.websocketEnabled = true;
        var cometdURL = location.protocol + "//" + location.host + config.contextPath + "/cometd";
        dojox.cometd.init({
            url: cometdURL,
            logLevel: "info"
        });

        room._username = name;

        dojo.addClass("join", "hidden");
        dojo.removeClass("joined", "hidden");
        dojo.byId("phrase").focus();
    },

    _unsubscribe: function()
    {
        if (room._chatSubscription)
        {
            dojox.cometd.unsubscribe(room._chatSubscription);
        }
        room._chatSubscription = null;
        if (room._membersSubscription)
        {
            dojox.cometd.unsubscribe(room._membersSubscription);
        }
        room._membersSubscription = null;
    },

    _subscribe: function()
    {
        room._chatSubscription = dojox.cometd.subscribe('/chat/demo', room.receive);
        room._membersSubscription = dojox.cometd.subscribe('/members/demo', room.members);
    },

    leave: function()
    {
        dojox.cometd.batch(function()
        {
            dojox.cometd.publish("/chat/demo", {
                user: room._username,
                membership: 'leave',
                chat: room._username + " has left"
            });
            room._unsubscribe();
        });
        dojox.cometd.disconnect();

        // switch the input form
        dojo.removeClass("join", "hidden");
        dojo.addClass("joined", "hidden");

        dojo.byId("username").focus();
        dojo.byId('members').innerHTML = "";

        room._username = null;
        room._lastUser = null;
        room._disconnecting = true;
    },

    chat: function()
    {
        var text = dojo.byId('phrase').value;
        dojo.byId('phrase').value = '';
        if (!text || !text.length) return;

        var colons = text.indexOf("::");
        if (colons > 0)
        {
            dojox.cometd.publish("/service/privatechat", {
                room: "/chat/demo", // This should be replaced by the room name
                user: room._username,
                chat: text.substring(colons + 2),
                peer: text.substring(0, colons)
            });
        }
        else
        {
            dojox.cometd.publish("/chat/demo", {
                user: room._username,
                chat: text
            });
        }
    },

    receive: function(message)
    {
        var fromUser = message.data.user;
        var membership = message.data.join || message.data.leave;
        var text = message.data.chat;

        if (!membership && fromUser == room._lastUser)
        {
            fromUser = "...";
        }
        else
        {
            room._lastUser = fromUser;
            fromUser += ":";
        }

        var chat = dojo.byId('chat');
        if (membership)
        {
            chat.innerHTML += "" + fromUser + " " + text + "
";
            room._lastUser = null;
        }
        else if (message.data.scope == "private")
        {
            chat.innerHTML += "" + fromUser + " [private] " + text + "
";
        }
        else
        {
            chat.innerHTML += "" + fromUser + " " + text + "
";
        }

        chat.scrollTop = chat.scrollHeight - chat.clientHeight;
    },

    members: function(message)
    {
        var members = dojo.byId('members');
        var list = "";
        for (var i in message.data)
            list += message.data[i] + "
";
        members.innerHTML = list;
    },

    _connectionInitialized: function()
    {
        // first time connection for this client, so subscribe and tell everybody.
        dojox.cometd.batch(function()
        {
            room._subscribe();
            dojox.cometd.publish('/chat/demo', {
                user: room._username,
                membership: 'join',
                chat: room._username + ' has joined'
            });
        });
    },

    _connectionEstablished: function()
    {
        // connection establish (maybe not for first time), so just
        // tell local user and update membership
        room.receive({
            data: {
                user: 'system',
                chat: 'Connection to Server Opened'
            }
        });
        dojox.cometd.publish('/service/members', {
            user: room._username,
            room: '/chat/demo'
        });
    },

    _connectionBroken: function()
    {
        room.receive({
            data: {
                user: 'system',
                chat: 'Connection to Server Broken'
            }
        });
        dojo.byId('members').innerHTML = "";
    },

    _connectionClosed: function()
    {
        room.receive({
            data: {
                user: 'system',
                chat: 'Connection to Server Closed'
            }
        });
    },

    _metaHandshake: function(message)
    {
    	if (message.successful)
        {
            room._connectionInitialized();
        }
    },

    _metaConnect: function(message)
    {
        if (room._disconnecting)
        {
            room._connected = false;
            room._connectionClosed();
        }
        else
        {
            var wasConnected = room._connected;
            room._connected = message.successful === true;
            if (!wasConnected && room._connected)
            {
                room._connectionEstablished();
            }
            else if (wasConnected && !room._connected)
            {
                room._connectionBroken();
            }
        }
    }
};

dojox.cometd.addListener("/meta/handshake", room, room._metaHandshake);
dojox.cometd.addListener("/meta/connect", room, room._metaConnect);
dojo.addOnLoad(room, "_init");
dojo.addOnUnload(function()
{
    if (room._username)
    {
        dojox.cometd.reload();
        org.cometd.COOKIE.set('org.cometd.demo.state', org.cometd.JSON.toJSON({
            username: room._username
        }), { 'max-age': 5 });
    }
    else
        dojox.cometd.disconnect();
});

<commit_msg>Fixed retrieval of the value of the "enable ack extension" checkbox. Turns out that dojo.query().attr() returns an array and assigning to a boolean was not working right.<commit_after>dojo.require("dojox.cometd");
dojo.require("dojox.cometd.timestamp");
dojo.require("dojox.cometd.ack");
dojo.require("dojox.cometd.reload");

var room = {
    _lastUser: null,
    _username: null,
    _connected: false,
    _disconnecting: false,
    _chatSubscription: null,
    _membersSubscription: null,

    _init: function()
    {
        dojo.removeClass("join", "hidden");
        dojo.addClass("joined", "hidden");
        dojo.byId('username').focus();

        dojo.query("#username").attr({
            "autocomplete": "off"
        }).onkeyup(function(e)
        {
            if (e.keyCode == dojo.keys.ENTER)
            {
                room.join(dojo.byId('username').value);
            }
        });

        dojo.query("#joinButton").onclick(function(e)
        {
            room.join(dojo.byId('username').value);
        });

        dojo.query("#phrase").attr({
            "autocomplete": "off"
        }).onkeyup(function(e)
        {
            if (e.keyCode == dojo.keys.ENTER)
            {
                room.chat();
            }
        });

        dojo.query("#sendButton").onclick(function(e)
        {
            room.chat();
        });

        dojo.query("#leaveButton").onclick(room, "leave");

        // Check if there was a saved application state
        var stateCookie = org.cometd.COOKIE?org.cometd.COOKIE.get('org.cometd.demo.state'):null;
        var state = stateCookie ? org.cometd.JSON.fromJSON(stateCookie) : null;
        // Restore the state, if present
        if (state)
        {
            dojo.byId('username').value=state.username;
            setTimeout(function()
            {
                // This will perform the handshake
                room.join(state.username);
            }, 0);
        }
    },

    join: function(name)
    {
        room._disconnecting = false;

        if (name == null || name.length == 0)
        {
            alert('Please enter a username');
            return;
        }

        dojox.cometd.ackEnabled = dojo.byId("ackEnabled").checked;

        dojox.cometd.websocketEnabled = true;
        var cometdURL = location.protocol + "//" + location.host + config.contextPath + "/cometd";
        dojox.cometd.init({
            url: cometdURL,
            logLevel: "info"
        });

        room._username = name;

        dojo.addClass("join", "hidden");
        dojo.removeClass("joined", "hidden");
        dojo.byId("phrase").focus();
    },

    _unsubscribe: function()
    {
        if (room._chatSubscription)
        {
            dojox.cometd.unsubscribe(room._chatSubscription);
        }
        room._chatSubscription = null;
        if (room._membersSubscription)
        {
            dojox.cometd.unsubscribe(room._membersSubscription);
        }
        room._membersSubscription = null;
    },

    _subscribe: function()
    {
        room._chatSubscription = dojox.cometd.subscribe('/chat/demo', room.receive);
        room._membersSubscription = dojox.cometd.subscribe('/members/demo', room.members);
    },

    leave: function()
    {
        dojox.cometd.batch(function()
        {
            dojox.cometd.publish("/chat/demo", {
                user: room._username,
                membership: 'leave',
                chat: room._username + " has left"
            });
            room._unsubscribe();
        });
        dojox.cometd.disconnect();

        // switch the input form
        dojo.removeClass("join", "hidden");
        dojo.addClass("joined", "hidden");

        dojo.byId("username").focus();
        dojo.byId('members').innerHTML = "";

        room._username = null;
        room._lastUser = null;
        room._disconnecting = true;
    },

    chat: function()
    {
        var text = dojo.byId('phrase').value;
        dojo.byId('phrase').value = '';
        if (!text || !text.length) return;

        var colons = text.indexOf("::");
        if (colons > 0)
        {
            dojox.cometd.publish("/service/privatechat", {
                room: "/chat/demo", // This should be replaced by the room name
                user: room._username,
                chat: text.substring(colons + 2),
                peer: text.substring(0, colons)
            });
        }
        else
        {
            dojox.cometd.publish("/chat/demo", {
                user: room._username,
                chat: text
            });
        }
    },

    receive: function(message)
    {
        var fromUser = message.data.user;
        var membership = message.data.join || message.data.leave;
        var text = message.data.chat;

        if (!membership && fromUser == room._lastUser)
        {
            fromUser = "...";
        }
        else
        {
            room._lastUser = fromUser;
            fromUser += ":";
        }

        var chat = dojo.byId('chat');
        if (membership)
        {
            chat.innerHTML += "" + fromUser + " " + text + "
";
            room._lastUser = null;
        }
        else if (message.data.scope == "private")
        {
            chat.innerHTML += "" + fromUser + " [private] " + text + "
";
        }
        else
        {
            chat.innerHTML += "" + fromUser + " " + text + "
";
        }

        chat.scrollTop = chat.scrollHeight - chat.clientHeight;
    },

    members: function(message)
    {
        var members = dojo.byId('members');
        var list = "";
        for (var i in message.data)
            list += message.data[i] + "
";
        members.innerHTML = list;
    },

    _connectionInitialized: function()
    {
        // first time connection for this client, so subscribe and tell everybody.
        dojox.cometd.batch(function()
        {
            room._subscribe();
            dojox.cometd.publish('/chat/demo', {
                user: room._username,
                membership: 'join',
                chat: room._username + ' has joined'
            });
        });
    },

    _connectionEstablished: function()
    {
        // connection establish (maybe not for first time), so just
        // tell local user and update membership
        room.receive({
            data: {
                user: 'system',
                chat: 'Connection to Server Opened'
            }
        });
        dojox.cometd.publish('/service/members', {
            user: room._username,
            room: '/chat/demo'
        });
    },

    _connectionBroken: function()
    {
        room.receive({
            data: {
                user: 'system',
                chat: 'Connection to Server Broken'
            }
        });
        dojo.byId('members').innerHTML = "";
    },

    _connectionClosed: function()
    {
        room.receive({
            data: {
                user: 'system',
                chat: 'Connection to Server Closed'
            }
        });
    },

    _metaHandshake: function(message)
    {
    	if (message.successful)
        {
            room._connectionInitialized();
        }
    },

    _metaConnect: function(message)
    {
        if (room._disconnecting)
        {
            room._connected = false;
            room._connectionClosed();
        }
        else
        {
            var wasConnected = room._connected;
            room._connected = message.successful === true;
            if (!wasConnected && room._connected)
            {
                room._connectionEstablished();
            }
            else if (wasConnected && !room._connected)
            {
                room._connectionBroken();
            }
        }
    }
};

dojox.cometd.addListener("/meta/handshake", room, room._metaHandshake);
dojox.cometd.addListener("/meta/connect", room, room._metaConnect);
dojo.addOnLoad(room, "_init");
dojo.addOnUnload(function()
{
    if (room._username)
    {
        dojox.cometd.reload();
        org.cometd.COOKIE.set('org.cometd.demo.state', org.cometd.JSON.toJSON({
            username: room._username
        }), { 'max-age': 5 });
    }
    else
        dojox.cometd.disconnect();
});

<|endoftext|>


<commit_before>describe('Dataset list directive', function() {
    'use strict';

    function strEndsWith(str, suffix) {
        return str.match(suffix + '$')[0] === suffix;
    }

    var scope, createElement, element;
    var datasets = [
        {
            'id': '12ce6c32-bf80-41c8-92e5-66d70f22ec1f',
            'name': 'US States',
            'author': 'anonymousUser',
            'created': '1437020219741',
            'type': 'text/csv',
            'certificationStep': 'NONE'
        },
        {
            'id': 'e93b9c92-e054-4f6a-a38f-ca52f22ead2b',
            'name': 'Customers',
            'author': 'anonymousUser',
            'created': '143702021974',
            'type': 'application/vnd.ms-excel',
            'certificationStep': 'PENDING'
        },
        {
            'id': 'e93b9c92-e054-4f6a-a38f-ca52f22ead3a',
            'name': 'Customers 2',
            'author': 'anonymousUser',
            'created': '14370202197',
            'certificationStep': 'CERTIFIED'
        }
    ];

    beforeEach(module('data-prep.dataset-list'));
    beforeEach(module('htmlTemplates'));
    beforeEach(module('pascalprecht.translate', function ($translateProvider) {
        $translateProvider.translations('en', {
            'INVENTORY_DETAILS': 'owned by {{author}}, created {{created | moment_from_now}}, contains {{records}} lines'
        });
        $translateProvider.preferredLanguage('en');
    }));

    beforeEach(inject(function($rootScope, $compile, $q, DatasetService) {
        scope = $rootScope.$new();
        createElement = function() {
            element = angular.element('<dataset-list></dataset-list>');
            $compile(element)(scope);
            scope.$digest();
            return element;
        };

        spyOn(DatasetService, 'datasetsList').and.returnValue(datasets);
        spyOn(DatasetService, 'getDatasets').and.callFake(function() {
            return $q.when(datasets);
        });
    }));

    afterEach(function() {
        scope.$destroy();
        element.remove();
    });

    it('should render dataset list', function($moment) {
        //when
        createElement();

        //then
        var icon = element.find('.inventory-icon').eq(0);
        var iconSrc = icon.find('> img')[0].src;
        var certificationIcon = icon.find('.pin');
        expect(strEndsWith(iconSrc, '/assets/images/inventory/csv_file.png')).toBe(true);
        expect(certificationIcon.length).toBe(0);
        expect(element.find('.inventory-title').eq(0).text()).toBe('US States');
        expect(element.find('.inventory-description').eq(0).text()).toBe('owned by anonymousUser, created '+$moment('1437020219741','x').fromNow()+', contains  lines');

        icon = element.find('.inventory-icon').eq(1);
        iconSrc = icon.find('> img')[0].src;
        certificationIcon = icon.find('.pin')[0].src;
        expect(strEndsWith(iconSrc, '/assets/images/inventory/xls_file.png')).toBe(true);
        expect(strEndsWith(certificationIcon, '/assets/images/certification-pending.png')).toBe(true);
        expect(element.find('.inventory-title').eq(1).text()).toBe('Customers');
        expect(element.find('.inventory-description').eq(1).text()).toBe('owned by anonymousUser, created '+$moment('143702021974','x').fromNow()+', contains  lines');

        icon = element.find('.inventory-icon').eq(2);
        iconSrc = icon.find('> img')[0].src;
        certificationIcon = icon.find('.pin')[0].src;
        expect(strEndsWith(iconSrc, '/assets/images/inventory/generic_file.png')).toBe(true);
        expect(strEndsWith(certificationIcon, '/assets/images/certification-certified.png')).toBe(true);
        expect(element.find('.inventory-title').eq(2).text()).toBe('Customers 2');
        expect(element.find('.inventory-description').eq(2).text()).toBe('owned by anonymousUser, created '+$moment('14370202197','x').fromNow()+', contains  lines');
    });
});
<commit_msg>correctly inject $moment<commit_after>describe('Dataset list directive', function() {
    'use strict';

    function strEndsWith(str, suffix) {
        return str.match(suffix + '$')[0] === suffix;
    }

    var scope, createElement, element;
    var datasets = [
        {
            'id': '12ce6c32-bf80-41c8-92e5-66d70f22ec1f',
            'name': 'US States',
            'author': 'anonymousUser',
            'created': '1437020219741',
            'type': 'text/csv',
            'certificationStep': 'NONE'
        },
        {
            'id': 'e93b9c92-e054-4f6a-a38f-ca52f22ead2b',
            'name': 'Customers',
            'author': 'anonymousUser',
            'created': '143702021974',
            'type': 'application/vnd.ms-excel',
            'certificationStep': 'PENDING'
        },
        {
            'id': 'e93b9c92-e054-4f6a-a38f-ca52f22ead3a',
            'name': 'Customers 2',
            'author': 'anonymousUser',
            'created': '14370202197',
            'certificationStep': 'CERTIFIED'
        }
    ];

    beforeEach(module('data-prep.dataset-list'));
    beforeEach(module('htmlTemplates'));
    beforeEach(module('pascalprecht.translate', function ($translateProvider) {
        $translateProvider.translations('en', {
            'INVENTORY_DETAILS': 'owned by {{author}}, created {{created | moment_from_now}}, contains {{records}} lines'
        });
        $translateProvider.preferredLanguage('en');
    }));

    beforeEach(inject(function($rootScope, $compile, $q, DatasetService) {
        scope = $rootScope.$new();
        createElement = function() {
            element = angular.element('<dataset-list></dataset-list>');
            $compile(element)(scope);
            scope.$digest();
            return element;
        };

        spyOn(DatasetService, 'datasetsList').and.returnValue(datasets);
        spyOn(DatasetService, 'getDatasets').and.callFake(function() {
            return $q.when(datasets);
        });
    }));

    afterEach(function() {
        scope.$destroy();
        element.remove();
    });

    it('should render dataset list', inject(function($moment) {
        //when
        createElement();

        //then
        var icon = element.find('.inventory-icon').eq(0);
        var iconSrc = icon.find('> img')[0].src;
        var certificationIcon = icon.find('.pin');
        expect(strEndsWith(iconSrc, '/assets/images/inventory/csv_file.png')).toBe(true);
        expect(certificationIcon.length).toBe(0);
        expect(element.find('.inventory-title').eq(0).text()).toBe('US States');
        expect(element.find('.inventory-description').eq(0).text()).toBe('owned by anonymousUser, created '+$moment('1437020219741','x').fromNow()+', contains  lines');

        icon = element.find('.inventory-icon').eq(1);
        iconSrc = icon.find('> img')[0].src;
        certificationIcon = icon.find('.pin')[0].src;
        expect(strEndsWith(iconSrc, '/assets/images/inventory/xls_file.png')).toBe(true);
        expect(strEndsWith(certificationIcon, '/assets/images/certification-pending.png')).toBe(true);
        expect(element.find('.inventory-title').eq(1).text()).toBe('Customers');
        expect(element.find('.inventory-description').eq(1).text()).toBe('owned by anonymousUser, created '+$moment('143702021974','x').fromNow()+', contains  lines');

        icon = element.find('.inventory-icon').eq(2);
        iconSrc = icon.find('> img')[0].src;
        certificationIcon = icon.find('.pin')[0].src;
        expect(strEndsWith(iconSrc, '/assets/images/inventory/generic_file.png')).toBe(true);
        expect(strEndsWith(certificationIcon, '/assets/images/certification-certified.png')).toBe(true);
        expect(element.find('.inventory-title').eq(2).text()).toBe('Customers 2');
        expect(element.find('.inventory-description').eq(2).text()).toBe('owned by anonymousUser, created '+$moment('14370202197','x').fromNow()+', contains  lines');
    }));
});
<|endoftext|>


<commit_before>  var data = {nodes:[],
  links:[]};

$(document).ready(function () {
  var svg = d3.select('svg');

  var masterServerDrag = d3.behavior.drag();
  d3.select('.masterServer').call(masterServerDrag);

  masterServerDrag.on('dragstart',function(){
    console.log("drag started");
    data.nodes.push({id:data.nodes.length+1,cx:0,cy:0,type:'Server'});
    console.log(d3.event);
  });

  masterServerDrag.on('drag',function(){
    console.log('dragging');
    //console.log(d3.event);
    data.nodes[data.nodes.length -1].cx = d3.event.x;
    data.nodes[data.nodes.length -1].cy = d3.event.y;
    //console.log(d3.event);
    updateNodes();
  });

  masterServerDrag.on('dragend',function(){
    console.log('drag ended');
  });

  var serverDrag = d3.behavior.drag();
  d3.selectAll('.server').call(serverDrag);

  serverDrag.on('drag',function(){
    d3.select(this).data()[0].cx = d3.event.x;
    d3.select(this).data()[0].cy = d3.event.y;
    updateNodes();
  });

  var masterHubDrag = d3.behavior.drag();
  d3.select('.masterHub').call(masterHubDrag);

  masterHubDrag.on('dragstart',function(){
    console.log("drag started");
    data.nodes.push({id:data.nodes.length+1,x:0,y:0,type:'Hub'});
    console.log(d3.event);
  });

  masterHubDrag.on('drag',function(){
    console.log('dragging');
    //console.log(d3.event);
    data.nodes[data.nodes.length -1].x = d3.event.x;
    data.nodes[data.nodes.length -1].y = d3.event.y;
    //console.log(d3.event);
    updateNodes();
  });

  masterHubDrag.on('dragend',function(){
    console.log('drag ended');
  });

  var hubDrag = d3.behavior.drag();
  d3.selectAll('.hub').call(hubDrag);

  hubDrag.on('drag',function(){
    d3.select(this).data()[0].x = d3.event.x;
    d3.select(this).data()[0].y = d3.event.y;
    updateNodes();
  });

  var masterLLinkButton = d3.select('.masterLLinkButton');
  var start;
  var end;
  masterLLinkButton.on('mouseover',function(){
    d3.select(this).classed('mouseover',true);
  })
  .on('mouseout',function(){
    d3.select(this).classed('mouseover',false);
  })
  .on('click',function(){
    d3.select(this).classed('active',true);
    var nodes = svg.selectAll('.nodes');
    nodes.classed('clickable',true)
    updateNodes();
  });

  function updateNodes(){
    var servers = svg.selectAll('circle.server')
      .data(data.nodes.filter(serverFilter),function(d) { return d.id;})
      .call(serverDrag);

    servers.attr('cx',function(d){return d.cx;})
      .attr('cy',function(d){return d.cy;});

    servers.enter().append('circle')
      .attr('cx',function(d){return d.cx;})
      .attr('cy',function(d){return d.cy;})
      .classed('server',true)
      .classed('nodes',true);

    var hubs = svg.selectAll('rect.hub')
      .data(data.nodes.filter(hubFilter),function(d) { return d.id;})
      .call(hubDrag);

    hubs.attr('x',function(d){return d.x;})
      .attr('y',function(d){return d.y;});

    hubs.enter().append('rect')
      .attr('x',function(d){return d.x;})
      .attr('y',function(d){return d.y;})
      .attr('width',10)
      .attr('height',10)
      .classed('hub',true)
      .classed('nodes',true);

    var clickableNodes = d3.selectAll('.nodes.clickable');
    clickableNodes.on('click',function(){
      if (!d3.select(this).classed('clickable')){
        return;
      }
      if (start == null){
        data.links.push({start:d3.select(this).data()[0].id,id:data.links.length +1});
        d3.select(this).classed('clickable',false);
        console.log(data.links);
        start = d3.select(this).data()[0].id;
        updateNodes();
        return;
      }
      data.links[data.links.length -1].end = d3.select(this).data()[0].id;
      data.links.push({start:data.links[data.links.length -1].end,end:data.links[data.links.length -1].start,id:data.links.length +1});
      clickableNodes.classed('clickable',false);
      console.log(data.links);
      start = null;
      updateNodes();
    });

    updateLinks();
  }

  function updateLinks(){
    var paths = svg.selectAll('.link')
      .data(data.links,function(d) { return d.id;});

    paths.attr('x1',getLinkX1CoOrd)
      .attr('x2',getLinkX2CoOrd)
      .attr('y1',getLinkY1CoOrd)
      .attr('y2',getLinkY2CoOrd);

    paths.enter().append('line')
      .attr('x1',getLinkX1CoOrd)
      .attr('x2',getLinkX2CoOrd)
      .attr('y1',getLinkY1CoOrd)
      .attr('y2',getLinkY2CoOrd)
      .classed('link',true);
  }

  function getLinkX1CoOrd(d){
    if (data.nodes[d.start -1].cx != null){
      return data.nodes[d.start -1].cx;
    }
    return data.nodes[d.start -1].x;
  }

  function getLinkX2CoOrd(d){
    if (d.end != null){
      if (data.nodes[d.end -1].cx != null){
        return data.nodes[d.end -1].cx
      }
      else{
        return data.nodes[d.end -1].x
      }
    }
    var coordinates = [0, 0];
    coordinates = d3.mouse(this);
    return coordinates[0];
  }

  function getLinkY1CoOrd(d){
    if (data.nodes[d.start -1].cy != null){
      return data.nodes[d.start -1].cy;
    }
    return data.nodes[d.start -1].y;
  }

  function getLinkY2CoOrd(d){
    if (d.end != null){
      if (data.nodes[d.end -1].cy != null){
        return data.nodes[d.end -1].cy
      }
      else{
        return data.nodes[d.end -1].y
      }
    }
    var coordinates = [0, 0];
    coordinates = d3.mouse(this);
    return coordinates[1];
  }

});

  function serverFilter(d){
    return d.type == 'Server';
  }

  function hubFilter(d){
    return d.type == 'Hub';
  }<commit_msg>moving to bidirectional in json<commit_after>  var data = {nodes:[],
  links:[]};

$(document).ready(function () {
  var svg = d3.select('svg');

  var masterServerDrag = d3.behavior.drag();
  d3.select('.masterServer').call(masterServerDrag);

  masterServerDrag.on('dragstart',function(){
    console.log("drag started");
    data.nodes.push({id:data.nodes.length+1,cx:0,cy:0,type:'Server'});
    console.log(d3.event);
  });

  masterServerDrag.on('drag',function(){
    console.log('dragging');
    //console.log(d3.event);
    data.nodes[data.nodes.length -1].cx = d3.event.x;
    data.nodes[data.nodes.length -1].cy = d3.event.y;
    //console.log(d3.event);
    updateNodes();
  });

  masterServerDrag.on('dragend',function(){
    console.log('drag ended');
  });

  var serverDrag = d3.behavior.drag();
  d3.selectAll('.server').call(serverDrag);

  serverDrag.on('drag',function(){
    d3.select(this).data()[0].cx = d3.event.x;
    d3.select(this).data()[0].cy = d3.event.y;
    updateNodes();
  });

  var masterHubDrag = d3.behavior.drag();
  d3.select('.masterHub').call(masterHubDrag);

  masterHubDrag.on('dragstart',function(){
    console.log("drag started");
    data.nodes.push({id:data.nodes.length+1,x:0,y:0,type:'Hub'});
    console.log(d3.event);
  });

  masterHubDrag.on('drag',function(){
    console.log('dragging');
    //console.log(d3.event);
    data.nodes[data.nodes.length -1].x = d3.event.x;
    data.nodes[data.nodes.length -1].y = d3.event.y;
    //console.log(d3.event);
    updateNodes();
  });

  masterHubDrag.on('dragend',function(){
    console.log('drag ended');
  });

  var hubDrag = d3.behavior.drag();
  d3.selectAll('.hub').call(hubDrag);

  hubDrag.on('drag',function(){
    d3.select(this).data()[0].x = d3.event.x;
    d3.select(this).data()[0].y = d3.event.y;
    updateNodes();
  });

  var masterLLinkButton = d3.select('.masterLLinkButton');
  var start;
  var end;
  masterLLinkButton.on('mouseover',function(){
    d3.select(this).classed('mouseover',true);
  })
  .on('mouseout',function(){
    d3.select(this).classed('mouseover',false);
  })
  .on('click',function(){
    d3.select(this).classed('active',true);
    var nodes = svg.selectAll('.nodes');
    nodes.classed('clickable',true)
    updateNodes();
  });

  function updateNodes(){
    var servers = svg.selectAll('circle.server')
      .data(data.nodes.filter(serverFilter),function(d) { return d.id;})
      .call(serverDrag);

    servers.attr('cx',function(d){return d.cx;})
      .attr('cy',function(d){return d.cy;});

    servers.enter().append('circle')
      .attr('cx',function(d){return d.cx;})
      .attr('cy',function(d){return d.cy;})
      .classed('server',true)
      .classed('nodes',true);

    var hubs = svg.selectAll('rect.hub')
      .data(data.nodes.filter(hubFilter),function(d) { return d.id;})
      .call(hubDrag);

    hubs.attr('x',function(d){return d.x;})
      .attr('y',function(d){return d.y;});

    hubs.enter().append('rect')
      .attr('x',function(d){return d.x;})
      .attr('y',function(d){return d.y;})
      .attr('width',10)
      .attr('height',10)
      .classed('hub',true)
      .classed('nodes',true);

    var clickableNodes = d3.selectAll('.nodes.clickable');
    clickableNodes.on('click',function(){
      if (!d3.select(this).classed('clickable')){
        return;
      }
      if (start == null){
        data.links.push({start:d3.select(this).data()[0].id,id:data.links.length +1,direction:"bi"});
        d3.select(this).classed('clickable',false);
        console.log(data.links);
        start = d3.select(this).data()[0].id;
        updateNodes();
        return;
      }
      data.links[data.links.length -1].end = d3.select(this).data()[0].id;
      //data.links.push({start:data.links[data.links.length -1].end,end:data.links[data.links.length -1].start,id:data.links.length +1});
      clickableNodes.classed('clickable',false);
      console.log(data.links);
      start = null;
      updateNodes();
    });

    updateLinks();
  }

  function updateLinks(){
    var paths = svg.selectAll('.link')
      .data(data.links,function(d) { return d.id;});

    paths.attr('x1',getLinkX1CoOrd)
      .attr('x2',getLinkX2CoOrd)
      .attr('y1',getLinkY1CoOrd)
      .attr('y2',getLinkY2CoOrd);

    paths.enter().append('line')
      .attr('x1',getLinkX1CoOrd)
      .attr('x2',getLinkX2CoOrd)
      .attr('y1',getLinkY1CoOrd)
      .attr('y2',getLinkY2CoOrd)
      .classed('link',true);
  }

  function getLinkX1CoOrd(d){
    if (data.nodes[d.start -1].cx != null){
      return data.nodes[d.start -1].cx;
    }
    return data.nodes[d.start -1].x;
  }

  function getLinkX2CoOrd(d){
    if (d.end != null){
      if (data.nodes[d.end -1].cx != null){
        return data.nodes[d.end -1].cx
      }
      else{
        return data.nodes[d.end -1].x
      }
    }
    var coordinates = [0, 0];
    coordinates = d3.mouse(this);
    return coordinates[0];
  }

  function getLinkY1CoOrd(d){
    if (data.nodes[d.start -1].cy != null){
      return data.nodes[d.start -1].cy;
    }
    return data.nodes[d.start -1].y;
  }

  function getLinkY2CoOrd(d){
    if (d.end != null){
      if (data.nodes[d.end -1].cy != null){
        return data.nodes[d.end -1].cy
      }
      else{
        return data.nodes[d.end -1].y
      }
    }
    var coordinates = [0, 0];
    coordinates = d3.mouse(this);
    return coordinates[1];
  }

});

  function serverFilter(d){
    return d.type == 'Server';
  }

  function hubFilter(d){
    return d.type == 'Hub';
  }<|endoftext|>


<commit_before>// For more information on the 'revealing module' pattern, see
//   http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript
var WebMonitor = (function($, undefined) {
  'use strict';

  var settings = {
    // Show `console.log` messages?
    debug: false,
    // Default job status polling timeout in milliseconds
    pollRate: 300,
    // Defaults for histogram drawing
    histogramDefaults: {
    },
    // See http://fgnass.github.io/spin.js/ for options and customisation
    spinnerDefaults: {
      lines: 12,
      length: 5,
      width: 2,
      radius: 5,
      trail: 50,
      shadow: false
    },
    // See http://eternicode.github.io/bootstrap-datepicker/
    datepickerDefaults: {
      format: 'dd/mm/yyyy',
      // This app doesn't now about the future
      endDate: '+0d',
      todayBtn: 'linked',
      // Always pop under the field
      orientation: 'top auto',
      autoclose: true,
      todayHighlight: true
    }
  };

  // Cross-browser compatible `console.log`. Only fires if settings.debug === true.
  // http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
  // Accepts:
  //   Any number of arguments of any type.
  // Returns:
  //   undefined
  var log = function() {
    log.history = log.history || [];
    log.history.push(arguments);
    if (window.console && settings.debug) {
      console.log(Array.prototype.slice.call(arguments));
    }
  };

  // Submit a job to the server
  // Accepts:
  //   taskName: Name of the task the job will run
  //   args: Object of arguments passed to the task as named arguments
  //   poll: Whether to poll the job until completion (default: true)
  //     The job is polled in intervals of WebMonitor.settings.pollRate until
  //     the job status is not `queued` or `started`.
  // Returns:
  //   jQuery.Deferred object which fires `resolve` on job completion and fires
  //     `reject` on either submission failure or job polling failure
  var submitJob = function(taskName, args, doPoll) {
    if (doPoll === undefined) {
      doPoll = true;
    }
    var jobRequest =  $.ajax('/jobs', {
      type: 'POST',
      contentType: 'application/json; charset=utf-8',
      dataType: 'json',
      data: JSON.stringify({task_name: taskName, args: args})
    });
    var jobPromise = $.Deferred();
    var poll = function(job) {
      var jobID = job['id'],
          jobStatus = job['status'];
      // Poll the job if it hasn't not completed, else fire `resolve`
      if (jobStatus === 'queued' || jobStatus === 'started') {
        setTimeout(function() {
          log('Polling job ID ' + jobID + ': ' + jobStatus);
          $.getJSON(job['uri'])
            .done(function(data, status) { poll(data['job']); })
            .fail(function(data, status) { jobPromise.reject(data, status); });
        }, settings.pollRate);
      } else {
        log('Job ' + jobID + ' completed: ' + jobStatus);
        jobPromise.resolve(job);
      }
    };
    jobRequest.done(function(data, status) {
      var job = data['job'];
      if (doPoll === true) {
        poll(job);
      }
    });
    // Fire `reject` if the submission fails
    jobRequest.fail(function(data, status) { jobPromise.reject(data, status); });
    return jobPromise;
  };

  // Create a task and submit a job for it to the queue
  //
  // Unlike submitJob, this method handles job failures by creating descriptive
  // HTML error messages.
  // This relies on the JSON response containing a `result` key which is the
  // task's return value, which itself contains a `success` key determining
  // whether the task completed successfully.
  // If `success` if false, the task's `message` key is retrieved.
  // Accepts:
  //   taskName: Name of the task the job will run
  //   args: Object of arguments passed to the task as named arguments
  // Returns:
  //   jQuery.Deferred object which fires `resolve` on job completion and fires
  //     `reject` on either submission failure, job polling failure, or task
  //     failure. On resolution, the JSON response object is passed, whereas
  //     on rejection a descriptive HTML error message is passed.
  var createTask = function(taskName, args) {
    // Create a polling job
    var jobPromise = submitJob(taskName, args, true);
    var taskPromise = $.Deferred();
    jobPromise.done(function(job) {
      // Did the job complete successfully or not?
      if (job['status'] === 'failed') {
        var message = '
The job completed unsuccessfully.
';
        taskPromise.reject(message);
      } else {
        // Did the task the job was running finish successfully or not?
        var result = job['result'];
        if (result['success'] === false) {
          var message = '
The task could not complete successfully. It returned the error message '
            + '' + result['message'] + '.'
            + '
';
          taskPromise.reject(message);
        } else {
          taskPromise.resolve(job);
        }
      }
    });
    jobPromise.fail(function(data, status) {
      var message;
      try {
        message = JSON.parse(data.responseText)['message'];
      } catch(e) {
        message = data.statusText;
      }
      message = '
The server responded with the error code '
        + '' + data.status + ''
        + ' and the message '
        + '' + message + '.'
        + '
';
      taskPromise.reject(message);
    });
    return taskPromise;
  };

  // Add a `Spinner` object to the `element`, using `settings.spinnerDefaults` as options.
  // Accepts:
  //   element: DOM element
  // Returns:
  //   Spinner object
  var appendSpinner = function(element) {
    return new Spinner(settings.spinnerDefaults).spin(element);
  }

  // Initialise globally required features, and call the chain of inits required for pageModule.
  // Accepts:
  //   pageModule: String of the form `x.y.z`. Starting from the top (`x`), the `init` function on each module is called, if it exists.
  //   pages: Object of page-specific objects. The `init` function of each page-specific object is called if its hierarchical position within
  //          `pages` matches a subsection of `pageModule`. For example, given a `pages` object like
  //            {
  //              home: {
  //                init: function() { ... }
  //              },
  //              example: {
  //                init: { function() {... },
  //                foo:  { init: function() { ... }},
  //                bar:  { init: function() { ... }}
  //              }
  //            }
  //          a `pageModule` of `home` would call `home.init`, whereas a `pageModule` of `example.foo` would fire *both* `example.init`
  //          and `example.foo.init`.
  // Returns:
  //   undefined
  var init = function(pageModule, pages) {
    var components = pageModule.split('/'),
        parentModule = pages,
        current = undefined,
        modules = [],
        $main = $('.main');

    // Work our way down the module chain, top to bottom, calling `init` on each successive child, if it exists.
    $.each(components, function(index, component) {
      // If a child doesn't exist in its parent, set the parent to the empty object
      if (component in parentModule) {
          current = parentModule[component];
          modules.push(current);
          if (current !== undefined && current.init !== undefined) {
            current.init();
          }
      } else {
          parentModule = {};
      }
    });
  };

  return {
    init: init,
    submitJob: submitJob,
    createTask: createTask,
    appendSpinner: appendSpinner,
    log: log,
    settings: settings
  };
})(jQuery);
<commit_msg>Fix child page modules not firing.<commit_after>// For more information on the 'revealing module' pattern, see
//   http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript
var WebMonitor = (function($, undefined) {
  'use strict';

  var settings = {
    // Show `console.log` messages?
    debug: false,
    // Default job status polling timeout in milliseconds
    pollRate: 300,
    // Defaults for histogram drawing
    histogramDefaults: {
    },
    // See http://fgnass.github.io/spin.js/ for options and customisation
    spinnerDefaults: {
      lines: 12,
      length: 5,
      width: 2,
      radius: 5,
      trail: 50,
      shadow: false
    },
    // See http://eternicode.github.io/bootstrap-datepicker/
    datepickerDefaults: {
      format: 'dd/mm/yyyy',
      // This app doesn't now about the future
      endDate: '+0d',
      todayBtn: 'linked',
      // Always pop under the field
      orientation: 'top auto',
      autoclose: true,
      todayHighlight: true
    }
  };

  // Cross-browser compatible `console.log`. Only fires if settings.debug === true.
  // http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
  // Accepts:
  //   Any number of arguments of any type.
  // Returns:
  //   undefined
  var log = function() {
    log.history = log.history || [];
    log.history.push(arguments);
    if (window.console && settings.debug) {
      console.log(Array.prototype.slice.call(arguments));
    }
  };

  // Submit a job to the server
  // Accepts:
  //   taskName: Name of the task the job will run
  //   args: Object of arguments passed to the task as named arguments
  //   poll: Whether to poll the job until completion (default: true)
  //     The job is polled in intervals of WebMonitor.settings.pollRate until
  //     the job status is not `queued` or `started`.
  // Returns:
  //   jQuery.Deferred object which fires `resolve` on job completion and fires
  //     `reject` on either submission failure or job polling failure
  var submitJob = function(taskName, args, doPoll) {
    if (doPoll === undefined) {
      doPoll = true;
    }
    var jobRequest =  $.ajax('/jobs', {
      type: 'POST',
      contentType: 'application/json; charset=utf-8',
      dataType: 'json',
      data: JSON.stringify({task_name: taskName, args: args})
    });
    var jobPromise = $.Deferred();
    var poll = function(job) {
      var jobID = job['id'],
          jobStatus = job['status'];
      // Poll the job if it hasn't not completed, else fire `resolve`
      if (jobStatus === 'queued' || jobStatus === 'started') {
        setTimeout(function() {
          log('Polling job ID ' + jobID + ': ' + jobStatus);
          $.getJSON(job['uri'])
            .done(function(data, status) { poll(data['job']); })
            .fail(function(data, status) { jobPromise.reject(data, status); });
        }, settings.pollRate);
      } else {
        log('Job ' + jobID + ' completed: ' + jobStatus);
        jobPromise.resolve(job);
      }
    };
    jobRequest.done(function(data, status) {
      var job = data['job'];
      if (doPoll === true) {
        poll(job);
      }
    });
    // Fire `reject` if the submission fails
    jobRequest.fail(function(data, status) { jobPromise.reject(data, status); });
    return jobPromise;
  };

  // Create a task and submit a job for it to the queue
  //
  // Unlike submitJob, this method handles job failures by creating descriptive
  // HTML error messages.
  // This relies on the JSON response containing a `result` key which is the
  // task's return value, which itself contains a `success` key determining
  // whether the task completed successfully.
  // If `success` if false, the task's `message` key is retrieved.
  // Accepts:
  //   taskName: Name of the task the job will run
  //   args: Object of arguments passed to the task as named arguments
  // Returns:
  //   jQuery.Deferred object which fires `resolve` on job completion and fires
  //     `reject` on either submission failure, job polling failure, or task
  //     failure. On resolution, the JSON response object is passed, whereas
  //     on rejection a descriptive HTML error message is passed.
  var createTask = function(taskName, args) {
    // Create a polling job
    var jobPromise = submitJob(taskName, args, true);
    var taskPromise = $.Deferred();
    jobPromise.done(function(job) {
      // Did the job complete successfully or not?
      if (job['status'] === 'failed') {
        var message = '
The job completed unsuccessfully.
';
        taskPromise.reject(message);
      } else {
        // Did the task the job was running finish successfully or not?
        var result = job['result'];
        if (result['success'] === false) {
          var message = '
The task could not complete successfully. It returned the error message '
            + '' + result['message'] + '.'
            + '
';
          taskPromise.reject(message);
        } else {
          taskPromise.resolve(job);
        }
      }
    });
    jobPromise.fail(function(data, status) {
      var message;
      try {
        message = JSON.parse(data.responseText)['message'];
      } catch(e) {
        message = data.statusText;
      }
      message = '
The server responded with the error code '
        + '' + data.status + ''
        + ' and the message '
        + '' + message + '.'
        + '
';
      taskPromise.reject(message);
    });
    return taskPromise;
  };

  // Add a `Spinner` object to the `element`, using `settings.spinnerDefaults` as options.
  // Accepts:
  //   element: DOM element
  // Returns:
  //   Spinner object
  var appendSpinner = function(element) {
    return new Spinner(settings.spinnerDefaults).spin(element);
  }

  // Initialise globally required features, and call the chain of inits required for pageModule.
  // Accepts:
  //   pageModule: String of the form `x.y.z`. Starting from the top (`x`), the `init` function on each module is called, if it exists.
  //   pages: Object of page-specific objects. The `init` function of each page-specific object is called if its hierarchical position within
  //          `pages` matches a subsection of `pageModule`. For example, given a `pages` object like
  //            {
  //              home: {
  //                init: function() { ... }
  //              },
  //              example: {
  //                init: { function() {... },
  //                foo:  { init: function() { ... }},
  //                bar:  { init: function() { ... }}
  //              }
  //            }
  //          a `pageModule` of `home` would call `home.init`, whereas a `pageModule` of `example.foo` would fire *both* `example.init`
  //          and `example.foo.init`.
  // Returns:
  //   undefined
  var init = function(pageModule, pages) {
    var components = pageModule.split('/'),
        parentModule = pages,
        current = undefined,
        $main = $('.main');

    // Work our way down the module chain, top to bottom, calling `init` on each successive child, if it exists.
    $.each(components, function(index, component) {
      // If a child doesn't exist in its parent, set the parent to the empty object
      if (component in parentModule) {
          current = parentModule[component];
          if (current !== undefined && current.init !== undefined) {
            current.init();
          }
          parentModule = current;
      } else {
          parentModule = {};
      }
    });
  };

  return {
    init: init,
    submitJob: submitJob,
    createTask: createTask,
    appendSpinner: appendSpinner,
    log: log,
    settings: settings
  };
})(jQuery);
<|endoftext|>


<commit_before>process.env.NODE_ENV = 'production'

const exec = require('child_process').execSync
const webpack = require('webpack')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const SriPlugin = require('webpack-subresource-integrity')
const I18nPlugin = require('i18n-webpack-plugin')
const CompressionPlugin = require('compression-webpack-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
// const BabiliPlugin = require('babili-webpack-plugin')
const ProgressPlugin = require('webpack/lib/ProgressPlugin')
const ManifestPlugin = require('webpack-manifest-plugin')
const OfflinePlugin = require('offline-plugin')
const PreloadWebpackPlugin = require('preload-webpack-plugin')
const FaviconsWebpackPlugin = require('favicons-webpack-plugin')
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
const ShakePlugin = require('webpack-common-shake').Plugin
// const git = require('git-rev-sync')
// const _ = require('lodash')
const path = require('path')
// NOTE: WebpackShellPlugin allows you to run custom shell commands before and after build
// const WebpackShellPlugin = require('webpack-shell-plugin')
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
const {APP_LANGUAGE, ANALYZE_BUNDLE} = process.env
let base = require('./webpack.base')
const config = require('../config')
let languages = config.i18n
const languageName = APP_LANGUAGE || 'en'

exec('rm -rf dist/client')
// NOTE: you can track versions with gitHash and store your build
// in dist folder with path like: /dist/<gitHash>/{yourFilesHere}
// const gitHash = git.short() //

// use hash filename to support long-term caching
base.output.filename = '[name].[chunkhash:8].js'
base.output.path = path.join(base.output.path, languageName)
base.output.crossOriginLoading = 'anonymous'
base.devtool = 'cheap-source-map'
base.module.rules.push(
  {
    test: /\.css$/,
    use: ExtractTextPlugin.extract({
      fallback: 'style-loader',
      use: ['css-loader', 'postcss-loader']
    })
  },
  {
    test: /\.scss$/,
    use: ExtractTextPlugin.extract({
      fallback: 'style-loader',
      use: ['css-loader', 'postcss-loader', 'sass-loader']
    })
  }
)

// Do you want to use bundle analyzer?
if (ANALYZE_BUNDLE) {
  base.plugins.push(new BundleAnalyzerPlugin())
}

// NOTE: if language was set, then build only this language
try {
  const langText = languages[languageName]
  languages = {[languageName]: langText}
} catch (e) {
  throw new Error(
    `Something went wrong with your i18n. Check that "${APP_LANGUAGE}" property exists in i18n object. ${e}`
  )
}

// add webpack plugins
base.plugins.push(
  new ProgressPlugin(),
  new ExtractTextPlugin({
    filename: '[name].[chunkhash:8].css',
    allChunks: true
  }),
  new OptimizeCssAssetsPlugin({
    cssProcessorOptions: {
      safe: true,
      discardComments: {
        removeAll: true
      }
    }
  }),
  new webpack.optimize.ModuleConcatenationPlugin(),
  new ShakePlugin(),
  // NOTE: you can use BabiliPlugin as an alternative to UglifyJSPlugin
  // new BabiliPlugin(),
  new UglifyJSPlugin({
    sourceMap: true,
    compress: {
      warnings: false,
      drop_console: true
    },
    output: {
      comments: false
    }
  }),
  // NOTE: Prepack is currently in alpha, be carefull with it
  // new PrepackWebpackPlugin(),
  // extract vendor chunks
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',
    minChunks: module => {
      // this assumes your vendor imports exist in the node_modules directory
      return module.context && module.context.indexOf('node_modules') !== -1
    }
  }),
  // extract lazy containers chunk
  new webpack.optimize.CommonsChunkPlugin({
    name: 'lazy-containers',
    chunks: ['lazy-containers'],
    async: true
  }),
  // manifest chunk, more info in webpack docs
  new webpack.optimize.CommonsChunkPlugin({
    name: 'manifest'
  }),
  new webpack.BannerPlugin({
    banner: config.banner
  }),
  // NOTE: this plugin looks cool, but there are few big issues:
  // 1. It sets invalid url to browserconfig.xml and manifest.json in index.html.
  // E.g: in generated index.html you can see:
  // <meta name="msapplication-config" content="browserconfig.xml">
  // 2. It looks like generated images aren't minified.(not sure)
  // 3. plugin is deprecated (at least look like it's deprecated)!
  // NOTE: It would be better to generate favicons without this plugin.
  new FaviconsWebpackPlugin({
    // add theme-color property
    background: config.manifest.theme,
    prefix: `favicons/`,
    logo: path.resolve(config.rootPath, './static/images/logo.png'),
    title: config.title,
    // Inject the html into the html-webpack-plugin
    inject: true,
    // which icons should be generated (see https://github.com/haydenbleasel/favicons#usage)
    icons: {
      android: true,
      appleIcon: true,
      appleStartup: true,
      coast: false,
      favicons: true,
      firefox: true,
      opengraph: false,
      twitter: true,
      yandex: false,
      windows: true
    }
  }),
  //
  // create manifest.json
  new ManifestPlugin({fileName: 'manifest.json', cache: config.manifest}),
  // generate <link rel="preload"> tags for async chunks
  new PreloadWebpackPlugin({
    rel: 'preload',
    as: 'script',
    include: 'asyncChunks'
  }),
  // https://caniuse.com/#feat=subresource-integrity
  // NOTE: please, read about SRI before using it!
  new SriPlugin({
    hashFuncNames: ['sha256', 'sha384'],
    enabled: true
  }),
  new CompressionPlugin({
    algorithm: 'gzip'
  }),
  new I18nPlugin(languages[languageName], {functionName: 'i18n'}),
  new HtmlWebpackPlugin({
    title: config.title,
    language: languageName,
    theme_color: config.manifest.theme_color,
    // minify: true,
    template: path.resolve(config.srcCommonPath, 'index.ejs'),
    filename: path.resolve(base.output.path, 'index.html'),
    chunksSortMode: 'dependency'
  }),
  new OfflinePlugin({
    // responseStrategy: 'network-first',
    // safeToUseOptionalCaches: false,
    // caches: {
    //   main: ['vendor.*.css', 'vendor.*.js']
    // },
    // excludes: ['.htaccess'],
    AppCache: false,
    // ServiceWorker: {
    //   navigateFallbackURL: '/?offline=true',
    //   events: true
    // }
  })
)

// minimize webpack output
base.stats = {
  colors: true,
  // Add children information
  children: false,
  // Add chunk information (setting this to `false` allows for a less verbose output)
  chunks: false,
  // Add built modules information to chunk information
  chunkModules: false,
  chunkOrigins: false,
  modules: false,
  reasons: true,
  errorDetails: true
}

// const build = Object.keys(languages).map(language => {
//   let baseConfigForLang = _.cloneDeep(base)
//   baseConfigForLang.output.path = path.join(
//     baseConfigForLang.output.path,
//     language
//   )
//
//   baseConfigForLang.plugins.push(
//     new I18nPlugin(languages[language], {functionName: 'i18n'}),
// new HtmlWebpackPlugin({
//   title: config.title,
//   language,
//   // minify: true,
//   template: path.resolve(config.srcCommonPath, 'index.ejs'),
//   filename: path.resolve(baseConfigForLang.output.path, 'index.html'),
//   chunksSortMode: 'dependency'
// })
//   )
//   return baseConfigForLang
// })

module.exports = base
<commit_msg>feat(webpack_config/client/webpack.prod): clean only dist/client/<languageName> dir, not all dist/client<commit_after>process.env.NODE_ENV = 'production'

const exec = require('child_process').execSync
const webpack = require('webpack')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const SriPlugin = require('webpack-subresource-integrity')
const I18nPlugin = require('i18n-webpack-plugin')
const CompressionPlugin = require('compression-webpack-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
// const BabiliPlugin = require('babili-webpack-plugin')
const ProgressPlugin = require('webpack/lib/ProgressPlugin')
const ManifestPlugin = require('webpack-manifest-plugin')
const OfflinePlugin = require('offline-plugin')
const PreloadWebpackPlugin = require('preload-webpack-plugin')
const FaviconsWebpackPlugin = require('favicons-webpack-plugin')
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
const ShakePlugin = require('webpack-common-shake').Plugin
// const git = require('git-rev-sync')
// const _ = require('lodash')
const path = require('path')
// NOTE: WebpackShellPlugin allows you to run custom shell commands before and after build
// const WebpackShellPlugin = require('webpack-shell-plugin')
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
const {APP_LANGUAGE, ANALYZE_BUNDLE} = process.env
let base = require('./webpack.base')
const config = require('../config')
let languages = config.i18n
const languageName = APP_LANGUAGE || 'en'

exec(`rm -rf ${config.distPath}/client/${languageName}`)
// NOTE: you can track versions with gitHash and store your build
// in dist folder with path like: /dist/<gitHash>/{yourFilesHere}
// const gitHash = git.short() //

// use hash filename to support long-term caching
base.output.filename = '[name].[chunkhash:8].js'
base.output.path = path.join(base.output.path, languageName)
base.output.crossOriginLoading = 'anonymous'
base.devtool = 'cheap-source-map'
base.module.rules.push(
	{
		test: /\.css$/,
		use: ExtractTextPlugin.extract({
			fallback: 'style-loader',
			use: ['css-loader', 'postcss-loader']
		})
	},
	{
		test: /\.scss$/,
		use: ExtractTextPlugin.extract({
			fallback: 'style-loader',
			use: ['css-loader', 'postcss-loader', 'sass-loader']
		})
	}
)

// Do you want to use bundle analyzer?
if (ANALYZE_BUNDLE) {
	base.plugins.push(new BundleAnalyzerPlugin())
}

// NOTE: if language was set, then build only this language
try {
	const langText = languages[languageName]
	languages = {[languageName]: langText}
} catch (e) {
	throw new Error(
		`Something went wrong with your i18n. Check that "${APP_LANGUAGE}" property exists in i18n object. ${e}`
	)
}

// add webpack plugins
base.plugins.push(
	new ProgressPlugin(),
	new ExtractTextPlugin({
		filename: '[name].[chunkhash:8].css',
		allChunks: true
	}),
	new OptimizeCssAssetsPlugin({
		cssProcessorOptions: {
			safe: true,
			discardComments: {
				removeAll: true
			}
		}
	}),
	new webpack.optimize.ModuleConcatenationPlugin(),
	new ShakePlugin(),
	// NOTE: you can use BabiliPlugin as an alternative to UglifyJSPlugin
	// new BabiliPlugin(),
	new UglifyJSPlugin({
		sourceMap: true,
		compress: {
			warnings: false,
			drop_console: true
		},
		output: {
			comments: false
		}
	}),
	// NOTE: Prepack is currently in alpha, be carefull with it
	// new PrepackWebpackPlugin(),
	// extract vendor chunks
	new webpack.optimize.CommonsChunkPlugin({
		name: 'vendor',
		minChunks: module => {
			// this assumes your vendor imports exist in the node_modules directory
			return module.context && module.context.indexOf('node_modules') !== -1
		}
	}),
	// extract lazy containers chunk
	new webpack.optimize.CommonsChunkPlugin({
		name: 'lazy-containers',
		chunks: ['lazy-containers'],
		async: true
	}),
	// manifest chunk, more info in webpack docs
	new webpack.optimize.CommonsChunkPlugin({
		name: 'manifest'
	}),
	new webpack.BannerPlugin({
		banner: config.banner
	}),
	// NOTE: this plugin looks cool, but there are few big issues:
	// 1. It sets invalid url to browserconfig.xml and manifest.json in index.html.
	// E.g: in generated index.html you can see:
	// <meta name="msapplication-config" content="browserconfig.xml">
	// 2. It looks like generated images aren't minified.(not sure)
	// 3. plugin is deprecated (at least look like it's deprecated)!
	// NOTE: It would be better to generate favicons without this plugin.
	new FaviconsWebpackPlugin({
		// add theme-color property
		background: config.manifest.theme,
		prefix: `favicons/`,
		logo: path.resolve(config.rootPath, './static/images/logo.png'),
		title: config.title,
		// Inject the html into the html-webpack-plugin
		inject: true,
		// which icons should be generated (see https://github.com/haydenbleasel/favicons#usage)
		icons: {
			android: true,
			appleIcon: true,
			appleStartup: true,
			coast: false,
			favicons: true,
			firefox: true,
			opengraph: false,
			twitter: true,
			yandex: false,
			windows: true
		}
	}),
	//
	// create manifest.json
	new ManifestPlugin({fileName: 'manifest.json', cache: config.manifest}),
	// generate <link rel="preload"> tags for async chunks
	new PreloadWebpackPlugin({
		rel: 'preload',
		as: 'script',
		include: 'asyncChunks'
	}),
	// https://caniuse.com/#feat=subresource-integrity
	// NOTE: please, read about SRI before using it!
	new SriPlugin({
		hashFuncNames: ['sha256', 'sha384'],
		enabled: true
	}),
	new CompressionPlugin({
		algorithm: 'gzip'
	}),
	new I18nPlugin(languages[languageName], {functionName: 'i18n'}),
	new HtmlWebpackPlugin({
		title: config.title,
		language: languageName,
		theme_color: config.manifest.theme_color,
		// minify: true,
		template: path.resolve(config.srcCommonPath, 'index.ejs'),
		filename: path.resolve(base.output.path, 'index.html'),
		chunksSortMode: 'dependency'
	})
	// new OfflinePlugin({
	// responseStrategy: 'network-first',
	// safeToUseOptionalCaches: false,
	// caches: {
	//   main: ['vendor.*.css', 'vendor.*.js']
	// },
	// excludes: ['.htaccess'],
	//   AppCache: false
	// })
)

// minimize webpack output
base.stats = {
	colors: true,
	// Add children information
	children: false,
	// Add chunk information (setting this to `false` allows for a less verbose output)
	chunks: false,
	// Add built modules information to chunk information
	chunkModules: false,
	chunkOrigins: false,
	modules: false,
	reasons: true,
	errorDetails: true
}

// const build = Object.keys(languages).map(language => {
//   let baseConfigForLang = _.cloneDeep(base)
//   baseConfigForLang.output.path = path.join(
//     baseConfigForLang.output.path,
//     language
//   )
//
//   baseConfigForLang.plugins.push(
//     new I18nPlugin(languages[language], {functionName: 'i18n'}),
// new HtmlWebpackPlugin({
//   title: config.title,
//   language,
//   // minify: true,
//   template: path.resolve(config.srcCommonPath, 'index.ejs'),
//   filename: path.resolve(baseConfigForLang.output.path, 'index.html'),
//   chunksSortMode: 'dependency'
// })
//   )
//   return baseConfigForLang
// })

module.exports = base
<|endoftext|>