<commit_before>angular.module('ion-google-place', [])
    .directive('ionGooglePlace', [
        '$ionicTemplateLoader',
        '$ionicBackdrop',
        '$ionicPlatform',
        '$q',
        '$timeout',
        '$rootScope',
        '$document',
        function($ionicTemplateLoader, $ionicBackdrop, $ionicPlatform, $q, $timeout, $rootScope, $document) {
            return {
                require: '?ngModel',
                restrict: 'E',
                template: '<input type="text" readonly="readonly" class="ion-google-place" autocomplete="off">',
                replace: true,
                scope: {
                    ngModel: '=?',
                    geocodeOptions: '=',
                    defaultLocations: '='
                },
                link: function(scope, element, attrs, ngModel) {
                    scope.locations = scope.defaultLocations || [];

                    var defaultLocationsWatcher = scope.$watch('defaultLocations', function (newValue, oldValue) {
                        if (newValue && newValue instanceof Array && newValue.length) {
                            scope.locations = newValue;
                            scope.defaultLocations = newValue;
                            defaultLocationsWatcher();
                        }
                    });

                    var unbindBackButtonAction;
                    var geocoder = new google.maps.Geocoder();
                    var searchEventTimeout = undefined;

                    // create fake hidden input, because google.maps.places.PlacesService won't work without it

                    var input = document.createElement('input');
                    input.type = 'text';
                    input.style.display = 'none';
                    input.className = 'fake-hidden-input';
                    document.body.appendChild(input);

                    var POPUP_TPL = [
                        '
',
                            '
',
                                '',
                                    '',
                                    '<input class="google-place-search" type="search" ng-model="searchQuery" placeholder="' + (attrs.searchPlaceholder || 'Enter an address, place or ZIP code') + '">',
                                '',
                                '<button class="button button-clear">',
                                    attrs.labelCancel || 'Cancel',
                                '</button>',
                            '
',
                            '<ion-content class="has-header has-header">',
                                '<ion-list>',
                                    '<ion-item ng-repeat="location in locations track by $index" type="item-text-wrap" ng-click="selectLocation(location)">',
                                        ' {{ location.name }}, {{location.formatted_address}}',
                                    '</ion-item>',
                                '</ion-list>',
                            '</ion-content>',
                        '
'
                    ].join('');

                    /*
                     * Check if location.name should be shown
                     */
                    scope.shouldNameBeShown = function(locationTypes) {
                        if (!locationTypes) {
                            return false;
                        }

                        var typesToExclude = ['street_address'],
                            shouldBeShown = true;

                        for (var i = 0; i < locationTypes.length; i++) {
                            if (typesToExclude.indexOf(locationTypes[i]) >= 0) {
                                shouldBeShown = false;
                                break;
                            }
                        }
                        return shouldBeShown;
                    }

                    var popupPromise = $ionicTemplateLoader.compile({
                        template: POPUP_TPL,
                        scope: scope,
                        appendTo: $document[0].body
                    });

                    popupPromise.then(function(el){
                        var searchInputElement = angular.element(el.element.find('input'));

                        scope.selectLocation = function(location){
                            ngModel.$setViewValue(location);
                            ngModel.$render();
                            el.element.css('display', 'none');

                            scope.searchQuery = '';
                            scope.locations = scope.defaultLocations || [];

                            $ionicBackdrop.release();

                            if (unbindBackButtonAction) {
                                unbindBackButtonAction();
                                unbindBackButtonAction = null;
                            }
                        };

                        function mapsGeocode(req, callback) {
                            geocoder.geocode(req, function(results, status) {
                                if (status !== google.maps.GeocoderStatus.OK) {
                                    return;
                                }
                                callback(results);
                            });
                        }

                        function placesGeocode(req, callback) {
                            var autocompleteService = new google.maps.places.AutocompleteService(),
                                googlePlacesService = new google.maps.places.PlacesService(document.getElementsByClassName('fake-hidden-input')[0]);

                            autocompleteService.getPlacePredictions({input: req.address}, function(predictions, status) {
                                if (status !== google.maps.places.PlacesServiceStatus.OK) {
                                    return;
                                }

                                var promises = predictions.map(function(prediction) {
                                    var defer = $q.defer();
                                    googlePlacesService.getDetails({reference: prediction.reference}, function(details) {
                                        defer.resolve(details);
                                    });
                                    return defer.promise;
                                });

                                $q.all(promises).then(function(data) {
                                    callback(data);
                                });

                            });
                        }

                        scope.$watch('searchQuery', function(query){
                            if (searchEventTimeout) $timeout.cancel(searchEventTimeout);
                            searchEventTimeout = $timeout(function() {
                                if(!query) return;
                                if(query.length < 3);

                                var req = scope.geocodeOptions || {};
                                req.address = query;

                                scope.locations = [];

                                if (attrs.geocodeService === 'places-api') {
                                    placesGeocode(req, function(results) {
                                        scope.locations = results.filter(function(result) {
                                            return result;
                                        });
                                    });
                                } else {
                                    mapsGeocode(req, function(results) {
                                        scope.$apply(function() {
                                            scope.locations = results;
                                        });
                                    });
                                }
                            }, 350); // we're throttling the input by 350ms to be nice to google's API
                        });

                        var onClick = function(e){
                            e.preventDefault();
                            e.stopPropagation();

                            $ionicBackdrop.retain();
                            unbindBackButtonAction = $ionicPlatform.registerBackButtonAction(closeOnBackButton, 250);

                            el.element.css('display', 'block');
                            searchInputElement[0].focus();
                            setTimeout(function(){
                                searchInputElement[0].focus();
                            },0);
                        };

                        var onCancel = function(e){
                            $ionicBackdrop.release();
                            el.element.css('display', 'none');

                            scope.searchQuery = '';
                            scope.locations = scope.defaultLocations || [];
                            scope.$$phase || scope.$digest();

                            if (unbindBackButtonAction){
                                unbindBackButtonAction();
                                unbindBackButtonAction = null;
                            }
                        };

                        closeOnBackButton = function(e){
                            e.preventDefault();

                            el.element.css('display', 'none');
                            $ionicBackdrop.release();

                            if (unbindBackButtonAction){
                                unbindBackButtonAction();
                                unbindBackButtonAction = null;
                            }
                        }

                        element.bind('click', onClick);
                        element.bind('touchend', onClick);

                        el.element.find('button').bind('click', onCancel);
                    });

                    if (attrs.placeholder){
                        element.attr('placeholder', attrs.placeholder);
                    }

                    ngModel.$formatters.unshift(function (modelValue) {
                        if (!modelValue) {
                            return '';
                        }
                        return modelValue;
                    });

                    ngModel.$parsers.unshift(function (viewValue) {
                        return viewValue;
                    });

                    ngModel.$render = function(){
                        if (!ngModel.$viewValue) {
                            element.val('');
                        } else {
                            element.val(ngModel.$viewValue.formatted_address || ngModel.$modelValue.formatted_address || '');
                        }
                    };

                    scope.$on('$destroy', function(){
                        if (unbindBackButtonAction){
                            unbindBackButtonAction();
                            unbindBackButtonAction = null;
                        }
                    });
                }
            };
        }
    ]);
