<commit_before>Modules.VERSION = '2.0.1';

this.__defineGetter__('PopupNotifications', function() { return window.PopupNotifications; });
this.__defineGetter__('gIdentityHandler', function() { return window.gIdentityHandler; });
this.__defineGetter__('gIdentityBox', function() { return $('identity-box'); });
this.__defineGetter__('gIdentityPopup', function() { return $('identity-popup'); });

// for the click handler modified by toCode
this.__defineGetter__('KeyEvent', function() { return window.KeyEvent; });
this.__defineGetter__('gURLBar', function() { return window.gURLBar; });

this.identityBox = {
	handleEvent: function(e) {
		switch(e.type) {
			case 'AskingForNodeOwner':
				if(e.target.anchorNode && e.target.anchorNode.id == 'page-proxy-favicon') {
					e.detail = 'identity-box';
					e.stopPropagation();
				}
				break;

			case 'popupshowing':
				if(typeof(skyLights) != 'undefined') {
					skyLights.update('identityBox', { active: e.target.anchorNode == skyLights.get('identityBox') });
				}
				break;

			case 'popuphiding':
				if(typeof(skyLights) != 'undefined') {
					skyLights.update('identityBox', { active: false });
				}
				break;

			case 'LoadedSkyLights':
				this.update(true);
				break;

			case 'UnloadingSkyLights':
				this.remove();
				break;
		}
	},

	update: function(doAction) {
		if(typeof(skyLights) == 'undefined') { return; }

		// default transparent state, for modes:
		//	gIdentityHandler.IDENTITY_MODE_UNKNOWN
		//	gIdentityHandler.IDENTITY_MODE_CHROMEUI
		//	gIdentityHandler.IDENTITY_MODE_FILE_URI

		var props = {
			tooltip: $('identity-icon-label').value,
			state: gIdentityBox.className,
			color: 'transparent'
		};

		switch(props.state) {
			case gIdentityHandler.IDENTITY_MODE_IDENTIFIED:
				props.color = 'hsl(82,100%,40%)';
				break;

			case gIdentityHandler.IDENTITY_MODE_DOMAIN_VERIFIED:
				props.color = 'hsl(220,100%,40%)';
				break;

			case gIdentityHandler.IDENTITY_MODE_MIXED_ACTIVE_LOADED:
			case gIdentityHandler.IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED:
			case gIdentityHandler.IDENTITY_MODE_MIXED_DISPLAY_LOADED:
				props.color = 'hsl(52,100%,50%)';
				break;

			default:
				// these were introduced in FF42
				if(Services.vc.compare(Services.appinfo.version, "42.0a1") >= 0) {
					switch(props.state) {
						case gIdentityHandler.MIXED_ACTIVE_BLOCKED_IDENTIFIED:
							props.color = 'hsl(82,100%,40%)';
							break;

						case gIdentityHandler.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED:
							props.color = 'hsl(220,100%,40%)';
							break;

						case gIdentityHandler.IDENTITY_MODE_USES_WEAK_CIPHER:
							props.color = 'hsl(52,100%,50%)';
							break;
					}
				}
				break;
		}

		if($('identity-icon-country-label').value) {
			if(props.tooltip) { props.tooltip += ' '; }
			props.tooltip += $('identity-icon-country-label').value;
		}

		if(gIdentityBox.tooltipText) {
			if(props.tooltip) { props.tooltip += ' - '; }
			props.tooltip += gIdentityBox.tooltipText;
		}

		if(doAction) {
			props.action = this.action;
		}

		skyLights.update('identityBox', props);
	},

	action: function(e) {
		var mixedBlocked = PopupNotifications.getNotification('mixed-content-blocked');
		if(mixedBlocked) {
			PopupNotifications._showPanel([mixedBlocked], skyLights.get('identityBox'));
			skyLights.update('identityBox', { active: true });

			Listeners.add($('notification-popup'), 'popuphiding', function() { skyLights.update('identityBox', { active: false }); }, false, true);
			return;
		}

		gIdentityHandler.handleIdentityButtonEvent(e);
	},

	remove: function() {
		if(typeof(skyLights) != 'undefined') {
			skyLights.remove('identityBox');
		}
	}
};

Modules.LOADMODULE = function() {
	gIdentityHandler.__mode = gIdentityHandler._mode;
	delete gIdentityHandler._mode;
	gIdentityHandler.__defineGetter__('_mode', function() { return this.__mode; });
	gIdentityHandler.__defineSetter__('_mode', function(v) {
		this.__mode = v;
		identityBox.update();
		return this.__mode;
	});

	if(Services.vc.compare(Services.appinfo.version, "42.0a1") < 0) {
		toCode.modify(gIdentityHandler, 'gIdentityHandler.handleIdentityButtonEvent', [
			// this changes the anchor of the identity box popup to the sky light, in case it was triggered from there and not from the actual identity box
			['this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft");',
				'this._identityPopup.openPopup(isAncestor(event.target, $("theFoxOnlyBetter-skyLights-identityBox")) ? $("theFoxOnlyBetter-skyLights-identityBox") : this._identityIcon, "bottomcenter topleft");'
			]
		]);
	} else {
		toCode.modify(gIdentityHandler, 'gIdentityHandler.handleIdentityButtonEvent', [
			// this changes the anchor of the identity box popup to the sky light, in case it was triggered from there and not from the actual identity box
			['this._identityPopup.openPopup(this._identityIcons, "bottomcenter topleft");',
				'this._identityPopup.openPopup(isAncestor(event.target, $("theFoxOnlyBetter-skyLights-identityBox")) ? $("theFoxOnlyBetter-skyLights-identityBox") : this._identityIcons, "bottomcenter topleft");'
			]
		]);
	}

	Listeners.add(gIdentityPopup, 'AskingForNodeOwner', identityBox);
	Listeners.add(gIdentityPopup, 'popupshowing', identityBox);
	Listeners.add(gIdentityPopup, 'popuphiding', identityBox);
	Listeners.add(window, 'LoadedSkyLights', identityBox);
	Listeners.add(window, 'UnloadingSkyLights', identityBox);

	identityBox.update(true);
};

Modules.UNLOADMODULE = function() {
	delete gIdentityHandler._mode;
	gIdentityHandler._mode = gIdentityHandler.__mode;
	delete gIdentityHandler.__mode;

	toCode.revert(gIdentityHandler, 'gIdentityHandler.handleIdentityButtonEvent');

	Listeners.remove(gIdentityPopup, 'AskingForNodeOwner', identityBox);
	Listeners.remove(gIdentityPopup, 'popupshowing', identityBox);
	Listeners.remove(gIdentityPopup, 'popuphiding', identityBox);
	Listeners.remove(window, 'LoadedSkyLights', identityBox);
	Listeners.remove(window, 'UnloadingSkyLights', identityBox);

	identityBox.remove();
};
<commit_msg>refactor some identityBox code<commit_after>Modules.VERSION = '2.1.0';

this.__defineGetter__('PopupNotifications', function() { return window.PopupNotifications; });
this.__defineGetter__('gIdentityHandler', function() { return window.gIdentityHandler; });
this.__defineGetter__('gIdentityBox', function() { return $('identity-box'); });
this.__defineGetter__('gIdentityPopup', function() { return $('identity-popup'); });

// for the click handler modified by toCode
this.__defineGetter__('KeyEvent', function() { return window.KeyEvent; });
this.__defineGetter__('gURLBar', function() { return window.gURLBar; });

this.identityBox = {
	initialized: false,

	handleEvent: function(e) {
		switch(e.type) {
			case 'AskingForNodeOwner':
				if(e.target.anchorNode && e.target.anchorNode.id == 'page-proxy-favicon') {
					e.detail = 'identity-box';
					e.stopPropagation();
				}
				break;

			case 'popupshowing':
				skyLights.update('identityBox', { active: e.target.anchorNode == skyLights.get('identityBox') });
				break;

			case 'popuphiding':
				skyLights.update('identityBox', { active: false });
				break;

			case 'LoadedSkyLights':
				this.init();
				break;

			case 'UnloadingSkyLights':
				this.deinit();
				break;
		}
	},

	init: function() {
		if(this.initialized) { return; }
		this.initialized = true;

		gIdentityHandler.__mode = gIdentityHandler._mode;
		delete gIdentityHandler._mode;
		gIdentityHandler.__defineGetter__('_mode', function() { return this.__mode; });
		gIdentityHandler.__defineSetter__('_mode', function(v) {
			this.__mode = v;
			identityBox.update();
			return this.__mode;
		});

		if(Services.vc.compare(Services.appinfo.version, "42.0a1") < 0) {
			toCode.modify(gIdentityHandler, 'gIdentityHandler.handleIdentityButtonEvent', [
				// this changes the anchor of the identity box popup to the sky light, in case it was triggered from there and not from the actual identity box
				['this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft");',
					'this._identityPopup.openPopup(isAncestor(event.target, $("theFoxOnlyBetter-skyLights-identityBox")) ? $("theFoxOnlyBetter-skyLights-identityBox") : this._identityIcon, "bottomcenter topleft");'
				]
			]);
		} else {
			// change the anchor of the identity box popup to the sky light, in case it was triggered from there and not from the actual identity box
			Piggyback.add('identityBox', gIdentityHandler, 'handleIdentityButtonEvent', function(event) {
				let light = skyLights.get('identityBox');
				let anchor = (isAncestor(event.target, light)) ? light : $('identity-icons');

				if(anchor != this._identityIcons) {
					delete this._identityIcons;
					this._identityIcons = anchor;
				}

				return true;
			}, Piggyback.MODE_BEFORE);
		}

		Listeners.add(gIdentityPopup, 'popupshowing', this);
		Listeners.add(gIdentityPopup, 'popuphiding', this);

		this.update(true);
	},

	deinit: function() {
		if(!this.initialized) { return; }
		this.initialized = false;

		delete gIdentityHandler._mode;
		gIdentityHandler._mode = gIdentityHandler.__mode;
		delete gIdentityHandler.__mode;

		if(Services.vc.compare(Services.appinfo.version, "42.0a1") < 0) {
			toCode.revert(gIdentityHandler, 'gIdentityHandler.handleIdentityButtonEvent');
		} else {
			Piggyback.revert('identityBox', gIdentityHandler, 'handleIdentityButtonEvent');
			delete gIdentityHandler._identityIcons;
			gIdentityHandler._identityIcons = $('identity-icons');
		}

		Listeners.remove(gIdentityPopup, 'popupshowing', this);
		Listeners.remove(gIdentityPopup, 'popuphiding', this);

		this.remove();
	},

	update: function(doAction) {
		// default transparent state, for modes:
		//	gIdentityHandler.IDENTITY_MODE_UNKNOWN
		//	gIdentityHandler.IDENTITY_MODE_CHROMEUI
		//	gIdentityHandler.IDENTITY_MODE_FILE_URI

		var props = {
			tooltip: $('identity-icon-label').value,
			state: gIdentityBox.className,
			color: 'transparent'
		};

		switch(props.state) {
			case gIdentityHandler.IDENTITY_MODE_IDENTIFIED:
				props.color = 'hsl(82,100%,40%)';
				break;

			case gIdentityHandler.IDENTITY_MODE_DOMAIN_VERIFIED:
				props.color = 'hsl(220,100%,40%)';
				break;

			case gIdentityHandler.IDENTITY_MODE_MIXED_ACTIVE_LOADED:
			case gIdentityHandler.IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED:
			case gIdentityHandler.IDENTITY_MODE_MIXED_DISPLAY_LOADED:
				props.color = 'hsl(52,100%,50%)';
				break;

			default:
				// these were introduced in FF42
				if(Services.vc.compare(Services.appinfo.version, "42.0a1") >= 0) {
					switch(props.state) {
						case gIdentityHandler.MIXED_ACTIVE_BLOCKED_IDENTIFIED:
							props.color = 'hsl(82,100%,40%)';
							break;

						case gIdentityHandler.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED:
							props.color = 'hsl(220,100%,40%)';
							break;

						case gIdentityHandler.IDENTITY_MODE_USES_WEAK_CIPHER:
							props.color = 'hsl(52,100%,50%)';
							break;
					}
				}
				break;
		}

		if($('identity-icon-country-label').value) {
			if(props.tooltip) { props.tooltip += ' '; }
			props.tooltip += $('identity-icon-country-label').value;
		}

		if(gIdentityBox.tooltipText) {
			if(props.tooltip) { props.tooltip += ' - '; }
			props.tooltip += gIdentityBox.tooltipText;
		}

		if(doAction) {
			props.action = this.action;
		}

		skyLights.update('identityBox', props);
	},

	action: function(e) {
		var mixedBlocked = PopupNotifications.getNotification('mixed-content-blocked');
		if(mixedBlocked) {
			PopupNotifications._showPanel([mixedBlocked], skyLights.get('identityBox'));
			skyLights.update('identityBox', { active: true });

			Listeners.add($('notification-popup'), 'popuphiding', function() { skyLights.update('identityBox', { active: false }); }, false, true);
			return;
		}

		gIdentityHandler.handleIdentityButtonEvent(e);
	},

	remove: function() {
		skyLights.remove('identityBox');
	}
};

