<commit_before>  var renderTargetsReqVar;
  var renderOnRecordTargetsReqVar;

  function renderTargets(doRefresh)
  {
	//console.log('rendering ' + renderWhileActiveclients);
	var sourcecomp = document.getElementById("pz2watch:activeclientsField");
    jsf.ajax.request(sourcecomp, null,{render: renderWhileActiveclients + " pz2watch:errorMessages"});
    if (doRefresh) {
		//console.log('Will do another ajax request after a timeout in order to render: pz2watch:activeclientsField');
	    renderTargetsReqVar=setTimeout(
	     function() {
	       //console.log('Making request for pz2watch:activeclientsField');
	       jsf.ajax.request(sourcecomp, null,{render: "pz2watch:activeclientsField"});
	     }
	     ,500);
    } else {
    	//console.log("No further updates from server requested");
    }
  }

  function renderOnRecordTargets(doRefresh)
  {
	console.log('rendering ' + renderWhileActiveclientsRecord);
	var sourcecomp = document.getElementById("pz2watch:activeclientsFieldRecord");
    jsf.ajax.request(sourcecomp, null,{render: renderWhileActiveclientsRecord});
    if (doRefresh) {
		console.log('Will do another ajax request after a timeout in order to render: pz2watch:activeclientsFieldRecord');
	    renderOnRecordTargetsReqVar=setTimeout(
	     function() {
	       console.log('Making request for pz2watch:activeclientsFieldRecord');
	       jsf.ajax.request(sourcecomp, null,{render: "pz2watch:activeclientsFieldRecord"});
	     }
	     ,2000);
    } else {
    	console.log("No further updates from server requested");
    	jsf.ajax.request(sourcecomp, null,{render: renderWhileActiveclientsRecord});
    }
  }

  function windowlocationhashListener () {
	  if (trackHistory) {
	      //console.log("browser hash update detected");
	      var stateKey = document.getElementById("pz2watch:windowlocationhash");
	      if (window.location.hash != stateKey.value) {
	        //console.log("updating stateKey with new browser hash: " + window.location.hash);
	        stateKey.value = window.location.hash;
	        if (! stateKey.value) window.location.hash = '#initial';
	        stateKey.onchange();
	      } else {
	        //console.log("State hash already has the value of the new browser hash - not updating state hash");
	      }
	  }
  }

  function fieldUpdateListener (data) {
	  if (data.status === "success") {
		var updates = data.responseXML.getElementsByTagName("update");
		for (var i=0, max=updates.length; i<max; i++) {
			var lsnri = fieldListeners.getListener(updates[i].getAttribute("id"));
			if (lsnri) {
				lsnri.invoke(updates[i]);
			}
		}
	  }
  }

  var Pz2listeners = function () {
	var lsnrs = {};
	this.addListener = function (key, lsnr) {
		lsnrs[key] =lsnr;
	};
	this.getListener = function (key) {
		return lsnrs[key];
	};
  };

  var fieldListeners = new Pz2listeners();

  var StateListener = function () {
	this.invoke = function (field) {
      var stateKeyDoc = StringtoXML(field.textContent || field.text);
      var stateKeyValue = stateKeyDoc.childNodes[0].getAttribute("value");
      //console.log('Application hash update detected. New value: ' + stateKeyValue);
      if (stateKeyValue != window.location.hash) {
        window.location.hash = stateKeyValue;
        //console.log("Browsers hash updated accordingly.");
      } else {
        //console.log("Browsers hash already has the value of the state hash. Not updating browser hash.");
      }

	};
  };

  var ActiveclientsListener = function () {
    this.invoke = function (field) {
      var updateDoc = StringtoXML(field.textContent || field.text);
      var activeClientsValue = (updateDoc.childNodes[0].textContent || updateDoc.childNodes[0].text);
      //console.log('Activeclients response detected: ' + activeClientsValue);
      clearTimeout(renderTargetsReqVar);
      if (activeClientsValue > '0') {
        renderTargets(true);
      } else {
    	renderTargets(false);
      }
	};
  };

  var ActiveclientsRecordListener = function () {
	    this.invoke = function (field) {
	      var updateDoc = StringtoXML(field.textContent || field.text);
	      var activeClientsRecordValue = (updateDoc.childNodes[0].textContent || updateDoc.childNodes[0].text);
	      console.log('Activeclients response for record detected: ' + activeClientsRecordValue);
	      clearTimeout(renderOnRecordTargetsReqVar);
	      if (activeClientsRecordValue > '0') {
	        renderOnRecordTargets(true);
	      } else {
	    	console.log('Active clients is 0, final rendering');
	    	renderOnRecordTargets(false);
	      }
		};
	  };

  jsf.ajax.addOnEvent(fieldUpdateListener);

  function StringtoXML(text){
		var doc;
	    if (window.ActiveXObject){
	      doc=new ActiveXObject('Microsoft.XMLDOM');
	      doc.async=false;
	      doc.loadXML(text);
	    } else {
	      var parser=new DOMParser();
	      doc=parser.parseFromString(text,'text/xml');
	    }
	    return doc;
  }

  var setUpListeners = function () {
    //console.log("Starts tracking activeclientsField");
    fieldListeners.addListener("pz2watch:activeclientsField", new ActiveclientsListener());
    fieldListeners.addListener("pz2watch:activeclientsFieldRecord", new ActiveclientsRecordListener());
    if (trackHistory) {
        //console.log("Starts tracking windowlocationhash field");
        fieldListeners.addListener("pz2watch:windowlocationhash", new StateListener());
        //console.log("Setting listener for browser onhashchange");
        window.onload = window.onhashchange = windowlocationhashListener;
    }
  };<commit_msg>Minor clean up<commit_after>  var renderTargetsReqVar;
  var renderOnRecordTargetsReqVar;

  function renderTargets(doRefresh)
  {
	//console.log('rendering ' + renderWhileActiveclients);
	var sourcecomp = document.getElementById("pz2watch:activeclientsField");
    jsf.ajax.request(sourcecomp, null,{render: renderWhileActiveclients + " pz2watch:errorMessages"});
    if (doRefresh) {
		//console.log('Will do another ajax request after a timeout in order to render: pz2watch:activeclientsField');
	    renderTargetsReqVar=setTimeout(
	     function() {
	       //console.log('Making request for pz2watch:activeclientsField');
	       jsf.ajax.request(sourcecomp, null,{render: "pz2watch:activeclientsField"});
	     }
	     ,500);
    } else {
    	//console.log("No further updates from server requested");
    }
  }

  function renderOnRecordTargets(doRefresh)
  {
	console.log('rendering ' + renderWhileActiveclientsRecord);
	var sourcecomp = document.getElementById("pz2watch:activeclientsFieldRecord");
    jsf.ajax.request(sourcecomp, null,{render: renderWhileActiveclientsRecord});
    if (doRefresh) {
		console.log('Will do another ajax request after a timeout in order to render: pz2watch:activeclientsFieldRecord');
	    renderOnRecordTargetsReqVar=setTimeout(
	     function() {
	       console.log('Making request for pz2watch:activeclientsFieldRecord');
	       jsf.ajax.request(sourcecomp, null,{render: "pz2watch:activeclientsFieldRecord"});
	     }
	     ,1000);
    } else {
    	//console.log("No further updates from server requested");
    }
  }

  function windowlocationhashListener () {
	  if (trackHistory) {
	      //console.log("browser hash update detected");
	      var stateKey = document.getElementById("pz2watch:windowlocationhash");
	      if (window.location.hash != stateKey.value) {
	        //console.log("updating stateKey with new browser hash: " + window.location.hash);
	        stateKey.value = window.location.hash;
	        if (! stateKey.value) window.location.hash = '#initial';
	        stateKey.onchange();
	      } else {
	        //console.log("State hash already has the value of the new browser hash - not updating state hash");
	      }
	  }
  }

  function fieldUpdateListener (data) {
	  if (data.status === "success") {
		var updates = data.responseXML.getElementsByTagName("update");
		for (var i=0, max=updates.length; i<max; i++) {
			var lsnri = fieldListeners.getListener(updates[i].getAttribute("id"));
			if (lsnri) {
				lsnri.invoke(updates[i]);
			}
		}
	  }
  }

  var Pz2listeners = function () {
	var lsnrs = {};
	this.addListener = function (key, lsnr) {
		lsnrs[key] =lsnr;
	};
	this.getListener = function (key) {
		return lsnrs[key];
	};
  };

  var fieldListeners = new Pz2listeners();

  var StateListener = function () {
	this.invoke = function (field) {
      var stateKeyDoc = StringtoXML(field.textContent || field.text);
      var stateKeyValue = stateKeyDoc.childNodes[0].getAttribute("value");
      //console.log('Application hash update detected. New value: ' + stateKeyValue);
      if (stateKeyValue != window.location.hash) {
        window.location.hash = stateKeyValue;
        //console.log("Browsers hash updated accordingly.");
      } else {
        //console.log("Browsers hash already has the value of the state hash. Not updating browser hash.");
      }

	};
  };

  var ActiveclientsListener = function () {
    this.invoke = function (field) {
      var updateDoc = StringtoXML(field.textContent || field.text);
      var activeClientsValue = (updateDoc.childNodes[0].textContent || updateDoc.childNodes[0].text);
      //console.log('Activeclients response detected: ' + activeClientsValue);
      clearTimeout(renderTargetsReqVar);
      if (activeClientsValue > '0') {
        renderTargets(true);
      } else {
    	renderTargets(false);
      }
	};
  };

  var ActiveclientsRecordListener = function () {
	    this.invoke = function (field) {
	      var updateDoc = StringtoXML(field.textContent || field.text);
	      var activeClientsRecordValue = (updateDoc.childNodes[0].textContent || updateDoc.childNodes[0].text);
	      console.log('Activeclients response for record detected: ' + activeClientsRecordValue);
	      clearTimeout(renderOnRecordTargetsReqVar);
	      if (activeClientsRecordValue > '0') {
	        renderOnRecordTargets(true);
	      } else {
	    	console.log('Active clients is 0, final rendering');
	    	renderOnRecordTargets(false);
	      }
		};
	  };

  jsf.ajax.addOnEvent(fieldUpdateListener);

  function StringtoXML(text){
		var doc;
	    if (window.ActiveXObject){
	      doc=new ActiveXObject('Microsoft.XMLDOM');
	      doc.async=false;
	      doc.loadXML(text);
	    } else {
	      var parser=new DOMParser();
	      doc=parser.parseFromString(text,'text/xml');
	    }
	    return doc;
  }

  var setUpListeners = function () {
    //console.log("Starts tracking activeclientsField");
    fieldListeners.addListener("pz2watch:activeclientsField", new ActiveclientsListener());
    fieldListeners.addListener("pz2watch:activeclientsFieldRecord", new ActiveclientsRecordListener());
    if (trackHistory) {
        //console.log("Starts tracking windowlocationhash field");
        fieldListeners.addListener("pz2watch:windowlocationhash", new StateListener());
        //console.log("Setting listener for browser onhashchange");
        window.onload = window.onhashchange = windowlocationhashListener;
    }
  };<|endoftext|>