<commit_msg>popup ID attribute<commit_after>angular.module('ion-google-place', [])
    .directive('ionGooglePlace', [
        '$ionicTemplateLoader',
        '$ionicBackdrop',
        '$ionicPlatform',
        '$q',
        '$timeout',
        '$rootScope',
        '$document',
        function($ionicTemplateLoader, $ionicBackdrop, $ionicPlatform, $q, $timeout, $rootScope, $document) {
            return {
                require: '?ngModel',
                restrict: 'E',
                template: '<input type="text" readonly="readonly" class="ion-google-place" autocomplete="off">',
                replace: true,
                scope: {
                    ngModel: '=?',
                    geocodeOptions: '=',
                    defaultLocations: '='
                },
                link: function(scope, element, attrs, ngModel) {
                    scope.locations = scope.defaultLocations || [];

                    var defaultLocationsWatcher = scope.$watch('defaultLocations', function (newValue, oldValue) {
                        if (newValue && newValue instanceof Array && newValue.length) {
                            scope.locations = newValue;
                            scope.defaultLocations = newValue;
                            defaultLocationsWatcher();
                        }
                    });

                    var unbindBackButtonAction;
                    var geocoder = new google.maps.Geocoder();
                    var searchEventTimeout = undefined;

                    // create fake hidden input, because google.maps.places.PlacesService won't work without it

                    var input = document.createElement('input');
                    input.type = 'text';
                    input.style.display = 'none';
                    input.className = 'fake-hidden-input';
                    document.body.appendChild(input);

                    var POPUP_TPL = [
                        '
',
                            '
',
                                '',
                                    '',
                                    '<input ' + (attrs.popupId ? ('id="' + attrs.popupId + '-input"') : '') + ' class="google-place-search" type="search" ng-model="searchQuery" placeholder="' + (attrs.searchPlaceholder || 'Enter an address, place or ZIP code') + '">',
                                '',
                                '<button class="button button-clear">',
                                    attrs.labelCancel || 'Cancel',
                                '</button>',
                            '
',
                            '<ion-content class="has-header has-header">',
                                '<ion-list>',
                                    '<ion-item ng-repeat="location in locations track by $index" type="item-text-wrap" ng-click="selectLocation(location)">',
                                        ' {{ location.name }}, {{location.formatted_address}}',
                                    '</ion-item>',
                                '</ion-list>',
                            '</ion-content>',
                        '
'
                    ].join('');

                    /*
                     * Check if location.name should be shown
                     */
                    scope.shouldNameBeShown = function(locationTypes) {
                        if (!locationTypes) {
                            return false;
                        }

                        var typesToExclude = ['street_address'],
                            shouldBeShown = true;

                        for (var i = 0; i < locationTypes.length; i++) {
                            if (typesToExclude.indexOf(locationTypes[i]) >= 0) {
                                shouldBeShown = false;
                                break;
                            }
                        }
                        return shouldBeShown;
                    }

                    var popupPromise = $ionicTemplateLoader.compile({
                        template: POPUP_TPL,
                        scope: scope,
                        appendTo: $document[0].body
                    });

                    popupPromise.then(function(el){
                        var searchInputElement = angular.element(el.element.find('input'));

                        scope.selectLocation = function(location){
                            ngModel.$setViewValue(location);
                            ngModel.$render();
                            el.element.css('display', 'none');

                            scope.searchQuery = '';
                            scope.locations = scope.defaultLocations || [];

                            $ionicBackdrop.release();

                            if (unbindBackButtonAction) {
                                unbindBackButtonAction();
                                unbindBackButtonAction = null;
                            }
                        };

                        function mapsGeocode(req, callback) {
                            geocoder.geocode(req, function(results, status) {
                                if (status !== google.maps.GeocoderStatus.OK) {
                                    return;
                                }
                                callback(results);
                            });
                        }

                        function placesGeocode(req, callback) {
                            var autocompleteService = new google.maps.places.AutocompleteService(),
                                googlePlacesService = new google.maps.places.PlacesService(document.getElementsByClassName('fake-hidden-input')[0]);

                            autocompleteService.getPlacePredictions({input: req.address}, function(predictions, status) {
                                if (status !== google.maps.places.PlacesServiceStatus.OK) {
                                    return;
                                }

                                var promises = predictions.map(function(prediction) {
                                    var defer = $q.defer();
                                    googlePlacesService.getDetails({reference: prediction.reference}, function(details) {
                                        defer.resolve(details);
                                    });
                                    return defer.promise;
                                });

                                $q.all(promises).then(function(data) {
                                    callback(data);
                                });

                            });
                        }

                        scope.$watch('searchQuery', function(query){
                            if (searchEventTimeout) $timeout.cancel(searchEventTimeout);
                            searchEventTimeout = $timeout(function() {
                                if(!query) return;
                                if(query.length < 3);

                                var req = scope.geocodeOptions || {};
                                req.address = query;

                                scope.locations = [];

                                if (attrs.geocodeService === 'places-api') {
                                    placesGeocode(req, function(results) {
                                        scope.locations = results.filter(function(result) {
                                            return result;
                                        });
                                    });
                                } else {
                                    mapsGeocode(req, function(results) {
                                        scope.$apply(function() {
                                            scope.locations = results;
                                        });
                                    });
                                }
                            }, 350); // we're throttling the input by 350ms to be nice to google's API
                        });

                        var onClick = function(e){
                            e.preventDefault();
                            e.stopPropagation();

                            $ionicBackdrop.retain();
                            unbindBackButtonAction = $ionicPlatform.registerBackButtonAction(closeOnBackButton, 250);

                            el.element.css('display', 'block');
                            searchInputElement[0].focus();
                            setTimeout(function(){
                                searchInputElement[0].focus();
                            },0);
                        };

                        var onCancel = function(e){
                            $ionicBackdrop.release();
                            el.element.css('display', 'none');

                            scope.searchQuery = '';
                            scope.locations = scope.defaultLocations || [];
                            scope.$$phase || scope.$digest();

                            if (unbindBackButtonAction){
                                unbindBackButtonAction();
                                unbindBackButtonAction = null;
                            }
                        };

                        closeOnBackButton = function(e){
                            e.preventDefault();

                            el.element.css('display', 'none');
                            $ionicBackdrop.release();

                            if (unbindBackButtonAction){
                                unbindBackButtonAction();
                                unbindBackButtonAction = null;
                            }
                        }

                        element.bind('click', onClick);
                        element.bind('touchend', onClick);

                        el.element.find('button').bind('click', onCancel);
                    });

                    if (attrs.placeholder){
                        element.attr('placeholder', attrs.placeholder);
                    }

                    ngModel.$formatters.unshift(function (modelValue) {
                        if (!modelValue) {
                            return '';
                        }
                        return modelValue;
                    });

                    ngModel.$parsers.unshift(function (viewValue) {
                        return viewValue;
                    });

                    ngModel.$render = function(){
                        if (!ngModel.$viewValue) {
                            element.val('');
                        } else {
                            element.val(ngModel.$viewValue.formatted_address || ngModel.$modelValue.formatted_address || '');
                        }
                    };

                    scope.$on('$destroy', function(){
                        if (unbindBackButtonAction){
                            unbindBackButtonAction();
                            unbindBackButtonAction = null;
                        }
                    });
                }
            };
        }
    ]);
<|endoftext|>


<commit_before>var app = angular.module('starter', ['ionic'])

.run(function($ionicPlatform) {
	$ionicPlatform.ready(function() {
		// Hide the accessory bar by default
		if(window.cordova && window.cordova.plugins.Keyboard) {
			cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
		}
		if(window.StatusBar) {
			StatusBar.styleDefault();
		}
	})
})

.controller('MapController', function($scope, $ionicLoading) {
	var map;
	$scope.mapLoaded = true;

	google.maps.event.addDomListener(window, 'load', function() {
		var mapOptions = {
			zoom: 14,
			mapTypeId: google.maps.MapTypeId.ROADMAP
		};

		map = new google.maps.Map(document.getElementById('map'), mapOptions);

		if(navigator.geolocation) {
			navigator.geolocation.getCurrentPosition(function(pos) {
				map.setCenter(new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude));
				var myLocation = new google.maps.Marker({
					position: new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude),
					map: map,
					title: 'You Are Here!'
				})
			}, function(err) {
				handleNoGeolocation(true);
			});
		} else {
			handleNoGeolocation(true);
		};

		var input = (document.getElementById('pac-input'));
		// Positions the search box to the top left corner
		map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
		var searchBox = new google.maps.places.SearchBox(input);
	})

	function handleNoGeolocation(errorFlag) {
	  if (errorFlag) {
	    var content = 'Error: The Geolocation service failed.';
	  } else {
	    var content = 'Error: Your browser doesn\'t support geolocation.';
	  }

	  var options = {
	    map: map,
	    position: new google.maps.LatLng(60, 105),
	    content: content
	  };

	  var infowindow = new google.maps.InfoWindow(options);
	  map.setCenter(options.position);

	}

//////////// SEARCH BOX //////////////////
  var markers = [];
  // Create the search box and link it to the UI element.
  //Location in the HTML to where the search box will be placed
  var input = (document.getElementById('pac-input'));
  //Positions the search box to the top left corner
  map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
  var searchBox = new google.maps.places.SearchBox(input);

  //Listens to changes when user submits in a location
  google.maps.event.addListener(searchBox, 'places_changed', function() {
    //Returns the query selected by the user, or null if no places have been found
    var places = searchBox.getPlaces();

    if (places.length == 0) {
      return;
    }
    for (var i = 0, marker; marker = markers[i]; i++) {
      marker.setMap(null);
    }

    markers = [];
    // For each place, get the icon, place name, and location.
    var bounds = new google.maps.LatLngBounds();
    for (var i = 0, place; place = places[i]; i++) {
      var image = {
        url: place.icon,
        size: new google.maps.Size(71, 71),
        origin: new google.maps.Point(0, 0),
        anchor: new google.maps.Point(17, 34),
        scaledSize: new google.maps.Size(25, 25)
      };
      console.log("INFO: ", image);

      // Create a marker for each place
      var marker = new google.maps.Marker({
        map: map,
        icon: image,
        title: place.name,
        position: place.geometry.location
      });

      markers.push(marker);

      bounds.extend(place.geometry.location);
    }

    map.fitBounds(bounds);
  });

	$scope.map = map;

})<commit_msg>fixes search bar function<commit_after>var app = angular.module('starter', ['ionic'])

.run(function($ionicPlatform) {
	$ionicPlatform.ready(function() {
		// Hide the accessory bar by default
		if(window.cordova && window.cordova.plugins.Keyboard) {
			cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
		}
		if(window.StatusBar) {
			StatusBar.styleDefault();
		}
	})
})

.controller('MapController', function($scope, $ionicLoading) {
	var map;
	$scope.mapLoaded = true;

	google.maps.event.addDomListener(window, 'load', function() {
		var mapOptions = {
			zoom: 14,
			mapTypeId: google.maps.MapTypeId.ROADMAP
		};

		map = new google.maps.Map(document.getElementById('map'), mapOptions);

		if(navigator.geolocation) {
			navigator.geolocation.getCurrentPosition(function(pos) {
				map.setCenter(new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude));
				var myLocation = new google.maps.Marker({
					position: new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude),
					map: map,
					title: 'You Are Here!'
				})
			}, function(err) {
				handleNoGeolocation(true);
			});
		} else {
			handleNoGeolocation(true);
		};

		var input = (document.getElementById('pac-input'));
		// Positions the search box to the top left corner
		map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
		var searchBox = new google.maps.places.SearchBox(input);
	})

	function handleNoGeolocation(errorFlag) {
	  if (errorFlag) {
	    var content = 'Error: The Geolocation service failed.';
	  } else {
	    var content = 'Error: Your browser doesn\'t support geolocation.';
	  }

	  var options = {
	    map: map,
	    position: new google.maps.LatLng(60, 105),
	    content: content
	  };

	  var infowindow = new google.maps.InfoWindow(options);
	  map.setCenter(options.position);

	}

//////////// SEARCH BOX //////////////////
  var markers = [];
  // Create the search box and link it to the UI element.
  //Location in the HTML to where the search box will be placed
  //Listens to changes when user submits in a location
  google.maps.event.addListener(searchBox, 'places_changed', function() {
    //Returns the query selected by the user, or null if no places have been found
    var places = searchBox.getPlaces();

    if (places.length == 0) {
      return;
    }
    for (var i = 0, marker; marker = markers[i]; i++) {
      marker.setMap(null);
    }

    markers = [];
    // For each place, get the icon, place name, and location.
    var bounds = new google.maps.LatLngBounds();
    for (var i = 0, place; place = places[i]; i++) {
      var image = {
        url: place.icon,
        size: new google.maps.Size(71, 71),
        origin: new google.maps.Point(0, 0),
        anchor: new google.maps.Point(17, 34),
        scaledSize: new google.maps.Size(25, 25)
      };
      console.log("INFO: ", image);

      // Create a marker for each place
      var marker = new google.maps.Marker({
        map: map,
        icon: image,
        title: place.name,
        position: place.geometry.location
      });

      markers.push(marker);

      bounds.extend(place.geometry.location);
    }

    map.fitBounds(bounds);
  });

	$scope.map = map;

})<|endoftext|>


<commit_before>/***********************************************************************
 * This class copies bower dependencies to the specified destination
 * Author: Copyright 2012-2014, Tyler Beck
 * License: MIT
 ***********************************************************************/

/**
 * Bower Copy Class
 * @param grunt
 * @param bowerPath
 * @param libPath
 * @param shim
 * @param map
 * @param useCommonPath
 * @param done
 * @returns {BowerCopy}
 * @constructor
 */