Modules.LOADMODULE = function() {
	Listeners.add(gIdentityPopup, 'AskingForNodeOwner', identityBox);
	Listeners.add(window, 'LoadedSkyLights', identityBox);
	Listeners.add(window, 'UnloadingSkyLights', identityBox);

	if(self.skyLights) {
		identityBox.init();
	}
};

Modules.UNLOADMODULE = function() {
	Listeners.remove(gIdentityPopup, 'AskingForNodeOwner', identityBox);
	Listeners.remove(window, 'LoadedSkyLights', identityBox);
	Listeners.remove(window, 'UnloadingSkyLights', identityBox);

	identityBox.deinit();
};
<|endoftext|>


<commit_before>#!/usr/local/bin/seed
Seed.import_namespace("Gtk");

image_off = new Gtk.Image({"file": "./tim-off.svg"});
image_on = new Gtk.Image({"file": "./tim-on.svg"});

/* SxS size*/
var size = 5;
var wincount = 0;

function create_board()
{
    var table = new Gtk.Table();
    table.resize(size,size);

    buttons = new Array(size);

    for (i = 0; i < size; ++i)
    {
	    buttons[i] = new Array(size);
	    for (j = 0; j < size; ++j)
	    {
		    buttons[i][j] = new Gtk.Button();

		    buttons[i][j].x = i;
		    buttons[i][j].y = j;

		    buttons[i][j].set_relief(Gtk.ReliefStyle.none);
		    buttons[i][j].can_focus = false;

		    buttons[i][j].signal_clicked.connect(buttoon, buttons[i][j]);
		    table.attach_defaults(buttons[i][j], j, j+1, i, i+1);
	    }
    }

    return table;
}

function clear_board()
{
    for(i = 0; i < size; ++i)
    {
        for(j = 0; j < size; ++j)
        {
            buttons[i][j].lit = false;
            buttons[i][j].set_image(new Gtk.Image({"pixbuf": image_off.pixbuf}));
        }
    }
}

function initialize_game()
{
    wincount = 0;

    clear_board();
    random_clicks(); // generate random puzzle
}

function do_click(x , y)
{
	if ( x < size - 1 )
		flip_color(x + 1, y);
	if ( x > 0 )
		flip_color(x - 1, y);
	if ( y < size - 1 )
		flip_color(x, y + 1);
	if ( y > 0 )
		flip_color(x, y - 1);

	flip_color(x,y);
}

function buttoon( button )
{
	do_click(this.x, this.y);

	if ( wincount == 0 )
	{
		Seed.print("GLORIOUS VICTORY");
		exit(0);
	}
}

/* simulate random clicks to generate a random but solvable puzzle */
function random_clicks()
{
	var count = Math.round(20 * Math.random());

	var sym = Math.floor(3*Math.random());

	for (q = 0; q < count + 5; ++q)
	{
	    i = Math.round(4 * Math.random());
	    j = Math.round(4 * Math.random());

	    do_click(i, j);

	    if (sym == 0)
	    {
		    do_click(Math.abs(i-4), j);
	    }
	    else if (sym == 1)
	    {
		    do_click(Math.abs(i-4), Math.abs(j-4));
	    }
	    else
	    {
		    do_click(i,Math.abs(j-4));
	    }
	}
	//do it again if you won already
	if ( wincount == 0 )
		random_clicks();
}

function flip_color(i, j)
{
	if ( buttons[i][j].lit )
	{
		--wincount;
		buttons[i][j].set_image(new Gtk.Image({"pixbuf": image_off.pixbuf}));
	}
	else
	{
		++wincount;
		buttons[i][j].set_image(new Gtk.Image({"pixbuf": image_on.pixbuf}));
	}

    buttons[i][j].lit = !buttons[i][j].lit;
}

Gtk.init(null, null);

var window = new Gtk.Window({"title": "Lights Off"});
window.resizable = false;
window.signal_hide.connect(Gtk.main_quit);
window.add(create_board());
window.show_all();
initialize_game();

Gtk.main();

<commit_msg>Constructor property.<commit_after>#!/usr/local/bin/seed
Seed.import_namespace("Gtk");

image_off = new Gtk.Image({"file": "./tim-off.svg"});
image_on = new Gtk.Image({"file": "./tim-on.svg"});

/* SxS size*/
var size = 5;
var wincount = 0;

function create_board()
{
    var table = new Gtk.Table();
    table.resize(size,size);

    buttons = new Array(size);

    for (i = 0; i < size; ++i)
    {
	    buttons[i] = new Array(size);
	    for (j = 0; j < size; ++j)
	    {
		    buttons[i][j] = new Gtk.Button();

		    buttons[i][j].x = i;
		    buttons[i][j].y = j;

		    buttons[i][j].set_relief(Gtk.ReliefStyle.none);
		    buttons[i][j].can_focus = false;

		    buttons[i][j].signal_clicked.connect(buttoon, buttons[i][j]);
		    table.attach_defaults(buttons[i][j], j, j+1, i, i+1);
	    }
    }

    return table;
}

function clear_board()
{
    for(i = 0; i < size; ++i)
    {
        for(j = 0; j < size; ++j)
        {
            buttons[i][j].lit = false;
            buttons[i][j].set_image(new Gtk.Image({"pixbuf": image_off.pixbuf}));
        }
    }
}

function initialize_game()
{
    wincount = 0;

    clear_board();
    random_clicks(); // generate random puzzle
}

function do_click(x , y)
{
	if ( x < size - 1 )
		flip_color(x + 1, y);
	if ( x > 0 )
		flip_color(x - 1, y);
	if ( y < size - 1 )
		flip_color(x, y + 1);
	if ( y > 0 )
		flip_color(x, y - 1);

	flip_color(x,y);
}

function buttoon( button )
{
	do_click(this.x, this.y);

	if ( wincount == 0 )
	{
		Seed.print("GLORIOUS VICTORY");
		exit(0);
	}
}

/* simulate random clicks to generate a random but solvable puzzle */
function random_clicks()
{
	var count = Math.round(20 * Math.random());

	var sym = Math.floor(3*Math.random());

	for (q = 0; q < count + 5; ++q)
	{
	    i = Math.round(4 * Math.random());
	    j = Math.round(4 * Math.random());

	    do_click(i, j);

	    if (sym == 0)
	    {
		    do_click(Math.abs(i-4), j);
	    }
	    else if (sym == 1)
	    {
		    do_click(Math.abs(i-4), Math.abs(j-4));
	    }
	    else
	    {
		    do_click(i,Math.abs(j-4));
	    }
	}
	//do it again if you won already
	if ( wincount == 0 )
		random_clicks();
}

function flip_color(i, j)
{
	if ( buttons[i][j].lit )
	{
		--wincount;
		buttons[i][j].set_image(new Gtk.Image({"pixbuf": image_off.pixbuf}));
	}
	else
	{
		++wincount;
		buttons[i][j].set_image(new Gtk.Image({"pixbuf": image_on.pixbuf}));
	}

    buttons[i][j].lit = !buttons[i][j].lit;
}

Gtk.init(null, null);

var window = new Gtk.Window({"title": "Lights Off", "resizable" : false});
window.signal_hide.connect(Gtk.main_quit);
window.add(create_board());
window.show_all();
initialize_game();

Gtk.main();

<|endoftext|>


<commit_before>'use strict';

const { mapValues, find, omit, pickBy } = require('lodash');

// Normalize IDL definition
const normalizeIdl = function (idl) {
  idl.models = normalizeModels(idl.models);
  return idl;
};

// Normalize IDL definition models
const normalizeModels = function (models) {
  return allTransforms.reduce((value, transforms) => {
    // Apply transformations in several passes, i.e. allTransforms[0], then allTransforms[1], etc.
    return transformModels({ value, key: 'models', transforms });
  }, models);
};

// Normalize IDL definition models, with one set of transforms
const transformModels = function ({ value, key, transforms, root, depth = 0 }) {
  // 'instance' check avoids infinite recursion
  if (!value || value.constructor !== Object || key === 'instance') { return value; }

  // Keep track of root and depth level
  root = root || value;
  // Depth 1 means top-level model, depth 3 means model attribute, depth 4 means model array attribute's item
  const type = [,'model',,'singleAttr','multipleAttr'][depth] || 'other';
  ++depth;

  // Sort keys for transformation order predictability, but pass order should be used instead for that
  const newValues = Object.keys(value).sort()
    .concat('any')
    // Fire each transform, if defined
    .filter(name => transforms[name])
    .map(name => transforms[name]({ value, key, root, type }))
    .filter(values => values);
  // Assign transforms return values, to a copy of `value`
  value = Object.assign({}, value, ...newValues);
  // Remove undefined values
  value = pickBy(value, val => val !== undefined);

  // Recurse over children
  return mapValues(value, (child, childKey) => transformModels({ value: child, key: childKey, transforms, root, depth }));
};

/**
 * List of transformations to apply to normalize IDL models
 * The top-level index is the pass number
 * The key is the property name on the model or any object inside the definition
 * Each function is fired with the following options:
 *  - {object} value - parent object
 *  - {string} key - parent object's key
 *  - {object} root - root object
 *  - {number} type - 'model', 'singleAttr', 'multipleAttr' or 'other'
 * Must return the new attributes to merge to `value`
 */