<commit_before>/**
 * Copyright (c) 2011 Bruno Jouhier <bruno.jouhier@sage.com>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
"use strict";
var fs = require("fs");
var path = require("path");
var compile = require("./compileSync");
var underscored = require("./underscored");
var registered = false;
var _options = {};

function homeDir() {
	if ((process.env.HOME === undefined) && (process.env.HOMEDRIVE === undefined)) return null;
	return process.env.HOME || ((process.env.HOMEDRIVE + process.env.HOMEPATH)).replace(/\\/g, "/");
}

function loadOptions(dir) {
	return (dir && fs.existsSync(dir + '/.streamline.json')) ? JSON.parse(fs.readFileSync(dir + '/.streamline.json', 'utf8')) : null;
}

exports.register = function(setoptions) {
	if (registered) return;
	registered = true;
	_options = setoptions || loadOptions('.');
	if (require.main)   // REPL doesn't have a require.main
		_options = _options || loadOptions(path.dirname(require.main.filename));
	_options = _options || loadOptions(homeDir());
	_options = _options || {};
	var pModule = require('module').prototype;
	var orig = pModule._compile;
	pModule._compile = function(content, filename) {
		// transform .js/.coffee files, but ignore ._js and ._coffee files;
		// they've already been transformed via require extension handlers.
		// (see underscored.js)
		content = /\._(js|coffee)$/.test(filename) ? content : compile.transformModule(content, filename, _options);
		return orig.call(this, content, filename);
	}
	_options.registerOnly = true;
	underscored.run(_options);
	if (!_options.fibers && !_options.generators) {
		var g = require("../globals");
		require("../callbacks/runtime");
		switch (_options.trampoline) {
			case "nextTick":
				g.trampoline = g.trampoline || {};
				g.trampoline.queue = process.nextTick;
				break;
			case "none":
				g.trampoline = null;
				break;
			case "sameTick":
				break;
		}
	}
};

Object.defineProperty(exports, "options", {
	enumerable: true,
	get: function() {
		return _options;
	}
});<commit_msg>Use .streamline.json options for REPL!<commit_after>/**
 * Copyright (c) 2011 Bruno Jouhier <bruno.jouhier@sage.com>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
"use strict";
var fs = require("fs");
var path = require("path");
var compile = require("./compileSync");
var underscored = require("./underscored");
var registered = false;
var _options = {};

function homeDir() {
	if ((process.env.HOME === undefined) && (process.env.HOMEDRIVE === undefined)) return null;
	return process.env.HOME || ((process.env.HOMEDRIVE + process.env.HOMEPATH)).replace(/\\/g, "/");
}

// Loads the first `.streamline.json` options file found (searching in priority
// order), or an empty object if no such file found.
function loadOptions() {
	var dirs = ['.'];
	if (require.main) {		// REPL doesn't have a require.main
		dirs.push(path.dirname(require.main.filename));
	}
	dirs.push(homeDir());
	for (var i = 0; i < dirs.length; i++) {
		var dir = dirs[i];
		if (dir && fs.existsSync(dir + '/.streamline.json')) {
			return JSON.parse(fs.readFileSync(dir + '/.streamline.json', 'utf8'));
		}
	}
	return {};
}

exports.register = function(setoptions) {
	if (registered) return;
	registered = true;

	// accept the given options, but load .streamline.json options as defaults:
	_options = loadOptions();
	for (var opt in setoptions || {}) {
		_options[opt] = setoptions[opt];
	}

	var pModule = require('module').prototype;
	var orig = pModule._compile;
	pModule._compile = function(content, filename) {
		// transform .js/.coffee files, but ignore ._js and ._coffee files;
		// they've already been transformed via require extension handlers.
		// (see underscored.js)
		content = /\._(js|coffee)$/.test(filename) ? content : compile.transformModule(content, filename, _options);
		return orig.call(this, content, filename);
	}
	_options.registerOnly = true;
	underscored.run(_options);
	if (!_options.fibers && !_options.generators) {
		var g = require("../globals");
		require("../callbacks/runtime");
		switch (_options.trampoline) {
			case "nextTick":
				g.trampoline = g.trampoline || {};
				g.trampoline.queue = process.nextTick;
				break;
			case "none":
				g.trampoline = null;
				break;
			case "sameTick":
				break;
		}
	}
};

Object.defineProperty(exports, "options", {
	enumerable: true,
	get: function() {
		return _options;
	}
});
<|endoftext|>


<commit_before>import React from 'react';
import Relay from 'react-relay'
import BSDSurvey from './BSDSurvey'
import {BernieColors, BernieText} from '../styles/bernie-css'
import {GoogleMapLoader, GoogleMap, Marker} from 'react-google-maps';
import {FlatButton, Paper} from 'material-ui';
import moment from 'moment';
import FontIcon from 'material-ui/lib/font-icon';
import SideBarLayout from '../SideBarLayout'
import GCSelectField from '../forms/GCSelectField';
import GCBooleanField from '../forms/GCBooleanField';

class BSDPhonebankRSVPSurvey extends React.Component {
  static propTypes = {
    onSubmitted : React.PropTypes.func,
    initialValues: React.PropTypes.object,
    survey: React.PropTypes.object
  }

  submit() {
    this.refs.survey.refs.component.submit()
  }

  state = {
    clickedMarker: null,
    selectedEventId: null,
    joinCallTeam: null,
    dateFilter: "upcoming"
  }

  styles = {
    question: {
      ...BernieText.secondaryTitle,
      fontWeight: 600,
      marginTop: 20,
      color: BernieColors.blue,
      fontSize: '1em',
      letterSpacing: '0em'
    },
    paragraph: {
      marginTop: 10
    }
  }

  momentWithOffset(startDate, utcOffset) {
    startDate = startDate * 1000
    return moment(startDate).utcOffset(utcOffset)
  }

  handleMarkerClick(marker) {
    this.setState({clickedMarker: marker})
  }

  selectButton(marker) {
    return (
      <FlatButton label='Select' style={{
        ...BernieText.inputLabel,
        backgroundColor: BernieColors.green,
        marginTop: 10,
      }}
        onTouchTap={(event) => {
          this.setState({
            clickedMarker: null,
            selectedEventId: marker.eventId
          })
          this.refs.survey.refs.component.setFieldValue('event_id', marker.eventId)
        }}
      />
    )
  }

  deselectButton() {
    return (
      <FlatButton
        label="Deselect"
        style={{
          ...BernieText.inputLabel,
          backgroundColor: BernieColors.red,
        }}
        onTouchTap={() => {
          this.setState({
            selectedEventId: null
          })
          this.refs.survey.refs.component.setFieldValue('event_id', '')
        }}
      />
    )
  }

  renderSelectedEvent() {
    if (!this.state.selectedEventId)
      return

    let event = this.props.interviewee.nearbyEvents.find((event) => event.eventIdObfuscated === this.state.selectedEventId)
    let content = (

Selected {event.name}

on {this.momentWithOffset(event.startDate, event.localUTCOffset).format('MMM D')}.

    )
    let sideBar = (

        {this.deselectButton()}

    )
    return (
      <Paper zDepth={0} style={{
        padding: '10px 10px 10px 10px',
        marginTop: 10,
        border: 'solid 1px ' + BernieColors.blue,
        minHeight: 25
      }}>
        <SideBarLayout
          containerStyle={{
            'border': 'none'
          }}
          sideBar={sideBar}
          content={content}
          contentViewStyle={{
            border: 'none',
            paddingRight: 10
          }}
          sideBarStyle={{
            border: 'none',
            textAlign: 'right',
            marginTop: 'auto',
            marginBottom: 'auto'
          }}
          sideBarPosition='right'
        />
      </Paper>
    )
  }

  renderMarkerDescription(marker) {
    let description =

    if (!marker)
      return

;
    if (marker.key === 'home')
      description = (

            {`${this.props.interviewee.firstName}'s home`}

      )
    let button =

;
    if (marker.key !== 'home') {
      let attendance = marker.attendeesCount
      if (marker.capacity)
        attendance = attendance + '/' + marker.capacity
      description = (

            {this.
              momentWithOffset(marker.startDate, marker.localUTCOffset).format('dddd, MMMM Do — h:mm A')}

            {marker.name}

{marker.venueName}

{marker.addr1}

{marker.addr2}

Attendance: {attendance}

Direct Link

            {this.state.selectedEventId === marker.eventId ? this.deselectButton() : this.selectButton(marker)}

      )
    }

    return (
      <Paper zDepth={0} style={{
        marginTop: 10,
        padding: '10px 10px 10px 10px',
        border: 'solid 1px ' + BernieColors.green
      }}>
        {description}
      </Paper>
    )
  }

  getEventAddr2(event) {
    let desc = ''
    if (event.venueAddr2)
      desc = desc + ' ' + event.venueAddr2;
    if (event.venueCity)
      desc = desc + ' ' + event.venueCity;
    if (event.venueState)
      desc = desc + ', ' + event.venueState
    return desc.trim();
  }

  renderMap() {
    let center = {
      lat: this.props.interviewee.address.latitude,
      lng: this.props.interviewee.address.longitude
    }
    let homeIcon = {
      path: 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z',
      fillColor: BernieColors.blue,
      fillOpacity: 1.0,
      scale: 1,
      strokeColor: BernieColors.blue,
      strokeWeight: 2
    };
    let markers = [
      {
        position: center,
        key: 'home',
        title: 'home',
        name: 'Interviewee home',
        icon: homeIcon
      }
    ];

    this.props.interviewee.nearbyEvents.forEach((event) => {
      if(this.state.dateFilter != 'upcoming'){
        if(!moment().add(this.state.dateFilter.split('_')[0], 'days').isSame(this.momentWithOffset(event.startDate, event.localUTCOffset), 'day')){
          return;
        }
      }
      let marker = {
        position: {
          lat: event.latitude,
          lng: event.longitude
        },
        localUTCOffset: event.localUTCOffset,
        key: event.id,
        title: event.name,
        name: event.name,
        startDate: event.startDate,
        venueName: event.venueName,
        addr1: event.venueAddr1,
        addr2: this.getEventAddr2(event),
        eventId: event.eventIdObfuscated,
        capacity: event.capacity,
        attendeesCount: event.attendeesCount,
        link: event.link,
        icon: null
      }
      if (this.state.clickedMarker && marker.eventId === this.state.clickedMarker.eventId) {
        // FIXME - the hex code is BernieColors.green hardcoded
        marker.icon = 'http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|4acc66'
      }
      else if (this.state.selectedEventId && marker.eventId === this.state.selectedEventId) {
        marker.icon = 'http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|147FD7'
      }
      markers.push(marker)
    })

    return (

      <GoogleMapLoader
        containerElement={

        }
        googleMapElement={
          <GoogleMap
            ref='map'
            options={{
              scrollwheel: false
            }}
            defaultZoom={9}
            defaultCenter={center}>
            {markers.map((marker, index) => {
              return (
                <Marker
                  {...marker}
                  onClick={this.handleMarkerClick.bind(this, marker)}
                />
              );
            })}
          </GoogleMap>
        }
      />

    )
  }

  getDateChoices(){
    let options = {"upcoming": "All Upcoming Events"}
    let WEEKDAY_DATE_FORMAT = 'dddd, MMMM Do';

    // comically large for test purposes
    // 10 or 14 would be well suited for the real world I think.
    for(let dayCount = 0; dayCount < 300; dayCount++){
      let label = '';
      if(dayCount == 0){
        label = 'Today, '
      } else if(dayCount == 1){
        label = 'Tomorrow, '
      }

      let date = moment().add(dayCount, 'days')

      // make sure we have events to accommodate that day.
      this.props.interviewee.nearbyEvents.forEach((event) => {
        if(this.momentWithOffset(event.startDate, event.localUTCOffset).isSame(date, 'day')){
          options[dayCount + "_days"] = label + date.format(WEEKDAY_DATE_FORMAT)
        }
      })

    }

    return options
  }

  renderDateFilters(){
    let content = (
      <GCSelectField
        choices={ this.getDateChoices() }
        onChange={(value) => {
          this.setState({
            dateFilter: value,
            clickedMarker: null
          });
        }}
        clearable={false}
        value={this.state.dateFilter}
        labelStyle={{
          paddingBottom: 0
        }}
      />
    )

    let sidebar = (

        Show events on date:

    )
    return (
      <SideBarLayout
        content={content}
        sideBar={sidebar}
        containerStyle={{
          border: 'none',
          paddingTop: 10
        }}
        sideBarStyle={{
          ...BernieText.inputLabel,
          border: 'none',
          width: 200
        }}
        contentViewStyle={{
          border: 'none'
        }}
      />
    )
  }

  render() {
    return (

Hi {this.props.interviewee.firstName || ''}, my name is {this.props.currentUser.firstName || '_______'} and I'm a volunteer with the Bernie Sanders campaign. I'm calling you because you signed up at some point to help out with the Bernie Sanders campaign.  Right now, we are trying to get as many volunteers as possible to show up to phone bank parties that other volunteers are hosting.  These phone banks are events where volunteers get together to contact voters in the early states.  It's an incredibly crucial part of our strategy to get Senator Sanders elected as president because we've seen that when volunteers talk to voters, Bernie starts doing better.

          I see that there is a phone bank being held near you on Thursday, January 2 at 555 Christopher St.

            Can I sign you up for this phone bank?

          {this.renderDateFilters()}

          {this.renderMap()}

        {this.renderSelectedEvent()}
        {this.renderMarkerDescription(this.state.clickedMarker)}

            [If yes, click 'Select' on the phone bank above] Great! I've signed you up!

            [If no] That's ok - is there a day that you are free to go to a phone bank party? There are 20 upcoming phone banks in your area. [Click the event date filter above to find a date that works for them]

            One last thing: we need more people to make calls like the one I'm making.

            <GCBooleanField
              label="Can we send you some information about joining our calling team?"
              labelStyle={this.styles.question}
              value={this.state.joinCallTeam}
              onChange={(value) => this.setState({joinCallTeam: value})}
            />

        <BSDSurvey
          ref='survey'
          style={{
            display: 'none'
          }}
          survey={this.props.survey}
          interviewee={this.props.interviewee}
          onSubmitted={this.props.onSubmitted}
          onLoaded={() => {
            this.refs.survey.refs.component.hideField('event_id')
          }}
        />

    )
  }
}

export default Relay.createContainer(BSDPhonebankRSVPSurvey, {
  initialVariables: {
    type: 'phonebank'
  },
  fragments: {
    survey: () => Relay.QL`
      fragment on Survey {
        ${BSDSurvey.getFragment('survey')}
      }
    `,
    currentUser: () => Relay.QL`
      fragment on User {
        firstName
      }
    `,
    interviewee: () => Relay.QL`
      fragment on Person {
        ${BSDSurvey.getFragment('interviewee')}
        firstName
        email
        address {
          latitude
          longitude
        }
        nearbyEvents(within:20, type:$type) {
          id
          eventIdObfuscated
          name
          startDate
          localTimezone
          localUTCOffset
          venueName
          venueAddr1
          venueAddr2
          venueCity
          venueState
          description
          latitude
          longitude
          capacity
          attendeesCount
          link
        }
      }
    `
  }
})

<commit_msg>Inline event description into script<commit_after>import React from 'react';
import Relay from 'react-relay'
import BSDSurvey from './BSDSurvey'
import {BernieColors, BernieText} from '../styles/bernie-css'
import {GoogleMapLoader, GoogleMap, Marker} from 'react-google-maps';
import {FlatButton, Paper} from 'material-ui';
import moment from 'moment';
import FontIcon from 'material-ui/lib/font-icon';
import SideBarLayout from '../SideBarLayout'
import GCSelectField from '../forms/GCSelectField';
import GCBooleanField from '../forms/GCBooleanField';

const WEEKDAY_DATE_FORMAT = 'dddd, MMMM Do';
class BSDPhonebankRSVPSurvey extends React.Component {
  static propTypes = {
    onSubmitted : React.PropTypes.func,
    initialValues: React.PropTypes.object,
    survey: React.PropTypes.object
  }

  submit() {
    this.refs.survey.refs.component.submit()
  }

  state = {
    clickedMarker: null,
    selectedEventId: null,
    joinCallTeam: null,
    dateFilter: "upcoming"
  }

  styles = {
    question: {
      ...BernieText.secondaryTitle,
      fontWeight: 600,
      marginTop: 20,
      color: BernieColors.blue,
      fontSize: '1em',
      letterSpacing: '0em'
    },
    paragraph: {
      marginTop: 10
    }
  }

  momentWithOffset(startDate, utcOffset) {
    startDate = startDate * 1000
    return moment(startDate).utcOffset(utcOffset)
  }

  handleMarkerClick(marker) {
    this.setState({clickedMarker: marker})
  }

  selectButton(marker) {
    return (
      <FlatButton label='Select' style={{
        ...BernieText.inputLabel,
        backgroundColor: BernieColors.green,
        marginTop: 10,
      }}
        onTouchTap={(event) => {
          this.setState({
            clickedMarker: null,
            selectedEventId: marker.eventId
          })
          this.refs.survey.refs.component.setFieldValue('event_id', marker.eventId)
        }}
      />
    )
  }

  deselectButton() {
    return (
      <FlatButton
        label="Deselect"
        style={{
          ...BernieText.inputLabel,
          backgroundColor: BernieColors.red,
        }}
        onTouchTap={() => {
          this.setState({
            selectedEventId: null
          })
          this.refs.survey.refs.component.setFieldValue('event_id', '')
        }}
      />
    )
  }

  renderSelectedEvent() {
    if (!this.state.selectedEventId)
      return

    let event = this.props.interviewee.nearbyEvents.find((event) => event.eventIdObfuscated === this.state.selectedEventId)
    let content = (

Selected {event.name}

on {this.momentWithOffset(event.startDate, event.localUTCOffset).format('MMM D')}.

    )
    let sideBar = (

        {this.deselectButton()}

    )
    return (
      <Paper zDepth={0} style={{
        padding: '10px 10px 10px 10px',
        marginTop: 10,
        border: 'solid 1px ' + BernieColors.blue,
        minHeight: 25
      }}>
        <SideBarLayout
          containerStyle={{
            'border': 'none'
          }}
          sideBar={sideBar}
          content={content}
          contentViewStyle={{
            border: 'none',
            paddingRight: 10
          }}
          sideBarStyle={{
            border: 'none',
            textAlign: 'right',
            marginTop: 'auto',
            marginBottom: 'auto'
          }}
          sideBarPosition='right'
        />
      </Paper>
    )
  }

  renderMarkerDescription(marker) {
    let description =

    if (!marker)
      return

;
    if (marker.key === 'home')
      description = (

            {`${this.props.interviewee.firstName}'s home`}

      )
    let button =

;
    if (marker.key !== 'home') {
      let attendance = marker.attendeesCount
      if (marker.capacity)
        attendance = attendance + '/' + marker.capacity
      description = (

            {this.
              momentWithOffset(marker.startDate, marker.localUTCOffset).format('dddd, MMMM Do — h:mm A')}

            {marker.name}

{marker.venueName}

{marker.addr1}

{marker.addr2}

Attendance: {attendance}

Direct Link

            {this.state.selectedEventId === marker.eventId ? this.deselectButton() : this.selectButton(marker)}

      )
    }

    return (
      <Paper zDepth={0} style={{
        marginTop: 10,
        padding: '10px 10px 10px 10px',
        border: 'solid 1px ' + BernieColors.green
      }}>
        {description}
      </Paper>
    )
  }

  getEventAddr2(event) {
    let desc = ''
    if (event.venueAddr2)
      desc = desc + ' ' + event.venueAddr2;
    if (event.venueCity)
      desc = desc + ' ' + event.venueCity;
    if (event.venueState)
      desc = desc + ', ' + event.venueState
    return desc.trim();
  }

  renderMap() {
    let center = {
      lat: this.props.interviewee.address.latitude,
      lng: this.props.interviewee.address.longitude
    }
    let homeIcon = {
      path: 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z',
      fillColor: BernieColors.blue,
      fillOpacity: 1.0,
      scale: 1,
      strokeColor: BernieColors.blue,
      strokeWeight: 2
    };
    let markers = [
      {
        position: center,
        key: 'home',
        title: 'home',
        name: 'Interviewee home',
        icon: homeIcon
      }
    ];

    this.props.interviewee.nearbyEvents.forEach((event) => {
      if(this.state.dateFilter != 'upcoming'){
        if(!moment().add(this.state.dateFilter.split('_')[0], 'days').isSame(this.momentWithOffset(event.startDate, event.localUTCOffset), 'day')){
          return;
        }
      }
      let marker = {
        position: {
          lat: event.latitude,
          lng: event.longitude
        },
        localUTCOffset: event.localUTCOffset,
        key: event.id,
        title: event.name,
        name: event.name,
        startDate: event.startDate,
        venueName: event.venueName,
        addr1: event.venueAddr1,
        addr2: this.getEventAddr2(event),
        eventId: event.eventIdObfuscated,
        capacity: event.capacity,
        attendeesCount: event.attendeesCount,
        link: event.link,
        icon: null
      }
      if (this.state.clickedMarker && marker.eventId === this.state.clickedMarker.eventId) {
        // FIXME - the hex code is BernieColors.green hardcoded
        marker.icon = 'http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|4acc66'
      }
      else if (this.state.selectedEventId && marker.eventId === this.state.selectedEventId) {
        marker.icon = 'http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|147FD7'
      }
      markers.push(marker)
    })

    return (

      <GoogleMapLoader
        containerElement={

        }
        googleMapElement={
          <GoogleMap
            ref='map'
            options={{
              scrollwheel: false
            }}
            defaultZoom={9}
            defaultCenter={center}>
            {markers.map((marker, index) => {
              return (
                <Marker
                  {...marker}
                  onClick={this.handleMarkerClick.bind(this, marker)}
                />
              );
            })}
          </GoogleMap>
        }
      />

    )
  }

  getDateChoices(){
    let options = {"upcoming": "All Upcoming Events"}

    // comically large for test purposes
    // 10 or 14 would be well suited for the real world I think.
    for(let dayCount = 0; dayCount < 300; dayCount++){
      let label = '';
      if(dayCount == 0){
        label = 'Today, '
      } else if(dayCount == 1){
        label = 'Tomorrow, '
      }

      let date = moment().add(dayCount, 'days')

      // make sure we have events to accommodate that day.
      this.props.interviewee.nearbyEvents.forEach((event) => {
        if(this.momentWithOffset(event.startDate, event.localUTCOffset).isSame(date, 'day')){
          options[dayCount + "_days"] = label + date.format(WEEKDAY_DATE_FORMAT)
        }
      })

    }

    return options
  }

  renderDateFilters(){
    let content = (
      <GCSelectField
        choices={ this.getDateChoices() }
        onChange={(value) => {
          this.setState({
            dateFilter: value,
            clickedMarker: null
          });
        }}
        clearable={false}
        value={this.state.dateFilter}
        labelStyle={{
          paddingBottom: 0
        }}
      />
    )

    let sidebar = (

        Show events on date:

    )
    return (
      <SideBarLayout
        content={content}
        sideBar={sidebar}
        containerStyle={{
          border: 'none',
          paddingTop: 10
        }}
        sideBarStyle={{
          ...BernieText.inputLabel,
          border: 'none',
          width: 200
        }}
        contentViewStyle={{
          border: 'none'
        }}
      />
    )
  }

  renderNo() {
    let eventCount = this.props.interviewee.nearbyEvents.length;
    let text =

    if (eventCount >= 2) {
      text = (

          [If no] That's ok - is there a day that you are free to go to a phone bank party? There are {eventCount} upcoming phone banks in your area. [Click the event date filter above to find a date that works for them and click on an event pin on the map to get its description].

      )
    }
    else
      text = (

          [If no] That's ok.  There's no other phone banks in your area upcoming, but check out https://map.berniesanders.com.  And if you are interested, sign up to host a phone bank there and we'll make sure people in your area find out about it!

      )
    return text
  }

  render() {
    return (

Hi {this.props.interviewee.firstName || ''}, my name is {this.props.currentUser.firstName || '_______'} and I'm a volunteer with the Bernie Sanders campaign. I'm calling you because you signed up at some point to help out with the Bernie Sanders campaign.  Right now, we are trying to get as many volunteers as possible to show up to phone bank parties that other volunteers are hosting.  These phone banks are events where volunteers get together to contact voters in the early states.  It's an incredibly crucial part of our strategy to get Senator Sanders elected as president because we've seen that when volunteers talk to voters, Bernie starts doing better.

          I see that there is a phone bank being held near you on {this.state.clickedMarker ? this.momentWithOffset(this.state.clickedMarker.startDate, this.state.clickedMarker.localUTCOffset).format(WEEKDAY_DATE_FORMAT) : '[event date]'} at {this.state.clickedMarker ? this.state.clickedMarker.addr1 : '[event address]'}.

            Can I sign you up for this phone bank?

          {this.renderDateFilters()}

          {this.renderMap()}

        {this.renderSelectedEvent()}
        {this.renderMarkerDescription(this.state.clickedMarker)}

            [If yes, click 'Select' on the phone bank above] Great! I've signed you up!

            {this.renderNo()}

            One last thing: we need more people to make calls like the one I'm making.

            <GCBooleanField
              label="Can we send you some information about joining our calling team?"
              labelStyle={this.styles.question}
              value={this.state.joinCallTeam}
              onChange={(value) => this.setState({joinCallTeam: value})}
            />

        <BSDSurvey
          ref='survey'
          style={{
            display: 'none'
          }}
          survey={this.props.survey}
          interviewee={this.props.interviewee}
          onSubmitted={this.props.onSubmitted}
          onLoaded={() => {
            this.refs.survey.refs.component.hideField('event_id')
          }}
        />

    )
  }
}

export default Relay.createContainer(BSDPhonebankRSVPSurvey, {
  initialVariables: {
    type: 'phonebank'
  },
  fragments: {
    survey: () => Relay.QL`
      fragment on Survey {
        ${BSDSurvey.getFragment('survey')}
      }
    `,
    currentUser: () => Relay.QL`
      fragment on User {
        firstName
      }
    `,
    interviewee: () => Relay.QL`
      fragment on Person {
        ${BSDSurvey.getFragment('interviewee')}
        firstName
        email
        address {
          latitude
          longitude
        }
        nearbyEvents(within:20, type:$type) {
          id
          eventIdObfuscated
          name
          startDate
          localTimezone
          localUTCOffset
          venueName
          venueAddr1
          venueAddr2
          venueCity
          venueState
          description
          latitude
          longitude
          capacity
          attendeesCount
          link
        }
      }
    `
  }
})

<|endoftext|>


<commit_before>//Pure JS based composition
define.class(function($server$,composition, role,require, $ui$,treeview,  cadgrid, splitcontainer, screen, view, label, button, $widgets$, propviewer, colorpicker){

	var Shader = this.Shader = require('$system/platform/$platform/shader$platform')

	define.class(this, "menubar", function($ui$, view){

	})

	define.class(this, "dockpanel", function($ui$, view, label){
		this.attributes = {
			title:{type:String, value:"Untitled"}
		}

		this.padding = 0;
		this.margin = 0;
		this.bgcolor = vec4("#506050");
		this.flex = 1;
		this.flexdirection ="column"
		this.bg = 0;
		this.render = function(){
			return [
				view({bgcolor:"#202220", margin:vec4(0,0,0,0), padding:vec4(0)},
					view({margin:vec4(1,1,2,0),bgcolor:"#8080a0", borderwidth:0,borderradius:vec4(4,4,0,0),padding:vec4(10,2,10,2)},
						label({text:this.title, margin:1, bg:0, fontsize:12, fgcolor: "white" })
					)
				)
				,this.constructor_children
			];
		}
	})

	define.class(this, "connection",function($ui$, view){
		this.attributes = {
			from:{type:String, value:""},
			to:{type:String, value:""},
			frompos:{type:vec2, value:vec2(0,0)},
			topos:{type:vec2, value:vec2(100,100)},
			linewidth:{type:float, value:14}
		}

		this.bgcolor = "blue"

		define.class(this, "connectionshader", this.Shader,function($ui$, view){
			this.updateorder = 0
			this.draworder = 0
			this.mesh = vec2.array()

			for(var i = 0;i<100;i++){
				this.mesh.push([i/100,-0.5])
				this.mesh.push([i/100, 0.5])
			}

			this.position = function(){
				uv = mesh.xy
				var a = mesh.x;
				var b = mesh.y * view.linewidth/2;
				pos = view.frompos * a + view.topos * (1-a);
				//pos = vec2(mesh.x * view.layout.width, mesh.y * view.layout.height)
				return vec4(pos, 0, 1) * view.totalmatrix * view.viewmatrix
			}

			this.color = function(){
				return view.bgcolor
			}
		})

		this.bg = this.connectionshader();
	})

	define.class(this, "block", function($ui$, view, label){

		this.position = "absolute" ;
		this.bgcolor = vec4("#708090" )
		this.padding = 2;
		this.borderradius = vec4(10,10,0,0);
		this.borderwidth = 0;

		this.attributes = {
			pos:{persist: true},
			attributelist:{type:Object, value:[]},
			title:{type:String, value:"Untitled"}
		}

		this.mouseleftdown = function(p){
			var props = this.find("mainproperties");
			if (props) props.target = this.name;
			this.startposition = p.global;
			this.startx = this.pos[0];
			this.starty = this.pos[1];

			this.mousemove = function(p){

				var dx = p.global[0] - this.startposition[0];
				var dy = p.global[1] - this.startposition[1];
				var x = Math.floor((this.startx+dx)/25)*25;
				var y = Math.floor((this.starty+dy)/25)*25;

				this.pos = vec2(x,y);

				this.redraw();
				this.relayout();

			}.bind(this);
		}

		this.mouseleftup = function(p){
			var x = Math.floor(this.pos[0]/25)*25;
			var y = Math.floor(this.pos[1]/25)*25;
			this.pos = vec2(x,y);
			this.redraw();
			this.relayout();

			this.mousemove = function(){};
		}

		this.flexdirection = "column"

		this.render = function(){
			return [
				label({text:this.title, bg:0, margin:4, fontsize: 12})
				,view({flexdirection:"row", alignitems:"stretch", bg:0 }
					,button({icon:"flask", text:"button" })
					,button({icon:"flask"})
				)
			]
		}
	})

	this.render = function(){ return [
		role(
			screen({bg:0,clearcolor:vec4('black'),flexwrap:"nowrap", flexdirection:"row"}
				,this.menubar({})

				,splitcontainer({}
					,splitcontainer({flex:0.3}
						,this.dockpanel({title:"Composition"}
							,treeview({flex:1})
						)
					)
					,this.dockpanel({title:"Patch"}
						,cadgrid({name:"centralconstructiongrid", overflow:"scroll" ,bgcolor: "#303030",minorsize:5,majorsize:25,  majorline:"#505040", minorline:"#404040"}
							,view({name:"connectionlayer", bg:0}
								,this.connection({from:"phone", to:"tv", bgcolor:"blue" })
							)
							,view({name:"blocklayer", bg:0}
								,this.block({name:"phone", title:"Phone", x:200, y:20})
								,this.block({name:"tv", title:"Television", x:50, y:200})
								,this.block({name:"tablet", title:"Tablet",x:200, y:20})
								,this.block({name:"thing", title:"Thing",x:200, y:20})
							)
						)
					)
					,splitcontainer({flex:0.5,direction:"horizontal"}
						,this.dockpanel({title:"Library"}
							,propviewer({flex:1,name:"mainpropviewer", target:"thebutton", flex:1, overflow:"scroll"})
						)
						,this.dockpanel({title:"Properties"}
							,propviewer({flex:2,name:"mainproperties", target:"centralconstructiongrid", flex:1, overflow:"scroll"})
						)
					)
				)
			)
		)
	]}
})<commit_msg>connections starting to be nice again<commit_after>//Pure JS based composition
define.class(function($server$,composition, role,require, $ui$,treeview,  cadgrid, splitcontainer, screen, view, label, button, $widgets$, propviewer, colorpicker){

	var Shader = this.Shader = require('$system/platform/$platform/shader$platform')

	define.class(this, "menubar", function($ui$, view){

	})

	define.class(this, "dockpanel", function($ui$, view, label){
		this.attributes = {
			title:{type:String, value:"Untitled"}
		}

		this.padding = 0;
		this.margin = 0;
		this.bgcolor = vec4("#506050");
		this.flex = 1;
		this.flexdirection ="column"
		this.bg = 0;

		this.render = function(){
			return [
				view({bgcolor:"#202220", margin:vec4(0,0,0,0), padding:vec4(0)},
					view({margin:vec4(1,1,2,0),bgcolor:"#8080a0", borderwidth:0,borderradius:vec4(4,4,0,0),padding:vec4(10,2,10,2)},
						label({text:this.title, margin:1, bg:0, fontsize:12, fgcolor: "white" })
					)
				)
				,this.constructor_children
			];
		}
	})

	define.class(this, "connection",function($ui$, view){
		this.attributes = {
			from:{type:String, value:""},
			to:{type:String, value:""},
			linewidth:{type:float, value:10, duration:0.5, motion:"bounce"},
			focussedcolor:{type:vec4, value:vec4("white")},
			focussedwidth:{type:float, value:20},
			bgcolor:{motion:"linear", duration: 0.1}
		}

		this.init = function(){
			this.neutralcolor = this.bgcolor;
			this.neutrallinewidth = this.linewidth;
		}

		this.focus =function(){
			console.log(this.focus);
			if (this.focus) {
				this.bgcolor = this.focussedcolor;
				this.linewidth = this.focussedwidth;
			}
			else{
				this.bgcolor = this.neutralcolor;
				this.linewidth = this.neutrallinewidth;
			}
		}
		this.frompos= vec2(0,0);
		this.topos= vec2(0,100);

		this.bgcolor = "#8090a0"

		define.class(this, "connectionshader", this.Shader,function($ui$, view){
			this.mesh = vec2.array()

			for(var i = 0;i<100;i++){
				this.mesh.push([i/100.0,-0.5])
				this.mesh.push([i/100.0, 0.5])
			}

			this.drawtype = this.TRIANGLE_STRIP

			this.B1 = function (t) { return t * t * t; }
			this.B2 = function (t) { return 3 * t * t * (1 - t); }
			this.B3 = function (t) { return 3 * t * (1 - t) * (1 - t); }
			this.B4 = function (t) { return (1 - t) * (1 - t) * (1 - t); }

			 this.bezier = function(percent,C1,C2,C3,C4) {

				var b1 = B1(percent);
				var b2 = B2(percent);
				var b3 = B3(percent);
				var b4 = B4(percent);

				return C1* b1 + C2 * b2 + C3 * b3 + C4 * b4;

			}

			this.position = function(){
				var a = mesh.x;
				var a2 = mesh.x+0.001;
				var b = mesh.y * view.linewidth;

				posA = this.bezier(a, view.frompos, view.frompos + vec2(100,0), view.topos - vec2(100,0), view.topos);
				posB = this.bezier(a2, view.frompos, view.frompos + vec2(100,0), view.topos - vec2(100,0), view.topos);

				var dp = normalize(posB - posA);

				var rev = vec2(-dp.y, dp.x);
				posA += rev * b;
				//pos = vec2(mesh.x * view.layout.width, mesh.y * view.layout.height)
				return vec4(posA, 0, 1) * view.totalmatrix * view.viewmatrix
			}

			this.color = function(){
				var a= 1.0-pow(abs(mesh.y*2.0), 2.5);
				return vec4(view.bgcolor.xyz,a);
			}
		})

		this.bg = this.connectionshader;
		this.calculateposition = function(){

			var F = this.find(this.from);
			var T = this.find(this.to);
			if (F && T){

				this.frompos = vec2(F.pos[0]+ F.layout.width-3,F.pos[1]+20);
				this.topos = vec2(T.pos[0],T.pos[1]+20);
			}

		}
		this.calculateposition();
		this.layout = function(){
			this.calculateposition();
		}
		this.render = function(){
			return [];
		}
	})

	define.class(this, "block", function($ui$, view, label){

		this.position = "absolute" ;
		this.bgcolor = vec4("#708090" )
		this.padding = 2;
		this.borderradius = vec4(10,10,1,1);
		this.borderwidth = 2;

		this.bordercolor = vec4("#607080")

		this.attributes = {
			pos:{persist: true},
			attributelist:{type:Object, value:[]},
			title:{type:String, value:"Untitled"}
		}

		this.mouseleftdown = function(p){
			var props = this.find("mainproperties");
			if (props) props.target = this.name;
			this.startposition = p.global;
			this.startx = this.pos[0];
			this.starty = this.pos[1];

			this.mousemove = function(p){

				var dx = p.global[0] - this.startposition[0];
				var dy = p.global[1] - this.startposition[1];
				var x = Math.floor((this.startx+dx)/25)*25;
				var y = Math.floor((this.starty+dy)/25)*25;

				this.pos = vec2(x,y);

				this.redraw();
				this.relayout();
				this.outer.updateconnections();

			}.bind(this);
		}

		this.mouseleftup = function(p){
			var x = Math.floor(this.pos[0]/25)*25;
			var y = Math.floor(this.pos[1]/25)*25;
			this.pos = vec2(x,y);
			this.redraw();
			this.relayout();

			this.mousemove = function(){};
		}

		this.flexdirection = "column"

		this.render = function(){
			return [
				label({text:this.title, bg:0, margin:4, fontsize: 12})
				,view({flexdirection:"row", alignitems:"stretch", bg:0 }
					,button({icon:"flask", text:"button" })
					,button({icon:"flask"})
				)
			]
		}
	})

	this.updateconnections = function(){
		var cl = this.find("connectionlayer");
		for(a in cl.children)
		{
			cl.children[a].layout =1 ;
		}
	}

	this.layout = function(){
		console.log("hmm");
		this.updateconnections();
	}

	this.render = function(){
		console.log("hmm!!");

		return [
		role(
			screen({bg:0,clearcolor:vec4('black'),flexwrap:"nowrap", flexdirection:"row"}
				,this.menubar({})

				,splitcontainer({}
					,splitcontainer({flex:0.3}
						,this.dockpanel({title:"Composition"}
							,treeview({flex:1})
						)
					)
					,this.dockpanel({title:"Patch"}
						,cadgrid({name:"centralconstructiongrid", overflow:"scroll" ,bgcolor: "#303030",minorsize:5,majorsize:25,  majorline:"#505040", minorline:"#404040"}
							,view({name:"connectionlayer", bg:0}
								,this.connection({from:"phone", to:"tv"})
								,this.connection({from:"tablet", to:"thing"})
							)
							,view({name:"blocklayer", bg:0, layout:function(){console.log("layout")}}
								,this.block({name:"phone", title:"Phone", x:200, y:20})
								,this.block({name:"tv", title:"Television", x:50, y:200})
								,this.block({name:"tablet", title:"Tablet",x:200, y:20})
								,this.block({name:"thing", title:"Thing",x:200, y:20})
							)
						)
					)
					,splitcontainer({flex:0.5,direction:"horizontal"}
						,this.dockpanel({title:"Library"}
							,propviewer({flex:1,name:"mainpropviewer", target:"thebutton", flex:1, overflow:"scroll"})
						)
						,this.dockpanel({title:"Properties"}
							,propviewer({flex:2,name:"mainproperties", target:"centralconstructiongrid", flex:1, overflow:"scroll"})
						)
					)
				)
			)
		)
	]}
})<|endoftext|>


<commit_before>'use babel';

import { fn } from './helpers';
import { File } from 'atom';
import mailconfig from '../settings/mailconfig.json';

let Builder = null;

export default class Mailer {

    constructor() {
        this.config = mailconfig;
        this.settings = atom.config.get('palladium');
        this.proto = this.settings.defaultProtocol;
        this.cfg = this.settings[this.proto];

        this.parser = new window.DOMParser();

        this.mailconfig = new File(fn.getProjectFile('.mailconfig'));
        this.mailoptions = {};
        this.mailoptions.html = null;
    }

    initializeMailer() {
        console.log("Palladium event");
        if (Builder === null) {
            this.libs = {
                path: require('path'),
                nodemailer: require('nodemailer'),
                smtp: require('nodemailer-smtp-transport'),
                htmlToText: require('nodemailer-html-to-text').htmlToText,
                plain: require('html-to-text')
            };
            Builder = true;
            console.log(Builder);
        }
    }

    changeSettings(newValue) {
        this.settings = newValue;
        this.proto = this.settings.defaultProtocol;
        this.cfg = this.settings[this.proto];
    }

    setSettings(cb) {
        if (typeof this.cfg.email !== 'undefined') {
            this.mailoptions.from = this.cfg.email;
        }

        this.setProtocol();
        if (typeof cb === "function") {
            this.authValid(cb);
        }
    }

    setProtocol() {
        let transport = null;
        let param = this.cfg;

        switch (this.proto) {
            case 'gmail':
                transport = fn.gmail(this.cfg);
                break;
            case 'smtp':
                param.auth = {};
                param.auth.user = this.cfg.user;
                param.auth.pass = this.cfg.pass;
                delete param.user;
                delete param.pass;

                transport = this.libs.smtp(param);
                break;
        }

        this.transporter = this.libs.nodemailer.createTransport(transport);
        this.transporter.use('compile', this.libs.htmlToText({ wordwrap: 77 }));
    }

    authValid(cb) {
        this.transporter.verify((error) => {
            if (error) {
                atom.notifications.addWarning('SMTP connect failed.', {
                    detail: error
                });
                return false;
            }

            cb();
        });
    }

    readmail() {
        let DOM = {};
        DOM.content = fn.activeFile().cachedContents;
        DOM.document = this.parser.parseFromString(DOM.content, "text/html");
        DOM.images = DOM.document.body.querySelectorAll('img');

        for (let i = 0; i < DOM.images.length; i++) {
            let src = DOM.images[i].getAttribute('src');

            src = 'cid:' + src.replace('cid:', '');
        }

        this.html = DOM.document.documentElement.outerHTML;
        this.savePlainText();

        return this.html;
    }

    valid() {
        if (!fn.hasProject() || !this.mailconfig.existsSync()) {
            atom.notifications.addWarning('Mailconfig not exists.');
            return false;
        }

        try {
            let cfg = JSON.parse(this.mailconfig.readSync(true));
            Object.assign(this.mailoptions, cfg);
        } catch (e) {
            atom.notifications.addError('Palladium configuration file is not valid!');
            return false;
        }

        return true;
    }

    savePlainText() {
        if (this.settings.plaintextSaveToFile) {
            let pt = {};
            pt.path = fn.activeFile().path;
            pt.parse = path.parse(pt.path);
            pt.parse.base = pt.parse.base.replace(pt.parse.ext, '.txt');
            pt.parse.ext = '.txt';
            pt.plain_path = path.format(pt.parse);
            pt.text = this.libs.plain.fromString(this.html, { wordwrap: 77 });
            pt.text = pt.text.replace(/(\n)\[cid:.*?\] |\[cid:.*?\]/g, '$1');

            new File(pt.plain_path).write(pt.text);
        }
    }

    attachments(dir, loc) {
        let attachments = [];
        let list = dir.getSubdirectory(loc).getEntriesSync();
        let acceptedExt = ['.jpg', '.png'];

        list.forEach((file) => {
            if (acceptedExt.indexOf(path.extname(file.getPath())) > -1) {
                let attachment = {};
                attachment.filename = file.getBaseName();
                attachment.cid = file.getBaseName();
                attachment.path = file.getPath();
                attachments.push(attachment);
            }
        });

        return attachments;
    }

}
<commit_msg>Remove logs<commit_after>'use babel';

import { fn } from './helpers';
import { File } from 'atom';
import mailconfig from '../settings/mailconfig.json';

let Builder = null;

export default class Mailer {

    constructor() {
        this.config = mailconfig;
        this.settings = atom.config.get('palladium');
        this.proto = this.settings.defaultProtocol;
        this.cfg = this.settings[this.proto];

        this.parser = new window.DOMParser();

        this.mailconfig = new File(fn.getProjectFile('.mailconfig'));
        this.mailoptions = {};
        this.mailoptions.html = null;
    }

    initializeMailer() {
        if (Builder === null) {
            this.libs = {
                path: require('path'),
                nodemailer: require('nodemailer'),
                smtp: require('nodemailer-smtp-transport'),
                htmlToText: require('nodemailer-html-to-text').htmlToText,
                plain: require('html-to-text')
            };
            Builder = true;
        }
    }

    changeSettings(newValue) {
        this.settings = newValue;
        this.proto = this.settings.defaultProtocol;
        this.cfg = this.settings[this.proto];
    }

    setSettings(cb) {
        if (typeof this.cfg.email !== 'undefined') {
            this.mailoptions.from = this.cfg.email;
        }

        this.setProtocol();
        if (typeof cb === "function") {
            this.authValid(cb);
        }
    }

    setProtocol() {
        let transport = null;
        let param = this.cfg;

        switch (this.proto) {
            case 'gmail':
                transport = fn.gmail(this.cfg);
                break;
            case 'smtp':
                param.auth = {};
                param.auth.user = this.cfg.user;
                param.auth.pass = this.cfg.pass;
                delete param.user;
                delete param.pass;

                transport = this.libs.smtp(param);
                break;
        }

        this.transporter = this.libs.nodemailer.createTransport(transport);
        this.transporter.use('compile', this.libs.htmlToText({ wordwrap: 77 }));
    }

    authValid(cb) {
        this.transporter.verify((error) => {
            if (error) {
                atom.notifications.addWarning('SMTP connect failed.', {
                    detail: error
                });
                return false;
            }

            cb();
        });
    }

    readmail() {
        let DOM = {};
        DOM.content = fn.activeFile().cachedContents;
        DOM.document = this.parser.parseFromString(DOM.content, "text/html");
        DOM.images = DOM.document.body.querySelectorAll('img');

        for (let i = 0; i < DOM.images.length; i++) {
            let src = DOM.images[i].getAttribute('src');

            src = 'cid:' + src.replace('cid:', '');
        }

        this.html = DOM.document.documentElement.outerHTML;
        this.savePlainText();

        return this.html;
    }

    valid() {
        if (!fn.hasProject() || !this.mailconfig.existsSync()) {
            atom.notifications.addWarning('Mailconfig not exists.');
            return false;
        }

        try {
            let cfg = JSON.parse(this.mailconfig.readSync(true));
            Object.assign(this.mailoptions, cfg);
        } catch (e) {
            atom.notifications.addError('Palladium configuration file is not valid!');
            return false;
        }

        return true;
    }

    savePlainText() {
        if (this.settings.plaintextSaveToFile) {
            let pt = {};
            pt.path = fn.activeFile().path;
            pt.parse = path.parse(pt.path);
            pt.parse.base = pt.parse.base.replace(pt.parse.ext, '.txt');
            pt.parse.ext = '.txt';
            pt.plain_path = path.format(pt.parse);
            pt.text = this.libs.plain.fromString(this.html, { wordwrap: 77 });
            pt.text = pt.text.replace(/(\n)\[cid:.*?\] |\[cid:.*?\]/g, '$1');

            new File(pt.plain_path).write(pt.text);
        }
    }

    attachments(dir, loc) {
        let attachments = [];
        let list = dir.getSubdirectory(loc).getEntriesSync();
        let acceptedExt = ['.jpg', '.png'];

        list.forEach((file) => {
            if (acceptedExt.indexOf(path.extname(file.getPath())) > -1) {
                let attachment = {};
                attachment.filename = file.getBaseName();
                attachment.cid = file.getBaseName();
                attachment.path = file.getPath();
                attachments.push(attachment);
            }
        });

        return attachments;
    }

}
<|endoftext|>


<commit_before>const { h, when, send, resolve, Value, computed } = require('mutant')
const nest = require('depnest')
const ssbMentions = require('ssb-mentions')
const extend = require('xtend')
const addSuggest = require('suggest-box')

exports.gives = nest('message.html.compose')

exports.needs = nest({
  'about.async.suggest': 'first',
  'channel.async.suggest': 'first',
  'emoji.async.suggest': 'first',
  'blob.html.input': 'first',
  'message.html.confirm': 'first',
  'drafts.sync.get': 'first',
  'drafts.sync.set': 'first'
})

exports.create = function (api) {
  return nest({ 'message.html.compose': compose })

  function compose ({ meta, location, prepublish, placeholder = 'Write a message', shrink = true }, cb) {
    if (typeof resolve(meta) !== 'object') throw new Error('Compose needs meta data about what sort of message composer you are making')
    if (!location) throw new Error('Compose expects a unique location so it can save drafts of messages')

    var files = []
    var filesById = {}
    var channelInputFocused = Value(false)
    var textAreaFocused = Value(false)
    var focused = computed([channelInputFocused, textAreaFocused], (a, b) => a || b)
    var hasContent = Value(false)

    var getProfileSuggestions = api.about.async.suggest()
    var getChannelSuggestions = api.channel.async.suggest()
    var getEmojiSuggestions = api.emoji.async.suggest()

    var blurTimeout = null

    var expanded = computed([shrink, focused, hasContent], (shrink, focused, hasContent) => {
      if (!shrink || hasContent) return true

      return focused
    })

    var channelInput = h('input.channel', {
      'ev-input': () => hasContent.set(!!channelInput.value),
      'ev-keyup': ev => {
        ev.target.value = ev.target.value.replace(/^#*([\w@%&])/, '#$1')
      },
      'ev-blur': () => {
        clearTimeout(blurTimeout)
        blurTimeout = setTimeout(() => channelInputFocused.set(false), 200)
      },
      'ev-focus': send(channelInputFocused.set, true),
      placeholder: '#channel (optional)',
      value: computed(meta.channel, ch => ch ? '#' + ch : null),
      disabled: when(meta.channel, true),
      title: when(meta.channel, 'Reply is in same channel as original message')
    })

    var draftPerstTimeout = null
    var draftLocation = location
    var textArea = h('textarea', {
      'ev-input': () => {
        hasContent.set(!!textArea.value)
        clearTimeout(draftPerstTimeout)
        draftPerstTimeout = setTimeout(() => {
          api.drafts.sync.set(draftLocation, textArea.value)
        }, 200)
      },
      'ev-blur': () => {
        clearTimeout(blurTimeout)
        blurTimeout = setTimeout(() => textAreaFocused.set(false), 200)
      },
      'ev-focus': send(textAreaFocused.set, true),
      placeholder
    })
    textArea.publish = publish // TODO: fix - clunky api for the keyboard shortcut to target

    // load draft
    let draft = api.drafts.sync.get(draftLocation)
    if (typeof draft === 'string') {
      textArea.value = draft
      hasContent.set(true)
    }

    var warningMessage = Value(null)
    var warning = h('section.warning',
      { className: when(warningMessage, '-open', '-closed') },
      [
        h('div.warning', warningMessage),
        h('div.close', { 'ev-click': () => warningMessage.set(null) }, 'x')
      ]
    )
    var fileInput = api.blob.html.input(file => {
      const megabytes = file.size / 1024 / 1024
      if (megabytes >= 5) {
        const rounded = Math.floor(megabytes*100)/100
        warningMessage.set([
          h('i.fa.fa-exclamation-triangle'),
          h('strong', file.name),
          ` is ${rounded}MB - the current limit is 5MB`
        ])
        return
      }

      files.push(file)
      filesById[file.link] = file

      const pos = textArea.selectionStart
      const embed = file.type.match(/^image/) ? '!' : ''
      const spacer = embed ? '\n' : ' '
      const insertLink = spacer + embed + '[' + file.name + ']' + '(' + file.link + ')' + spacer

      textArea.value = textArea.value.slice(0, pos) + insertLink + textArea.value.slice(pos)

      console.log('added:', file)
    })

    fileInput.onclick = () => hasContent.set(true)

    var publishBtn = h('button', { 'ev-click': publish }, 'Publish')

    var actions = h('section.actions', [
      fileInput,
      publishBtn
    ])

    var composer = h('Compose', {
      classList: when(expanded, '-expanded', '-contracted')
    }, [
      channelInput,
      textArea,
      warning,
      actions
    ])

    addSuggest(channelInput, (inputText, cb) => {
      if (inputText[0] === '#') {
        cb(null, getChannelSuggestions(inputText.slice(1)))
      }
    }, {cls: 'PatchSuggest'})
    channelInput.addEventListener('suggestselect', ev => {
      channelInput.value = ev.detail.id  // HACK : this over-rides the markdown value
    })

    addSuggest(textArea, (inputText, cb) => {
      const char = inputText[0]
      const wordFragment = inputText.slice(1)

      if (char === '@') cb(null, getProfileSuggestions(wordFragment))
      if (char === '#') cb(null, getChannelSuggestions(wordFragment))
      if (char === ':') cb(null, getEmojiSuggestions(wordFragment))
    }, {cls: 'PatchSuggest'})

    return composer

    // scoped

    function publish () {
      publishBtn.disabled = true

      const channel = channelInput.value.startsWith('#')
        ? channelInput.value.substr(1).trim()
        : channelInput.value.trim()
      const mentions = ssbMentions(textArea.value).map(mention => {
        // merge markdown-detected mention with file info
        var file = filesById[mention.link]
        if (file) {
          if (file.type) mention.type = file.type
          if (file.size) mention.size = file.size
        }
        return mention
      })

      var content = extend(resolve(meta), {
        text: textArea.value,
        channel,
        mentions
      })

      if (!channel) delete content.channel
      if (!mentions.length) delete content.mentions
      if (content.recps && content.recps.length === 0) delete content.recps

      try {
        if (typeof prepublish === 'function') {
          content = prepublish(content)
        }
      } catch (err) {
        publishBtn.disabled = false
        if (cb) cb(err)
        else throw err
      }

      return api.message.html.confirm(content, done)

      function done (err, msg) {
        publishBtn.disabled = false
        if (err) throw err
        else if (msg) {
          textArea.value = ''
          api.drafts.sync.set(draftLocation, '')
        }
        if (cb) cb(err, msg)
      }
    }
  }
}
<commit_msg>ACTUALLY install the new remove drafts method<commit_after>const { h, when, send, resolve, Value, computed } = require('mutant')
const nest = require('depnest')
const ssbMentions = require('ssb-mentions')
const extend = require('xtend')
const addSuggest = require('suggest-box')

exports.gives = nest('message.html.compose')

exports.needs = nest({
  'about.async.suggest': 'first',
  'channel.async.suggest': 'first',
  'emoji.async.suggest': 'first',
  'blob.html.input': 'first',
  'message.html.confirm': 'first',
  'drafts.sync.get': 'first',
  'drafts.sync.set': 'first',
  'drafts.sync.remove': 'first'
})

exports.create = function (api) {
  return nest({ 'message.html.compose': compose })

  function compose ({ meta, location, prepublish, placeholder = 'Write a message', shrink = true }, cb) {
    if (typeof resolve(meta) !== 'object') throw new Error('Compose needs meta data about what sort of message composer you are making')
    if (!location) throw new Error('Compose expects a unique location so it can save drafts of messages')

    var files = []
    var filesById = {}
    var channelInputFocused = Value(false)
    var textAreaFocused = Value(false)
    var focused = computed([channelInputFocused, textAreaFocused], (a, b) => a || b)
    var hasContent = Value(false)

    var getProfileSuggestions = api.about.async.suggest()
    var getChannelSuggestions = api.channel.async.suggest()
    var getEmojiSuggestions = api.emoji.async.suggest()

    var blurTimeout = null

    var expanded = computed([shrink, focused, hasContent], (shrink, focused, hasContent) => {
      if (!shrink || hasContent) return true

      return focused
    })

    var channelInput = h('input.channel', {
      'ev-input': () => hasContent.set(!!channelInput.value),
      'ev-keyup': ev => {
        ev.target.value = ev.target.value.replace(/^#*([\w@%&])/, '#$1')
      },
      'ev-blur': () => {
        clearTimeout(blurTimeout)
        blurTimeout = setTimeout(() => channelInputFocused.set(false), 200)
      },
      'ev-focus': send(channelInputFocused.set, true),
      placeholder: '#channel (optional)',
      value: computed(meta.channel, ch => ch ? '#' + ch : null),
      disabled: when(meta.channel, true),
      title: when(meta.channel, 'Reply is in same channel as original message')
    })

    var draftPerstTimeout = null
    var draftLocation = location
    var textArea = h('textarea', {
      'ev-input': () => {
        hasContent.set(!!textArea.value)
        clearTimeout(draftPerstTimeout)
        draftPerstTimeout = setTimeout(() => {
          api.drafts.sync.set(draftLocation, textArea.value)
        }, 200)
      },
      'ev-blur': () => {
        clearTimeout(blurTimeout)
        blurTimeout = setTimeout(() => textAreaFocused.set(false), 200)
      },
      'ev-focus': send(textAreaFocused.set, true),
      placeholder
    })
    textArea.publish = publish // TODO: fix - clunky api for the keyboard shortcut to target

    // load draft
    let draft = api.drafts.sync.get(draftLocation)
    if (typeof draft === 'string') {
      textArea.value = draft
      hasContent.set(true)
    }

    var warningMessage = Value(null)
    var warning = h('section.warning',
      { className: when(warningMessage, '-open', '-closed') },
      [
        h('div.warning', warningMessage),
        h('div.close', { 'ev-click': () => warningMessage.set(null) }, 'x')
      ]
    )
    var fileInput = api.blob.html.input(file => {
      const megabytes = file.size / 1024 / 1024
      if (megabytes >= 5) {
        const rounded = Math.floor(megabytes*100)/100
        warningMessage.set([
          h('i.fa.fa-exclamation-triangle'),
          h('strong', file.name),
          ` is ${rounded}MB - the current limit is 5MB`
        ])
        return
      }

      files.push(file)
      filesById[file.link] = file

      const pos = textArea.selectionStart
      const embed = file.type.match(/^image/) ? '!' : ''
      const spacer = embed ? '\n' : ' '
      const insertLink = spacer + embed + '[' + file.name + ']' + '(' + file.link + ')' + spacer

      textArea.value = textArea.value.slice(0, pos) + insertLink + textArea.value.slice(pos)

      console.log('added:', file)
    })

    fileInput.onclick = () => hasContent.set(true)

    var publishBtn = h('button', { 'ev-click': publish }, 'Publish')

    var actions = h('section.actions', [
      fileInput,
      publishBtn
    ])

    var composer = h('Compose', {
      classList: when(expanded, '-expanded', '-contracted')
    }, [
      channelInput,
      textArea,
      warning,
      actions
    ])

    addSuggest(channelInput, (inputText, cb) => {
      if (inputText[0] === '#') {
        cb(null, getChannelSuggestions(inputText.slice(1)))
      }
    }, {cls: 'PatchSuggest'})
    channelInput.addEventListener('suggestselect', ev => {
      channelInput.value = ev.detail.id  // HACK : this over-rides the markdown value
    })

    addSuggest(textArea, (inputText, cb) => {
      const char = inputText[0]
      const wordFragment = inputText.slice(1)

      if (char === '@') cb(null, getProfileSuggestions(wordFragment))
      if (char === '#') cb(null, getChannelSuggestions(wordFragment))
      if (char === ':') cb(null, getEmojiSuggestions(wordFragment))
    }, {cls: 'PatchSuggest'})

    return composer

    // scoped

    function publish () {
      publishBtn.disabled = true

      const channel = channelInput.value.startsWith('#')
        ? channelInput.value.substr(1).trim()
        : channelInput.value.trim()
      const mentions = ssbMentions(textArea.value).map(mention => {
        // merge markdown-detected mention with file info
        var file = filesById[mention.link]
        if (file) {
          if (file.type) mention.type = file.type
          if (file.size) mention.size = file.size
        }
        return mention
      })

      var content = extend(resolve(meta), {
        text: textArea.value,
        channel,
        mentions
      })

      if (!channel) delete content.channel
      if (!mentions.length) delete content.mentions
      if (content.recps && content.recps.length === 0) delete content.recps

      try {
        if (typeof prepublish === 'function') {
          content = prepublish(content)
        }
      } catch (err) {
        publishBtn.disabled = false
        if (cb) cb(err)
        else throw err
      }

      return api.message.html.confirm(content, done)

      function done (err, msg) {
        publishBtn.disabled = false
        if (err) throw err
        else if (msg) {
          textArea.value = ''
          api.drafts.sync.remove(draftLocation)
        }
        if (cb) cb(err, msg)
      }
    }
  }
}
<|endoftext|>


<commit_before>const dummyMembership = {
  save: () => {},
  dummy: true
}

module.exports = function checkAndSetMembership (req, res, next) {
  var communityId = req.param('communityId')
  // if no community id is specified, continue.
  // this is for Search, which can be limited to a specific community
  // or performed across all communities a user can access.
  if (!communityId) {
    return next()
  }

  return Community.findActive(communityId)
  .then(community => {
    if (!community) return res.notFound()

    var allowed
    res.locals.community = community

    // in these special cases, make sure that res.locals.membership is set,
    // because controllers will use its existence to distinguish between someone
    // who should see all community content and someone who should see only
    // public content
    if (Admin.isSignedIn(req) ||
      TokenAuth.isPermitted(res, community.id)) {
      allowed = true
    }

    // but still look up the actual membership if available, so that things
    // still behave as expected (e.g. in the case of an admin removing their own
    // membership)
    return Membership.find(req.session.userId, community.id)
    .then(function (membership) {
      if (membership || allowed) {
        res.locals.membership = membership || dummyMembership
        next()
      } else if (community.get('network_id') && req.session.userId) {
        Network.containsUser(community.get('network_id'), req.session.userId)
          .then(contains => contains ? next() : res.forbidden())
      } else if (res.locals.publicAccessAllowed) {
        // if public access is allowed we don't set membership; controllers use
        // its absence to determine that they should only show public content
        return next()
      } else {
        sails.log.debug(format('policy: checkAndSetMembership: fail. user %s, community %s',
          req.session.userId, community.id))
        res.forbidden()
      }
    })
  })
}
<commit_msg>change order of checks<commit_after>const dummyMembership = {
  save: () => {},
  dummy: true
}

module.exports = function checkAndSetMembership (req, res, next) {
  var communityId = req.param('communityId')
  // if no community id is specified, continue.
  // this is for Search, which can be limited to a specific community
  // or performed across all communities a user can access.
  if (!communityId) {
    return next()
  }

  return Community.findActive(communityId)
  .then(community => {
    if (!community) return res.notFound()

    var allowed
    res.locals.community = community

    // in these special cases, make sure that res.locals.membership is set,
    // because controllers will use its existence to distinguish between someone
    // who should see all community content and someone who should see only
    // public content
    if (Admin.isSignedIn(req) ||
      TokenAuth.isPermitted(res, community.id)) {
      allowed = true
    }

    // but still look up the actual membership if available, so that things
    // still behave as expected (e.g. in the case of an admin removing their own
    // membership)
    return Membership.find(req.session.userId, community.id)
    .then(function (membership) {
      if (membership || allowed) {
        res.locals.membership = membership || dummyMembership
        return next()
      } else if (res.locals.publicAccessAllowed) {
        // if public access is allowed we don't set membership; controllers use
        // its absence to determine that they should only show public content
        return next()
      } else if (community.get('network_id') && req.session.userId) {
        Network.containsUser(community.get('network_id'), req.session.userId)
          .then(contains => contains ? next() : res.forbidden())
      } else {
        sails.log.debug(format('policy: checkAndSetMembership: fail. user %s, community %s',
          req.session.userId, community.id))
        res.forbidden()
      }
    })
  })
}
<|endoftext|>