module.exports = function BowerCopy( grunt, bowerPath, libPath, shim, map, useCommonPath, done ) {
	/*================================================
	 * Dependencies
	 *===============================================*/
	var bower = require( 'bower' );
	var path = require( 'path' );
	var glob = require( 'glob' );

	/*================================================
	 * Public Attributes
	 *===============================================*/

	/*================================================
	 * Private Attributes
	 *===============================================*/
	var expandedMap = {};

	/*================================================
	 * Public Methods
	 *===============================================*/
	this.execute = function() {

		grunt.verbose.writeln( 'BowerCopy::execute' );

		//first make sure everything needed is set
		if ( validateParameters() ) {

			//expand map
			expandedMap = expandMap( map );

			//get component paths
			bower.commands.list( {paths: true} )
					.on( 'end', handleListResults );
		}

	};

	/*================================================
	 * Private Methods
	 *===============================================*/
	/**
	 * validates bower copy task parameters
	 * @returns {boolean}
	 */
	function validateParameters() {
		grunt.verbose.writeln( 'BowerCopy::validateParameters' );
		//flag used to determine if validation passes
		var check = true;

		//lib path must be set
		bowerPath = bowerPath || "bower_components";
		grunt.verbose.writeln( 'bowerPath: ' + bowerPath );
		if ( typeof bowerPath != 'string' ) {
			grunt.log.error( 'Bower path must be a string.' );
			check = false;
		}

		//lib path must be set
		grunt.verbose.writeln( 'libPath: ' + libPath );
		if ( !libPath || typeof libPath != 'string' ) {
			grunt.log.error( 'Default destination path must be configured.' );
			check = false;
		}

		//shim isn't required, but must be a key value object
		shim = shim || {};
		grunt.verbose.writeln( 'shim:' );
		grunt.verbose.writeln( JSON.stringify( shim, undefined, "  " ) );
		if ( typeof shim != "object" ) {
			grunt.log.error( 'shim must be an object.' );
			check = false;
		}

		//map isn't required, but must be a key value object
		map = map || {};
		grunt.verbose.writeln( 'map:' );
		grunt.verbose.writeln( JSON.stringify( map, undefined, "  " ) );
		if ( typeof map != "object" ) {
			grunt.log.error( 'map must be an object.' );
			check = false;
		}

		//useCommonPath isn't required, but must be boolean
		useCommonPath = useCommonPath || false;
		grunt.verbose.writeln( 'useCommonPath: ' + useCommonPath );
		if ( typeof useCommonPath != "boolean" ) {
			grunt.log.error( 'map must be a boolean value.' );
			check = false;
		}

		return check;
	}

	/**
	 * pre-flattens and normalizes map values for easier utilization
	 * @param map
	 * @returns {{}}
	 */
		//TODO: add full globbing for from and to mappings
	function expandMap( map ) {
		grunt.verbose.writeln( 'BowerCopy::expandMap' );
		var expanded = {};

		for ( var k in map ) {
			if ( map.hasOwnProperty( k ) ) {
				var value = map[ k ];

				//get full source path
				var src = path.normalize( path.join( bowerPath, k ) );
				var exists = grunt.file.exists( src );

				//continue if the source exists
				if ( exists ) {
					if ( grunt.file.isFile( src ) ) {
						//src is a file, it must be mapped to a string value
						if ( typeof value == "string" ) {
							expanded[ src ] = path.normalize( path.join( libPath, value ) );
						}
						else {
							grunt.log.error( 'Invalid mapped value for: ' + src );
						}
					}
					else {
						//src is a component directory,
						//   value can be an object containing specific file mappings
						//      or
						//   value can be a string path

						var fsrc, fpath;
						if ( typeof value == "object" ) {
							//map specific files
							for ( var f in value ) {
								if ( value.hasOwnProperty( f ) ) {
									//get file source
									fsrc = path.join( src, f );
									//get file destination path
									fpath = path.normalize( path.join( libPath, value[ f ] ) );
									if ( grunt.file.isFile( fsrc ) ) {
										//source exists, continue with mapping
										expanded[ fsrc ] = fpath;
									}
									else if ( grunt.file.isDir( fsrc ) ) {
										grunt.verbose.writeln( '   map directory: ' + fsrc + '/**' );
										glob.sync( fsrc + '/**', { dot: true } ).forEach( function( filename ) {
											if ( grunt.file.isFile( filename ) ) {
												fpath = path.normalize( path.join( libPath, value[ f ], filename.replace( fsrc, "" ) ) );
												grunt.verbose.writeln( '     from:' + filename );
												grunt.verbose.writeln( '       to:' + fpath );
												expanded[ filename ] = fpath;
											}
										} );

									}
									else {
										grunt.log.error( 'Could not locate source path: ' + fsrc );
									}
								}
							}

						}
						else if ( typeof value == "string" ) {
							//map entire directory
							grunt.verbose.writeln( '   map directory: ' + src + '/**' );
							glob.sync( src + '/**', { dot: true } ).forEach( function( filename ) {
								if ( grunt.file.isFile( filename ) ) {
									fpath = path.normalize( path.join( libPath, value, filename.replace( src, "" ) ) );
									grunt.verbose.writeln( '     from:' + filename );
									grunt.verbose.writeln( '       to:' + fpath );

									expanded[ filename ] = fpath;
								}
							} );
						}
						else {
							grunt.log.error( 'Invalid mapped value for: ' + src );
						}
					}
				}
				else {
					grunt.log.error( 'Could not locate source path: ' + src );
				}
			}
		}

		return expanded;
	}

	/**
	 * bower list results handler
	 * @param results
	 */
	function handleListResults( results ) {
		grunt.verbose.writeln( 'BowerCopy::handleListResults' );

		for ( var k in results ) {
			if ( results.hasOwnProperty( k ) ) {
				grunt.verbose.writeln( '------------------------------------' );
				grunt.verbose.writeln( '    ' + k + ' - ' + results[k] );
				copyComponentFiles( k, results[k] );
			}
		}
		done();
	}

	/**
	 * copies specified component files based on mapping
	 * @param name
	 * @param files
	 * @returns {boolean}
	 */
	function copyComponentFiles( name, files ) {
		grunt.verbose.writeln( 'BowerCopy::copyComponentFiles - ' + name );
		//get map of files to copy
		var componentMap = getComponentMapping( name, files );

		//copy files

		for ( var k in componentMap ) {
			if ( componentMap.hasOwnProperty( k ) ) {
				grunt.file.copy( k, componentMap[ k ] );
			}
		}

	}

	/**
	 * gets file mapping for specified component
	 * @param name
	 * @param files
	 * @returns {{}}
	 */
	function getComponentMapping( name, files ) {
		grunt.verbose.writeln( 'BowerCopy::getComponentMapping - ' + name );

		//first get list of files
		var fileList = [];
		if ( shim[ name ] != undefined ) {
			grunt.verbose.writeln( '    using shim value' );
			//use shim value
			fileList = fileList.concat( shim[ name ] );
		}
		else {
			grunt.verbose.writeln( '    using result value - ' + files );
			//use value
			fileList = fileList.concat( files );
		}

		grunt.verbose.writeln( '   files:' );
		fileList.forEach( function( filename ) {
			grunt.verbose.writeln( '      ' + filename );
		} );

		//get common path for building destination
		var commonPath = getCommonPathBase( fileList );

		//build default mapping
		var componentMap = {};
		for ( var i = 0, l = fileList.length; i < l; i++ ) {
			//need to iterate over glob style matches
			glob.sync( path.join( name, fileList[i] ), { cwd: bowerPath, dot: true } ).forEach( function( filename ) {
				var src = path.normalize( path.join( bowerPath, filename ) );
				if ( grunt.file.isFile( src ) ) {
					if ( expandedMap[ src ] != undefined ) {
						//use user configured mapping if set
						componentMap[ src ] = expandedMap[ src ];
					}
					else {
						var newFilename = commonPath == "" ? filename : filename.replace( commonPath, "" );
						componentMap[ src ] = path.normalize( path.join( libPath, newFilename ) );
					}
				}
			} );
		}

		return componentMap;

	}

	/**
	 * determines a shared base path among paths
	 * @param paths {Array} of strings
	 * @returns {String}
	 */
	function getCommonPathBase( paths ) {

		var list = [];
		var minLength = 999999999;

		var commonPath = "";

		//break up paths into parts
		paths.forEach( function( file ) {

			//get resolved path parts for file directory
			var parts = path.dirname( path.resolve( file ) ).split( path.sep );
			list.push( parts );

			//save minimum path length for next step
			minLength = Math.min( minLength, parts.length );

		} );

		var listLength = list.length;

		//check for common parts
		if ( listLength > 1 ) {
			var common = true;
			var index = 0;
			for ( var i = 0; i < minLength; i++ ) {
				for ( var j = 1; j < listLength; j++ ) {
					if ( list[ j ][ i ] != list[ j - 1 ][ i ] ) {
						common = false;
						break;
					}
				}
				if ( !common ) {
					index = i;
					break;
				}
			}

			//build new paths array
			//for ( var n=0; n<listLength; n++ ) {
			//	newPaths.push( path.join( list[n].slice( index ) ) );
			//}

			commonPath = list[0].slice( 0, index ).join( path.sep );

		}
		else if ( listLength == 1 ) {
			commonPath = path.basename( list[0] );
		}

		return commonPath;

	}

	return this;

};
<commit_msg>updated verbose logging to check globbed path<commit_after>/***********************************************************************
 * This class copies bower dependencies to the specified destination
 * Author: Copyright 2012-2014, Tyler Beck
 * License: MIT
 ***********************************************************************/

/**
 * Bower Copy Class
 * @param grunt
 * @param bowerPath
 * @param libPath
 * @param shim
 * @param map
 * @param useCommonPath
 * @param done
 * @returns {BowerCopy}
 * @constructor
 */