const allTransforms = [

  {
    // { instanceof '...' } -> { type: 'object', instanceof: '...' }
    instanceof({ value, root, type }) {
      let instance;
      if (type.endsWith('Attr')) {
        instance = find(root, (_, modelName) => modelName === value.instanceof);
      }
      return { type: 'object', instance };
    },

    // Adds def.propName refering to property name
    type({ key, type }) {
      // Only for top-level models and single attributes
      if (!['model', 'singleAttr'].includes(type)) { return; }
      return { propName: key };
    },
  },

  {
    // Dereference `instanceof` pointers, using a shallow copy, except for few attributes
    instance({ value }) {
      // Make sure we do not copy `required` attribute from top-level model
      const keysToProtect = Object.keys(value).concat('required');
      // copy only the keys from top-level model not defined in submodel
      const modelProps = omit(value.instance, keysToProtect);
      return Object.assign(modelProps, { instance: undefined });
    },

    // Add model.instanceof to top-level models
    any({ key, type }) {
      if (type !== 'model') { return; }
      return { instanceof: key };
    },
  }

];

module.exports = {
  normalizeIdl,
};
<commit_msg>Use transform help in IDL normalization layer<commit_after>'use strict';

const { find, omit } = require('lodash');

const { transform } = require('../utilities');

// Normalize IDL definition
const normalizeIdl = function (idl) {
  idl.models = normalizeModels(idl.models);
  return idl;
};

// Normalize IDL definition models
const normalizeModels = function (models) {
  return transform({ transforms })({ input: models });
};

// List of transformations to apply to normalize IDL models
const transforms = [

  {
    // Depth 1 means top-level model, depth 3 means model attribute, depth 4 means model array attribute's item
    any({ depth, key }) {
      let depthType;
      if (depth === 1) {
        depthType = 'model';
      } else if (depth === 3) {
        depthType = 'singleAttr';
      } else if (depth === 4 && key === 'items') {
        depthType = 'multipleAttr';
      }
      return { depthType };
    },
  },

  {
    // { instanceof '...' } -> { type: 'object', instanceof: '...' }
    instanceof: () => ({ type: 'object' }),

    // Adds def.propName refering to property name
    type({ key, parent }) {
      // Only for top-level models and single attributes
      if (!['model', 'singleAttr'].includes(parent.depthType)) { return; }
      return { propName: key };
    },

    // Add model.instanceof to top-level models
    any({ key, parent }) {
      if (parent.depthType !== 'model') { return; }
      return { instanceof: key };
    },
  },

  {
    // { instanceof '...' } -> { instanceof: '...', ...copyOfTopLevelModel }
    // Must be last because this is done by copy, not reference
    instanceof({ value, root, parent }) {
      if (!['singleAttr', 'multipleAttr'].includes(parent.depthType)) { return; }
      const instance = find(root, (_, modelName) => modelName === value);
      // Make sure we do not copy `required` attribute from top-level model
      const keysToProtect = Object.keys(parent).concat('required');
      // Dereference `instanceof` pointers, using a shallow copy, except for few attributes
      // copy only the keys from top-level model not defined in submodel
      const newProps = omit(instance, keysToProtect);
      // Avoid infinite recursion
      newProps.__noRecurse = true;
      return newProps;
    },
  },

];

module.exports = {
  normalizeIdl,
};
<|endoftext|>


<commit_before>/*
 * BSD 3-Clause License
 *
 * Copyright (c) 2017, MapCreator
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 *  Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import {camel as camelCase, pascal as pascalCase, snake as snakeCase} from 'case';
import {DeletedState} from './enums';
import {hashObject} from './utils/hash';
import {getTypeName} from './utils/reflection';
import {encodeQueryString} from './utils/requests';

/**
 * Used for keeping track of the request parameters
 */
export default class RequestParameters {
  /**
   * RequestParameters constructor
   * @param {Object} object - properties
   */
  constructor(object = {}) {
    this._watch = [];

    // Apply defaults
    RequestParameters.keys().forEach(x => this._resolve(x));

    // Apply properties
    for (const key of Object.keys(object)) {
      const Key = camelCase(key);

      if (key[0] === '_' || !RequestParameters.keys().includes(Key)) {
        continue;
      }

      this._update(Key, object[key]);
    }
  }

  // region instance
  // region instance getters
  /**
   * Get page number
   * @returns {Number} - Page number
   * @throws TypeError
   */
  get page() {
    return this._resolve('page');
  }

  /**
   * Get rows per page
   * @returns {Number} - Per page
   * @throws TypeError
   */
  get perPage() {
    return this._resolve('perPage');
  }

  /**
   * Search query
   * @returns {Object<String, String|Array<String>>} - Query
   * @throws TypeError
   */
  get search() {
    return this._resolve('search');
  }

  /**
   * Get sort options
   * @returns {Array<String>} - Per page
   * @throws TypeError
   */
  get sort() {
    return this._resolve('sort');
  }

  /**
   * If deleted items should be shown
   * @returns {String} - Deleted items filter state
   * @see {@link DeletedState}
   */
  get deleted() {
    return this._resolve('deleted');
  }

  // endregion instance getters

  // region instance setters
  /**
   * Page number
   * @param {Number} value - Page number
   */
  set page(value) {
    this._update('page', value);
  }

  /**
   * Rows per page number
   * @param {Number} value - Per page
   */
  set perPage(value) {
    this._update('perPage', value);
  }

  /**
   * Search query
   * @param {Object<String, String|Array<String>>} value - Search query
   */
  set search(value) {
    this._update('search', value);
  }

  /**
   * Sort query
   * @param {Array<String>} value - Sort query
   */
  set sort(value) {
    this._update('sort', value);
  }

  /**
   * Deleted items filter state
   * @param {String} value - Deleted items filter state
   * @see {@link DeletedState}
   */
  set deleted(value) {
    this._update('deleted', value);
  }

  // endregion instance setters
  // endregion instance

  // region static
  // region getters
  /**
   * Default page number
   * @returns {Number} - Page number
   */
  static get page() {
    return RequestParameters._page || 1;
  }

  /**
   * Default per page
   * @returns {Number} - Per page
   */
  static get perPage() {
    return RequestParameters._perPage || Number(process.env.PER_PAGE) || 12;
  }

  /**
   * Default search query
   * @returns {Object<String, String|Array<String>>} - Search query
   */
  static get search() {
    return RequestParameters._search || {};
  }

  /**
   * Default sort query
   * @returns {Array<String>} - Sort query
   */
  static get sort() {
    return RequestParameters._sort || [];
  }

  /**
   * Default deleted items filter state
   * @returns {String} -  Deleted items filter state
   */
  static get deleted() {
    return RequestParameters._deleted || DeletedState.NONE;
  }

  // endregion getters

  // region setters
  /**
   * Default page number
   * @param {Number} value - Page number
   */
  static set page(value) {
    RequestParameters._page = RequestParameters._validatePage(value);
  }

  /**
   * Default per page
   * @param {Number} value - Per page
   */
  static set perPage(value) {
    RequestParameters._perPage = RequestParameters._validatePerPage(value);
  }

  /**
   * Default search query
   * @param {Object<String, String|Array<String>>} value - Search query
   */
  static set search(value) {
    RequestParameters._search = RequestParameters._validateSearch(value);
  }

  /**
   * Default sort query
   * @param {Array<String>} value - Sort query
   */
  static set sort(value) {
    RequestParameters._sort = RequestParameters._validateSort(value);
  }

  /**
   * Default deleted items filter state
   * @param {String} value -  Deleted items filter state
   */
  static set deleted(value) {
    RequestParameters._deleted = RequestParameters._validateDeleted(value);
  }

  // endregion setters
  // endregion static

  // region validators
  /**
   * Validators should work the same as laravel's ::validate method. This means
   * this means that they will throw a TypeError or return a normalized result.
   */

  static _validatePage(value) {
    if (typeof value !== 'number') {
      throw new TypeError(`Expected page to be of type 'number' instead got '${typeof value}'`);
    }

    return Math.round(value);
  }

  static _validatePerPage(value) {
    if (typeof value !== 'number') {
      throw new TypeError(`Expected page to be of type 'Number' instead got '${getTypeName(value)}'`);
    }

    value = Math.round(value);
    value = Math.min(50, value); // Upper limit is 50
    value = Math.max(1, value); // Lower limit is 1

    return value;
  }

  static _validateSearch(value) {
    if (typeof value !== 'object' || Array.isArray(value)) {
      throw new TypeError(`Expected value to be of type "Object" got "${getTypeName(value)}"`);
    }

    // Normalization macro
    const normalize = x => typeof x === 'number' ? x.toString() : x;

    for (let key of Object.keys(value)) {
      key = normalize(key);
      value[key] = normalize(value[key]);

      if (typeof key !== 'string') {
        throw new TypeError(`Expected key to be of type "String" got "${getTypeName(key)}"`);
      }

      if (Array.isArray(value[key])) {
        if (value[key].length > 0) {
          for (const query of value[key]) {
            if (typeof query !== 'string') {
              throw new TypeError(`Expected query for "${key}" to be of type "String" got "${getTypeName(query)}"`);
            }
          }
        } else {
          // Drop empty nodes
          delete value[key];
        }
      } else if (value[key] === null) {
        delete value[key];
      } else if (typeof value[key] !== 'string') {
        throw new TypeError(`Expected query value to be of type "String" or "Array" got "${getTypeName(value[key])}"`);
      }
    }

    return value;
  }

  static _validateSort(value) {
    if (typeof value === 'string') {
      return this._validateSort(value.split(','));
    }

    if (!(value instanceof Array)) {
      throw new TypeError(`Expected sort value to be of type "Array" got "${getTypeName(value)}"`);
    }

    // Array keys type checking
    value
      .filter(x => typeof x !== 'string')
      .forEach(x => {
        throw new TypeError(`Expected sort array values to be of type "String" got "${getTypeName(x)}"`);
      });

    // Don't do regex matching because it's something
    // we can just let the server do for us.

    return value;
  }

  static _validateDeleted(value) {
    if (typeof value !== 'string') {
      throw new TypeError(`Expected deleted to be of type "string" got "${getTypeName(value)}". See: DeletedState`);
    }

    value = value.toLowerCase();

    const possible = DeletedState.values();

    if (!possible.includes(value)) {
      throw new TypeError(`Expected deleted to be one of ${possible.join(', ')}, got ${value}`);
    }

    return value;
  }

  // endregion validators

  _resolve(name) {
    const _name = '_' + name;

    if (!this[_name]) {
      // Confuse esdoc
      (this || {})[_name] = RequestParameters[name];
    }

    return this[_name];
  }

  _update(name, value) {
    const _name = '_' + name;

    value = RequestParameters['_validate' + pascalCase(name)](value);
    (this || {})[_name] = value; // Weird syntax confuses esdoc

    this._watch.forEach(m => m(name, value));
  }

