<commit_before>// this sets the background color of the master UIView (when there are no windows/tab groups on it)
Titanium.UI.setBackgroundColor('#000');

//
// create base UI tab and root window
//
var winmain = Titanium.UI.createWindow({
    title:'Less Rooster Home',
    backgroundColor:'#fff'
});

var labelGebouw = Titanium.UI.createLabel({
	color:'#999',
	text:'Gebouw',
	font:{fontSize:20,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:15,
	left:20
});

var pickerGebouw = Ti.UI.createPicker({
	top:10,
	left:200
});

var dataGebouw = [];
dataGebouw[0]=Ti.UI.createPickerRow({title:'De vest',custom_item:'v'});
dataGebouw[1]=Ti.UI.createPickerRow({title:'De ham',custom_item:'h'});
dataGebouw[2]=Ti.UI.createPickerRow({title:'Kruidtuin',custom_item:'k'});

// turn on the selection indicator (off by default)
pickerGebouw.selectionIndicator = true;

pickerGebouw.add(dataGebouw);

var labelVerdiep = Titanium.UI.createLabel({
	color:'#999',
	text:'Verdiep',
	font:{fontSize:20,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:40,
	left:20
});

var pickerVerdiep = Ti.UI.createPicker({
	top:25,
	left:200
});

var dataVerdiep = [];
dataVerdiep[0]=Ti.UI.createPickerRow({title:'Verdiep 1',custom_item:'v1'});
dataVerdiep[1]=Ti.UI.createPickerRow({title:'Verdiep 2',custom_item:'v2'});
dataVerdiep[2]=Ti.UI.createPickerRow({title:'Verdiep 3',custom_item:'v3'});

// turn on the selection indicator (off by default)
pickerVerdiep.selectionIndicator = true;

pickerVerdiep.add(dataVerdiep);

var labelLokaal = Titanium.UI.createLabel({
	color:'#999',
	text:'Lokaal',
	font:{fontSize:20,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:65,
	left:20
});

var pickerLokaal = Ti.UI.createPicker({
	top:50,
	left:200
});

var dataLokaal = [];
dataLokaal[0]=Ti.UI.createPickerRow({title:'Lokaal 1',custom_item:'l1'});
dataLokaal[1]=Ti.UI.createPickerRow({title:'Lokaal 2',custom_item:'l2'});
dataLokaal[2]=Ti.UI.createPickerRow({title:'Lokaal 3',custom_item:'l3'});

// turn on the selection indicator (off by default)
pickerLokaal.selectionIndicator = true;

pickerLokaal.add(dataLokaal);

pickerGebouw.addEventListener ('change',function()
  {
    labelGebouw.text = pickerGebouw.getSelectedRow(0).title;
    winmain.remove(pickerGebouw);
    winmain.add(pickerVerdiep);
    winmain.add(labelVerdiep);
  });

pickerVerdiep.addEventListener ('change',function()
  {
  	pickerVerdiep.top = 85;
    winmain.add(pickerVerdiep);
    labelVerdiep.text = pickerVerdiep.getSelectedRow(0).title;
    winmain.remove(pickerVerdiep);
    winmain.add(labelLokaal);
    winmain.add(pickerLokaal);
  });

pickerLokaal.addEventListener ('change',function()
  {
  	pickerLokaal.top = 115;
    labelLokaal.text = pickerLokaal.getSelectedRow(0).title;
    winmain.remove(pickerLokaal);
  });

winmain.add(labelGebouw);
winmain.add(pickerGebouw);

//lijst binnen halen met problemen op die locatie

var label1_4 = Titanium.UI.createLabel({
	text:'Bestaande Problemen',
	color: 'black',
	backgroundColor: 'grey',
	font:{fontSize:21,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:100,
	left:20
});

var nodeTable = Ti.UI.createTableView({
  	height: '80%',
  	width: '80%',
  	align: 'center',
  	top:130

});

winmain.add(label1_4);
winmain.add(nodeTable);

var xhr = Titanium.Network.createHTTPClient();

var nodes = [];

  xhr.onload = function()
  {
    //Just log the responseText for fun
    Ti.API.info(this.responseText);

    //We translate the json string into a neat object
    var response = JSON.parse(this.responseText);

    for (var i in response) {
      node = response[i];
      var row = Ti.UI.createTableViewRow({
        title : node.title,
        path : node.uri
      });
      nodes.push(row);
    }
    nodeTable.data = nodes;
  };

  xhr.onerror = function ()
  {
    Ti.API.info('Unable to fetch nodes from Drupal');
    nodeTable.data = nodes;
  };
// open the client
xhr.open('GET','http://www.vandenboschan2011.dreamhosters.com/drupal-7.10/api/node');

// send the data
xhr.send();

var xhrNode = Titanium.Network.createHTTPClient();
var nodePath;

//item van lijst wordt geselecteerd --> extra info tonen.
nodeTable.addEventListener("click", function(e){

	nodePath = e.rowData.path;

	// open the client
	xhrNode.open('GET',nodePath);

	// send the data
	xhrNode.send();

}
);

xhrNode.onload = function()
{
	//Just log the responseText for fun
	Ti.API.info(this.responseText);

	//We translate the json string into a neat object
	var response = JSON.parse(this.responseText);
	labelItem.text = response.title;
	//gebouw, verdiep en lokaal
	for (var i in response.field_gebouw) {
		//gebouw
      gebouw = response.field_gebouw[i];
      labelGebouw.text = "gebouw: "+gebouw[0].value;

      //verdiep
      verdiep = response.field_verdiep[i];
      labelVerdiep.text = "verdiep: "+verdiep[0].value;

      //lokaal
      lokaal = response.field_lokaal[i];
      labelLokaal.text = "lokaal: "+lokaal[0].value;
    }

/*
	//posten naar drupal service:
	var xhrTest = Titanium.Network.createHTTPClient();

	var loginObject = {
		"title" : labelItem.text,
		"type" : "node",
		"body" : {
			"und" : [{
				"value" : "testen posten via titanium",
				"summary" : "",
				"format" : "filtered_html",
				"safe_value" : "
Beamer wil niet meer afsluiten.
\n",
				"safe_summary" : ""
			}]
		},
		"field_gebouw" : {
			"und" : [{
				"value" : "" + gebouw[0].value + "",
				"format" : null,
				"safe_value" : "De vest"
			}]
		},
		"field_verdiep" : {
			"und" : [{
				"value" : "" + verdiep[0].value + "",
				"format" : null,
				"safe_value" : "3"
			}]
		},
		"field_lokaal" : {
			"und" : [{
				"value" : "" + lokaal[0].value + "",
				"format" : null,
				"safe_value" : "12"
			}]
		}
	}
	var loginString = JSON.stringify(loginObject);
	xhrTest.open('POST', 'http://www.vandenboschan2011.dreamhosters.com/drupal-7.10/api/node');
	// set the content-type header
	xhrTest.setRequestHeader('content-type', 'application/json');

	// send the data
	xhrTest.send(loginString);

----- einde posten naar drupal service!----
*/

};

xhrNode.onerror = function ()
{
	Ti.API.info('Unable to fetch nodes from Drupal');
};
var labelItem = Titanium.UI.createLabel({
	color: 'black',
	backgroundColor: 'grey',
	font:{fontSize:21,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:450,
	left:10
});
var labelGebouw = Titanium.UI.createLabel({
	color: 'black',
	backgroundColor: 'grey',
	font:{fontSize:21,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:500,
	left:10
});
var labelVerdiep = Titanium.UI.createLabel({
	color: 'black',
	backgroundColor: 'grey',
	font:{fontSize:21,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:550,
	left:10
});
var labelLokaal = Titanium.UI.createLabel({
	color: 'black',
	backgroundColor: 'grey',
	font:{fontSize:21,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:600,
	left:10

});
winmain.add(labelItem);
winmain.add(labelLokaal);
winmain.add(labelVerdiep);
winmain.add(labelGebouw);

//knop nieuw probleem
var button = Titanium.UI.createButton({
	title:'Nieuw Probleem',
	color: '#A90329',
	align: 'center',
	top:380,
});

button.addEventListener('click',function(e)
{
	//variables meesturen
	win2.titleProblem = labelItem.text;
	//window openen
	win2.open();
});

winmain.add(button);

winmain.open();

// create 2nd window
//
var win2 = Titanium.UI.createWindow({
    title:'Tab 2',
    backgroundColor:'#fff',
    url:'probleem.js',
});<commit_msg>komma weggehaald (moest er niet staan :) )<commit_after>// this sets the background color of the master UIView (when there are no windows/tab groups on it)
Titanium.UI.setBackgroundColor('#000');

//
// create base UI tab and root window
//
var winmain = Titanium.UI.createWindow({
    title:'Less Rooster Home',
    backgroundColor:'#fff'
});

var labelGebouw = Titanium.UI.createLabel({
	color:'#999',
	text:'Gebouw',
	font:{fontSize:20,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:15,
	left:20
});

var pickerGebouw = Ti.UI.createPicker({
	top:10,
	left:200
});

var dataGebouw = [];
dataGebouw[0]=Ti.UI.createPickerRow({title:'De vest',custom_item:'v'});
dataGebouw[1]=Ti.UI.createPickerRow({title:'De ham',custom_item:'h'});
dataGebouw[2]=Ti.UI.createPickerRow({title:'Kruidtuin',custom_item:'k'});

// turn on the selection indicator (off by default)
pickerGebouw.selectionIndicator = true;

pickerGebouw.add(dataGebouw);

var labelVerdiep = Titanium.UI.createLabel({
	color:'#999',
	text:'Verdiep',
	font:{fontSize:20,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:40,
	left:20
});

var pickerVerdiep = Ti.UI.createPicker({
	top:25,
	left:200
});

var dataVerdiep = [];
dataVerdiep[0]=Ti.UI.createPickerRow({title:'Verdiep 1',custom_item:'v1'});
dataVerdiep[1]=Ti.UI.createPickerRow({title:'Verdiep 2',custom_item:'v2'});
dataVerdiep[2]=Ti.UI.createPickerRow({title:'Verdiep 3',custom_item:'v3'});

// turn on the selection indicator (off by default)
pickerVerdiep.selectionIndicator = true;

pickerVerdiep.add(dataVerdiep);

var labelLokaal = Titanium.UI.createLabel({
	color:'#999',
	text:'Lokaal',
	font:{fontSize:20,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:65,
	left:20
});

var pickerLokaal = Ti.UI.createPicker({
	top:50,
	left:200
});

var dataLokaal = [];
dataLokaal[0]=Ti.UI.createPickerRow({title:'Lokaal 1',custom_item:'l1'});
dataLokaal[1]=Ti.UI.createPickerRow({title:'Lokaal 2',custom_item:'l2'});
dataLokaal[2]=Ti.UI.createPickerRow({title:'Lokaal 3',custom_item:'l3'});

// turn on the selection indicator (off by default)
pickerLokaal.selectionIndicator = true;

pickerLokaal.add(dataLokaal);

pickerGebouw.addEventListener ('change',function()
  {
    labelGebouw.text = pickerGebouw.getSelectedRow(0).title;
    winmain.remove(pickerGebouw);
    winmain.add(pickerVerdiep);
    winmain.add(labelVerdiep);
  });

pickerVerdiep.addEventListener ('change',function()
  {
  	pickerVerdiep.top = 85;
    winmain.add(pickerVerdiep);
    labelVerdiep.text = pickerVerdiep.getSelectedRow(0).title;
    winmain.remove(pickerVerdiep);
    winmain.add(labelLokaal);
    winmain.add(pickerLokaal);
  });

pickerLokaal.addEventListener ('change',function()
  {
  	pickerLokaal.top = 115;
    labelLokaal.text = pickerLokaal.getSelectedRow(0).title;
    winmain.remove(pickerLokaal);
  });

winmain.add(labelGebouw);
winmain.add(pickerGebouw);

//lijst binnen halen met problemen op die locatie

var label1_4 = Titanium.UI.createLabel({
	text:'Bestaande Problemen',
	color: 'black',
	backgroundColor: 'grey',
	font:{fontSize:21,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:100,
	left:20
});

var nodeTable = Ti.UI.createTableView({
  	height: '80%',
  	width: '80%',
  	align: 'center',
  	top:130

});

winmain.add(label1_4);
winmain.add(nodeTable);

var xhr = Titanium.Network.createHTTPClient();

var nodes = [];

  xhr.onload = function()
  {
    //Just log the responseText for fun
    Ti.API.info(this.responseText);

    //We translate the json string into a neat object
    var response = JSON.parse(this.responseText);

    for (var i in response) {
      node = response[i];
      var row = Ti.UI.createTableViewRow({
        title : node.title,
        path : node.uri
      });
      nodes.push(row);
    }
    nodeTable.data = nodes;
  };

  xhr.onerror = function ()
  {
    Ti.API.info('Unable to fetch nodes from Drupal');
    nodeTable.data = nodes;
  };
// open the client
xhr.open('GET','http://www.vandenboschan2011.dreamhosters.com/drupal-7.10/api/node');

// send the data
xhr.send();

var xhrNode = Titanium.Network.createHTTPClient();
var nodePath;

//item van lijst wordt geselecteerd --> extra info tonen.
nodeTable.addEventListener("click", function(e){

	nodePath = e.rowData.path;

	// open the client
	xhrNode.open('GET',nodePath);

	// send the data
	xhrNode.send();

}
);

xhrNode.onload = function()
{
	//Just log the responseText for fun
	Ti.API.info(this.responseText);

	//We translate the json string into a neat object
	var response = JSON.parse(this.responseText);
	labelItem.text = response.title;
	//gebouw, verdiep en lokaal
	for (var i in response.field_gebouw) {
		//gebouw
      gebouw = response.field_gebouw[i];
      labelGebouw.text = "gebouw: "+gebouw[0].value;

      //verdiep
      verdiep = response.field_verdiep[i];
      labelVerdiep.text = "verdiep: "+verdiep[0].value;

      //lokaal
      lokaal = response.field_lokaal[i];
      labelLokaal.text = "lokaal: "+lokaal[0].value;
    }

/*
	//posten naar drupal service:
	var xhrTest = Titanium.Network.createHTTPClient();

	var loginObject = {
		"title" : labelItem.text,
		"type" : "node",
		"body" : {
			"und" : [{
				"value" : "testen posten via titanium",
				"summary" : "",
				"format" : "filtered_html",
				"safe_value" : "
Beamer wil niet meer afsluiten.
\n",
				"safe_summary" : ""
			}]
		},
		"field_gebouw" : {
			"und" : [{
				"value" : "" + gebouw[0].value + "",
				"format" : null,
				"safe_value" : "De vest"
			}]
		},
		"field_verdiep" : {
			"und" : [{
				"value" : "" + verdiep[0].value + "",
				"format" : null,
				"safe_value" : "3"
			}]
		},
		"field_lokaal" : {
			"und" : [{
				"value" : "" + lokaal[0].value + "",
				"format" : null,
				"safe_value" : "12"
			}]
		}
	}
	var loginString = JSON.stringify(loginObject);
	xhrTest.open('POST', 'http://www.vandenboschan2011.dreamhosters.com/drupal-7.10/api/node');
	// set the content-type header
	xhrTest.setRequestHeader('content-type', 'application/json');

	// send the data
	xhrTest.send(loginString);

----- einde posten naar drupal service!----
*/

};

xhrNode.onerror = function ()
{
	Ti.API.info('Unable to fetch nodes from Drupal');
};
var labelItem = Titanium.UI.createLabel({
	color: 'black',
	backgroundColor: 'grey',
	font:{fontSize:21,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:450,
	left:10
});
var labelGebouw = Titanium.UI.createLabel({
	color: 'black',
	backgroundColor: 'grey',
	font:{fontSize:21,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:500,
	left:10
});
var labelVerdiep = Titanium.UI.createLabel({
	color: 'black',
	backgroundColor: 'grey',
	font:{fontSize:21,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:550,
	left:10
});
var labelLokaal = Titanium.UI.createLabel({
	color: 'black',
	backgroundColor: 'grey',
	font:{fontSize:21,fontFamily:'Helvetica Neue'},
	textAlign:'center',
	width:'auto',
	top:600,
	left:10

});
winmain.add(labelItem);
winmain.add(labelLokaal);
winmain.add(labelVerdiep);
winmain.add(labelGebouw);

//knop nieuw probleem
var button = Titanium.UI.createButton({
	title:'Nieuw Probleem',
	color: '#A90329',
	align: 'center',
	top:380,
});

button.addEventListener('click',function(e)
{
	//variables meesturen
	win2.titleProblem = labelItem.text;
	//window openen
	win2.open();
});

winmain.add(button);

winmain.open();

// create 2nd window
//
var win2 = Titanium.UI.createWindow({
    title:'Tab 2',
    backgroundColor:'#fff',
    url:'probleem.js'
});<|endoftext|>


<commit_before>beers = [
  {
    "name": "Belgian Tripel",
    "shortName": "BT",
    "done": false,
    "gone": false,
    "state": "Fermenting",
    "steps": [
      {
        "name": "Brew Day",
        "start": "01\/19\/2015",
        "end": "01\/19\/2015"
      },
      {
        "name": "Fermentation",
        "start": "01\/20\/2015",
        "end": "02\/20\/2015"
      },
      {
        "name": "Conditioning",
        "start": "02\/21\/2015",
        "end": "03\/14\/2015"
      }
    ]
  },
  {
    "name": "IPA",
    "shortName": "IPA",
    "done": false,
    "gone": false,
    "state": "Bottled and Conditioning",
    "steps": [
      {
        "name": "Brew Day",
        "start": "01\/19\/2015",
        "end": "01\/19\/2015"
      },
      {
        "name": "Fermentation",
        "start": "01\/20\/2015",
        "end": "01\/25\/2015"
      },
      {
        "name": "Dry Hop",
        "start": "01\/26\/2015",
        "end": "01\/26\/2015"
      },
      {
        "name": "Fermentation",
        "start": "01\/27\/2015",
        "end": "02\/06\/2015"
      },
      {
        "name": "Conditioning",
        "start": "02\/07\/2015",
        "end": "02\/20\/2015"
      }
    ]
  },
  {
    "name": "Watch Your Tongue Ginger Beer",
    "shortName": "GB",
    "done": true,
    "gone": true,
    "state": "All gone!",
    "steps": [
      {
        "name": "Brew Day",
        "start": "01\/06\/2015",
        "end": "01\/06\/2015"
      },
      {
        "name": "Fermentation",
        "start": "01\/07\/2015",
        "end": "01\/10\/2015"
      },
      {
        "name": "\"Cold Crash\"",
        "start": "01\/11\/2015",
        "end": "01\/18\/2015"
      },
      {
        "name": "Conditioning",
        "start": "01\/19\/2015",
        "end": "01\/20\/2015"
      }
    ]
  },
  {
    "name": "Winter Break Red",
    "shortName": "WBR",
    "done": true,
    "gone": true,
    "state": "All gone!",
    "steps": [
      {
        "name": "Brew Day",
        "start": "12\/17\/2014",
        "end": "12\/17\/2014"
      },
      {
        "name": "Fermentation",
        "start": "12\/18\/2014",
        "end": "01\/06\/2015"
      },
      {
        "name": "Conditioning",
        "start": "01\/07\/2015",
        "end": "01\/16\/2015"
      }
    ]
  },
  {
    "name": "Responsible Red",
    "shortName": "RR",
    "done": true,
    "gone": true,
    "state": "All gone!",
    "steps": [
      {
        "name": "Brew Day",
        "start": "11\/16\/2014",
        "end": "11\/16\/2014"
      },
      {
        "name": "Fermentation",
        "start": "11\/17\/2014",
        "end": "11\/30\/2014"
      },
      {
        "name": "Conditioning",
        "start": "12\/01\/2014",
        "end": "12\/14\/2014"
      }
    ]
  }
]
<commit_msg>Updating status of the IPA<commit_after>beers = [
  {
    "name": "Belgian Tripel",
    "shortName": "BT",
    "done": false,
    "gone": false,
    "state": "Fermenting",
    "steps": [
      {
        "name": "Brew Day",
        "start": "01\/19\/2015",
        "end": "01\/19\/2015"
      },
      {
        "name": "Fermentation",
        "start": "01\/20\/2015",
        "end": "02\/20\/2015"
      },
      {
        "name": "Conditioning",
        "start": "02\/21\/2015",
        "end": "03\/14\/2015"
      }
    ]
  },
  {
    "name": "IPA",
    "shortName": "IPA",
    "done": true,
    "gone": false,
    "state": "Bottled and Conditioning",
    "steps": [
      {
        "name": "Brew Day",
        "start": "01\/19\/2015",
        "end": "01\/19\/d2015"
      },
      {
        "name": "Fermentation",
        "start": "01\/20\/2015",
        "end": "01\/25\/2015"
      },
      {
        "name": "Dry Hop",
        "start": "01\/26\/2015",
        "end": "01\/26\/2015"
      },
      {
        "name": "Fermentation",
        "start": "01\/27\/2015",
        "end": "02\/06\/2015"
      },
      {
        "name": "Conditioning",
        "start": "02\/07\/2015",
        "end": "02\/17\/2015"
      }
    ]
  },
  {
    "name": "Watch Your Tongue Ginger Beer",
    "shortName": "GB",
    "done": true,
    "gone": true,
    "state": "All gone!",
    "steps": [
      {
        "name": "Brew Day",
        "start": "01\/06\/2015",
        "end": "01\/06\/2015"
      },
      {
        "name": "Fermentation",
        "start": "01\/07\/2015",
        "end": "01\/10\/2015"
      },
      {
        "name": "\"Cold Crash\"",
        "start": "01\/11\/2015",
        "end": "01\/18\/2015"
      },
      {
        "name": "Conditioning",
        "start": "01\/19\/2015",
        "end": "01\/20\/2015"
      }
    ]
  },
  {
    "name": "Winter Break Red",
    "shortName": "WBR",
    "done": true,
    "gone": true,
    "state": "All gone!",
    "steps": [
      {
        "name": "Brew Day",
        "start": "12\/17\/2014",
        "end": "12\/17\/2014"
      },
      {
        "name": "Fermentation",
        "start": "12\/18\/2014",
        "end": "01\/06\/2015"
      },
      {
        "name": "Conditioning",
        "start": "01\/07\/2015",
        "end": "01\/16\/2015"
      }
    ]
  },
  {
    "name": "Responsible Red",
    "shortName": "RR",
    "done": true,
    "gone": true,
    "state": "All gone!",
    "steps": [
      {
        "name": "Brew Day",
        "start": "11\/16\/2014",
        "end": "11\/16\/2014"
      },
      {
        "name": "Fermentation",
        "start": "11\/17\/2014",
        "end": "11\/30\/2014"
      },
      {
        "name": "Conditioning",
        "start": "12\/01\/2014",
        "end": "12\/14\/2014"
      }
    ]
  }
]
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react'
import Dropdown from 'shared/components/Dropdown'

const noop = () => {}

const SourceSelector = ({sources = [], source}) =>

InfluxDB Source Selector

      Using data from
      <Dropdown
        items={sources}
        buttonSize="btn-sm"
        menuClass="dropdown-astronaut"
        useAutoComplete={true}
        selected={sources.find(s => s.id === source.id).text || '(No values)'}
        onChoose={noop}
        toggleStyle={{width: '75%'}}
      />

const {arrayOf, shape} = PropTypes

SourceSelector.propTypes = {
  sources: arrayOf(shape()).isRequired,
  source: shape({}).isRequired,
}

export default SourceSelector
<commit_msg>Tweak styles<commit_after>import React, {PropTypes} from 'react'
import Dropdown from 'shared/components/Dropdown'

const noop = () => {}

const SourceSelector = ({sources = [], source}) =>

InfluxDB Source Selector

      Using data from
      <Dropdown
        items={sources}
        buttonSize="btn-sm"
        menuClass="dropdown-astronaut"
        useAutoComplete={true}
        selected={sources.find(s => s.id === source.id).text || '(No values)'}
        onChoose={noop}
        toggleStyle={{width: '50%'}}
      />

const {arrayOf, shape} = PropTypes

SourceSelector.propTypes = {
  sources: arrayOf(shape()).isRequired,
  source: shape({}).isRequired,
}

export default SourceSelector
<|endoftext|>


<commit_before>/* конфигурационный файл libuv
 * для компиляции в составе с inanity2
 * написан вручную, в основном переводом из uv.gyp
 */

var macros = ['HAVE_CONFIG_H', '_WIN32_WINNT=0x0502', 'EIO_STACKSIZE=262144', '_GNU_SOURCE'];

var includes = ['include', 'include/uv-private', 'src/ares/config_win32', 'src'];

exports.configureCompiler = function(objectFile, compiler) {
	// объектные файлы: <conf>/object
	var a = /^([^\/]+)\/([^\/]+)$/.exec(objectFile);
	compiler.configuration = a[1];
	compiler.setSourceFile(a[2].replace(/\./g, '/') + '.c');
	for ( var i = 0; i < macros.length; ++i)
		compiler.addMacro(macros[i]);
	for ( var i = 0; i < includes.length; ++i)
		compiler.addIncludeDir(includes[i]);
};

var objects = [
// general
'src/uv-common', 'src/ares/ares_cancel', 'src/ares/ares__close_sockets', 'src/ares/ares_data', 'src/ares/ares_destroy', 'src/ares/ares_expand_name', 'src/ares/ares_expand_string', 'src/ares/ares_fds', 'src/ares/ares_free_hostent', 'src/ares/ares_free_string', 'src/ares/ares_gethostbyaddr', 'src/ares/ares_gethostbyname', 'src/ares/ares__get_hostent', 'src/ares/ares_getnameinfo', 'src/ares/ares_getopt', 'src/ares/ares_getsock', 'src/ares/ares_init', 'src/ares/ares_library_init', 'src/ares/ares_llist', 'src/ares/ares_mkquery', 'src/ares/ares_nowarn', 'src/ares/ares_options', 'src/ares/ares_parse_aaaa_reply', 'src/ares/ares_parse_a_reply', 'src/ares/ares_parse_mx_reply', 'src/ares/ares_parse_ns_reply', 'src/ares/ares_parse_ptr_reply', 'src/ares/ares_parse_srv_reply', 'src/ares/ares_parse_txt_reply', 'src/ares/ares_process', 'src/ares/ares_query', 'src/ares/ares__read_line', 'src/ares/ares_search', 'src/ares/ares_send', 'src/ares/ares_strcasecmp', 'src/ares/ares_strdup',
	'src/ares/ares_strerror', 'src/ares/ares_timeout', 'src/ares/ares__timeval', 'src/ares/ares_version', 'src/ares/ares_writev', 'src/ares/bitncmp', 'src/ares/inet_net_pton', 'src/ares/inet_ntop',
	// windows
	'src/ares/windows_port', 'src/ares/ares_getenv', 'src/ares/ares_platform', 'src/win/async', 'src/win/cares', 'src/win/core', 'src/win/error', 'src/win/fs', 'src/win/fs-event', 'src/win/getaddrinfo', 'src/win/handle', 'src/win/loop-watcher', 'src/win/pipe', 'src/win/process', 'src/win/req', 'src/win/stream', 'src/win/tcp', 'src/win/tty', 'src/win/threadpool', 'src/win/threads', 'src/win/timer', 'src/win/udp', 'src/win/util', 'src/win/winapi', 'src/win/winsock', ];

exports.configureComposer = function(libraryFile, composer) {
	// файлы библиотек: <conf>/library
	var a = /^(([^\/]+)\/)([^\/]+)$/.exec(libraryFile);
	var confDir = a[1];
	composer.configuration = a[2];
	for ( var i = 0; i < objects.length; ++i)
		composer.addObjectFile(confDir + objects[i].replace(/\//g, '.'));
};
<commit_msg>libuv build configuration updated<commit_after>/* конфигурационный файл libuv
 * для компиляции в составе с inanity2
 * написан вручную, в основном переводом из uv.gyp
 */

var macros = ['HAVE_CONFIG_H', '_WIN32_WINNT=0x0502', 'EIO_STACKSIZE=262144', '_GNU_SOURCE'];

var includes = ['include', 'include/uv-private', 'src/ares/config_win32', 'src'];

exports.configureCompiler = function(objectFile, compiler) {
	// объектные файлы: <conf>/object
	var a = /^([^\/]+)\/([^\/]+)$/.exec(objectFile);
	compiler.configuration = a[1];
	compiler.setSourceFile(a[2].replace(/\./g, '/') + '.c');
	for ( var i = 0; i < macros.length; ++i)
		compiler.addMacro(macros[i]);
	for ( var i = 0; i < includes.length; ++i)
		compiler.addIncludeDir(includes[i]);
};

var objects = [
// general
'src/uv-common', 'src/ares/ares_cancel', 'src/ares/ares__close_sockets', 'src/ares/ares_data', 'src/ares/ares_destroy', 'src/ares/ares_expand_name', 'src/ares/ares_expand_string', 'src/ares/ares_fds', 'src/ares/ares_free_hostent', 'src/ares/ares_free_string', 'src/ares/ares_gethostbyaddr', 'src/ares/ares_gethostbyname', 'src/ares/ares__get_hostent', 'src/ares/ares_getnameinfo', 'src/ares/ares_getopt', 'src/ares/ares_getsock', 'src/ares/ares_init', 'src/ares/ares_library_init', 'src/ares/ares_llist', 'src/ares/ares_mkquery', 'src/ares/ares_nowarn', 'src/ares/ares_options', 'src/ares/ares_parse_aaaa_reply', 'src/ares/ares_parse_a_reply', 'src/ares/ares_parse_mx_reply', 'src/ares/ares_parse_ns_reply', 'src/ares/ares_parse_ptr_reply', 'src/ares/ares_parse_srv_reply', 'src/ares/ares_parse_txt_reply', 'src/ares/ares_process', 'src/ares/ares_query', 'src/ares/ares__read_line', 'src/ares/ares_search', 'src/ares/ares_send', 'src/ares/ares_strcasecmp', 'src/ares/ares_strdup',
	'src/ares/ares_strerror', 'src/ares/ares_timeout', 'src/ares/ares__timeval', 'src/ares/ares_version', 'src/ares/ares_writev', 'src/ares/bitncmp', 'src/ares/inet_net_pton', 'src/ares/inet_ntop',
	// windows
	'src/ares/windows_port', 'src/ares/ares_getenv', 'src/ares/ares_platform', 'src/win/async', 'src/win/cares', 'src/win/core', 'src/win/dl', 'src/win/error', 'src/win/fs', 'src/win/fs-event', 'src/win/getaddrinfo', 'src/win/handle', 'src/win/loop-watcher', 'src/win/pipe', 'src/win/thread', 'src/win/process', 'src/win/req', 'src/win/stream', 'src/win/tcp', 'src/win/tty', 'src/win/threadpool', 'src/win/timer', 'src/win/udp', 'src/win/util', 'src/win/winapi', 'src/win/winsock', ];

exports.configureComposer = function(libraryFile, composer) {
	// файлы библиотек: <conf>/library
	var a = /^(([^\/]+)\/)([^\/]+)$/.exec(libraryFile);
	var confDir = a[1];
	composer.configuration = a[2];
	for ( var i = 0; i < objects.length; ++i)
		composer.addObjectFile(confDir + objects[i].replace(/\//g, '.'));
};
<|endoftext|>


<commit_before>// ==============================
// Button
// ==============================

import { gradientVertical } from '../../../utils/css';
import { darken, fade, lighten } from '../../../utils/color';
import theme from '../../../theme';

// Common Styles
// ----------------

exports.common = {
	// Base Button
	// ----------------
	base: {
		'appearance': 'none',
		'background': 'none',
		'border': '1px solid transparent',
		'borderRadius': theme.borderRadius.default,
		'cursor': 'pointer',
		'display': 'inline-block',
		'fontWeight': theme.button.font.weight,
		'height': theme.component.height,
		'lineHeight': theme.component.lineHeight,
		'marginBottom': 0,
		'padding': `0 ${theme.button.paddingHorizontal}`,
		'outline': 0,
		'textAlign': 'center',
		'touchAction': 'manipulation',
		'userSelect': 'none',
		'verticalAlign': 'middle',
		'whiteSpace': 'nowrap',

		':hover': {
			color: theme.button.default.textColor,
			textDecoration: 'none',
		},
		':focus': {
			color: theme.button.default.textColor,
			textDecoration: 'none',
		},
	},
	// Block Display
	// ----------------
	block: {
		display: 'block',
		paddingLeft: 0,
		paddingRight: 0,
		width: '100%',
	},
	// Disabled
	// ----------------
	disabled: {
		opacity: 0.4,
		pointerEvents: 'none',
	},
	// Sizes
	// ----------------
	large: {
		fontSize: theme.font.size.large,
	},
	default: {
		fontSize: theme.font.size.default,
	},
	small: {
		fontSize: theme.font.size.small,
	},
	xsmall: {
		fontSize: theme.font.size.xsmall,
		lineHeight: '1.9',
		paddingLeft: '.66em',
		paddingRight: '.66em',
	},
};

// Fill Variant
// ----------------
function buttonFillVariant (textColor, bgColor) {
	const hoverStyles = {
		...gradientVertical(lighten(bgColor, 10), darken(bgColor, 5)),
		borderColor: `${darken(bgColor, 5)} ${darken(bgColor, 10)} ${darken(bgColor, 15)}`,
		boxShadow: '0 1px 0 rgba(0,0,0,0.1)',
		color: textColor,
		outline: 'none',
	};
	const focusStyles = {
		...gradientVertical(lighten(bgColor, 10), darken(bgColor, 5)),
		borderColor: `${darken(bgColor, 5)} ${darken(bgColor, 10)} ${darken(bgColor, 15)}`,
		boxShadow: `0 0 0 3px ${fade(bgColor, 25)}`,
		color: textColor,
		outline: 'none',
	};
	const activeStyles = {
		backgroundColor: darken(bgColor, 10),
		backgroundImage: 'none',
		borderColor: `${darken(bgColor, 25)} ${darken(bgColor, 15)} ${darken(bgColor, 10)}`,
		boxShadow: 'inset 0 1px 2px rgba(0, 0, 0, 0.1)',
	};
	return {
		base: {
			...gradientVertical(lighten(bgColor, 5), darken(bgColor, 10), bgColor),
			'borderColor': `${darken(bgColor, 10)} ${darken(bgColor, 20)} ${darken(bgColor, 25)}`,
			'boxShadow': 'inset 0 1px 0 rgba(255, 255, 255, 0.1)',
			'color': textColor,
			'fontWeight': 400,
			'textShadow': '0 -1px 0 rgba(0, 0, 0, 0.25)',

			':hover': hoverStyles,
			':focus': focusStyles,
			':active': activeStyles,
		},
		active: activeStyles,
	};
}
// TODO: This is pretty hacky, needs to be consolidated with the Variant() method
// above (needs more theme variables to be implemented though)
function buttonFillDefault () {
	const hoverStyles = {
		...gradientVertical('#fff', '#eee'),
		borderColor: `${darken(theme.input.border.color, 5)} ${darken(theme.input.border.color, 5)} ${darken(theme.input.border.color, 10)}`,
		boxShadow: '0 1px 0 rgba(0,0,0,0.1)',
		color: theme.color.text,
	};
	const focusStyles = {
		borderColor: theme.color.primary,
		boxShadow: `0 0 0 3px ${fade(theme.color.primary, 10)}`,
		color: theme.color.text,
		outline: 'none',
	};
	const activeStyles = {
		background: '#e6e6e6',
		borderColor: darken(theme.input.border.color, 10),
		boxShadow: 'inset 0 1px 2px rgba(0, 0, 0, 0.1)',
		color: theme.color.text,
	};
	return {
		base: {
			...gradientVertical('#fafafa', '#eaeaea'),
			'border': `1px solid ${theme.input.border.color}`,
			'borderColor': `${theme.input.border.color} ${darken(theme.input.border.color, 6)} ${darken(theme.input.border.color, 12)}`,
			'color': theme.color.text,
			'textShadow': '0 1px 0 white',

			':hover': hoverStyles,
			':focus': focusStyles,
			':active': activeStyles,
		},
		active: activeStyles,
	};
}
exports.fill = (color) => {
	switch (color) {
		case 'default':
			return buttonFillDefault();
		case 'cancel':
		case 'delete':
			return buttonFillVariant('white', theme.button.danger.bgColor);
		default:
			return buttonFillVariant('white', theme.button[color].bgColor);
	}
};

// Hollow Variant
// ----------------
function buttonHollowVariant (textColor, borderColor) {
	const focusAndHoverStyles = {
		backgroundImage: 'none',
		backgroundColor: fade(borderColor, 15),
		borderColor: darken(borderColor, 15),
		boxShadow: 'none',
		color: textColor,
		outline: 'none',
	};
	const focusOnlyStyles = {
		boxShadow: `0 0 0 3px ${fade(borderColor, 10)}`,
	};
	const activeStyles = {
		backgroundColor: fade(borderColor, 35),
		borderColor: darken(borderColor, 25),
		boxShadow: 'none',
	};

	return {
		base: {
			'background': 'none',
			'borderColor': borderColor,
			'color': textColor,

			':hover': focusAndHoverStyles,
			':focus ': Object.assign({}, focusAndHoverStyles, focusOnlyStyles),
			':active': activeStyles,
		},
		active: activeStyles,
	};
};
exports.hollow = (color) => {
	// TODO: better handling of cancel and delete colors
	if (color === 'cancel' || color === 'delete') color = 'danger';

	return buttonHollowVariant(theme.button[color].bgColor, theme.button[color].borderColor);
};

// Link Variant
// ----------------
function buttonLinkVariant (textColor, hoverColor) {
	const hoverStyles = {
		color: hoverColor,
		textDecoration: 'underline',
	};
	return {
		base: {
			'background': 'none',
			'border': 0,
			'boxShadow': 'none',
			'color': textColor,
			'fontWeight': 'normal',
			'outline': 'none',

			':hover': hoverStyles,
			':focus': hoverStyles,
			':active': hoverStyles,
		},
		active: hoverStyles,
	};
};
function buttonLinkDelete () {
	const styles = buttonLinkVariant(theme.color.gray40, theme.color.danger);
	const hoverStyles = {
		...gradientVertical(lighten(theme.color.danger, 10), darken(theme.color.danger, 10)),
		backgroundColor: theme.color.danger,
		borderColor: `${darken(theme.color.danger, 4)} ${darken(theme.color.danger, 8)} ${darken(theme.color.danger, 12)}`,
		boxShadow: '0 1px 0 rgba(0,0,0,0.1)',
		color: 'white',
		textDecoration: 'none',
	};
	const activeStyles = {
		backgroundColor: darken(theme.color.danger, 4),
		backgroundImage: 'none',
		borderColor: `${darken(theme.color.danger, 12)} ${darken(theme.color.danger, 8)} ${darken(theme.color.danger, 8)}`,
		boxShadow: 'inset 0 1px 2px rgba(0, 0, 0, 0.1)',
		color: 'white',
	};
	return {
		base: {
			...styles.base,
			':hover': hoverStyles,
			':focus': hoverStyles,
			':active': activeStyles,
		},
		active: activeStyles,
	};
}

exports.link = (color) => {
	switch (color) {
		case 'default':
			return buttonLinkVariant(theme.color.link, theme.color.linkHover);
		case 'cancel':
			return buttonLinkVariant(theme.color.gray40, theme.color.danger);
		case 'delete':
			return buttonLinkDelete();
		default:
			return buttonLinkVariant(theme.color[color], theme.color[color]);
	}
};
<commit_msg>Button: should have padding, even when block<commit_after>// ==============================
// Button
// ==============================

import { gradientVertical } from '../../../utils/css';
import { darken, fade, lighten } from '../../../utils/color';
import theme from '../../../theme';

// Common Styles
// ----------------

exports.common = {
	// Base Button
	// ----------------
	base: {
		'appearance': 'none',
		'background': 'none',
		'border': '1px solid transparent',
		'borderRadius': theme.borderRadius.default,
		'cursor': 'pointer',
		'display': 'inline-block',
		'fontWeight': theme.button.font.weight,
		'height': theme.component.height,
		'lineHeight': theme.component.lineHeight,
		'marginBottom': 0,
		'padding': `0 ${theme.button.paddingHorizontal}`,
		'outline': 0,
		'textAlign': 'center',
		'touchAction': 'manipulation',
		'userSelect': 'none',
		'verticalAlign': 'middle',
		'whiteSpace': 'nowrap',

		':hover': {
			color: theme.button.default.textColor,
			textDecoration: 'none',
		},
		':focus': {
			color: theme.button.default.textColor,
			textDecoration: 'none',
		},
	},
	// Block Display
	// ----------------
	block: {
		display: 'block',
		width: '100%',
	},
	// Disabled
	// ----------------
	disabled: {
		opacity: 0.4,
		pointerEvents: 'none',
	},
	// Sizes
	// ----------------
	large: {
		fontSize: theme.font.size.large,
	},
	default: {
		fontSize: theme.font.size.default,
	},
	small: {
		fontSize: theme.font.size.small,
	},
	xsmall: {
		fontSize: theme.font.size.xsmall,
		lineHeight: '1.9',
		paddingLeft: '.66em',
		paddingRight: '.66em',
	},
};

// Fill Variant
// ----------------
function buttonFillVariant (textColor, bgColor) {
	const hoverStyles = {
		...gradientVertical(lighten(bgColor, 10), darken(bgColor, 5)),
		borderColor: `${darken(bgColor, 5)} ${darken(bgColor, 10)} ${darken(bgColor, 15)}`,
		boxShadow: '0 1px 0 rgba(0,0,0,0.1)',
		color: textColor,
		outline: 'none',
	};
	const focusStyles = {
		...gradientVertical(lighten(bgColor, 10), darken(bgColor, 5)),
		borderColor: `${darken(bgColor, 5)} ${darken(bgColor, 10)} ${darken(bgColor, 15)}`,
		boxShadow: `0 0 0 3px ${fade(bgColor, 25)}`,
		color: textColor,
		outline: 'none',
	};
	const activeStyles = {
		backgroundColor: darken(bgColor, 10),
		backgroundImage: 'none',
		borderColor: `${darken(bgColor, 25)} ${darken(bgColor, 15)} ${darken(bgColor, 10)}`,
		boxShadow: 'inset 0 1px 2px rgba(0, 0, 0, 0.1)',
	};
	return {
		base: {
			...gradientVertical(lighten(bgColor, 5), darken(bgColor, 10), bgColor),
			'borderColor': `${darken(bgColor, 10)} ${darken(bgColor, 20)} ${darken(bgColor, 25)}`,
			'boxShadow': 'inset 0 1px 0 rgba(255, 255, 255, 0.1)',
			'color': textColor,
			'fontWeight': 400,
			'textShadow': '0 -1px 0 rgba(0, 0, 0, 0.25)',

			':hover': hoverStyles,
			':focus': focusStyles,
			':active': activeStyles,
		},
		active: activeStyles,
	};
}
// TODO: This is pretty hacky, needs to be consolidated with the Variant() method
// above (needs more theme variables to be implemented though)
function buttonFillDefault () {
	const hoverStyles = {
		...gradientVertical('#fff', '#eee'),
		borderColor: `${darken(theme.input.border.color, 5)} ${darken(theme.input.border.color, 5)} ${darken(theme.input.border.color, 10)}`,
		boxShadow: '0 1px 0 rgba(0,0,0,0.1)',
		color: theme.color.text,
	};
	const focusStyles = {
		borderColor: theme.color.primary,
		boxShadow: `0 0 0 3px ${fade(theme.color.primary, 10)}`,
		color: theme.color.text,
		outline: 'none',
	};
	const activeStyles = {
		background: '#e6e6e6',
		borderColor: darken(theme.input.border.color, 10),
		boxShadow: 'inset 0 1px 2px rgba(0, 0, 0, 0.1)',
		color: theme.color.text,
	};
	return {
		base: {
			...gradientVertical('#fafafa', '#eaeaea'),
			'border': `1px solid ${theme.input.border.color}`,
			'borderColor': `${theme.input.border.color} ${darken(theme.input.border.color, 6)} ${darken(theme.input.border.color, 12)}`,
			'color': theme.color.text,
			'textShadow': '0 1px 0 white',

			':hover': hoverStyles,
			':focus': focusStyles,
			':active': activeStyles,
		},
		active: activeStyles,
	};
}
exports.fill = (color) => {
	switch (color) {
		case 'default':
			return buttonFillDefault();
		case 'cancel':
		case 'delete':
			return buttonFillVariant('white', theme.button.danger.bgColor);
		default:
			return buttonFillVariant('white', theme.button[color].bgColor);
	}
};

// Hollow Variant
// ----------------
function buttonHollowVariant (textColor, borderColor) {
	const focusAndHoverStyles = {
		backgroundImage: 'none',
		backgroundColor: fade(borderColor, 15),
		borderColor: darken(borderColor, 15),
		boxShadow: 'none',
		color: textColor,
		outline: 'none',
	};
	const focusOnlyStyles = {
		boxShadow: `0 0 0 3px ${fade(borderColor, 10)}`,
	};
	const activeStyles = {
		backgroundColor: fade(borderColor, 35),
		borderColor: darken(borderColor, 25),
		boxShadow: 'none',
	};

	return {
		base: {
			'background': 'none',
			'borderColor': borderColor,
			'color': textColor,

			':hover': focusAndHoverStyles,
			':focus ': Object.assign({}, focusAndHoverStyles, focusOnlyStyles),
			':active': activeStyles,
		},
		active: activeStyles,
	};
};
exports.hollow = (color) => {
	// TODO: better handling of cancel and delete colors
	if (color === 'cancel' || color === 'delete') color = 'danger';

	return buttonHollowVariant(theme.button[color].bgColor, theme.button[color].borderColor);
};

// Link Variant
// ----------------
function buttonLinkVariant (textColor, hoverColor) {
	const hoverStyles = {
		color: hoverColor,
		textDecoration: 'underline',
	};
	return {
		base: {
			'background': 'none',
			'border': 0,
			'boxShadow': 'none',
			'color': textColor,
			'fontWeight': 'normal',
			'outline': 'none',

			':hover': hoverStyles,
			':focus': hoverStyles,
			':active': hoverStyles,
		},
		active: hoverStyles,
	};
};
function buttonLinkDelete () {
	const styles = buttonLinkVariant(theme.color.gray40, theme.color.danger);
	const hoverStyles = {
		...gradientVertical(lighten(theme.color.danger, 10), darken(theme.color.danger, 10)),
		backgroundColor: theme.color.danger,
		borderColor: `${darken(theme.color.danger, 4)} ${darken(theme.color.danger, 8)} ${darken(theme.color.danger, 12)}`,
		boxShadow: '0 1px 0 rgba(0,0,0,0.1)',
		color: 'white',
		textDecoration: 'none',
	};
	const activeStyles = {
		backgroundColor: darken(theme.color.danger, 4),
		backgroundImage: 'none',
		borderColor: `${darken(theme.color.danger, 12)} ${darken(theme.color.danger, 8)} ${darken(theme.color.danger, 8)}`,
		boxShadow: 'inset 0 1px 2px rgba(0, 0, 0, 0.1)',
		color: 'white',
	};
	return {
		base: {
			...styles.base,
			':hover': hoverStyles,
			':focus': hoverStyles,
			':active': activeStyles,
		},
		active: activeStyles,
	};
}

exports.link = (color) => {
	switch (color) {
		case 'default':
			return buttonLinkVariant(theme.color.link, theme.color.linkHover);
		case 'cancel':
			return buttonLinkVariant(theme.color.gray40, theme.color.danger);
		case 'delete':
			return buttonLinkDelete();
		default:
			return buttonLinkVariant(theme.color[color], theme.color[color]);
	}
};
<|endoftext|>


<commit_before>/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {ExpansionOptions, variableServiceForDoc} from './variables';
import {Priority} from '../../../src/service/navigation';
import {Services} from '../../../src/services';
import {WindowInterface} from '../../../src/window-interface';
import {addMissingParamsToUrl, addParamToUrl} from '../../../src/url';
import {createElementWithAttributes} from '../../../src/dom';
import {createLinker} from './linker';
import {dict} from '../../../src/utils/object';
import {getHighestAvailableDomain} from '../../../src/cookies';
import {isObject} from '../../../src/types';
import {user} from '../../../src/log';

/** @const {string} */
const TAG = 'amp-analytics/linker-manager';

/** @const {string} */
const LINKER_CREATED = 'i-amphtml-linker-created';

export class LinkerManager {
  /**
   * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc
   * @param {!JsonObject} config
   * @param {?string} type
   * @param {!Element} element
   */
  constructor(ampdoc, config, type, element) {
    /** @const @private {!../../../src/service/ampdoc-impl.AmpDoc} */
    this.ampdoc_ = ampdoc;

    /** @private {?JsonObject|undefined} */
    this.config_ = config['linkers'];

    /** @const @private {!JsonObject} */
    this.vars_ = config['vars'] || {};

    /** @const @private {?string} */
    this.type_ = type;

    /** @const @private {!Element} */
    this.element_ = element;

    /** @private {!Array<Promise>} */
    this.allLinkerPromises_ = [];

    /** @const @private {!JsonObject} */
    this.resolvedIds_ = dict();

    /** @const @private {!../../../src/service/url-impl.Url} */
    this.urlService_ = Services.urlForDoc(this.element_);

    /** @const @private {!Promise<../../amp-form/0.1/form-submit-service.FormSubmitService>} */
    this.formSubmitService_ = Services.formSubmitForDoc(ampdoc);

    /** @private {?UnlistenDef} */
    this.formSubmitUnlistener_ = null;

    /** @const @private {!./variables.VariableService} */
    this.variableService_ = variableServiceForDoc(this.ampdoc_);

    /** @private {?string} */
    this.highestAvailableDomain_ = null;
  }

  /**
   * Start resolving any macros that may exist in the linker configuration
   * and register the callback with the navigation service. Since macro
   * resolution is asynchronous the callback may be looking for these values
   * before they are ready.
   */
  init() {
    if (!isObject(this.config_)) {
      return;
    }

    this.highestAvailableDomain_ = getHighestAvailableDomain(this.ampdoc_.win);

    this.config_ = this.processConfig_(
      /** @type {!JsonObject} */ (this.config_)
    );
    // Each linker config has it's own set of macros to resolve.
    this.allLinkerPromises_ = Object.keys(this.config_).map(name => {
      const ids = this.config_[name]['ids'];
      // Keys for linker data.
      const keys = Object.keys(ids);
      // Expand the value of each key value pair (if necessary).
      const valuePromises = keys.map(key => {
        const expansionOptions = new ExpansionOptions(
          this.vars_,
          /* opt_iterations */ undefined,
          /* opt_noencode */ true
        );
        return this.expandTemplateWithUrlParams_(ids[key], expansionOptions);
      });

      return Promise.all(valuePromises).then(values => {
        // Rejoin each key with its expanded value.
        const expandedIds = {};
        values.forEach((value, i) => {
          // Omit pair if value resolves to empty.
          if (value) {
            expandedIds[keys[i]] = value;
          }
        });
        this.resolvedIds_[name] = expandedIds;
      });
    });

    if (this.allLinkerPromises_.length) {
      const navigation = Services.navigationForDoc(this.ampdoc_);
      navigation.registerAnchorMutator((element, event) => {
        if (!element.href || event.type !== 'click') {
          return;
        }
        this.maybeWriteHref_(element);
      }, Priority.ANALYTICS_LINKER);
      navigation.registerNavigateToMutator(
        url => this.applyLinkers_(url),
        Priority.ANALYTICS_LINKER
      );
    }

    this.enableFormSupport_();

    return Promise.all(this.allLinkerPromises_);
  }

  /**
   * TODO: Revisit this logic after #22787 is complete.
   * Applys any matching linkers to the elements href. If no linkers exist,
   * will not set href.
   * @param {!Element} element
   */
  maybeWriteHref_(element) {
    const {href} = element;
    const maybeDecoratedUrl = this.applyLinkers_(href);
    if (href !== maybeDecoratedUrl) {
      element.href = maybeDecoratedUrl;
    }
  }

  /**
   * Remove any listeners created to manage form submission.
   */
  dispose() {
    if (this.formSubmitUnlistener_) {
      this.formSubmitUnlistener_();
    }
  }

  /**
   * @param {!JsonObject} config
   * @return {!JsonObject}
   * @private
   */
  processConfig_(config) {
    const processedConfig = dict();
    const defaultConfig = {
      enabled: this.isLegacyOptIn_() && this.isSafari12OrAbove_(),
    };
    const linkerNames = Object.keys(config).filter(key => {
      const value = config[key];
      const isLinkerConfig = isObject(value);
      if (!isLinkerConfig) {
        defaultConfig[key] = value;
      }
      return isLinkerConfig;
    });

    const location = WindowInterface.getLocation(this.ampdoc_.win);
    const isProxyOrigin = this.urlService_.isProxyOrigin(location);
    linkerNames.forEach(name => {
      const mergedConfig = Object.assign({}, defaultConfig, config[name]);

      if (mergedConfig['enabled'] !== true) {
        user().info(
          TAG,
          'linker config for %s is not enabled and will be ignored.',
          name
        );
        return;
      }

      if (!isProxyOrigin && mergedConfig['proxyOnly'] !== false) {
        return;
      }

      if (!mergedConfig['ids']) {
        user().error(TAG, '"ids" is a required field for use of "linkers".');
        return;
      }

      processedConfig[name] = mergedConfig;
    });
    return processedConfig;
  }

  /**
   * Expands spec using provided expansion options and applies url replacement
   * if necessary.
   * @param {string} template Expression that needs to be expanded.
   * @param {!ExpansionOptions} expansionOptions Expansion options.
   * @return {!Promise<string>} expanded template.
   */
  expandTemplateWithUrlParams_(template, expansionOptions) {
    const bindings = this.variableService_.getMacros();
    return this.variableService_
      .expandTemplate(template, expansionOptions)
      .then(expanded => {
        const urlReplacements = Services.urlReplacementsForDoc(this.element_);
        return urlReplacements.expandUrlAsync(expanded, bindings);
      });
  }

  /**
   * If the document has existing cid meta tag they do not need to explicity
   * opt-in to use linker.
   * @return {boolean}
   * @private
   */
  isLegacyOptIn_() {
    const optInMeta = this.ampdoc_.win.document.head./*OK*/ querySelector(
      'meta[name="amp-google-client-id-api"][content="googleanalytics"]'
    );
    if (
      !optInMeta ||
      optInMeta.hasAttribute(LINKER_CREATED) ||
      this.type_ !== 'googleanalytics'
    ) {
      return false;
    }

    optInMeta.setAttribute(LINKER_CREATED, '');
    return true;
  }

  /**
   * If the browser is Safari 12 or above.
   * @return {boolean}
   * @private
   */
  isSafari12OrAbove_() {
    const platform = Services.platformFor(this.ampdoc_.win);
    return platform.isSafari() && platform.getMajorVersion() >= 12;
  }

  /**
   * Apply linkers to the given url. Linker params are appended if there
   * are matching linker configs.
   *
   * @param {string} url
   * @return {string}
   * @private
   */
  applyLinkers_(url) {
    const linkerConfigs = this.config_;
    for (const linkerName in linkerConfigs) {
      // The linker param is created asynchronously. This callback should be
      // synchronous, so we skip if value is not there yet.
      if (this.resolvedIds_[linkerName]) {
        url = this.maybeAppendLinker_(
          url,
          linkerName,
          linkerConfigs[linkerName]
        );
      }
    }
    return url;
  }

  /**
   * Appends the linker param if the given url falls within rules defined in
   * linker configuration.
   * @param {string} url
   * @param {string} name
   * @param {!Object} config
   * @return {string}
   * @private
   */
  maybeAppendLinker_(url, name, config) {
    const /** @type {Array} */ domains = config['destinationDomains'];
    if (this.isDomainMatch_(url, name, domains)) {
      const linkerValue = createLinker(
        /* version */ '1',
        this.resolvedIds_[name]
      );
      if (linkerValue) {
        const params = dict();
        params[name] = linkerValue;
        return addMissingParamsToUrl(url, params);
      }
    }
    return url;
  }

  /**
   * Check to see if the url is a match for the given set of domains.
   * @param {string} url
   * @param {string} name Name given in linker config.
   * @param {?Array} domains
   */
  isDomainMatch_(url, name, domains) {
    const {hostname} = this.urlService_.parse(url);
    // If given domains, but not in the right format.
    if (domains && !Array.isArray(domains)) {
      user().warn(TAG, '%s destinationDomains must be an array.', name);
      return false;
    }

    // If destinationDomain is specified specifically, respect it.
    if (domains) {
      return this.destinationDomainsMatch_(domains, hostname);
    }

    // Fallback to default behavior

    // Don't append linker for exact domain match, relative urls, or
    // fragments.
    const winHostname = WindowInterface.getHostname(this.ampdoc_.win);
    if (winHostname === hostname) {
      return false;
    }

    const {sourceUrl, canonicalUrl} = Services.documentInfoForDoc(this.ampdoc_);
    const canonicalOrigin = this.urlService_.parse(canonicalUrl).hostname;
    const isFriendlyCanonicalOrigin = areFriendlyDomains(
      canonicalOrigin,
      hostname
    );
    // Default to all subdomains matching (if there's one) plus canonicalOrigin

    if (this.highestAvailableDomain_) {
      const destinationDomain = [
        this.highestAvailableDomain_,
        '*' + this.highestAvailableDomain_,
      ];
      return (
        this.destinationDomainsMatch_(destinationDomain, hostname) ||
        isFriendlyCanonicalOrigin
      );
    }

    // In the case where highestAvailableDomain cannot be found.
    // (proxyOrigin, no <meta name='amp-cookie-scope'> found)
    // default to friendly domain matching.
    const sourceOrigin = this.urlService_.parse(sourceUrl).hostname;
    return (
      areFriendlyDomains(sourceOrigin, hostname) || isFriendlyCanonicalOrigin
    );
  }

  /**
   * Helper method to find out if hostname match the destinationDomain array.
   * @param {Array<string>} domains
   * @param {string} hostname
   * @return {boolean}
   */
  destinationDomainsMatch_(domains, hostname) {
    for (let i = 0; i < domains.length; i++) {
      const domain = domains[i];
      // Exact match.
      if (domain === hostname) {
        return true;
      }
      // Allow wildcard subdomain matching.
      if (domain.indexOf('*') !== -1 && isWildCardMatch(hostname, domain)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Register callback that will handle form sumbits.
   */
  enableFormSupport_() {
    if (this.formSubmitUnlistener_) {
      return;
    }

    this.formSubmitService_.then(formService => {
      this.formSubmitUnlistener_ = formService.beforeSubmit(
        this.handleFormSubmit_.bind(this)
      );
    });
  }

  /**
   * Check to see if any linker configs match this form's url, if so, send
   * along the resolved linker value
   * @param {!../../amp-form/0.1/form-submit-service.FormSubmitEventDef} event
   */
  handleFormSubmit_(event) {
    const {form, actionXhrMutator} = event;

    for (const linkerName in this.config_) {
      const config = this.config_[linkerName];
      const /** @type {Array} */ domains = config['destinationDomains'];

      const url =
        form.getAttribute('action-xhr') || form.getAttribute('action');

      if (this.isDomainMatch_(url, linkerName, domains)) {
        this.addDataToForm_(form, actionXhrMutator, linkerName);
      }
    }
  }

  /**
   * Add the linker data to form. If action-xhr is present we can update the
   * action-xhr, if not we fallback to adding hidden inputs.
   * @param {!Element} form
   * @param {function(string)} actionXhrMutator
   * @param {string} linkerName
   */
  addDataToForm_(form, actionXhrMutator, linkerName) {
    const ids = this.resolvedIds_[linkerName];
    if (!ids) {
      // Form was clicked before macros resolved.
      return;
    }

    const linkerValue = createLinker(/* version */ '1', ids);

    // Runtime controls submits with `action-xhr`, so we can append the linker
    // param
    const actionXhrUrl = form.getAttribute('action-xhr');
    if (actionXhrUrl) {
      const decoratedUrl = addParamToUrl(actionXhrUrl, linkerName, linkerValue);
      return actionXhrMutator(decoratedUrl);
    }

    // If we are not using `action-xhr` it must be a GET request using the
    // standard action attribute. Browsers will not let you change this in the
    // middle of a submit, so we add the input hidden attributes.
    this.addHiddenInputs_(form, linkerName, linkerValue);
  }

  /**
   * Add the linker pairs as <input> elements to form.
   * @param {!Element} form
   * @param {string} linkerName
   * @param {string} linkerValue
   */
  addHiddenInputs_(form, linkerName, linkerValue) {
    const attrs = dict({
      'type': 'hidden',
      'name': linkerName,
      'value': linkerValue,
    });

    const inputEl = createElementWithAttributes(
      /** @type {!Document} */ (form.ownerDocument),
      'input',
      attrs
    );
    form.appendChild(inputEl);
  }
}

/**
 * Domains are considered to be friends if they are identical
 * after removing these prefixes: m. www. amp.
 * URL scheme & port are not taken into consideration.
 *
 * Note that this algorithm will break corner cases like
 *   www.com vs amp.com vs m.com
 * Or
 *   amp.wordpress.com vs www.wordpress.com
 *
 * @param {string} domain1
 * @param {string} domain2
 * @return {boolean}
 * @visibleForTesting
 */
export function areFriendlyDomains(domain1, domain2) {
  return getBaseDomain(domain1) === getBaseDomain(domain2);
}

/**
 * Strips out all prefixing m. www. amp. from a domain name.
 * @param {string} domain
 * @return {string}
 */
function getBaseDomain(domain) {
  return domain.replace(/^(?:www\.|m\.|amp\.)+/, '');
}

/**
 * Escape any regex flags other than `*`
 * @param {string} str
 */
function regexEscape(str) {
  return str.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&');
}

/**
 * Allows specified wildcard matching of domains.
 * Example:
 *    `*.foo.com` matches `amp.foo.com`
 *    `*.foo.com*` matches `amp.foo.com.uk`
 * @param {string} hostname
 * @param {string} domain
 * @return {boolean}
 * @visibleForTesting
 */
export function isWildCardMatch(hostname, domain) {
  const escaped = regexEscape(domain);
  const regex = escaped.replace(/\*/g, '.*');
  return new RegExp('^' + regex + '$').test(hostname);
}
<commit_msg>cookie support in linker (#23207)<commit_after>/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {ExpansionOptions, variableServiceForDoc} from './variables';
import {Priority} from '../../../src/service/navigation';
import {Services} from '../../../src/services';
import {WindowInterface} from '../../../src/window-interface';
import {addMissingParamsToUrl, addParamToUrl} from '../../../src/url';
import {cookieReader} from './cookie-reader';
import {createElementWithAttributes} from '../../../src/dom';
import {createLinker} from './linker';
import {dict} from '../../../src/utils/object';
import {getHighestAvailableDomain} from '../../../src/cookies';
import {isObject} from '../../../src/types';
import {user} from '../../../src/log';

/** @const {string} */
const TAG = 'amp-analytics/linker-manager';

/** @const {string} */
const LINKER_CREATED = 'i-amphtml-linker-created';

export class LinkerManager {
  /**
   * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc
   * @param {!JsonObject} config
   * @param {?string} type
   * @param {!Element} element
   */
  constructor(ampdoc, config, type, element) {
    /** @const @private {!../../../src/service/ampdoc-impl.AmpDoc} */
    this.ampdoc_ = ampdoc;

    /** @private {?JsonObject|undefined} */
    this.config_ = config['linkers'];

    /** @const @private {!JsonObject} */
    this.vars_ = config['vars'] || {};

    /** @const @private {?string} */
    this.type_ = type;

    /** @const @private {!Element} */
    this.element_ = element;

    /** @private {!Array<Promise>} */
    this.allLinkerPromises_ = [];

    /** @const @private {!JsonObject} */
    this.resolvedIds_ = dict();

    /** @const @private {!../../../src/service/url-impl.Url} */
    this.urlService_ = Services.urlForDoc(this.element_);

    /** @const @private {!Promise<../../amp-form/0.1/form-submit-service.FormSubmitService>} */
    this.formSubmitService_ = Services.formSubmitForDoc(ampdoc);

    /** @private {?UnlistenDef} */
    this.formSubmitUnlistener_ = null;

    /** @const @private {!./variables.VariableService} */
    this.variableService_ = variableServiceForDoc(this.ampdoc_);

    /** @private {?string} */
    this.highestAvailableDomain_ = null;
  }

  /**
   * Start resolving any macros that may exist in the linker configuration
   * and register the callback with the navigation service. Since macro
   * resolution is asynchronous the callback may be looking for these values
   * before they are ready.
   */
  init() {
    if (!isObject(this.config_)) {
      return;
    }

    this.highestAvailableDomain_ = getHighestAvailableDomain(this.ampdoc_.win);

    this.config_ = this.processConfig_(
      /** @type {!JsonObject} */ (this.config_)
    );
    // Each linker config has it's own set of macros to resolve.
    this.allLinkerPromises_ = Object.keys(this.config_).map(name => {
      const ids = this.config_[name]['ids'];
      // Keys for linker data.
      const keys = Object.keys(ids);
      // Expand the value of each key value pair (if necessary).
      const valuePromises = keys.map(key => {
        const expansionOptions = new ExpansionOptions(
          this.vars_,
          /* opt_iterations */ undefined,
          /* opt_noencode */ true
        );
        return this.expandTemplateWithUrlParams_(ids[key], expansionOptions);
      });

      return Promise.all(valuePromises).then(values => {
        // Rejoin each key with its expanded value.
        const expandedIds = {};
        values.forEach((value, i) => {
          // Omit pair if value resolves to empty.
          if (value) {
            expandedIds[keys[i]] = value;
          }
        });
        this.resolvedIds_[name] = expandedIds;
      });
    });

    if (this.allLinkerPromises_.length) {
      const navigation = Services.navigationForDoc(this.ampdoc_);
      navigation.registerAnchorMutator((element, event) => {
        if (!element.href || event.type !== 'click') {
          return;
        }
        this.maybeWriteHref_(element);
      }, Priority.ANALYTICS_LINKER);
      navigation.registerNavigateToMutator(
        url => this.applyLinkers_(url),
        Priority.ANALYTICS_LINKER
      );
    }

    this.enableFormSupport_();

    return Promise.all(this.allLinkerPromises_);
  }

  /**
   * TODO: Revisit this logic after #22787 is complete.
   * Applys any matching linkers to the elements href. If no linkers exist,
   * will not set href.
   * @param {!Element} element
   */
  maybeWriteHref_(element) {
    const {href} = element;
    const maybeDecoratedUrl = this.applyLinkers_(href);
    if (href !== maybeDecoratedUrl) {
      element.href = maybeDecoratedUrl;
    }
  }

  /**
   * Remove any listeners created to manage form submission.
   */
  dispose() {
    if (this.formSubmitUnlistener_) {
      this.formSubmitUnlistener_();
    }
  }

  /**
   * @param {!JsonObject} config
   * @return {!JsonObject}
   * @private
   */
  processConfig_(config) {
    const processedConfig = dict();
    const defaultConfig = {
      enabled: this.isLegacyOptIn_() && this.isSafari12OrAbove_(),
    };
    const linkerNames = Object.keys(config).filter(key => {
      const value = config[key];
      const isLinkerConfig = isObject(value);
      if (!isLinkerConfig) {
        defaultConfig[key] = value;
      }
      return isLinkerConfig;
    });

    const location = WindowInterface.getLocation(this.ampdoc_.win);
    const isProxyOrigin = this.urlService_.isProxyOrigin(location);
    linkerNames.forEach(name => {
      const mergedConfig = Object.assign({}, defaultConfig, config[name]);

      if (mergedConfig['enabled'] !== true) {
        user().info(
          TAG,
          'linker config for %s is not enabled and will be ignored.',
          name
        );
        return;
      }

      if (!isProxyOrigin && mergedConfig['proxyOnly'] !== false) {
        return;
      }

      if (!mergedConfig['ids']) {
        user().error(TAG, '"ids" is a required field for use of "linkers".');
        return;
      }

      processedConfig[name] = mergedConfig;
    });
    return processedConfig;
  }

  /**
   * Expands spec using provided expansion options and applies url replacement
   * if necessary.
   * @param {string} template Expression that needs to be expanded.
   * @param {!ExpansionOptions} expansionOptions Expansion options.
   * @return {!Promise<string>} expanded template.
   */
  expandTemplateWithUrlParams_(template, expansionOptions) {
    const bindings = this.variableService_.getMacros();

    // TODO: (@zhouyx) Duplicate of binding in requests and linker.
    // Move to varaible Servie once there's a way to detect FIE in that service
    bindings['COOKIE'] = name =>
      cookieReader(this.ampdoc_.win, this.element_, name);

    return this.variableService_
      .expandTemplate(template, expansionOptions)
      .then(expanded => {
        const urlReplacements = Services.urlReplacementsForDoc(this.element_);
        return urlReplacements.expandUrlAsync(expanded, bindings);
      });
  }

  /**
   * If the document has existing cid meta tag they do not need to explicity
   * opt-in to use linker.
   * @return {boolean}
   * @private
   */
  isLegacyOptIn_() {
    const optInMeta = this.ampdoc_.win.document.head./*OK*/ querySelector(
      'meta[name="amp-google-client-id-api"][content="googleanalytics"]'
    );
    if (
      !optInMeta ||
      optInMeta.hasAttribute(LINKER_CREATED) ||
      this.type_ !== 'googleanalytics'
    ) {
      return false;
    }

    optInMeta.setAttribute(LINKER_CREATED, '');
    return true;
  }

  /**
   * If the browser is Safari 12 or above.
   * @return {boolean}
   * @private
   */
  isSafari12OrAbove_() {
    const platform = Services.platformFor(this.ampdoc_.win);
    return platform.isSafari() && platform.getMajorVersion() >= 12;
  }

  /**
   * Apply linkers to the given url. Linker params are appended if there
   * are matching linker configs.
   *
   * @param {string} url
   * @return {string}
   * @private
   */
  applyLinkers_(url) {
    const linkerConfigs = this.config_;
    for (const linkerName in linkerConfigs) {
      // The linker param is created asynchronously. This callback should be
      // synchronous, so we skip if value is not there yet.
      if (this.resolvedIds_[linkerName]) {
        url = this.maybeAppendLinker_(
          url,
          linkerName,
          linkerConfigs[linkerName]
        );
      }
    }
    return url;
  }

  /**
   * Appends the linker param if the given url falls within rules defined in
   * linker configuration.
   * @param {string} url
   * @param {string} name
   * @param {!Object} config
   * @return {string}
   * @private
   */
  maybeAppendLinker_(url, name, config) {
    const /** @type {Array} */ domains = config['destinationDomains'];
    if (this.isDomainMatch_(url, name, domains)) {
      const linkerValue = createLinker(
        /* version */ '1',
        this.resolvedIds_[name]
      );
      if (linkerValue) {
        const params = dict();
        params[name] = linkerValue;
        return addMissingParamsToUrl(url, params);
      }
    }
    return url;
  }

  /**
   * Check to see if the url is a match for the given set of domains.
   * @param {string} url
   * @param {string} name Name given in linker config.
   * @param {?Array} domains
   */
  isDomainMatch_(url, name, domains) {
    const {hostname} = this.urlService_.parse(url);
    // If given domains, but not in the right format.
    if (domains && !Array.isArray(domains)) {
      user().warn(TAG, '%s destinationDomains must be an array.', name);
      return false;
    }

    // If destinationDomain is specified specifically, respect it.
    if (domains) {
      return this.destinationDomainsMatch_(domains, hostname);
    }

    // Fallback to default behavior

    // Don't append linker for exact domain match, relative urls, or
    // fragments.
    const winHostname = WindowInterface.getHostname(this.ampdoc_.win);
    if (winHostname === hostname) {
      return false;
    }

    const {sourceUrl, canonicalUrl} = Services.documentInfoForDoc(this.ampdoc_);
    const canonicalOrigin = this.urlService_.parse(canonicalUrl).hostname;
    const isFriendlyCanonicalOrigin = areFriendlyDomains(
      canonicalOrigin,
      hostname
    );
    // Default to all subdomains matching (if there's one) plus canonicalOrigin

    if (this.highestAvailableDomain_) {
      const destinationDomain = [
        this.highestAvailableDomain_,
        '*' + this.highestAvailableDomain_,
      ];
      return (
        this.destinationDomainsMatch_(destinationDomain, hostname) ||
        isFriendlyCanonicalOrigin
      );
    }

    // In the case where highestAvailableDomain cannot be found.
    // (proxyOrigin, no <meta name='amp-cookie-scope'> found)
    // default to friendly domain matching.
    const sourceOrigin = this.urlService_.parse(sourceUrl).hostname;
    return (
      areFriendlyDomains(sourceOrigin, hostname) || isFriendlyCanonicalOrigin
    );
  }

  /**
   * Helper method to find out if hostname match the destinationDomain array.
   * @param {Array<string>} domains
   * @param {string} hostname
   * @return {boolean}
   */
  destinationDomainsMatch_(domains, hostname) {
    for (let i = 0; i < domains.length; i++) {
      const domain = domains[i];
      // Exact match.
      if (domain === hostname) {
        return true;
      }
      // Allow wildcard subdomain matching.
      if (domain.indexOf('*') !== -1 && isWildCardMatch(hostname, domain)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Register callback that will handle form sumbits.
   */
  enableFormSupport_() {
    if (this.formSubmitUnlistener_) {
      return;
    }

    this.formSubmitService_.then(formService => {
      this.formSubmitUnlistener_ = formService.beforeSubmit(
        this.handleFormSubmit_.bind(this)
      );
    });
  }

  /**
   * Check to see if any linker configs match this form's url, if so, send
   * along the resolved linker value
   * @param {!../../amp-form/0.1/form-submit-service.FormSubmitEventDef} event
   */
  handleFormSubmit_(event) {
    const {form, actionXhrMutator} = event;

    for (const linkerName in this.config_) {
      const config = this.config_[linkerName];
      const /** @type {Array} */ domains = config['destinationDomains'];

      const url =
        form.getAttribute('action-xhr') || form.getAttribute('action');

      if (this.isDomainMatch_(url, linkerName, domains)) {
        this.addDataToForm_(form, actionXhrMutator, linkerName);
      }
    }
  }

  /**
   * Add the linker data to form. If action-xhr is present we can update the
   * action-xhr, if not we fallback to adding hidden inputs.
   * @param {!Element} form
   * @param {function(string)} actionXhrMutator
   * @param {string} linkerName
   */
  addDataToForm_(form, actionXhrMutator, linkerName) {
    const ids = this.resolvedIds_[linkerName];
    if (!ids) {
      // Form was clicked before macros resolved.
      return;
    }

    const linkerValue = createLinker(/* version */ '1', ids);

    // Runtime controls submits with `action-xhr`, so we can append the linker
    // param
    const actionXhrUrl = form.getAttribute('action-xhr');
    if (actionXhrUrl) {
      const decoratedUrl = addParamToUrl(actionXhrUrl, linkerName, linkerValue);
      return actionXhrMutator(decoratedUrl);
    }

    // If we are not using `action-xhr` it must be a GET request using the
    // standard action attribute. Browsers will not let you change this in the
    // middle of a submit, so we add the input hidden attributes.
    this.addHiddenInputs_(form, linkerName, linkerValue);
  }

  /**
   * Add the linker pairs as <input> elements to form.
   * @param {!Element} form
   * @param {string} linkerName
   * @param {string} linkerValue
   */
  addHiddenInputs_(form, linkerName, linkerValue) {
    const attrs = dict({
      'type': 'hidden',
      'name': linkerName,
      'value': linkerValue,
    });

    const inputEl = createElementWithAttributes(
      /** @type {!Document} */ (form.ownerDocument),
      'input',
      attrs
    );
    form.appendChild(inputEl);
  }
}

/**
 * Domains are considered to be friends if they are identical
 * after removing these prefixes: m. www. amp.
 * URL scheme & port are not taken into consideration.
 *
 * Note that this algorithm will break corner cases like
 *   www.com vs amp.com vs m.com
 * Or
 *   amp.wordpress.com vs www.wordpress.com
 *
 * @param {string} domain1
 * @param {string} domain2
 * @return {boolean}
 * @visibleForTesting
 */
export function areFriendlyDomains(domain1, domain2) {
  return getBaseDomain(domain1) === getBaseDomain(domain2);
}

/**
 * Strips out all prefixing m. www. amp. from a domain name.
 * @param {string} domain
 * @return {string}
 */
function getBaseDomain(domain) {
  return domain.replace(/^(?:www\.|m\.|amp\.)+/, '');
}

/**
 * Escape any regex flags other than `*`
 * @param {string} str
 */
function regexEscape(str) {
  return str.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&');
}

/**
 * Allows specified wildcard matching of domains.
 * Example:
 *    `*.foo.com` matches `amp.foo.com`
 *    `*.foo.com*` matches `amp.foo.com.uk`
 * @param {string} hostname
 * @param {string} domain
 * @return {boolean}
 * @visibleForTesting
 */
export function isWildCardMatch(hostname, domain) {
  const escaped = regexEscape(domain);
  const regex = escaped.replace(/\*/g, '.*');
  return new RegExp('^' + regex + '$').test(hostname);
}
<|endoftext|>