module.exports = function BowerCopy( grunt, bowerPath, libPath, shim, map, useCommonPath, done ) {
	/*================================================
	 * Dependencies
	 *===============================================*/
	var bower = require( 'bower' );
	var path = require( 'path' );
	var glob = require( 'glob' );

	/*================================================
	 * Public Attributes
	 *===============================================*/

	/*================================================
	 * Private Attributes
	 *===============================================*/
	var expandedMap = {};

	/*================================================
	 * Public Methods
	 *===============================================*/
	this.execute = function() {

		grunt.verbose.writeln( 'BowerCopy::execute' );

		//first make sure everything needed is set
		if ( validateParameters() ) {

			//expand map
			expandedMap = expandMap( map );

			//get component paths
			bower.commands.list( {paths: true} )
					.on( 'end', handleListResults );
		}

	};

	/*================================================
	 * Private Methods
	 *===============================================*/
	/**
	 * validates bower copy task parameters
	 * @returns {boolean}
	 */
	function validateParameters() {
		grunt.verbose.writeln( 'BowerCopy::validateParameters' );
		//flag used to determine if validation passes
		var check = true;

		//lib path must be set
		bowerPath = bowerPath || "bower_components";
		grunt.verbose.writeln( 'bowerPath: ' + bowerPath );
		if ( typeof bowerPath != 'string' ) {
			grunt.log.error( 'Bower path must be a string.' );
			check = false;
		}

		//lib path must be set
		grunt.verbose.writeln( 'libPath: ' + libPath );
		if ( !libPath || typeof libPath != 'string' ) {
			grunt.log.error( 'Default destination path must be configured.' );
			check = false;
		}

		//shim isn't required, but must be a key value object
		shim = shim || {};
		grunt.verbose.writeln( 'shim:' );
		grunt.verbose.writeln( JSON.stringify( shim, undefined, "  " ) );
		if ( typeof shim != "object" ) {
			grunt.log.error( 'shim must be an object.' );
			check = false;
		}

		//map isn't required, but must be a key value object
		map = map || {};
		grunt.verbose.writeln( 'map:' );
		grunt.verbose.writeln( JSON.stringify( map, undefined, "  " ) );
		if ( typeof map != "object" ) {
			grunt.log.error( 'map must be an object.' );
			check = false;
		}

		//useCommonPath isn't required, but must be boolean
		useCommonPath = useCommonPath || false;
		grunt.verbose.writeln( 'useCommonPath: ' + useCommonPath );
		if ( typeof useCommonPath != "boolean" ) {
			grunt.log.error( 'map must be a boolean value.' );
			check = false;
		}

		return check;
	}

	/**
	 * pre-flattens and normalizes map values for easier utilization
	 * @param map
	 * @returns {{}}
	 */
		//TODO: add full globbing for from and to mappings
	function expandMap( map ) {
		grunt.verbose.writeln( 'BowerCopy::expandMap' );
		var expanded = {};

		for ( var k in map ) {
			if ( map.hasOwnProperty( k ) ) {
				var value = map[ k ];

				//get full source path
				var src = path.normalize( path.join( bowerPath, k ) );
				var exists = grunt.file.exists( src );

				//continue if the source exists
				if ( exists ) {
					if ( grunt.file.isFile( src ) ) {
						//src is a file, it must be mapped to a string value
						if ( typeof value == "string" ) {
							expanded[ src ] = path.normalize( path.join( libPath, value ) );
						}
						else {
							grunt.log.error( 'Invalid mapped value for: ' + src );
						}
					}
					else {
						//src is a component directory,
						//   value can be an object containing specific file mappings
						//      or
						//   value can be a string path

						var fsrc, fpath;
						if ( typeof value == "object" ) {
							//map specific files
							for ( var f in value ) {
								if ( value.hasOwnProperty( f ) ) {
									//get file source
									fsrc = path.join( src, f );
									//get file destination path
									fpath = path.normalize( path.join( libPath, value[ f ] ) );
									if ( grunt.file.isFile( fsrc ) ) {
										//source exists, continue with mapping
										expanded[ fsrc ] = fpath;
									}
									else if ( grunt.file.isDir( fsrc ) ) {
										grunt.verbose.writeln( '   map directory: ' + fsrc + '/**' );
										glob.sync( fsrc + '/**', { dot: true } ).forEach( function( filename ) {
											if ( grunt.file.isFile( filename ) ) {
												fpath = path.normalize( path.join( libPath, value[ f ], filename.replace( fsrc, "" ) ) );
												grunt.verbose.writeln( '     from:' + filename );
												grunt.verbose.writeln( '       to:' + fpath );
												expanded[ filename ] = fpath;
											}
										} );

									}
									else {
										grunt.log.error( 'Could not locate source path: ' + fsrc );
									}
								}
							}

						}
						else if ( typeof value == "string" ) {
							//map entire directory
							grunt.verbose.writeln( '   map directory: ' + src + '/**' );
							glob.sync( src + '/**', { dot: true } ).forEach( function( filename ) {
								if ( grunt.file.isFile( filename ) ) {
									fpath = path.normalize( path.join( libPath, value, filename.replace( src, "" ) ) );
									grunt.verbose.writeln( '     from:' + filename );
									grunt.verbose.writeln( '       to:' + fpath );

									expanded[ filename ] = fpath;
								}
							} );
						}
						else {
							grunt.log.error( 'Invalid mapped value for: ' + src );
						}
					}
				}
				else {
					grunt.log.error( 'Could not locate source path: ' + src );
				}
			}
		}

		return expanded;
	}

	/**
	 * bower list results handler
	 * @param results
	 */
	function handleListResults( results ) {
		grunt.verbose.writeln( 'BowerCopy::handleListResults' );

		for ( var k in results ) {
			if ( results.hasOwnProperty( k ) ) {
				grunt.verbose.writeln( '------------------------------------' );
				grunt.verbose.writeln( '    ' + k + ' - ' + results[k] );
				copyComponentFiles( k, results[k] );
			}
		}
		done();
	}

	/**
	 * copies specified component files based on mapping
	 * @param name
	 * @param files
	 * @returns {boolean}
	 */
	function copyComponentFiles( name, files ) {
		grunt.verbose.writeln( 'BowerCopy::copyComponentFiles - ' + name );
		//get map of files to copy
		var componentMap = getComponentMapping( name, files );

		//copy files
		for ( var k in componentMap ) {
			if ( componentMap.hasOwnProperty( k ) ) {
				grunt.file.copy( k, componentMap[ k ] );
			}
		}

	}

	/**
	 * gets file mapping for specified component
	 * @param name
	 * @param files
	 * @returns {{}}
	 */
	function getComponentMapping( name, files ) {
		grunt.verbose.writeln( 'BowerCopy::getComponentMapping - ' + name );

		//first get list of files
		var fileList = [];
		if ( shim[ name ] != undefined ) {
			grunt.verbose.writeln( '    using shim value' );
			//use shim value
			fileList = fileList.concat( shim[ name ] );
		}
		else {
			grunt.verbose.writeln( '    using result value - ' + files );
			//use value
			fileList = fileList.concat( files );
		}

		//get common path for building destination
		var commonPath = getCommonPathBase( fileList );

		//build default mapping
		var componentMap = {};
		grunt.verbose.writeln( '   mapping files:' );
		for ( var i = 0, l = fileList.length; i < l; i++ ) {
			//need to iterate over glob style matches
			glob.sync( path.join( name, fileList[i] ), { cwd: bowerPath, dot: true } ).forEach( function( filename ) {
				var src = path.normalize( path.join( bowerPath, filename ) );
				grunt.verbose.writeln( '      ' + src );
				if ( grunt.file.isFile( src ) ) {
					if ( expandedMap[ src ] != undefined ) {
						//use user configured mapping if set
						componentMap[ src ] = expandedMap[ src ];
					}
					else {
						var newFilename = commonPath == "" ? filename : filename.replace( commonPath, "" );
						componentMap[ src ] = path.normalize( path.join( libPath, newFilename ) );
					}
				}
			} );
		}

		return componentMap;

	}

	/**
	 * determines a shared base path among paths
	 * @param paths {Array} of strings
	 * @returns {String}
	 */
	function getCommonPathBase( paths ) {

		var list = [];
		var minLength = 999999999;

		var commonPath = "";

		//break up paths into parts
		paths.forEach( function( file ) {

			//get resolved path parts for file directory
			var parts = path.dirname( path.resolve( file ) ).split( path.sep );
			list.push( parts );

			//save minimum path length for next step
			minLength = Math.min( minLength, parts.length );

		} );

		var listLength = list.length;

		//check for common parts
		if ( listLength > 1 ) {
			var common = true;
			var index = 0;
			for ( var i = 0; i < minLength; i++ ) {
				for ( var j = 1; j < listLength; j++ ) {
					if ( list[ j ][ i ] != list[ j - 1 ][ i ] ) {
						common = false;
						break;
					}
				}
				if ( !common ) {
					index = i;
					break;
				}
			}

			//build new paths array
			//for ( var n=0; n<listLength; n++ ) {
			//	newPaths.push( path.join( list[n].slice( index ) ) );
			//}

			commonPath = list[0].slice( 0, index ).join( path.sep );

		}
		else if ( listLength == 1 ) {
			commonPath = path.basename( list[0] );
		}

		return commonPath;

	}

	return this;

};
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Stack Overflow Extras (SOX)
// @namespace    https://github.com/soscripted/sox
// @version      DEV
// @description  Adds a bunch of optional features to sites in the Stack Exchange Network.
// @contributor  ᴉʞuǝ (stackoverflow.com/users/1454538/)
// @contributor  ᔕᖺᘎᕊ (stackexchange.com/users/4337810/)
// @updateURL    https://rawgit.com/soscripted/sox/master/sox.user.js
// @match        *://*.stackoverflow.com/*
// @match        *://*.stackexchange.com/*
// @match        *://*.superuser.com/*
// @match        *://*.serverfault.com/*
// @match        *://*.askubuntu.com/*
// @match        *://*.stackapps.com/*
// @match        *://*.mathoverflow.net/*
// @require      https://code.jquery.com/jquery-2.1.4.min.js
// @require      https://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js
// @require      https://cdn.rawgit.com/timdown/rangyinputs/master/rangyinputs-jquery-src.js
// @require      https://cdn.rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js
// @require      https://cdn.rawgit.com/camagu/jquery-feeds/master/jquery.feeds.js
// @require      https://rawgit.com/soscripted/sox/master/sox.helpers.js?v=02092016a
// @require      https://rawgit.com/soscripted/sox/master/sox.features.js?v=02092016a
// @resource     settingsDialog https://rawgit.com/soscripted/sox/master/sox-dialog.html
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_getResourceText
// ==/UserScript==
/*jshint multistr: true */
(function(sox, $, undefined) {
    const SOX = "Stack Overflow Extras";
    const SOX_SETTINGS = "SOXSETTINGS";

    var $settingsDialog = $(GM_getResourceText("settingsDialog")),
        $soxSettingsDialog,
        $soxSettingsDialogFeatures,
        $soxSettingsSave,
        $soxSettingsReset,
        $soxSettingsClose;

    sox.init = function() {

        // add sox CSS file and font-awesome CSS file
        $("head").append("<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css'>")
                 .append("<link rel='stylesheet' type='text/css' href='https://rawgit.com/soscripted/sox/master/sox.css' />");
        $("body").append($settingsDialog);

        $soxSettingsDialog = $("#sox-settings-dialog");
        $soxSettingsDialogFeatures = $soxSettingsDialog.find("#sox-settings-dialog-features");
        $soxSettingsSave = $soxSettingsDialog.find("#sox-settings-dialog-save");
        $soxSettingsReset = $soxSettingsDialog.find("#sox-settings-dialog-reset");
        $soxSettingsClose = $soxSettingsDialog.find("#sox-settings-dialog-close");

        loadFeatures(); //load all the features in the settings dialog

        // add settings icon to navbar
        var $soxSettingsButton = $("<a/>", {
                id: "soxSettingsButton",
                class: "topbar-icon",
                style: "background-image: none; padding: 10px 0 0 10px; font-size: 14px; color: #999;",
                title: "Change SOX Settings",
                click: function(e) {
                    e.preventDefault();
                    $("#sox-settings-dialog").toggle();
                }
            }),
            $icon = $("<i/>", {
                class: "fa fa-cogs"
            });
        $soxSettingsButton.append($icon).appendTo("div.network-items");

        // add click handlers for the buttons in the settings dialog
        $soxSettingsClose.on("click", function() {
            $soxSettingsDialog.hide();
        });
        $soxSettingsReset.on("click", function() {
            reset();
            location.reload(); // reload page to reflect changed settings
        });
        $soxSettingsSave.on("click", function() {
            extras = [];
            $soxSettingsDialogFeatures.find('input[type=checkbox]:checked').each(function() {
                var x = $(this).attr('id');
                extras.push(x); //Add the function's ID (also the checkbox's ID) to the array
            });
            save(extras);
            location.reload(); // reload page to reflect changed settings
        });

        // check if settings exist and execute desired functions
        if (isAvailable()) {
            extras = getSettings();
            if (isDeprecated()) { //if the setting is set but a deprecated, non-existent feature exists, then delete the setting and act as if it is new
                reset();
            } else {
                for (i = 0; i < extras.length; ++i) {
                    $soxSettingsDialogFeatures.find("#" + extras[i]).prop('checked', true);
                    features[extras[i]](); //Call the functions that were chosen
                }
            }
        } else {
            // no settings found, mark all inputs as checked and display settings dialog
            $soxSettingsDialogFeatures.find("input").prop("checked", true);

            setTimeout(function(){
                $soxSettingsDialog.show();
            }, 500);

            //$soxSettingsDialog.delay(800).show(); //not working -- why!?
        }

    };

    function isAvailable() {
        //return ~GM_getValue("sox-featureOptions", -1) ? false : true;
        return (GM_getValue(SOX_SETTINGS, -1) == -1 ? false : true);
    }

    function getSettings() {
        return JSON.parse(GM_getValue(SOX_SETTINGS));
    }

    function reset() {
        GM_deleteValue(SOX_SETTINGS);
    }

    function save(options) {
        GM_setValue(SOX_SETTINGS, JSON.stringify(options));
        console.log("sox settings saved: " + JSON.stringify(options));
    }

    function isDeprecated() { //checks whether the saved settings contain a deprecated feature
        //TODO: add function names to an array and loop instead of || .. || .. ||
        settings = getSettings();
        var deprecatedFeatures = ['answerCountSidebar', 'highlightClosedQuestions', 'unHideAnswer', 'flaggingPercentages'];
        for (i=0; i<deprecatedFeatures; i++) {
            if (settings.indexOf(deprecatedFeatures[i]) != -1) {
                return true;
            }
        }
    }

    function addFeatures(features) {
        $.each(features, function(i, o) {
            var $div = $("<div/>"),
                $label = $("<label/>"),
                $input = $("<input/>", {
                    id: o[0],
                    "type": "checkbox",
                    style: "margin-right: 5px;"
                });
            $div.append($label);
            $label.append($input);
            $input.after(o[1]);
            $soxSettingsDialogFeatures.append($div);
        });
    }

    function addCategory(name) {
        var $div = $("<div/>"),
            $h3 = $("<h3/>", {
                text: name
            });
        $div.append($h3);
        $soxSettingsDialogFeatures.append($div);
    }

    function loadFeatures() {
        addCategory("Appearance");
        addFeatures([
            ["grayOutVotes", "Gray out deleted votes"],
            ["moveBounty", "Move 'start bounty' to top"],
            ["dragBounty", "Make bounty box draggable"],
            ["renameChat", "Prepend 'Chat - ' to chat tabs' titles"],
            ["exclaim", "Remove exclamation mark on message"],
            ["employeeStar", "Add star after employee names"],
            ["bulletReplace", "Replace triangluar bullets with normal ones"],
            ["addEllipsis", "Add ellipsis to long names"],
            ["fixedTopbar", "Fix topbar position"],
            ["displayName", "Display name before gravatar"],
            ["unspoil", "Add a link to show all spoilers in a post"],
            ["spoilerTip", "Differentiate spoilers from empty blockquotes"],
            ["stickyVoteButtons", "Make vote buttons next to posts sticky whilst scrolling on that post"],
            ["betterCSS", "Add extra CSS for voting signs and favourite button (currently only on Android SE)"],
            ["standOutDupeCloseMigrated", "Add colourful, more apparent signs to questions that are on hold, duplicates, closed or migrated on question lists"],
            ["colorAnswerer", "Color answerer's comments"],
            ["highlightQuestions", "Alternate favourite questions highlighing"],
            ["metaNewQuestionAlert", "Add an extra mod diamond to the topbar that alerts you if there is a new question posted on the child-meta of the current site"],
            ["hideHotNetworkQuestions", "Hide the Hot Network Questions module"],
            ["hideHireMe", "Hide the Looking for a Job module"],
            ["hideCommunityBulletin", "Hide the Community Bulletin module"],
            ["hideSearchBar", "Replaces the searchbar with an link to the search page"]
        ]);

        addCategory("Flags");
        addFeatures([
            ["flagOutcomeTime", "Show the flag outcome time when viewing your Flag History"],
            ["flagPercentages", "Show flagging percentages for each type in the Flag Summary"]
            // lots more to come
        ]);

        addCategory("Editing");
        addFeatures([
            ["kbdAndBullets", "Add KBD and list buttons to editor toolbar"],
            ["editComment", "Pre-defined edit comment options"],
            ["editReasonTooltip", "Add a tooltip to posts showing the latest revision's comment on 'edited [date] at [time]'"],
            ["addSBSBtn", "Add a button the the editor toolbar to start side-by-side editing"],
            ["linkQuestionAuthorName", "Add a button in the editor toolbar to insert a hyperlink to a post and add the author automatically"],
            ["titleEditDiff", "Make title edits show seperately rather than merged"]
        ]);
        addCategory("Comments");

        addFeatures([
            ["moveCommentsLink", "Move 'show x more comments' to the top"],
            ["commentShortcuts", "Use Ctrl+I,B,K (to italicise, bolden and add code backticks) in comments"],
            ["quickCommentShortcutsMain", "Add shortcuts to add pre-defined comments to comment fields"],
            ["commentReplies", "Add reply links to comments for quick replying (without having to type someone's username)"],
            ["autoShowCommentImages", "View linked images (to imgur) in comments inline"],
            ["showCommentScores", "Show your comment and comment replies scores in your profile tabs"]
        ]);

        addCategory("Unsorted");
        addFeatures([
            //other
            ["shareLinksMarkdown", "Change 'share' link to format of [post-name](url)"],
            ["parseCrossSiteLinks", "Parse titles to links cross-SE-sites"],
            ["confirmNavigateAway", "Add a confirmation dialog before navigating away on pages whilst you are still typing a comment"],
            ["sortByBountyAmount", "Add an option to filter bounties by their amount"],
            ["isQuestionHot", "Add a label on questions which are hot-network questions"],
            ["answerTagsSearch", "Show tags for the question an answer belongs to on search pages (for better context)"],
            ["metaChatBlogStackExchangeButton", "Show meta, chat and blog buttons on hover of a site under the StackExchange button"],
            ["alwaysShowImageUploadLinkBox", "Always show the 'Link from the web' box when uploading an image"],
            ["addAuthorNameToInboxNotifications", "Add the author's name to notifications in the inbox"],
            ["scrollToTop", "Add Scroll To Top button"],
            ["linkedPostsInline", "Display linked posts inline (with an arrow)"]
        ]);
    }

}(window.sox = window.sox || {}, jQuery));