  // region utils
  /**
   * Urlencode parameters
   * @returns {string} - HTTP query
   */
  encode() {
    const data = this.toObject();

    // Fix column names for sort
    data.sort = data.sort.map(x =>
      snakeCase(x).replace(/^_/, '-'),
    ).join(',');

    // Fix column names for search
    for (const key of Object.keys(data.search)) {
      const snakeKey = snakeCase(key);

      if (key !== snakeKey) {
        data.search[snakeKey] = data.search[key];
        delete data.search[key];
      }
    }

    return encodeQueryString(data);
  }

  /**
   * Convert to object
   * @returns {Object} - Object
   */
  toObject() {
    const data = {};

    RequestParameters
      .keys()
      .forEach(key => {
        data[snakeCase(key)] = this._resolve(key);
      });

    return data;
  }

  /**
   * Copy object
   * @returns {RequestParameters} - Copy
   */
  copy() {
    return new RequestParameters(this.toObject());
  }

  /**
   * Different parameters
   * @returns {Array<String>} - keys
   */
  static keys() {
    // enumeration is disabled for properties
    return [
      'page',
      'perPage',
      'search',
      'sort',
      'deleted',
    ];
  }

  /**
   * Generates a cache token
   * @returns {string} - Cache token
   */
  token() {
    const data = this.toObject();

    delete data['page'];
    delete data['per_page'];

    return hashObject(data);
  }

  /**
   * Resets all parameters back to default
   * @returns {void}
   */
  static resetDefaults() {
    for (const key of RequestParameters.keys()) {
      delete RequestParameters['_' + key];
    }
  }

  // endregion utils
}
<commit_msg>Add extra requestParameters<commit_after>/*
 * BSD 3-Clause License
 *
 * Copyright (c) 2017, MapCreator
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 *  Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import {camel as camelCase, pascal as pascalCase, snake as snakeCase} from 'case';
import {DeletedState} from './enums';
import {hashObject} from './utils/hash';
import {getTypeName} from './utils/reflection';
import {encodeQueryString} from './utils/requests';

/**
 * Used for keeping track of the request parameters
 */
export default class RequestParameters {
  /**
   * RequestParameters constructor
   * @param {Object} object - properties
   */
  constructor(object = {}) {
    this._watch = [];

    // Apply defaults
    RequestParameters.keys().forEach(x => this._resolve(x));

    // Apply properties
    for (const key of Object.keys(object)) {
      const Key = camelCase(key);

      if (key[0] === '_' || !RequestParameters.keys().includes(Key)) {
        continue;
      }

      this._update(Key, object[key]);
    }
  }

  // region instance
  // region instance getters
  /**
   * Get page number
   * @returns {Number} - Page number
   * @throws TypeError
   */
  get page() {
    return this._resolve('page');
  }

  /**
   * Get rows per page
   * @returns {Number} - Per page
   * @throws TypeError
   */
  get perPage() {
    return this._resolve('perPage');
  }

  /**
   * Search query
   * @returns {Object<String, String|Array<String>>} - Query
   * @throws TypeError
   */
  get search() {
    return this._resolve('search');
  }

  /**
   * Get sort options
   * @returns {Array<String>} - Per page
   * @throws TypeError
   */
  get sort() {
    return this._resolve('sort');
  }

  /**
   * If deleted items should be shown
   * @returns {String} - Deleted items filter state
   * @see {@link DeletedState}
   */
  get deleted() {
    return this._resolve('deleted');
  }

  /**
   * Extra parameters
   * @returns {Object} - Extra parameters
   */
  get extra() {
    return this._resolve('extra');
  }

  // endregion instance getters

  // region instance setters
  /**
   * Page number
   * @param {Number} value - Page number
   */
  set page(value) {
    this._update('page', value);
  }

  /**
   * Rows per page number
   * @param {Number} value - Per page
   */
  set perPage(value) {
    this._update('perPage', value);
  }

  /**
   * Search query
   * @param {Object<String, String|Array<String>>} value - Search query
   */
  set search(value) {
    this._update('search', value);
  }

  /**
   * Sort query
   * @param {Array<String>} value - Sort query
   */
  set sort(value) {
    this._update('sort', value);
  }

  /**
   * Deleted items filter state
   * @param {String} value - Deleted items filter state
   * @see {@link DeletedState}
   */
  set deleted(value) {
    this._update('deleted', value);
  }

  /**
   * Extra request parameters
   * @param {Object} value - Extra request parameters
   */
  set extra(value) {
    this._update('extra', value);
  }

  // endregion instance setters
  // endregion instance

  // region static
  // region getters
  /**
   * Default page number
   * @returns {Number} - Page number
   */
  static get page() {
    return RequestParameters._page || 1;
  }

  /**
   * Default per page
   * @returns {Number} - Per page
   */
  static get perPage() {
    return RequestParameters._perPage || Number(process.env.PER_PAGE) || 12;
  }

  /**
   * Default search query
   * @returns {Object<String, String|Array<String>>} - Search query
   */
  static get search() {
    return RequestParameters._search || {};
  }

  /**
   * Default sort query
   * @returns {Array<String>} - Sort query
   */
  static get sort() {
    return RequestParameters._sort || [];
  }

  /**
   * Default deleted items filter state
   * @returns {String} -  Deleted items filter state
   */
  static get deleted() {
    return RequestParameters._deleted || DeletedState.NONE;
  }

  /**
   * Default extra request parameters
   * @returns {Object} - Extra request parameters
   */
  static get extra() {
    return RequestParameters._extra || {};
  }

  // endregion getters

  // region setters
  /**
   * Default page number
   * @param {Number} value - Page number
   */
  static set page(value) {
    RequestParameters._page = RequestParameters._validatePage(value);
  }

  /**
   * Default per page
   * @param {Number} value - Per page
   */
  static set perPage(value) {
    RequestParameters._perPage = RequestParameters._validatePerPage(value);
  }

  /**
   * Default search query
   * @param {Object<String, String|Array<String>>} value - Search query
   */
  static set search(value) {
    RequestParameters._search = RequestParameters._validateSearch(value);
  }

  /**
   * Default sort query
   * @param {Array<String>} value - Sort query
   */
  static set sort(value) {
    RequestParameters._sort = RequestParameters._validateSort(value);
  }

  /**
   * Default deleted items filter state
   * @param {String} value -  Deleted items filter state
   */
  static set deleted(value) {
    RequestParameters._deleted = RequestParameters._validateDeleted(value);
  }

  /**
   * Default extra request parameters
   * @param {Object} value - Extra request parameters
   */
  static set extra(value) {
    RequestParameters._extra = RequestParameters._validateExtra(value);
  }

  // endregion setters
  // endregion static

  // region validators
  /**
   * Validators should work the same as laravel's ::validate method. This means
   * this means that they will throw a TypeError or return a normalized result.
   */

  static _validatePage(value) {
    if (typeof value !== 'number') {
      throw new TypeError(`Expected page to be of type 'number' instead got '${typeof value}'`);
    }

    return Math.round(value);
  }

  static _validatePerPage(value) {
    if (typeof value !== 'number') {
      throw new TypeError(`Expected page to be of type 'Number' instead got '${getTypeName(value)}'`);
    }

    value = Math.round(value);
    value = Math.min(50, value); // Upper limit is 50
    value = Math.max(1, value); // Lower limit is 1

    return value;
  }

  static _validateSearch(value) {
    if (typeof value !== 'object' || Array.isArray(value)) {
      throw new TypeError(`Expected value to be of type "Object" got "${getTypeName(value)}"`);
    }

    // Normalization macro
    const normalize = x => typeof x === 'number' ? x.toString() : x;

    for (let key of Object.keys(value)) {
      key = normalize(key);
      value[key] = normalize(value[key]);

      if (typeof key !== 'string') {
        throw new TypeError(`Expected key to be of type "String" got "${getTypeName(key)}"`);
      }

      if (Array.isArray(value[key])) {
        if (value[key].length > 0) {
          for (const query of value[key]) {
            if (typeof query !== 'string') {
              throw new TypeError(`Expected query for "${key}" to be of type "String" got "${getTypeName(query)}"`);
            }
          }
        } else {
          // Drop empty nodes
          delete value[key];
        }
      } else if (value[key] === null) {
        delete value[key];
      } else if (typeof value[key] !== 'string') {
        throw new TypeError(`Expected query value to be of type "String" or "Array" got "${getTypeName(value[key])}"`);
      }
    }

    return value;
  }

  static _validateSort(value) {
    if (typeof value === 'string') {
      return this._validateSort(value.split(','));
    }

    if (!(value instanceof Array)) {
      throw new TypeError(`Expected sort value to be of type "Array" got "${getTypeName(value)}"`);
    }

    // Array keys type checking
    value
      .filter(x => typeof x !== 'string')
      .forEach(x => {
        throw new TypeError(`Expected sort array values to be of type "String" got "${getTypeName(x)}"`);
      });

    // Don't do regex matching because it's something
    // we can just let the server do for us.

    return value;
  }

  static _validateDeleted(value) {
    if (typeof value !== 'string') {
      throw new TypeError(`Expected deleted to be of type "string" got "${getTypeName(value)}". See: DeletedState`);
    }

    value = value.toLowerCase();

    const possible = DeletedState.values();

    if (!possible.includes(value)) {
      throw new TypeError(`Expected deleted to be one of ${possible.join(', ')}, got ${value}`);
    }

    return value;
  }

  static _validateExtra(value) {
    if (typeof value !== 'object') {
      throw new TypeError(`Expected extra to be of type 'object', got '${getTypeName(value)}'`);
    }
  }

  // endregion validators

  _resolve(name) {
    const _name = '_' + name;

    if (!this[_name]) {
      // Confuse esdoc
      (this || {})[_name] = RequestParameters[name];
    }

    return this[_name];
  }

  _update(name, value) {
    const _name = '_' + name;

    value = RequestParameters['_validate' + pascalCase(name)](value);
    (this || {})[_name] = value; // Weird syntax confuses esdoc

    this._watch.forEach(m => m(name, value));
  }

  // region utils
  /**
   * Urlencode parameters
   * @returns {string} - HTTP query
   */
  encode() {
    const data = this.toObject();

    data.sort = data.sort.join(',');

    return encodeQueryString(data);
  }

  /**
   * Convert to object
   * @returns {Object} - Object
   */
  toObject() {
    const data = {};

    RequestParameters
      .keys()
      .forEach(key => {
        // Skip extra key
        if (key === 'extra') {
          return;
        }

        data[snakeCase(key)] = this._resolve(key);
      });

    // Fix column names for sort
    data.sort = data.sort.map(x =>
      snakeCase(x).replace(/^_/, '-'),
    );

    // Fix column names for search
    for (const key of Object.keys(data.search)) {
      const snakeKey = snakeCase(key);

      if (key !== snakeKey) {
        data.search[snakeKey] = data.search[key];
        delete data.search[key];
      }
    }

    // Overwrite using extra properties
    const extra = this._resolve('extra');

    for (const key of Object.keys(extra)) {
      data[key] = extra[key];
    }

    return data;
  }

  /**
   * Copy object
   * @returns {RequestParameters} - Copy
   */
  copy() {
    return new RequestParameters(this.toObject());
  }