// initialize sox
sox.init(); //TODO: not sure if we really need this to be a function, script is wrapped in IIFE so init code could just go at the top.
<commit_msg>exclude sede<commit_after>// ==UserScript==
// @name         Stack Overflow Extras (SOX)
// @namespace    https://github.com/soscripted/sox
// @version      DEV
// @description  Adds a bunch of optional features to sites in the Stack Exchange Network.
// @contributor  ᴉʞuǝ (stackoverflow.com/users/1454538/)
// @contributor  ᔕᖺᘎᕊ (stackexchange.com/users/4337810/)
// @updateURL    https://rawgit.com/soscripted/sox/master/sox.user.js
// @match        *://*.stackoverflow.com/*
// @match        *://*.stackexchange.com/*
// @match        *://*.superuser.com/*
// @match        *://*.serverfault.com/*
// @match        *://*.askubuntu.com/*
// @match        *://*.stackapps.com/*
// @match        *://*.mathoverflow.net/*
// @exclude      *://data.stackexchange.com/*
// @require      https://code.jquery.com/jquery-2.1.4.min.js
// @require      https://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js
// @require      https://cdn.rawgit.com/timdown/rangyinputs/master/rangyinputs-jquery-src.js
// @require      https://cdn.rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js
// @require      https://cdn.rawgit.com/camagu/jquery-feeds/master/jquery.feeds.js
// @require      https://rawgit.com/soscripted/sox/master/sox.helpers.js?v=02092016a
// @require      https://rawgit.com/soscripted/sox/master/sox.features.js?v=02092016a
// @resource     settingsDialog https://rawgit.com/soscripted/sox/master/sox-dialog.html
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_getResourceText
// ==/UserScript==
/*jshint multistr: true */
(function(sox, $, undefined) {
    const SOX = "Stack Overflow Extras";
    const SOX_SETTINGS = "SOXSETTINGS";

    var $settingsDialog = $(GM_getResourceText("settingsDialog")),
        $soxSettingsDialog,
        $soxSettingsDialogFeatures,
        $soxSettingsSave,
        $soxSettingsReset,
        $soxSettingsClose;

    sox.init = function() {

        // add sox CSS file and font-awesome CSS file
        $("head").append("<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css'>")
                 .append("<link rel='stylesheet' type='text/css' href='https://rawgit.com/soscripted/sox/master/sox.css' />");
        $("body").append($settingsDialog);

        $soxSettingsDialog = $("#sox-settings-dialog");
        $soxSettingsDialogFeatures = $soxSettingsDialog.find("#sox-settings-dialog-features");
        $soxSettingsSave = $soxSettingsDialog.find("#sox-settings-dialog-save");
        $soxSettingsReset = $soxSettingsDialog.find("#sox-settings-dialog-reset");
        $soxSettingsClose = $soxSettingsDialog.find("#sox-settings-dialog-close");

        loadFeatures(); //load all the features in the settings dialog

        // add settings icon to navbar
        var $soxSettingsButton = $("<a/>", {
                id: "soxSettingsButton",
                class: "topbar-icon",
                style: "background-image: none; padding: 10px 0 0 10px; font-size: 14px; color: #999;",
                title: "Change SOX Settings",
                click: function(e) {
                    e.preventDefault();
                    $("#sox-settings-dialog").toggle();
                }
            }),
            $icon = $("<i/>", {
                class: "fa fa-cogs"
            });
        $soxSettingsButton.append($icon).appendTo("div.network-items");

        // add click handlers for the buttons in the settings dialog
        $soxSettingsClose.on("click", function() {
            $soxSettingsDialog.hide();
        });
        $soxSettingsReset.on("click", function() {
            reset();
            location.reload(); // reload page to reflect changed settings
        });
        $soxSettingsSave.on("click", function() {
            extras = [];
            $soxSettingsDialogFeatures.find('input[type=checkbox]:checked').each(function() {
                var x = $(this).attr('id');
                extras.push(x); //Add the function's ID (also the checkbox's ID) to the array
            });
            save(extras);
            location.reload(); // reload page to reflect changed settings
        });

        // check if settings exist and execute desired functions
        if (isAvailable()) {
            extras = getSettings();
            if (isDeprecated()) { //if the setting is set but a deprecated, non-existent feature exists, then delete the setting and act as if it is new
                reset();
            } else {
                for (i = 0; i < extras.length; ++i) {
                    $soxSettingsDialogFeatures.find("#" + extras[i]).prop('checked', true);
                    features[extras[i]](); //Call the functions that were chosen
                }
            }
        } else {
            // no settings found, mark all inputs as checked and display settings dialog
            $soxSettingsDialogFeatures.find("input").prop("checked", true);

            setTimeout(function(){
                $soxSettingsDialog.show();
            }, 500);

            //$soxSettingsDialog.delay(800).show(); //not working -- why!?
        }

    };

    function isAvailable() {
        //return ~GM_getValue("sox-featureOptions", -1) ? false : true;
        return (GM_getValue(SOX_SETTINGS, -1) == -1 ? false : true);
    }

    function getSettings() {
        return JSON.parse(GM_getValue(SOX_SETTINGS));
    }

    function reset() {
        GM_deleteValue(SOX_SETTINGS);
    }

    function save(options) {
        GM_setValue(SOX_SETTINGS, JSON.stringify(options));
        console.log("sox settings saved: " + JSON.stringify(options));
    }

    function isDeprecated() { //checks whether the saved settings contain a deprecated feature
        //TODO: add function names to an array and loop instead of || .. || .. ||
        settings = getSettings();
        var deprecatedFeatures = ['answerCountSidebar', 'highlightClosedQuestions', 'unHideAnswer', 'flaggingPercentages'];
        for (i=0; i<deprecatedFeatures; i++) {
            if (settings.indexOf(deprecatedFeatures[i]) != -1) {
                return true;
            }
        }
    }

    function addFeatures(features) {
        $.each(features, function(i, o) {
            var $div = $("<div/>"),
                $label = $("<label/>"),
                $input = $("<input/>", {
                    id: o[0],
                    "type": "checkbox",
                    style: "margin-right: 5px;"
                });
            $div.append($label);
            $label.append($input);
            $input.after(o[1]);
            $soxSettingsDialogFeatures.append($div);
        });
    }

    function addCategory(name) {
        var $div = $("<div/>"),
            $h3 = $("<h3/>", {
                text: name
            });
        $div.append($h3);
        $soxSettingsDialogFeatures.append($div);
    }

    function loadFeatures() {
        addCategory("Appearance");
        addFeatures([
            ["grayOutVotes", "Gray out deleted votes"],
            ["moveBounty", "Move 'start bounty' to top"],
            ["dragBounty", "Make bounty box draggable"],
            ["renameChat", "Prepend 'Chat - ' to chat tabs' titles"],
            ["exclaim", "Remove exclamation mark on message"],
            ["employeeStar", "Add star after employee names"],
            ["bulletReplace", "Replace triangluar bullets with normal ones"],
            ["addEllipsis", "Add ellipsis to long names"],
            ["fixedTopbar", "Fix topbar position"],
            ["displayName", "Display name before gravatar"],
            ["unspoil", "Add a link to show all spoilers in a post"],
            ["spoilerTip", "Differentiate spoilers from empty blockquotes"],
            ["stickyVoteButtons", "Make vote buttons next to posts sticky whilst scrolling on that post"],
            ["betterCSS", "Add extra CSS for voting signs and favourite button (currently only on Android SE)"],
            ["standOutDupeCloseMigrated", "Add colourful, more apparent signs to questions that are on hold, duplicates, closed or migrated on question lists"],
            ["colorAnswerer", "Color answerer's comments"],
            ["highlightQuestions", "Alternate favourite questions highlighing"],
            ["metaNewQuestionAlert", "Add an extra mod diamond to the topbar that alerts you if there is a new question posted on the child-meta of the current site"],
            ["hideHotNetworkQuestions", "Hide the Hot Network Questions module"],
            ["hideHireMe", "Hide the Looking for a Job module"],
            ["hideCommunityBulletin", "Hide the Community Bulletin module"],
            ["hideSearchBar", "Replaces the searchbar with an link to the search page"]
        ]);

        addCategory("Flags");
        addFeatures([
            ["flagOutcomeTime", "Show the flag outcome time when viewing your Flag History"],
            ["flagPercentages", "Show flagging percentages for each type in the Flag Summary"]
            // lots more to come
        ]);

        addCategory("Editing");
        addFeatures([
            ["kbdAndBullets", "Add KBD and list buttons to editor toolbar"],
            ["editComment", "Pre-defined edit comment options"],
            ["editReasonTooltip", "Add a tooltip to posts showing the latest revision's comment on 'edited [date] at [time]'"],
            ["addSBSBtn", "Add a button the the editor toolbar to start side-by-side editing"],
            ["linkQuestionAuthorName", "Add a button in the editor toolbar to insert a hyperlink to a post and add the author automatically"],
            ["titleEditDiff", "Make title edits show seperately rather than merged"]
        ]);
        addCategory("Comments");

        addFeatures([
            ["moveCommentsLink", "Move 'show x more comments' to the top"],
            ["commentShortcuts", "Use Ctrl+I,B,K (to italicise, bolden and add code backticks) in comments"],
            ["quickCommentShortcutsMain", "Add shortcuts to add pre-defined comments to comment fields"],
            ["commentReplies", "Add reply links to comments for quick replying (without having to type someone's username)"],
            ["autoShowCommentImages", "View linked images (to imgur) in comments inline"],
            ["showCommentScores", "Show your comment and comment replies scores in your profile tabs"]
        ]);

        addCategory("Unsorted");
        addFeatures([
            //other
            ["shareLinksMarkdown", "Change 'share' link to format of [post-name](url)"],
            ["parseCrossSiteLinks", "Parse titles to links cross-SE-sites"],
            ["confirmNavigateAway", "Add a confirmation dialog before navigating away on pages whilst you are still typing a comment"],
            ["sortByBountyAmount", "Add an option to filter bounties by their amount"],
            ["isQuestionHot", "Add a label on questions which are hot-network questions"],
            ["answerTagsSearch", "Show tags for the question an answer belongs to on search pages (for better context)"],
            ["metaChatBlogStackExchangeButton", "Show meta, chat and blog buttons on hover of a site under the StackExchange button"],
            ["alwaysShowImageUploadLinkBox", "Always show the 'Link from the web' box when uploading an image"],
            ["addAuthorNameToInboxNotifications", "Add the author's name to notifications in the inbox"],
            ["scrollToTop", "Add Scroll To Top button"],
            ["linkedPostsInline", "Display linked posts inline (with an arrow)"]
        ]);
    }

}(window.sox = window.sox || {}, jQuery));