  /**
   * Different parameters
   * @returns {Array<String>} - keys
   */
  static keys() {
    // enumeration is disabled for properties
    return [
      'page',
      'perPage',
      'search',
      'sort',
      'deleted',
      'extra',
    ];
  }

  /**
   * Generates a cache token
   * @returns {string} - Cache token
   */
  token() {
    const data = this.toObject();

    delete data['page'];
    delete data['per_page'];

    return hashObject(data);
  }

  /**
   * Resets all parameters back to default
   * @returns {void}
   */
  static resetDefaults() {
    for (const key of RequestParameters.keys()) {
      delete RequestParameters['_' + key];
    }
  }

  // endregion utils
}
<|endoftext|>


<commit_before>'use strict';

(function (exports) {
    'use strict';

    var getPageUrl = function getPageUrl() {
        if (document.querySelector('meta[property="og:url"]') && document.querySelector('meta[property="og:url"]').getAttribute('content')) {
            return document.querySelector('meta[property="og:url"]').getAttribute('content');
        }

        return window.location.href;
    };

    // constants
    var TOOLTIP_HEIGHT = 50;
    var FACTOR = 1.33;
    var TWITTER_LIMIT_LENGTH = 140;
    var TWITTER_URL_LENGTH_COUNT = 24;
    var TWITTER_QUOTES = 2;
    var TWITTER_DOTS = 3;
    var TOOLTIP_TIMEOUT = 250;
    var FACEBOOK_DISPLAY_MODES = {
        popup: 'popup',
        page: 'page'
    };

    var REAL_TWITTER_LIMIT = TWITTER_LIMIT_LENGTH - TWITTER_URL_LENGTH_COUNT - TWITTER_QUOTES - TWITTER_DOTS;

    var SOCIAL = {
        twitter: 'twitter',
        buffer: 'buffer',
        digg: 'digg',
        linkedin: 'linkedin',
        stumbleupon: 'stumbleupon',
        reddit: 'reddit',
        tumblr: 'tumblr',
        facebook: 'facebook'
    };

    var NO_START_WITH = /[ .,!?/\\\+\-=*£$€:~§%^µ)(|@"{}&#><_]/g;
    var NO_ENDS_WITH = /[ ,/\\\+\-=*£$€:~§%^µ)(|@"{}&#><_]/g;
    var PAGE_URL = getPageUrl();

    // globals
    var tooltip = undefined;
    var parameters = undefined;
    var selected = {};

    var extend = function extend(out) {
        out = out || {};

        for (var i = 1; i < arguments.length; i += 1) {
            if (arguments[i]) {
                for (var key in arguments[i]) {
                    if (arguments[i].hasOwnProperty(key)) {
                        out[key] = arguments[i][key];
                    }
                }
            }
        }
        return out;
    };

    var hideTooltip = function hideTooltip() {
        tooltip.classList.remove('active');
    };

    var showTooltip = function showTooltip() {
        tooltip.classList.add('active');
    };

    var smartSanitize = function smartSanitize(text) {
        while (text.length && text[0].match(NO_START_WITH)) {
            text = text.substring(1, text.length);
        }

        while (text.length && text[text.length - 1].match(NO_ENDS_WITH)) {
            text = text.substring(0, text.length - 1);
        }

        return text;
    };

    var sanitizeText = function sanitizeText(text) {
        var sociaType = arguments.length <= 1 || arguments[1] === undefined ? '' : arguments[1];

        var author = '';
        var tweetLimit = REAL_TWITTER_LIMIT;

        if (!text) {
            return '';
        }

        if (parameters.twitterUsername && sociaType === SOCIAL.twitter) {
            author = ' via @' + parameters.twitterUsername;
            tweetLimit = REAL_TWITTER_LIMIT - author.length;
        }

        if (text.length > REAL_TWITTER_LIMIT) {
            text = text.substring(0, tweetLimit);
            text = text.substring(0, text.lastIndexOf(' ')) + '...';
        } else {
            text = text.substring(0, tweetLimit + TWITTER_DOTS);
        }

        return smartSanitize(text);
    };

    var generateSocialUrl = function generateSocialUrl(socialType, text) {
        if (parameters.sanitize) {
            text = sanitizeText(text, socialType);
        } else {
            text = smartSanitize(text);
        }

        var twitterUrl = 'https://twitter.com/intent/tweet?url=' + PAGE_URL + '&text="' + text + '"';

        if (parameters.twitterUsername && parameters.twitterUsername.length) {
            twitterUrl += '&via=' + parameters.twitterUsername;
        }

        if (parameters.twitterHashtag && parameters.twitterHashtag.length) {
            twitterUrl += '&hashtags=' + parameters.twitterHashtag;
        }

        var facebookUrl = 'https://facebook.com/sharer.php?u='+ PAGE_URL +'&quote=' + text ;

        var urls = {
            twitter: twitterUrl,
            buffer: 'https://buffer.com/add?text="' + text + '"&url=' + PAGE_URL,
            digg: 'http://digg.com/submit?url=' + PAGE_URL + '&title=' + text,
            linkedin: 'https://www.linkedin.com/shareArticle?url=' + PAGE_URL + '&title=' + text,
            stumbleupon: 'http://www.stumbleupon.com/submit?url=' + PAGE_URL + '&title=' + text,
            reddit: 'https://reddit.com/submit?url=' + PAGE_URL + '&title=' + text,
            tumblr: 'https://www.tumblr.com/widgets/share/tool?canonicalUrl=' + PAGE_URL + '&caption=' + text,
            facebook: facebookUrl
        };

        if (urls.hasOwnProperty(socialType)) {
            return urls[socialType];
        }

        return '';
    };

    var updateTooltip = function updateTooltip(rect) {
        var actualPosition = document.documentElement.scrollTop || document.body.scrollTop;
        var body = document.querySelector('body');

        tooltip.style.top = actualPosition + rect.top - TOOLTIP_HEIGHT * FACTOR + 'px';
        tooltip.style.left = rect.left + rect.width / 2 - body.getBoundingClientRect().width / 2 + 'px';

        Array.prototype.forEach.call(parameters.buttons, function (btn) {
            tooltip.querySelector('.share-selected-text-btn-' + btn).href = generateSocialUrl(btn, selected.text);
        });

        window.setTimeout(function () {
            showTooltip();
        }, parameters.tooltipTimeout);
    };

    var generateAnchorTag = function generateAnchorTag(anchorType) {
        var customIconClass = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];

        var anchorTag = document.createElement('A');
        var anchorIcon = document.createElement('i');

        if (parameters.anchorsClass) {
            anchorTag.classList.add('share-selected-text-btn', 'share-selected-text-btn-' + anchorType, '' + parameters.anchorsClass);
        } else {
            anchorTag.classList.add('share-selected-text-btn', 'share-selected-text-btn-' + anchorType);
        }

        if (customIconClass) {
            anchorIcon.classList.add('' + customIconClass);
        } else {
            anchorIcon.classList.add('icon-sst-' + anchorType, 'fa', 'fa-' + anchorType);
        }

        anchorIcon.style.pointerEvents = 'none';
        anchorTag.addEventListener('click', function (e) {
            e.preventDefault();
            var windowFeatures = 'status=no,menubar=no,location=no,scrollbars=no,width=720,height=540';
            var url = e.target.href;
            window.open(url, 'Share this post', windowFeatures);
        });

        anchorTag.href = generateSocialUrl(anchorType, selected.text ? selected.text : '');
        anchorTag.appendChild(anchorIcon);
        return anchorTag;
    };

    var generateTooltip = function generateTooltip() {
        var body = document.querySelector('body');
        var mainDiv = document.createElement('DIV');
        var btnContainer = document.createElement('DIV');

        mainDiv.classList.add('share-selected-text-main-container');
        btnContainer.classList.add('share-selected-text-inner');

        if (parameters.tooltipClass) {
            btnContainer.classList.add(parameters.tooltipClass);
        }

        mainDiv.style.height = TOOLTIP_HEIGHT + 'px';
        mainDiv.style.top = 0;
        mainDiv.style.left = 0;

        Array.prototype.forEach.call(parameters.buttons, function (btn) {
            var aTag = generateAnchorTag(btn);
            btnContainer.appendChild(aTag);
        });

        mainDiv.appendChild(btnContainer);
        body.appendChild(mainDiv);

        return mainDiv;
    };

    var getSelectedText = function getSelectedText() {
        var text = '';
        var selection = undefined;

        if (window.getSelection) {
            selection = window.getSelection();
            text = selection.toString();
        } else if (document.selection && document.selection.type !== 'Control') {
            selection = document.selection.createRange();
            text = selection.text;
        }

        return {
            selection: selection,
            text: text
        };
    };

    var shareTooltip = function shareTooltip() {
        selected = getSelectedText();

        if (selected.text.length) {
            var oRange = selected.selection.getRangeAt(0);
            var oRect = oRange.getBoundingClientRect();
            updateTooltip(oRect);
        } else {
            hideTooltip();
        }
    };

    exports.shareSelectedText = function (element, args) {
        var elt = document.querySelectorAll(element);

        parameters = extend({
            tooltipClass: '',
            sanitize: true,
            buttons: [SOCIAL.twitter, SOCIAL.buffer],
            anchorsClass: '',
            twitterUsername: '',
            twitterHashtag: '',
            facebookAppID: '',
            facebookDisplayMode: FACEBOOK_DISPLAY_MODES.page,
            tooltipTimeout: TOOLTIP_TIMEOUT
        }, args);

        tooltip = generateTooltip();

        Array.prototype.forEach.call(elt, function (el) {
            el.addEventListener('mouseup', function () {
                shareTooltip();
            });
        });
    };
})(window);

/*global jQuery, shareSelectedText*/
if (window.jQuery) {
    (function ($, shareSelected) {
        'use strict';

        var shareSelectedify = function shareSelectedify(el, options) {
            shareSelected(el, options);
        };

        $.fn.shareSelectedText = function (options) {
            return shareSelectedify(this.selector, options);
        };
    })(jQuery, shareSelectedText);
}<commit_msg>Added ability to hastag<commit_after>'use strict';

(function (exports) {
    'use strict';

    var getPageUrl = function getPageUrl() {
        if (document.querySelector('meta[property="og:url"]') && document.querySelector('meta[property="og:url"]').getAttribute('content')) {
            return document.querySelector('meta[property="og:url"]').getAttribute('content');
        }

        return window.location.href;
    };

    // constants
    var TOOLTIP_HEIGHT = 50;
    var FACTOR = 1.33;
    var TWITTER_LIMIT_LENGTH = 140;
    var TWITTER_URL_LENGTH_COUNT = 24;
    var TWITTER_QUOTES = 2;
    var TWITTER_DOTS = 3;
    var TOOLTIP_TIMEOUT = 250;
    var FACEBOOK_DISPLAY_MODES = {
        popup: 'popup',
        page: 'page'
    };

    var REAL_TWITTER_LIMIT = TWITTER_LIMIT_LENGTH - TWITTER_URL_LENGTH_COUNT - TWITTER_QUOTES - TWITTER_DOTS;

    var SOCIAL = {
        twitter: 'twitter',
        buffer: 'buffer',
        digg: 'digg',
        linkedin: 'linkedin',
        stumbleupon: 'stumbleupon',
        reddit: 'reddit',
        tumblr: 'tumblr',
        facebook: 'facebook'
    };

    var NO_START_WITH = /[ .,!?/\\\+\-=*£$€:~§%^µ)(|@"{}&#><_]/g;
    var NO_ENDS_WITH = /[ ,/\\\+\-=*£$€:~§%^µ)(|@"{}&#><_]/g;
    var PAGE_URL = getPageUrl();

    // globals
    var tooltip = undefined;
    var parameters = undefined;
    var selected = {};

    var extend = function extend(out) {
        out = out || {};

        for (var i = 1; i < arguments.length; i += 1) {
            if (arguments[i]) {
                for (var key in arguments[i]) {
                    if (arguments[i].hasOwnProperty(key)) {
                        out[key] = arguments[i][key];
                    }
                }
            }
        }
        return out;
    };

    var hideTooltip = function hideTooltip() {
        tooltip.classList.remove('active');
    };

    var showTooltip = function showTooltip() {
        tooltip.classList.add('active');
    };

    var smartSanitize = function smartSanitize(text) {
        while (text.length && text[0].match(NO_START_WITH)) {
            text = text.substring(1, text.length);
        }

        while (text.length && text[text.length - 1].match(NO_ENDS_WITH)) {
            text = text.substring(0, text.length - 1);
        }

        return text;
    };

    var sanitizeText = function sanitizeText(text) {
        var sociaType = arguments.length <= 1 || arguments[1] === undefined ? '' : arguments[1];

        var author = '';
        var hashtag = '';
        var tweetLimit = REAL_TWITTER_LIMIT;

        if (!text) {
            return '';
        }

        if (parameters.twitterUsername && sociaType === SOCIAL.twitter) {
            author = ' via @' + parameters.twitterUsername;
            tweetLimit = REAL_TWITTER_LIMIT - author.length;
        }

        if (parameters.twitterHashtag && sociaType === SOCIAL.twitter) {
            hashtag = ' #' + parameters.twitterHashtag;
            tweetLimit = tweetLimit - hashtag.length;
        }

        if (text.length > REAL_TWITTER_LIMIT) {
            text = text.substring(0, tweetLimit);
            text = text.substring(0, text.lastIndexOf(' ')) + '...';
        } else {
            text = text.substring(0, tweetLimit + TWITTER_DOTS);
        }

        return smartSanitize(text);
    };

    var generateSocialUrl = function generateSocialUrl(socialType, text) {
        if (parameters.sanitize) {
            text = sanitizeText(text, socialType);
        } else {
            text = smartSanitize(text);
        }

        var twitterUrl = 'https://twitter.com/intent/tweet?url=' + PAGE_URL + '&text="' + text + '"';

        if (parameters.twitterUsername && parameters.twitterUsername.length) {
            twitterUrl += '&via=' + parameters.twitterUsername;
        }

        if (parameters.twitterHashtag && parameters.twitterHashtag.length) {
            twitterUrl += '&hashtags=' + parameters.twitterHashtag;
        }

        var facebookUrl = 'https://facebook.com/sharer.php?u='+ PAGE_URL +'&quote=' + text ;

        var urls = {
            twitter: twitterUrl,
            buffer: 'https://buffer.com/add?text="' + text + '"&url=' + PAGE_URL,
            digg: 'http://digg.com/submit?url=' + PAGE_URL + '&title=' + text,
            linkedin: 'https://www.linkedin.com/shareArticle?url=' + PAGE_URL + '&title=' + text,
            stumbleupon: 'http://www.stumbleupon.com/submit?url=' + PAGE_URL + '&title=' + text,
            reddit: 'https://reddit.com/submit?url=' + PAGE_URL + '&title=' + text,
            tumblr: 'https://www.tumblr.com/widgets/share/tool?canonicalUrl=' + PAGE_URL + '&caption=' + text,
            facebook: facebookUrl
        };

        if (urls.hasOwnProperty(socialType)) {
            return urls[socialType];
        }

        return '';
    };

    var updateTooltip = function updateTooltip(rect) {
        var actualPosition = document.documentElement.scrollTop || document.body.scrollTop;
        var body = document.querySelector('body');

        tooltip.style.top = actualPosition + rect.top - TOOLTIP_HEIGHT * FACTOR + 'px';
        tooltip.style.left = rect.left + rect.width / 2 - body.getBoundingClientRect().width / 2 + 'px';

        Array.prototype.forEach.call(parameters.buttons, function (btn) {
            tooltip.querySelector('.share-selected-text-btn-' + btn).href = generateSocialUrl(btn, selected.text);
        });

        window.setTimeout(function () {
            showTooltip();
        }, parameters.tooltipTimeout);
    };

    var generateAnchorTag = function generateAnchorTag(anchorType) {
        var customIconClass = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];

        var anchorTag = document.createElement('A');
        var anchorIcon = document.createElement('i');

        if (parameters.anchorsClass) {
            anchorTag.classList.add('share-selected-text-btn', 'share-selected-text-btn-' + anchorType, '' + parameters.anchorsClass);
        } else {
            anchorTag.classList.add('share-selected-text-btn', 'share-selected-text-btn-' + anchorType);
        }

        if (customIconClass) {
            anchorIcon.classList.add('' + customIconClass);
        } else {
            anchorIcon.classList.add('icon-sst-' + anchorType, 'fa', 'fa-' + anchorType);
        }

        anchorIcon.style.pointerEvents = 'none';
        anchorTag.addEventListener('click', function (e) {
            e.preventDefault();
            var windowFeatures = 'status=no,menubar=no,location=no,scrollbars=no,width=720,height=540';
            var url = e.target.href;
            window.open(url, 'Share this post', windowFeatures);
        });

        anchorTag.href = generateSocialUrl(anchorType, selected.text ? selected.text : '');
        anchorTag.appendChild(anchorIcon);
        return anchorTag;
    };

    var generateTooltip = function generateTooltip() {
        var body = document.querySelector('body');
        var mainDiv = document.createElement('DIV');
        var btnContainer = document.createElement('DIV');

        mainDiv.classList.add('share-selected-text-main-container');
        btnContainer.classList.add('share-selected-text-inner');

        if (parameters.tooltipClass) {
            btnContainer.classList.add(parameters.tooltipClass);
        }

        mainDiv.style.height = TOOLTIP_HEIGHT + 'px';
        mainDiv.style.top = 0;
        mainDiv.style.left = 0;

        Array.prototype.forEach.call(parameters.buttons, function (btn) {
            var aTag = generateAnchorTag(btn);
            btnContainer.appendChild(aTag);
        });

        mainDiv.appendChild(btnContainer);
        body.appendChild(mainDiv);

        return mainDiv;
    };

    var getSelectedText = function getSelectedText() {
        var text = '';
        var selection = undefined;

        if (window.getSelection) {
            selection = window.getSelection();
            text = selection.toString();
        } else if (document.selection && document.selection.type !== 'Control') {
            selection = document.selection.createRange();
            text = selection.text;
        }

        return {
            selection: selection,
            text: text
        };
    };

    var shareTooltip = function shareTooltip() {
        selected = getSelectedText();

        if (selected.text.length) {
            var oRange = selected.selection.getRangeAt(0);
            var oRect = oRange.getBoundingClientRect();
            updateTooltip(oRect);
        } else {
            hideTooltip();
        }
    };

    exports.shareSelectedText = function (element, args) {
        var elt = document.querySelectorAll(element);

        parameters = extend({
            tooltipClass: '',
            sanitize: true,
            buttons: [SOCIAL.twitter, SOCIAL.buffer],
            anchorsClass: '',
            twitterUsername: '',
            twitterHashtag: '',
            facebookAppID: '',
            facebookDisplayMode: FACEBOOK_DISPLAY_MODES.page,
            tooltipTimeout: TOOLTIP_TIMEOUT
        }, args);

        tooltip = generateTooltip();

        Array.prototype.forEach.call(elt, function (el) {
            el.addEventListener('mouseup', function () {
                shareTooltip();
            });
        });
    };
})(window);

/*global jQuery, shareSelectedText*/
if (window.jQuery) {
    (function ($, shareSelected) {
        'use strict';

        var shareSelectedify = function shareSelectedify(el, options) {
            shareSelected(el, options);
        };

        $.fn.shareSelectedText = function (options) {
            return shareSelectedify(this.selector, options);
        };
    })(jQuery, shareSelectedText);
}<|endoftext|>


<commit_before>/*
 * Copyright (c) 2008-2014 The Open Source Geospatial Foundation
 *
 * Published under the BSD license.
 * See https://github.com/geoext/geoext2/blob/master/license.txt for the full
 * text of the license.
 */

/*
 * @include OpenLayers/Feature/Vector.js
 * @include OpenLayers/Geometry.js
 * @include GeoExt/panel/Map.js
 * @requires GeoExt/Version.js
 */

/**
 * A popup is a specialized window that supports anchoring to a particular
 * location in a {@link GeoExt.panel.Map MapPanel}.
 *
 * When a popup is anchored to a {@link #location}, that means that
 * the popup will visibly point to the location on the map,
 * and move accordingly when the map is panned or zoomed.
 *
 * When you use the popup in an application, make sure to include the proper
 * stylesheet depending on the Ext theme that you use: `popup-classic.css`,
 * `popup-access.css`, 'popup-gray.css` or `popup-neptune.css`.
 *
 * Example:
 *
 *     var popup = Ext.create('GeoExt.window.Popup', {
 *         title: "My Popup",
 *         location: feature,
 *         width: 200,
 *         html: "
Popup content
",
 *         collapsible: true
 *     });
 *
 * Or create it via `xtype` declaration:
 *
 * Example:
 *
 *     var popup = {
 *         xtype: 'gx_popup',
 *         title: "My Popup",
 *         location: feature,
 *         width: 200,
 *         html: "
Popup content
",
 *         collapsible: true
 *     };
 */
Ext.define('GeoExt.window.Popup', {
    extend: 'Ext.window.Window',
    alias: 'widget.gx_popup',
    alternateClassName : 'GeoExt.Popup',
    requires: [
        'GeoExt.Version'
    ],

    /*
     * Some Ext.Window defaults need to be overriden here
     * because some Ext.Window behavior is not currently supported.
     */

    /**
     * Whether the popup is currently inside the map viewport.
     *
     * @property {Boolean} insideViewport
     * @private
     */
    insideViewport: null,

    /**
     * Animate the transition when the panel is collapsed. Collapsing animation
     * is not supported yet for popups.
     *
     * @property {Boolean} animCollapse
     * @private
     */
    animCollapse: false,

    /**
     * Enable dragging of this Panel. Because the popup defaults to being
     * anchored, and anchored popups should not be draggable.
     *
     * @property {Boolean} draggable
     * @private
     */
    draggable: false,

    /**
     * Give the popup window a shadow. Because shadows are not supported yet for
     * popups (the shadow does not look good with the anchor).
     *
     * @property {Boolean} shadow
     * @private
     */
    shadow: false,

    /**
     * The popup should have a "unpin" tool that unanchors it from
     * its location.
     *
     * @cfg {Boolean} unpinnable
     */
    unpinnable: true,

    /**
     * The map this popup will be anchored to (only required if `anchored`
     * is set to `true` and the map cannot be derived from the `location`'s
     * layer).
     *
     * @cfg {GeoExt.panel.Map/OpenLayers.Map} map
     */
    map: null,

    /**
     * The popup begins anchored to its location.
     *
     * @cfg {Boolean} anchored
     */
    anchored: true,

    /**
     * The popup should pan the map so that the popup is fully in view when it
     * is rendered.
     *
     * @cfg {Boolean} panIn
     */
    panIn: true,

    /**
     * A location for this popup's anchor.
     *
     * @cfg {OpenLayers.Feature.Vector/OpenLayers.LonLat/OpenLayers.Pixel} location
     */
    location: null,

    /**
     * CSS class name for the popup DOM elements.
     *
     * @property {String} popupCls
     * @private
     */
    popupCls: "gx-popup",

    /**
     * CSS class name for the popup's anchor.
     *
     * @cfg {String} ancCls
     */
    ancCls: null,

    /**
     * Controls the anchor position for the popup. If set to `auto`, the anchor
     * will be positioned on the top or the bottom of the window, minimizing map
     * movement. Supported values are `bottom-left`, `bottom-right`, `top-left`,
     * `top-right` or `auto`.
     *
     * @cfg {String} anchorPosition
     */
    anchorPosition: "auto",

    initComponent: function() {
        if(this.map instanceof GeoExt.panel.Map) {
            this.map = this.map.map;
        }
        if(!this.map && this.location instanceof OpenLayers.Feature.Vector &&
                                                        this.location.layer) {
            this.map = this.location.layer.map;
        }
        if (this.location instanceof OpenLayers.Feature.Vector) {
            this.location = this.location.geometry;
        }
        if (this.location instanceof OpenLayers.Geometry) {
            if (typeof this.location.getCentroid == "function") {
                this.location = this.location.getCentroid();
            }
            this.location = this.location.getBounds().getCenterLonLat();
        } else if (this.location instanceof OpenLayers.Pixel) {
            this.location = this.map.getLonLatFromViewPortPx(this.location);
        } else {
            this.anchored = false;
        }

        var mapExtent =  this.map.getExtent();
        if (mapExtent && this.location) {
            this.insideViewport = mapExtent.containsLonLat(this.location);
        }

        if(this.anchored) {
            this.addAnchorEvents();
        }

        this.elements += ',anc';

        this.callParent(arguments);
    },

    /**
     * The "onRender" listener of this component.
     * Executes when the popup is rendered and creates the anchor div
     *
     * @param {Object} ct
     * @param {Object} position
     * @private
     */
    onRender: function(ct, position) {
        this.callParent(arguments);
        this.addCls(this.popupCls);
        this.ancCls = this.popupCls + "-anc";

        //create anchor dom element.
        //this.createElement("anc", this.el.dom);
        var dh = Ext.core.DomHelper; // create shorthand alias
        // specification the anchor div
        var spec = {
            tag: 'div',
            cls: this.ancCls
        };

        var ancDiv = dh.append(
            this.el.dom, // the context element
            spec      // the specification object
        );
        this.anc = Ext.get(ancDiv);
    },

    /**
     * Initializes the tools on the popup.  In particular it adds the 'unpin'
     * tool if the popup is unpinnable.
     *
     * @private
     */
    initTools : function() {
        if(this.unpinnable) {
            if (!this.tools) {
                this.tools = [];
            }
            this.tools.push({
                type:'unpin',
                handler: Ext.bind(this.unanchorPopup, this, [])
            });
        }
        this.callParent(arguments);
    },

    /**
     * Override.
     *
     * @inheritdoc
     * @private
     */
    show: function() {
        this.callParent(arguments);
        if(this.anchored) {
            this.position();
            if(this.panIn && !this._mapMove) {
                this.panIntoView();
            }
        }
    },

    /**
     * Override.
     *
     * @inheritdoc
     * @private
     */
    maximize: function() {
        if(!this.maximized && this.anc) {
            this.unanchorPopup();
        }
        this.callParent(arguments);
    },

    /**
     * Sets the size of the popup, taking into account the size of the anchor.
     *
     * @param {Integer} w the width to apply.
     * @param {Integer} h the height to apply.
     */
    setSize: function(w, h) {
        if(this.anc) {
            var ancSize = this.anc.getSize();

            if(typeof w == 'object') {
                h = w.height - ancSize.height;
                w = w.width;
            } else if(!isNaN(h)){
                h = h - ancSize.height;
            }
        }
        this.callParent([w,h]);
    },

    /**
     * Positions the popup relative to its current location.
     *
     * @private
     */
    position: function() {
        if(this._mapMove === true) {
            this.insideViewport = this.map.getExtent().containsLonLat(this.location);
            if(this.insideViewport !== this.isVisible()) {
                this.setVisible(this.insideViewport);
            }
        }

        if(this.isVisible()) {
            var locationPx = this.map.getPixelFromLonLat(this.location),
                mapBox = Ext.fly(this.map.div).getBox(true),
                top = locationPx.y + mapBox.y,
                left = locationPx.x + mapBox.x,
                elSize = this.el.getSize(),
                ancSize = this.anc.getSize(),
                ancPos = this.anchorPosition;

            if (ancPos.indexOf("right") > -1 || locationPx.x > mapBox.width / 2) {
                // right
                this.anc.addCls("right");
                var ancRight = this.el.getX(true) + elSize.width -
                               this.anc.getX(true) - ancSize.width;
                left -= elSize.width - ancRight - ancSize.width / 2;
            } else {
                // left
                this.anc.removeCls("right");
                var ancLeft = this.anc.getLeft(true);
                left -= ancLeft + ancSize.width / 2;
            }

            if (ancPos.indexOf("bottom") > -1 || locationPx.y > mapBox.height / 2) {
                // bottom
                this.anc.removeCls("top");
                // position the anchor
                var popupHeight = this.getHeight();
                if (isNaN(popupHeight) === false) {
                    this.anc.setTop((popupHeight-1) + "px");
                }

                top -= elSize.height + ancSize.height;

            } else {
                // top
                this.anc.addCls("top");
                // remove eventually set top property (bottom-case)
                this.anc.setTop("");
                top += ancSize.height; // ok
            }

            this.setPosition(left, top);
        }
    },

    /**
     * Unanchors a popup from its location. This removes the popup from its
     * MapPanel and adds it to the page body.
     *
     * @private
     */
    unanchorPopup: function() {
        this.removeAnchorEvents();

        //make the window draggable
        this.draggable = true;
        this.header.addCls("x-window-header-draggable");
        var ddDelegate = '#' + Ext.escapeId(this.header.id),
            ddConfig = Ext.applyIf({
                el: this.el,
                delegate: ddDelegate,
                constrain: this.constrain,
                // `constrainHeader` in an Ext.window.Window maps
                // to `constrainDelegate` of the Ext.util.ComponentDragger
                constrainDelegate: this.constrainHeader ? ddDelegate : false,
                constrainTo: this.constrainTo
            }, this.draggable);
        this.dd = new Ext.util.ComponentDragger(this, ddConfig);

        //remove anchor
        this.anc.remove();
        this.anc = null;

        //hide unpin tool
        this.tools.unpin.hide();
    },

    /**
     * Pans the MapPanel's map so that an anchored popup can come entirely
     * into view, with padding specified as per normal OpenLayers. Map popup
     * padding.
     *
     * @private
     */
    panIntoView: function() {
        var mapBox = Ext.fly(this.map.div).getBox(true);

        //assumed viewport takes up whole body element of map panel
        var popupPos =  this.getPosition(true);
        popupPos[0] -= mapBox.x;
        popupPos[1] -= mapBox.y;

        var panelSize = [mapBox.width, mapBox.height]; // [X,Y]

        var popupSize = this.getSize();

        var newPos = [popupPos[0], popupPos[1]];

        //For now, using native OpenLayers popup padding.  This may not be ideal.
        var padding = this.map.paddingForPopups;

        // X
        if(popupPos[0] < padding.left) {
            newPos[0] = padding.left;
        } else if(popupPos[0] + popupSize.width > panelSize[0] - padding.right) {
            newPos[0] = panelSize[0] - padding.right - popupSize.width;
        }

        // Y
        if(popupPos[1] < padding.top) {
            newPos[1] = padding.top;
        } else if(popupPos[1] + popupSize.height > panelSize[1] - padding.bottom) {
            newPos[1] = panelSize[1] - padding.bottom - popupSize.height;
        }

        var dx = popupPos[0] - newPos[0];
        var dy = popupPos[1] - newPos[1];

        this.map.pan(dx, dy);
    },

    /**
     * Called during map movements; does reposition the popup.
     *
     * @private
     */
    onMapMove: function() {
        if (!(this.hidden && this.insideViewport)){
            this._mapMove = true;
            this.position();
            delete this._mapMove;
        }
    },

    /**
     * Adds appropriate anchor events.
     *
     * @private
     */
    addAnchorEvents: function() {
        this.map.events.on({
            "move" : this.onMapMove,
            scope : this
        });

        this.on({
            "resize": this.position,
            scope: this
        });
    },

    /**
     * Removes previously bound anchor events.
     *
     * @private
     */
    removeAnchorEvents: function() {
        //stop position with location
        this.map.events.un({
            "move" : this.onMapMove,
            scope : this
        });

        this.un("resize", this.position, this);
    },

    /**
     * Cleanup events before destroying the popup.
     *
     * @private
     */
    beforeDestroy: function() {
        if(this.anchored) {
            this.removeAnchorEvents();
        }
        this.callParent(arguments);
    }

});
<commit_msg>Fixed #1 Border problems in geoext Fixed also problem with anchor CSS.<commit_after>/*
 * Copyright (c) 2008-2014 The Open Source Geospatial Foundation
 *
 * Published under the BSD license.
 * See https://github.com/geoext/geoext2/blob/master/license.txt for the full
 * text of the license.
 */

/*
 * @include OpenLayers/Feature/Vector.js
 * @include OpenLayers/Geometry.js
 * @include GeoExt/panel/Map.js
 * @requires GeoExt/Version.js
 */

/**
 * A popup is a specialized window that supports anchoring to a particular
 * location in a {@link GeoExt.panel.Map MapPanel}.
 *
 * When a popup is anchored to a {@link #location}, that means that
 * the popup will visibly point to the location on the map,
 * and move accordingly when the map is panned or zoomed.
 *
 * When you use the popup in an application, make sure to include the proper
 * stylesheet depending on the Ext theme that you use: `popup-classic.css`,
 * `popup-access.css`, 'popup-gray.css` or `popup-neptune.css`.
 *
 * Example:
 *
 *     var popup = Ext.create('GeoExt.window.Popup', {
 *         title: "My Popup",
 *         location: feature,
 *         width: 200,
 *         html: "
Popup content
",
 *         collapsible: true
 *     });
 *
 * Or create it via `xtype` declaration:
 *
 * Example:
 *
 *     var popup = {
 *         xtype: 'gx_popup',
 *         title: "My Popup",
 *         location: feature,
 *         width: 200,
 *         html: "
Popup content
",
 *         collapsible: true
 *     };
 */
Ext.define('GeoExt.window.Popup', {
    extend: 'Ext.window.Window',
    alias: 'widget.gx_popup',
    alternateClassName : 'GeoExt.Popup',
    requires: [
        'GeoExt.Version'
    ],

    /*
     * Some Ext.Window defaults need to be overriden here
     * because some Ext.Window behavior is not currently supported.
     */

    /**
     * Whether the popup is currently inside the map viewport.
     *
     * @property {Boolean} insideViewport
     * @private
     */
    insideViewport: null,

    /**
     * Animate the transition when the panel is collapsed. Collapsing animation
     * is not supported yet for popups.
     *
     * @property {Boolean} animCollapse
     * @private
     */
    animCollapse: false,

    /**
     * Enable dragging of this Panel. Because the popup defaults to being
     * anchored, and anchored popups should not be draggable.
     *
     * @property {Boolean} draggable
     * @private
     */
    draggable: false,

    /**
     * Give the popup window a shadow. Because shadows are not supported yet for
     * popups (the shadow does not look good with the anchor).
     *
     * @property {Boolean} shadow
     * @private
     */
    shadow: false,

    /**
     * The popup should have a "unpin" tool that unanchors it from
     * its location.
     *
     * @cfg {Boolean} unpinnable
     */
    unpinnable: true,

    /**
     * The map this popup will be anchored to (only required if `anchored`
     * is set to `true` and the map cannot be derived from the `location`'s
     * layer).
     *
     * @cfg {GeoExt.panel.Map/OpenLayers.Map} map
     */
    map: null,

    /**
     * The popup begins anchored to its location.
     *
     * @cfg {Boolean} anchored
     */
    anchored: true,

    /**
     * The popup should pan the map so that the popup is fully in view when it
     * is rendered.
     *
     * @cfg {Boolean} panIn
     */
    panIn: true,

    /**
     * A location for this popup's anchor.
     *
     * @cfg {OpenLayers.Feature.Vector/OpenLayers.LonLat/OpenLayers.Pixel} location
     */
    location: null,

    /**
     * CSS class name for the popup DOM elements.
     *
     * @property {String} popupCls
     * @private
     */
    popupCls: "gx-popup",

    /**
     * CSS class name for the popup's anchor.
     *
     * @cfg {String} ancCls
     */
    ancCls: null,

    /**
     * Controls the anchor position for the popup. If set to `auto`, the anchor
     * will be positioned on the top or the bottom of the window, minimizing map
     * movement. Supported values are `bottom-left`, `bottom-right`, `top-left`,
     * `top-right` or `auto`.
     *
     * @cfg {String} anchorPosition
     */
    anchorPosition: "auto",

    initComponent: function() {
        if(this.map instanceof GeoExt.panel.Map) {
            this.map = this.map.map;
        }
        if(!this.map && this.location instanceof OpenLayers.Feature.Vector &&
                                                        this.location.layer) {
            this.map = this.location.layer.map;
        }
        if (this.location instanceof OpenLayers.Feature.Vector) {
            this.location = this.location.geometry;
        }
        if (this.location instanceof OpenLayers.Geometry) {
            if (typeof this.location.getCentroid == "function") {
                this.location = this.location.getCentroid();
            }
            this.location = this.location.getBounds().getCenterLonLat();
        } else if (this.location instanceof OpenLayers.Pixel) {
            this.location = this.map.getLonLatFromViewPortPx(this.location);
        } else {
            this.anchored = false;
        }

        var mapExtent =  this.map.getExtent();
        if (mapExtent && this.location) {
            this.insideViewport = mapExtent.containsLonLat(this.location);
        }

        if(this.anchored) {
            this.addAnchorEvents();
        }
        this.elements += ',anc';

        this.callParent(arguments);
    },

    /**
     * The "onRender" listener of this component.
     * Executes when the popup is rendered and creates the anchor div
     *
     * @param {Object} ct
     * @param {Object} position
     * @private
     */
    onRender: function(ct, position) {
        this.callParent(arguments);
        this.addCls(this.popupCls);
        if (!this.ancCls) {
        	this.ancCls = this.popupCls + "-anc";
        }

        //create anchor dom element.
        //this.createElement("anc", this.el.dom);
        var dh = Ext.core.DomHelper; // create shorthand alias
        // specification the anchor div
        var spec = {
            tag: 'div',
            cls: this.ancCls
        };

        var ancDiv = dh.append(
            this.el.dom, // the context element
            spec      // the specification object
        );
        this.anc = Ext.get(ancDiv);
    },

    /**
     * Initializes the tools on the popup.  In particular it adds the 'unpin'
     * tool if the popup is unpinnable.
     *
     * @private
     */
    initTools : function() {
        if(this.unpinnable) {
            if (!this.tools) {
                this.tools = [];
            }
            this.tools.push({
                type:'unpin',
                handler: Ext.bind(this.unanchorPopup, this, [])
            });
        }
        this.callParent(arguments);
    },

    /**
     * Override.
     *
     * @inheritdoc
     * @private
     */
    show: function() {
        this.callParent(arguments);
        if(this.anchored) {
            this.position();
            if(this.panIn && !this._mapMove) {
                this.panIntoView();
            }
        }
    },

    /**
     * Override.
     *
     * @inheritdoc
     * @private
     */
    maximize: function() {
        if(!this.maximized && this.anc) {
            this.unanchorPopup();
        }
        this.callParent(arguments);
    },

    /**
     * Sets the size of the popup, taking into account the size of the anchor.
     *
     * @param {Integer} w the width to apply.
     * @param {Integer} h the height to apply.
     */
    setSize: function(w, h) {
        if(this.anc) {
            var ancSize = this.anc.getSize();

            if(typeof w == 'object') {
                h = w.height - ancSize.height;
                w = w.width;
            } else if(!isNaN(h)){
                h = h - ancSize.height;
            }
        }
        this.callParent([w,h]);
    },

    /**
     * Positions the popup relative to its current location.
     *
     * @private
     */
    position: function() {
        if(this._mapMove === true) {
            this.insideViewport = this.map.getExtent().containsLonLat(this.location);
            if(this.insideViewport !== this.isVisible()) {
                this.setVisible(this.insideViewport);
            }
        }

        if(this.isVisible()) {
            var locationPx = this.map.getPixelFromLonLat(this.location),
                mapBox = Ext.fly(this.map.div).getBox(true),
                top = locationPx.y + mapBox.y,
                left = locationPx.x + mapBox.x,
                elSize = this.el.getSize(),
                ancSize = this.anc.getSize(),
                ancPos = this.anchorPosition;

            if (ancPos.indexOf("right") > -1 || locationPx.x > mapBox.width / 2) {
                // right
                this.anc.addCls("right");
                var ancRight = this.el.getX(true) + elSize.width -
                               this.anc.getX(true) - ancSize.width;
                left -= elSize.width - ancRight - ancSize.width / 2;
            } else {
                // left
                this.anc.removeCls("right");
                var ancLeft = this.anc.getLeft(true);
                left -= ancLeft + this.getBorderPadding().beforeY + ancSize.width / 2;
            }

            if (ancPos.indexOf("bottom") > -1 || locationPx.y > mapBox.height / 2) {
                // bottom
                this.anc.removeCls("top");
                // position the anchor
                var popupHeight = this.getHeight();
                if (isNaN(popupHeight) === false) {
                    this.anc.setTop((popupHeight-1-this.getBorderPadding().afterX) + "px");
                }
                top -= elSize.height + ancSize.height;

            } else {
                // top
                this.anc.removeCls("top");
                // remove eventually set top property (bottom-case)
                this.anc.setTop(-ancSize.height-1-this.getBorderPadding().beforeX + "px");
                top += ancSize.height; // ok
            }

            this.setPosition(left, top);
        }
    },

    /**
     * Unanchors a popup from its location. This removes the popup from its
     * MapPanel and adds it to the page body.
     *
     * @private
     */
    unanchorPopup: function() {
        this.removeAnchorEvents();

        //make the window draggable
        this.draggable = true;
        this.header.addCls("x-window-header-draggable");
        var ddDelegate = '#' + Ext.escapeId(this.header.id),
            ddConfig = Ext.applyIf({
                el: this.el,
                delegate: ddDelegate,
                constrain: this.constrain,
                // `constrainHeader` in an Ext.window.Window maps
                // to `constrainDelegate` of the Ext.util.ComponentDragger
                constrainDelegate: this.constrainHeader ? ddDelegate : false,
                constrainTo: this.constrainTo
            }, this.draggable);
        this.dd = new Ext.util.ComponentDragger(this, ddConfig);

        //remove anchor
        this.anc.remove();
        this.anc = null;

        //hide unpin tool
        this.tools.unpin.hide();
    },

    /**
     * Pans the MapPanel's map so that an anchored popup can come entirely
     * into view, with padding specified as per normal OpenLayers. Map popup
     * padding.
     *
     * @private
     */
    panIntoView: function() {
        var mapBox = Ext.fly(this.map.div).getBox(true);

        //assumed viewport takes up whole body element of map panel
        var popupPos =  this.getPosition(true);
        popupPos[0] -= mapBox.x;
        popupPos[1] -= mapBox.y;

        var panelSize = [mapBox.width, mapBox.height]; // [X,Y]

        var popupSize = this.getSize();

        var newPos = [popupPos[0], popupPos[1]];

        //For now, using native OpenLayers popup padding.  This may not be ideal.
        var padding = this.map.paddingForPopups;

        // X
        if(popupPos[0] < padding.left) {
            newPos[0] = padding.left;
        } else if(popupPos[0] + popupSize.width > panelSize[0] - padding.right) {
            newPos[0] = panelSize[0] - padding.right - popupSize.width;
        }

        // Y
        if(popupPos[1] < padding.top) {
            newPos[1] = padding.top;
        } else if(popupPos[1] + popupSize.height > panelSize[1] - padding.bottom) {
            newPos[1] = panelSize[1] - padding.bottom - popupSize.height;
        }

        var dx = popupPos[0] - newPos[0];
        var dy = popupPos[1] - newPos[1];

        this.map.pan(dx, dy);
    },

    /**
     * Called during map movements; does reposition the popup.
     *
     * @private
     */
    onMapMove: function() {
        if (!(this.hidden && this.insideViewport)){
            this._mapMove = true;
            this.position();
            delete this._mapMove;
        }
    },

    /**
     * Adds appropriate anchor events.
     *
     * @private
     */
    addAnchorEvents: function() {
        this.map.events.on({
            "move" : this.onMapMove,
            scope : this
        });

        this.on({
            "resize": this.position,
            scope: this
        });
    },

    /**
     * Removes previously bound anchor events.
     *
     * @private
     */
    removeAnchorEvents: function() {
        //stop position with location
        this.map.events.un({
            "move" : this.onMapMove,
            scope : this
        });

        this.un("resize", this.position, this);
    },

    /**
     * Cleanup events before destroying the popup.
     *
     * @private
     */
    beforeDestroy: function() {
        if(this.anchored) {
            this.removeAnchorEvents();
        }
        this.callParent(arguments);
    }

});
<|endoftext|>