// initialize sox
sox.init(); //TODO: not sure if we really need this to be a function, script is wrapped in IIFE so init code could just go at the top.
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Stack Overflow Extras (SOX)
// @namespace    https://github.com/soscripted/sox
// @homepage     https://github.com/soscripted/sox
// @homepageURL  https://github.com/soscripted/sox
// @version      2.6.11 DEV
// @description  Extra optional features for Stack Overflow and Stack Exchange sites
// @contributor  ᴉʞuǝ (https://stackoverflow.com/users/1454538/, https://github.com/mezmi)
// @contributor  ᔕᖺᘎᕊ (https://stackexchange.com/users/4337810/, https://github.com/shu8)
// @contributor  Sir-Cumference (https://stackexchange.com/users/4119142/, https://github.com/Sir-Cumference)
// @contributor  GaurangTandon (https://github.com/GaurangTandon)
// @contributor  double-beep (https://stackexchange.com/users/14688437/double-beep, https://github.com/double-beep)
// @updateURL    https://cdn.jsdelivr.net/gh/soscripted/sox@dev/sox.user.js

// @match        *://*.stackoverflow.com/*
// @match        *://*.stackexchange.com/*
// @match        *://*.superuser.com/*
// @match        *://*.serverfault.com/*
// @match        *://*.askubuntu.com/*
// @match        *://*.stackapps.com/*
// @match        *://*.mathoverflow.net/*
// @match        *://github.com/soscripted/*
// @match        *://soscripted.github.io/sox/*

// @exclude      *://data.stackexchange.com/*
// @exclude      *://api.stackexchange.com/*
// @exclude      *://stackoverflow.com/c/*

// @require      https://code.jquery.com/jquery-3.3.1.min.js
// @require      https://code.jquery.com/ui/1.12.1/jquery-ui.min.js
// @require      https://api.stackexchange.com/js/2.0/all.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.5.3/jquery.timeago.min.js

// @require      sox.common.js
// @require      sox.github.js
// @require      sox.dialog.js
// @require      sox.features.js

// @resource     css sox.css
// @resource     dialog sox.dialog.html
// @resource     featuresJSON sox.features.info.json
// @resource     common sox.common.info.json
// @resource     sprites sox.sprites.svg

// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @grant        GM_info
// ==/UserScript==
/*jshint loopfunc: true*/
(function(sox, $) {
  'use strict';

  function init() {
    if (sox.location.on('github.com/soscripted')) {
      try {
        sox.github.init(sox.info.version, sox.info.handler);
      } catch (e) {
        throw ('SOX: There was an error while attempting to initialize the sox.github.js file, please report this on GitHub.\n' + e);
      }
      return;
    }

    if (sox.location.on('soscripted.github.io/sox/#access_token')) { //save access token
      try {
        const access_token = window.location.href.split('=')[1].split('&')[0];
        sox.loginfo('ACCESS TOKEN: ', access_token);
        GM_setValue('SOX-accessToken', access_token);
        alert('Access token successfully saved! You can close this window :)');
      } catch (e) {
        throw ('SOX: There was an error saving your access token');
      }
      return;
    }
    if (sox.info.debugging) {
      sox.debug('DEBUGGING SOX VERSION ' + sox.info.version);
      sox.debug('----------------saved variables---------------------');
      sox.settings.writeToConsole(true); //true => hide access token
      sox.debug('----------------end saved variables---------------------');
    }

    const spritesDiv = $('<div/>', { html: GM_getResourceText('sprites') });
    $('head').append(spritesDiv);

    GM_addStyle(GM_getResourceText('css'));

    const settings = sox.settings.load();
    //returns undefined if not set

    const featureInfo = JSON.parse(GM_getResourceText('featuresJSON'));

    try {
      sox.debug('SOX object', sox);
      sox.dialog.init({
        version: sox.info.version,
        features: featureInfo,
        settings: settings,
        lastVersionInstalled: sox.info.lastVersionInstalled,
      });
    } catch (e) {
      throw ('SOX: There was an error while attempting to initialize the SOX Settings Dialog, please report this on GitHub.\n' + e);
    }

    if (sox.settings.available) {
      // Execute features
      performance.mark('allFeatures-start');
      for (let i = 0; i < settings.length; ++i) {
        const category = settings[i].split('-')[0];
        const featureId = settings[i].split('-')[1];

        if (!(category in featureInfo.categories)) { //if we ever rename a category
          sox.loginfo('Deleting feature "' + settings[i] + '" (category rename?)');
          settings.splice(i, 1);
          sox.settings.save(settings);
          continue;
        }

        const feature = featureInfo.categories[category].filter(obj => {
          return obj.name == featureId;
        })[0];

        let runFeature = true;
        try {
          //NOTE: there is no else if() because it is possible to have both match and exclude patterns..
          //which could have minor exceptions making it neccessary to check both
          if (feature.match !== '') {
            const sites = feature.match.split(',');

            for (let pattern = 0; pattern < sites.length; pattern++) {
              if (!sox.location.matchWithPattern(sites[pattern])) {
                runFeature = false; //none of the patterns match the current site.. yet.
              } else {
                runFeature = true;
                break; //if it does match, then stop looping; we want the feature to run
              }
            }
          }
          if (feature.exclude !== '') {
            const sites = feature.exclude.split(',');

            for (let pattern = 0; pattern < sites.length; pattern++) {
              if (sox.location.matchWithPattern(sites[pattern])) { //if current site is in list, DON'T run feature
                runFeature = false; //don't run feature
                break; //no need to keep on looping
              }
            }
          }
          if (runFeature) {
            sox.debug('running ' + featureId);
            performance.mark(`${featureId}-start`);
            if (feature.settings) {
              const settingsToPass = GM_getValue('SOX-' + featureId + '-settings') ? JSON.parse(GM_getValue('SOX-' + featureId + '-settings')) : {};
              sox.features[featureId](settingsToPass); //run the feature if match and exclude conditions are met, pass on settings object
            } else {
              sox.features[featureId](); //run the feature if match and exclude conditions are met
            }
            performance.mark(`${featureId}-end`);
            performance.measure(featureId, `${featureId}-start`, `${featureId}-end`);
          }
        } catch (err) {
          if (!sox.features[featureId] || !feature) { //remove deprecated/'corrupt' feature IDs from saved settings
            sox.loginfo('Deleting feature "' + settings[i] + '" (feature not found)');
            settings.splice(i, 1);
            sox.settings.save(settings);
            $('#sox-settings-dialog-features').find('#' + settings[i].split('-')[1]).parent().parent().remove();
          } else {
            $('#sox-settings-dialog-features').find('#' + settings[i].split('-')[1]).parent().css('color', 'red').attr('title', 'There was an error loading this feature. Please raise an issue on GitHub.');
            sox.error('There was an error loading the feature "' + settings[i] + '". Please raise an issue on GitHub, and copy the following error log:\n' + err);
          }
        }
      }
      performance.mark('allFeatures-end');
      performance.measure('allFeatures', 'allFeatures-start', 'allFeatures-end');
      sox.debug('Performance Data', performance.getEntriesByType('measure'));
    }

    //custom events....
    sox.helpers.observe([...document.getElementsByClassName('post-layout')], '.new_comment, .comment, .comments, .comment-text', node => {
      sox.debug('sox-new-comment event triggered');
      $(document).trigger('sox-new-comment', [node]);
    });

    sox.helpers.observe(document.body, 'textarea[id^="wmd-input"]', node => {
      sox.debug('sox-edit-window event triggered');
      $(document).trigger('sox-edit-window', [node]);
    });

    sox.helpers.observe(document.body, '.reviewable-post, .review-content', node => {
      sox.debug('sox-new-review-post-appeared event triggered');
      $(document).trigger('sox-new-review-post-appeared', [node]);
    });

    const chatBody = document.getElementById('chat-body');
    if (chatBody) {
      sox.helpers.observe(chatBody, '.user-popup', node => {
        sox.debug('sox-chat-user-popup event triggered');
        $(document).trigger('sox-chat-user-popup', [node]);
      });
    }

    if (GM_getValue('SOX-accessToken', -1) == -1) { //set access token
      //This was originally a series of IIFEs appended to the head which used the SE API JS SDK but
      //it was very uncertain and often caused issues, especially in FF
      //it now uses a Github page to show the access token
      //and detects that page and saves it automatically.
      //this seems to be a much cleaner and easier-to-debug method!
      GM_setValue('SOX-accessToken', -2); //once we ask the user once, don't ask them again: set the value to -2 so this IF never evaluates to true
      const askUserToAuthorise = window.confirm('To get the most out of SOX, you should get an access token! Please press "OK" to continue and follow the instructions in the window that opens. NOTE: this message will not appear again; if you choose not to, you can click the key at the bottom of the settings dialog at anytime to get one.');
      if (askUserToAuthorise) window.open('https://stackexchange.com/oauth/dialog?client_id=7138&scope=no_expiry&redirect_uri=http://soscripted.github.io/sox/');
      sox.warn('Please go to the following URL to get your access token for certain SOX features', 'https://stackexchange.com/oauth/dialog?client_id=7138&scope=no_expiry&redirect_uri=http://soscripted.github.io/sox/');
    }
  }
  sox.ready(init);
})(window.sox = window.sox || {}, jQuery);
<commit_msg>Bump version number<commit_after>// ==UserScript==
// @name         Stack Overflow Extras (SOX)
// @namespace    https://github.com/soscripted/sox
// @homepage     https://github.com/soscripted/sox
// @homepageURL  https://github.com/soscripted/sox
// @version      2.6.12 DEV
// @description  Extra optional features for Stack Overflow and Stack Exchange sites
// @contributor  ᴉʞuǝ (https://stackoverflow.com/users/1454538/, https://github.com/mezmi)
// @contributor  ᔕᖺᘎᕊ (https://stackexchange.com/users/4337810/, https://github.com/shu8)
// @contributor  Sir-Cumference (https://stackexchange.com/users/4119142/, https://github.com/Sir-Cumference)
// @contributor  GaurangTandon (https://github.com/GaurangTandon)
// @contributor  double-beep (https://stackexchange.com/users/14688437/double-beep, https://github.com/double-beep)
// @updateURL    https://cdn.jsdelivr.net/gh/soscripted/sox@dev/sox.user.js

// @match        *://*.stackoverflow.com/*
// @match        *://*.stackexchange.com/*
// @match        *://*.superuser.com/*
// @match        *://*.serverfault.com/*
// @match        *://*.askubuntu.com/*
// @match        *://*.stackapps.com/*
// @match        *://*.mathoverflow.net/*
// @match        *://github.com/soscripted/*
// @match        *://soscripted.github.io/sox/*

// @exclude      *://data.stackexchange.com/*
// @exclude      *://api.stackexchange.com/*
// @exclude      *://stackoverflow.com/c/*

// @require      https://code.jquery.com/jquery-3.3.1.min.js
// @require      https://code.jquery.com/ui/1.12.1/jquery-ui.min.js
// @require      https://api.stackexchange.com/js/2.0/all.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.5.3/jquery.timeago.min.js

// @require      sox.common.js
// @require      sox.github.js
// @require      sox.dialog.js
// @require      sox.features.js

// @resource     css sox.css
// @resource     dialog sox.dialog.html
// @resource     featuresJSON sox.features.info.json
// @resource     common sox.common.info.json
// @resource     sprites sox.sprites.svg

// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @grant        GM_info
// ==/UserScript==
/*jshint loopfunc: true*/
(function(sox, $) {
  'use strict';

  function init() {
    if (sox.location.on('github.com/soscripted')) {
      try {
        sox.github.init(sox.info.version, sox.info.handler);
      } catch (e) {
        throw ('SOX: There was an error while attempting to initialize the sox.github.js file, please report this on GitHub.\n' + e);
      }
      return;
    }

    if (sox.location.on('soscripted.github.io/sox/#access_token')) { //save access token
      try {
        const access_token = window.location.href.split('=')[1].split('&')[0];
        sox.loginfo('ACCESS TOKEN: ', access_token);
        GM_setValue('SOX-accessToken', access_token);
        alert('Access token successfully saved! You can close this window :)');
      } catch (e) {
        throw ('SOX: There was an error saving your access token');
      }
      return;
    }
    if (sox.info.debugging) {
      sox.debug('DEBUGGING SOX VERSION ' + sox.info.version);
      sox.debug('----------------saved variables---------------------');
      sox.settings.writeToConsole(true); //true => hide access token
      sox.debug('----------------end saved variables---------------------');
    }

    const spritesDiv = $('<div/>', { html: GM_getResourceText('sprites') });
    $('head').append(spritesDiv);

    GM_addStyle(GM_getResourceText('css'));

    const settings = sox.settings.load();
    //returns undefined if not set

    const featureInfo = JSON.parse(GM_getResourceText('featuresJSON'));

    try {
      sox.debug('SOX object', sox);
      sox.dialog.init({
        version: sox.info.version,
        features: featureInfo,
        settings: settings,
        lastVersionInstalled: sox.info.lastVersionInstalled,
      });
    } catch (e) {
      throw ('SOX: There was an error while attempting to initialize the SOX Settings Dialog, please report this on GitHub.\n' + e);
    }

    if (sox.settings.available) {
      // Execute features
      performance.mark('allFeatures-start');
      for (let i = 0; i < settings.length; ++i) {
        const category = settings[i].split('-')[0];
        const featureId = settings[i].split('-')[1];

        if (!(category in featureInfo.categories)) { //if we ever rename a category
          sox.loginfo('Deleting feature "' + settings[i] + '" (category rename?)');
          settings.splice(i, 1);
          sox.settings.save(settings);
          continue;
        }

        const feature = featureInfo.categories[category].filter(obj => {
          return obj.name == featureId;
        })[0];

        let runFeature = true;
        try {
          //NOTE: there is no else if() because it is possible to have both match and exclude patterns..
          //which could have minor exceptions making it neccessary to check both
          if (feature.match !== '') {
            const sites = feature.match.split(',');

            for (let pattern = 0; pattern < sites.length; pattern++) {
              if (!sox.location.matchWithPattern(sites[pattern])) {
                runFeature = false; //none of the patterns match the current site.. yet.
              } else {
                runFeature = true;
                break; //if it does match, then stop looping; we want the feature to run
              }
            }
          }
          if (feature.exclude !== '') {
            const sites = feature.exclude.split(',');

            for (let pattern = 0; pattern < sites.length; pattern++) {
              if (sox.location.matchWithPattern(sites[pattern])) { //if current site is in list, DON'T run feature
                runFeature = false; //don't run feature
                break; //no need to keep on looping
              }
            }
          }
          if (runFeature) {
            sox.debug('running ' + featureId);
            performance.mark(`${featureId}-start`);
            if (feature.settings) {
              const settingsToPass = GM_getValue('SOX-' + featureId + '-settings') ? JSON.parse(GM_getValue('SOX-' + featureId + '-settings')) : {};
              sox.features[featureId](settingsToPass); //run the feature if match and exclude conditions are met, pass on settings object
            } else {
              sox.features[featureId](); //run the feature if match and exclude conditions are met
            }
            performance.mark(`${featureId}-end`);
            performance.measure(featureId, `${featureId}-start`, `${featureId}-end`);
          }
        } catch (err) {
          if (!sox.features[featureId] || !feature) { //remove deprecated/'corrupt' feature IDs from saved settings
            sox.loginfo('Deleting feature "' + settings[i] + '" (feature not found)');
            settings.splice(i, 1);
            sox.settings.save(settings);
            $('#sox-settings-dialog-features').find('#' + settings[i].split('-')[1]).parent().parent().remove();
          } else {
            $('#sox-settings-dialog-features').find('#' + settings[i].split('-')[1]).parent().css('color', 'red').attr('title', 'There was an error loading this feature. Please raise an issue on GitHub.');
            sox.error('There was an error loading the feature "' + settings[i] + '". Please raise an issue on GitHub, and copy the following error log:\n' + err);
          }
        }
      }
      performance.mark('allFeatures-end');
      performance.measure('allFeatures', 'allFeatures-start', 'allFeatures-end');
      sox.debug('Performance Data', performance.getEntriesByType('measure'));
    }

    //custom events....
    sox.helpers.observe([...document.getElementsByClassName('post-layout')], '.new_comment, .comment, .comments, .comment-text', node => {
      sox.debug('sox-new-comment event triggered');
      $(document).trigger('sox-new-comment', [node]);
    });

    sox.helpers.observe(document.body, 'textarea[id^="wmd-input"]', node => {
      sox.debug('sox-edit-window event triggered');
      $(document).trigger('sox-edit-window', [node]);
    });

    sox.helpers.observe(document.body, '.reviewable-post, .review-content', node => {
      sox.debug('sox-new-review-post-appeared event triggered');
      $(document).trigger('sox-new-review-post-appeared', [node]);
    });

    const chatBody = document.getElementById('chat-body');
    if (chatBody) {
      sox.helpers.observe(chatBody, '.user-popup', node => {
        sox.debug('sox-chat-user-popup event triggered');
        $(document).trigger('sox-chat-user-popup', [node]);
      });
    }

    if (GM_getValue('SOX-accessToken', -1) == -1) { //set access token
      //This was originally a series of IIFEs appended to the head which used the SE API JS SDK but
      //it was very uncertain and often caused issues, especially in FF
      //it now uses a Github page to show the access token
      //and detects that page and saves it automatically.
      //this seems to be a much cleaner and easier-to-debug method!
      GM_setValue('SOX-accessToken', -2); //once we ask the user once, don't ask them again: set the value to -2 so this IF never evaluates to true
      const askUserToAuthorise = window.confirm('To get the most out of SOX, you should get an access token! Please press "OK" to continue and follow the instructions in the window that opens. NOTE: this message will not appear again; if you choose not to, you can click the key at the bottom of the settings dialog at anytime to get one.');
      if (askUserToAuthorise) window.open('https://stackexchange.com/oauth/dialog?client_id=7138&scope=no_expiry&redirect_uri=http://soscripted.github.io/sox/');
      sox.warn('Please go to the following URL to get your access token for certain SOX features', 'https://stackexchange.com/oauth/dialog?client_id=7138&scope=no_expiry&redirect_uri=http://soscripted.github.io/sox/');
    }
  }
  sox.ready(init);
})(window.sox = window.sox || {}, jQuery);
<|endoftext|>


<commit_before>/*var showStep = function() {
  $('#product-info').children().removeClass('show');
  $('#product-info').children(':nth-child(' + Session.get('postState') + ')').addClass('show');
  }
  */

var verifyField = function(a) {
    var b = a.find('input, select').val();
    return (b != undefined && b != '') || Session.get('postState') == 5;
}

//The following code will take care of verfying and submitting all of the posts, when I care to finish writing it.
Template.addTitle.rendered = function(){
    $('form').submit(function(e) {
	e.preventDefault();
        var temp;
        if(Session.get('tempProdForm'))
	    temp = Session.get('tempProdForm');
	temp.title = $('input[name=title]').val();
	Session.set('tempProdForm',temp);
	Router.go('/sell/info');
    });
}

//should i put all of this code in it's own stuff hm
Template.addInfo.rendered = function() {
    $('form').submit(function(e) {
        e.preventDefault();
        temp = Session.get('tempProdForm');
        temp.title = $('input[name=title]').val();
	temp.category = $('input[name=category]').val();
	temp.so = $('input[name=so]').val();
	temp.bin = $('input[name=bin]').val();
	temp.condition = $('select[name="condition"]').val();
	temp.description = $('#itemDescription').html();
        Session.set('tempProdForm', title);
        Router.go('/post/preview');
    });
}

Template.postPreview.rendered = function(){
    $('#addProdBut').click(function(e) {
	e.preventDefault();
	console.log("adding item");
	Meteor.call('addProduct', Session.get('tempProdForm'), function(e,r) {
	    console.log("something");
	    if(e) {
		alert(e);
	    }else{
		console.log("done");
		Router.go('/post/' + r);
	    }
	});
    });
}

Template.pageAddProduct.rendered = function(){
    if(!Session.get('tempProdForm'))
	Session.set('tempProdForm',{});
};

/*
Template.postPreview.helpers({
    'title': function() {
	if(Session.get('tempProdForm'))
    return Session.get('tempProdForm').title;
    },
    'category': function() {
	if(Session.get('tempProdForm'))
    return Session.get('tempProdForm').category;
//return $('[name=category]').val();
    },
    'so': function() {
	if(Session.get('tempProdForm'))
    return Session.get('tempProdForm').so;
//return $('[name=so]').val();
    },
    'bin': function() {
	if(Session.get('tempProdForm'))
    return Session.get('tempProdForm').bin;
//return $('[name=bin]').val();
    },
    'condition': function() {
	if(Session.get('tempProdForm'))
	    return Session.get('tempProdForm').condition;
	//return $('[name=condition]').val();
    },
    'description': function() {
	if(Session.get('tempProdForm'))
	    return Session.get('tempProdForm').description;
	//return $('#itemDescription').html();
    },
});
*/
<commit_msg>finished the back end to post item<commit_after>/*var showStep = function() {
  $('#product-info').children().removeClass('show');
  $('#product-info').children(':nth-child(' + Session.get('postState') + ')').addClass('show');
  }
  */

var verifyField = function(a) {
    var b = a.find('input, select').val();
    return (b != undefined && b != '') || Session.get('postState') == 5;
}

//The following code will take care of verfying and submitting all of the posts, when I care to finish writing it.
Template.addTitle.rendered = function(){
    $('form').submit(function(e) {
	e.preventDefault();
        var temp;
        if(Session.get('tempProdForm'))
	    temp = Session.get('tempProdForm');
	temp.title = $('input[name=title]').val();
	Session.set('tempProdForm',temp);
	Router.go('/sell/info');
    });
}

//should i put all of this code in it's own stuff hm
Template.addInfo.rendered = function() {
    $('form').submit(function(e) {
        e.preventDefault();
        temp = Session.get('tempProdForm');
        temp.title = $('input[name=title]').val();
	temp.category = $('input[name=category]').val();
	temp.so = $('input[name=so]').val();
	temp.bin = $('input[name=bin]').val();
	temp.condition = $('select[name="condition"]').val();
	temp.description = $('#itemDescription').html();
        Session.set('tempProdForm', temp);
        Router.go('/sell/preview');
    });
}

Template.postPreview.rendered = function(){
    $('#addProdBut').click(function(e) {
	e.preventDefault();
	console.log("adding item");
	Meteor.call('addProduct', Session.get('tempProdForm'), function(e,r) {
	    console.log("something");
	    if(e) {
		alert(e);
	    }else{
		console.log("done");
		Router.go('/post/' + r);
	    }
	});
    });
}

Template.pageAddProduct.rendered = function(){
    if(!Session.get('tempProdForm'))
	Session.set('tempProdForm',{});
};

Template.postPreview.helpers({
    'title': function() {
	if(Session.get('tempProdForm'))
    return Session.get('tempProdForm').title;
    },
    'category': function() {
	if(Session.get('tempProdForm'))
    return Session.get('tempProdForm').category;
//return $('[name=category]').val();
    },
    'so': function() {
	if(Session.get('tempProdForm'))
    return Session.get('tempProdForm').so;
//return $('[name=so]').val();
    },
    'bin': function() {
	if(Session.get('tempProdForm'))
    return Session.get('tempProdForm').bin;
//return $('[name=bin]').val();
    },
    'condition': function() {
	if(Session.get('tempProdForm'))
	    return Session.get('tempProdForm').condition;
	//return $('[name=condition]').val();
    },
    'description': function() {
	if(Session.get('tempProdForm'))
	    return Session.get('tempProdForm').description;
	//return $('#itemDescription').html();
    },
});
<|endoftext|>