<commit_before>import React from 'react';
import { connect } from 'react-redux';
import autobind    from 'autobind-decorator';
import get         from 'lodash.get';
import { injectAsyncReducer } from '../../store';

import {
    load,
    edit,
    save,
    remove,
    edit_change,
    edit_clear,
} from './_actions';

import {
    show,
} from '../../container/wnd-delete/_actions';

import {
	Badge,
	Button,
	FormGroup,
	InputGroup,
	Col,
	ControlLabel,
	Form,
	FormControl,
	Glyphicon,
	Row
} from 'react-bootstrap';

import WndDelete  from '../../container/wnd-delete';

import Text       from '../../tag/form-text';
import Wait       from '../../tag/wait';
import WndError   from '../../tag/wnd-error';
import BlockError from '../../tag/block-error';
import Checkbox   from '../../tag/form-checkbox';
import UniControl from '../../tag/uni-control';
import Menu       from '../../container/menu';

import './styles.css';

@autobind
class Task extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
        }
    }

    componentWillMount(){
	this.clickCancel();
    }

    componentWillUpdate(nextProps, nextState) {
	const id = get( nextProps, 'match.params.id', '');
	const { data } = nextProps;
	if (data.id && id === 'new') {
		//should work ONLY when user pressed Add after new item was created
		//(componentWillMount will not be fired in this case as it is same page)
		this.clickCancel(nextProps);
	}
    }

    onChange(event) {
    	const id  = event.target.id;
    	let value = event.target.value;

	if (id=="done" && value==true) value=Date.now();

	this.props.edit_change(id, value);
    }

    clickEdit(){
	const id = get( this.props, 'match.params.id', '');
    	this.props.edit(id);
    }

    clickSave(){
	const { data } = this.props;
	const id = get( this.props, 'match.params.id', '');
    	this.props.save(id, data);
    }

    clickCancel(props){
	if (!props) props = this.props;
	const id = get( props, 'match.params.id', '');
    	if (id=='new') {
		props.edit_clear();
	} else {
		props.load(id);
	}
    }

    clickDeleteShow(){
	this.props.delete_wnd_show();
    }

    clickDeleteOk(){
	const { data } = this.props;
	const id = get( this.props, 'match.params.id', '');
	this.props.remove(id);
    }

    clickBadge( value ){
	const { data } = this.props;
	const { name } = data;

	this.props.edit_change("name", value + ": " + (name || ""));
    }

    render() {
	const { dataList:raw_data } = this.props;
	const tags = [];
	for (var i in raw_data) {
		const s = raw_data[i].name.split(":");
		if (s[1]) {
			const name = s[0].trim().toUpperCase();
			let index=-1;
			for (var i=0; i<tags.length && index==-1; i++) {
				if ((tags[i].name||"").trim().toUpperCase() == name) index=i;
			}

			if (index == -1) {
				tags.push({name:s[0], qty:1});
			} else {
				tags[index].qty++;
			}
		}
	}
	tags.sort( (a, b)=>{
		if (a.qty  b.qty) return -1;
		return 0;
	})
//console.log("TAGS", tags);
//console.log("TAGS SHOW", tags.filter(_=>(_.qty>1)));
//console.log("TAGS SHOW NAMES", tags.filter(_=>(_.qty>1)).map(_=>_.name));

	const id   = get( this.props, 'match.params.id', '');
	const edit = (id == 'new' ? true :  'edit'== get( this.props, 'match.params.mode', ''));

	const { data, locked, error, save_error } = this.props;

	const {
		name,
		forToday,
		done,
	} = data;

	return (

			{locked && <Wait size={1} type="cover" />}
			<WndDelete name="task" onOk={this.clickDeleteOk}/>
			<Row>
			<Menu/>
			</Row>

			<Row>
			{!locked && error && <BlockError error={error} />}
			{save_error        && <WndError title="Error" error={save_error} onClose={closeError} />}
			{!locked && !error &&

			<Col md={5} xs={12}>
				<Form horizontal>
					<FormGroup controlId="name" key="name">
						<Col componentClass={ControlLabel} sm={5}>
							Name
						</Col>
						<Col sm={7}>
							<UniControl
								editable={edit}
								onChange={this.onChange}
								value={name} />

							{tags.filter((_,index) => {
								return index < 6;
							})
							.map(_=>{
								return <Badge style={{cursor:'pointer', backgroundColor: '#337ab7', marginTop:20}} onClick={this.clickBadge.bind(this, _.name)}>{_.name}</Badge>
							},this)}
						</Col>
					</FormGroup>
					<FormGroup controlId="forToday" key="forToday">
						<Col componentClass={ControlLabel} sm={5}>
							forToday
						</Col>
						<Col sm={7}>
							<UniControl
								id="forToday"
								type="boolean"
								editable={edit}
								onChange={this.onChange}
								value={forToday}
							/>
						</Col>
					</FormGroup>
					<FormGroup controlId="done" key="done">
						<Col componentClass={ControlLabel} sm={5}>
							Done
						</Col>
						<Col sm={7}>
							<UniControl
								id="done"
								type="boolean"
								editable={edit}
								onChange={this.onChange}
								value={done}
							/>
						</Col>
					</FormGroup>
				</Form>
				{!edit &&

						<Button key='button-edit' bsStyle="primary" onClick={this.clickEdit}>Edit</Button>
						<Button key='button-delete' onClick={this.clickDeleteShow}>Delete</Button>

				}
				{edit &&

					<Col sm={3}>
					</Col>
					<Col sm={2}>

						<Button key='button-delete' onClick={this.clickDeleteShow}>Delete</Button>

					</Col>
					<Col sm={7}>

	                               		<Button key='button-save' bsStyle="primary" onClick={this.clickSave}>Save</Button>
	                               		<Button key='button-cancel' onClick={this.clickCancel}>Cancel</Button>

					</Col>

				}
			</Col>

			}
			</Row>

        );
    }
}

const mapStateToProps = state => ({
    locked   : state.task.locked,
    error    : state.task.error,
    data     : state.task.data,
    edit     : state.task.edit,
    dataList : (state.taskList ? state.taskList.data : {}),
});

const mapDispatchToProps = {
    load,
    edit,
    save,
    remove,
    edit_change,
    edit_clear,

    delete_wnd_show : show,
}

injectAsyncReducer('task', require('./_reducer').default);

export default connect(mapStateToProps, mapDispatchToProps)(Task);
<commit_msg>tags v1 fixes<commit_after>import React from 'react';
import { connect } from 'react-redux';
import autobind    from 'autobind-decorator';
import get         from 'lodash.get';
import { injectAsyncReducer } from '../../store';

import {
    load,
    edit,
    save,
    remove,
    edit_change,
    edit_clear,
} from './_actions';

import {
    show,
} from '../../container/wnd-delete/_actions';

import {
	Badge,
	Button,
	FormGroup,
	InputGroup,
	Col,
	ControlLabel,
	Form,
	FormControl,
	Glyphicon,
	Row
} from 'react-bootstrap';

import WndDelete  from '../../container/wnd-delete';

import Text       from '../../tag/form-text';
import Wait       from '../../tag/wait';
import WndError   from '../../tag/wnd-error';
import BlockError from '../../tag/block-error';
import Checkbox   from '../../tag/form-checkbox';
import UniControl from '../../tag/uni-control';
import Menu       from '../../container/menu';

import './styles.css';

@autobind
class Task extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
        }
    }

    componentWillMount(){
	this.clickCancel();
    }

    componentWillUpdate(nextProps, nextState) {
	const id = get( nextProps, 'match.params.id', '');
	const { data } = nextProps;
	if (data.id && id === 'new') {
		//should work ONLY when user pressed Add after new item was created
		//(componentWillMount will not be fired in this case as it is same page)
		this.clickCancel(nextProps);
	}
    }

    onChange(event) {
    	const id  = event.target.id;
    	let value = event.target.value;

	if (id=="done" && value==true) value=Date.now();

	this.props.edit_change(id, value);
    }

    clickEdit(){
	const id = get( this.props, 'match.params.id', '');
    	this.props.edit(id);
    }

    clickSave(){
	const { data } = this.props;
	const id = get( this.props, 'match.params.id', '');
    	this.props.save(id, data);
    }

    clickCancel(props){
	if (!props) props = this.props;
	const id = get( props, 'match.params.id', '');
    	if (id=='new') {
		props.edit_clear();
	} else {
		props.load(id);
	}
    }

    clickDeleteShow(){
	this.props.delete_wnd_show();
    }

    clickDeleteOk(){
	const { data } = this.props;
	const id = get( this.props, 'match.params.id', '');
	this.props.remove(id);
    }

    clickBadge( value ){
	const { data } = this.props;
	const { name } = data;

	this.props.edit_change("name", value + ": " + (name || ""));
    }

    getTags(){
	const { dataList:raw_data } = this.props;
	const tags = [];
	for (var i in raw_data) {
		const s = raw_data[i].name.split(":");
		if (s[1]) {
			const name = s[0].trim().toUpperCase();
			let index=-1;
			for (var i=0; i<tags.length && index==-1; i++) {
				if ((tags[i].name||"").trim().toUpperCase() == name) index=i;
			}

			if (index == -1) {
				tags.push({name:s[0], qty:1});
			} else {
				tags[index].qty++;
			}
		}
	}
	tags.sort( (a, b)=>{
		if (a.qty  b.qty) return -1;
		return 0;
	})
//console.log("TAGS", tags);
//console.log("TAGS SHOW", tags.filter(_=>(_.qty>1)));
//console.log("TAGS SHOW NAMES", tags.filter(_=>(_.qty>1)).map(_=>_.name));

	return tags;
    }

    render() {
	const id   = get( this.props, 'match.params.id', '');
	const edit = (id == 'new' ? true :  'edit'== get( this.props, 'match.params.mode', ''));

	const tags = edit ? this.getTags() : [];

	const { data, locked, error, save_error } = this.props;

	const {
		name,
		forToday,
		done,
	} = data;

	return (

			{locked && <Wait size={1} type="cover" />}
			<WndDelete name="task" onOk={this.clickDeleteOk}/>
			<Row>
			<Menu/>
			</Row>

			<Row>
			{!locked && error && <BlockError error={error} />}
			{save_error        && <WndError title="Error" error={save_error} onClose={closeError} />}
			{!locked && !error &&

			<Col md={5} xs={12}>
				<Form horizontal>
					<FormGroup controlId="name" key="name">
						<Col componentClass={ControlLabel} sm={5}>
							Name
						</Col>
						<Col sm={7}>
							<UniControl ref="name"
								editable={edit}
								onChange={this.onChange}
								value={name} />

							{tags.filter((_,index) => {
								return index < 6;
							})
							.map(_=>{
								return <Badge style={{cursor:'pointer', backgroundColor: '#337ab7', marginTop:20}} onClick={this.clickBadge.bind(this, _.name)}>{_.name}</Badge>
							},this)}
						</Col>
					</FormGroup>
					<FormGroup controlId="forToday" key="forToday">
						<Col componentClass={ControlLabel} sm={5}>
							forToday
						</Col>
						<Col sm={7}>
							<UniControl
								id="forToday"
								type="boolean"
								editable={edit}
								onChange={this.onChange}
								value={forToday}
							/>
						</Col>
					</FormGroup>
					<FormGroup controlId="done" key="done">
						<Col componentClass={ControlLabel} sm={5}>
							Done
						</Col>
						<Col sm={7}>
							<UniControl
								id="done"
								type="boolean"
								editable={edit}
								onChange={this.onChange}
								value={done}
							/>
						</Col>
					</FormGroup>
				</Form>
				{!edit &&

						<Button key='button-edit' bsStyle="primary" onClick={this.clickEdit}>Edit</Button>
						<Button key='button-delete' onClick={this.clickDeleteShow}>Delete</Button>

				}
				{edit &&

					<Col sm={3}>
					</Col>
					<Col sm={2}>

						<Button key='button-delete' onClick={this.clickDeleteShow}>Delete</Button>

					</Col>
					<Col sm={7}>

	                               		<Button key='button-save' bsStyle="primary" onClick={this.clickSave}>Save</Button>
	                               		<Button key='button-cancel' onClick={this.clickCancel}>Cancel</Button>

					</Col>

				}
			</Col>

			}
			</Row>

        );
    }
}

const mapStateToProps = state => ({
    locked   : state.task.locked,
    error    : state.task.error,
    data     : state.task.data,
    edit     : state.task.edit,
    dataList : (state.taskList ? state.taskList.data : {}),
});

const mapDispatchToProps = {
    load,
    edit,
    save,
    remove,
    edit_change,
    edit_clear,

    delete_wnd_show : show,
}

injectAsyncReducer('task', require('./_reducer').default);

export default connect(mapStateToProps, mapDispatchToProps)(Task);
<|endoftext|>


<commit_before>// ==UserScript==
// @name        Robin Assistant
// @description Growth in peace
// @namespace   com.github.leoverto
// @include     https://www.reddit.com/robin/
// @include     https://www.reddit.com/robin
// @version     1.10
// @author      LeoVerto, Wiiplay123, Getnamo, K2L8M11N2
// @grant       none
// ==/UserScript==

var autoVote = true;
var disableVoteMsgs = true;
var filterSpam = true;
var filterNonAscii = true;
var version = "1.10";

var ownName = $('.user a').text();
var filteredSpamCount = 0;
var filteredVoteCount = 0;
var filteredNonAsciiCount = 0;
var userCount = 0;

var votes = {
  grow: 0,
  stay: 0,
  abandon: 0,
  abstain: 0,
  action: 'Unknown'
}

var votesLastUpdated = 0;

var startTime = new Date();

var manualThaiList = ["̍", "̎", "̄", "̅", "̿", "̑", "̆", "̐", "͒", "͗", "\
", "͑", "̇", "̈", "̊", "͂", "̓", "̈́", "͊", "͋", "͌", "\
", "̃", "̂", "̌", "͐", "̀", "́", "̋", "̏", "̒", "̓", "\
", "̔", "̽", "̉", "ͣ", "ͤ", "ͥ", "ͦ", "ͧ", "ͨ", "ͩ", "\
", "ͪ", "ͫ", "ͬ", "ͭ", "ͮ", "ͯ", "̾", "͛", "͆", "̚", "\
", "̕", "̛", "̀", "́", "͘", "̡", "̢", "̧", "̨", "̴", "\
", "̵", "̶", "", "͜", "͝", "͞", "͟", "͠", "͢", "̸", "\
", "̷", "͡", "҉", "\
", "̖", "̗", "̘", "̙", "̜", "̝", "̞", "̟", "̠", "̤", "\
", "̥", "̦", "̩", "̪", "̫", "̬", "̭", "̮", "̯", "̰", "\
", "̱", "̲", "̳", "̹", "̺", "̻", "̼", "ͅ", "͇", "͈", "\
", "͉", "͍", "͎", "͓", "͔", "͕", "͖", "͙", "͚", "̣", "\
"];

var spamBlacklist = ["spam the most used",
  "ຈل͜ຈ", "hail", "autovoter", "staying", "﷽", "group to stay", "pasta",
  "mins remaining. status", ">>>>", "trump", "#420", "้", "็", "◕_◕",
  "<<<<", "growing is all we know", "f it ends on you", "heil", "hitler",
  "timecube", "\( ͡° ͜ʖ ͡°\)", "◕", "guys can you please not spam the chat",
  "ｍｅｍｅｓ ｏｆ ｃａｐｉｔａｌｉｓｍ", "𝐁𝐄𝐑𝐍𝐈𝐄 𝐒𝐀𝐍𝐃𝐌𝐀𝐍", "█▄█▄",  "卐",
  "spam the most used phrase", "moob hunter", "someone in chat annoying",
  "cool ppl list", "can't beat me", "smexy", "my ruler", "bean",
  "voting will end", "current standings", "numbers and tits"
];

var nonEnglishSpamRegex = "[^\x00-\x7F]+";

function rewriteCSS() {
  $(".robin-chat--body").css({
    "height": "80vh"
  });
}

function sendMessage(msg) {
  $(".text-counter-input")[0].value = msg;
  $(".text-counter-input")[0].nextSibling.click();
}

// Custom options
function addOptions() {
  // Remove possible existing custom options
  $("#customOptions").remove();

  var customOptions = document.createElement("div");
  customOptions.id = "customOptions";
  customOptions.className =
    "robin-chat--sidebar-widget robin-chat--notification-widget";

  var header = "Robin-Assistant " + version +
    " Configuration"

  var autoVoteOption = createCheckbox("auto-vote",
    "Automatically vote Grow", autoVote, autoVoteListener, false);

  var filters = "Filters"

  var filterVotesOption = createCheckbox("filter-votes",
    "Vote Messages", disableVoteMsgs, disableVoteMsgsListener, true);
  var filterSpamOption = createCheckbox("filter-spam",
    "Common spam", filterSpam, filterSpamListener, true);
  var filterNonAsciiOption = createCheckbox("filter-nonascii",
    "Non-ascii", filterNonAscii, filterNonAsciiListener, true);

  var userCounter =
    "
Users here: 0";
  var voteGrow =
    "
Grow: 0";
  var voteStay =
    "
Stay: 0";
  var voteAbandon =
    "
Abandon: 0";
  var voteAbstain =
    "
Abstain: 0";
  var timer =
    "
Time Left: 0";
  var nextAction =
    "
Unknown";

  $(customOptions).insertAfter("#robinDesktopNotifier");
  $(customOptions).append(header);
  $(customOptions).append(autoVoteOption);
  $(customOptions).append(filters);
  $(customOptions).append(filterVotesOption);
  $(customOptions).append(filterSpamOption);
  $(customOptions).append(filterNonAsciiOption);
  $(customOptions).append(userCounter);
  $(customOptions).append(voteGrow);
  $(customOptions).append(voteStay);
  $(customOptions).append(voteAbandon);
  $(customOptions).append(voteAbstain);
  $(customOptions).append(nextAction);
  $(customOptions).append(timer);
}

function createCheckbox(name, description, checked, listener, counter) {
  var label = document.createElement("label");

  var checkbox = document.createElement("input");
  checkbox.name = name;
  checkbox.type = "checkbox";
  checkbox.onclick = listener;
  $(checkbox).prop("checked", checked);

  var description = document.createTextNode(description);

  label.appendChild(checkbox);
  label.appendChild(description);

  if (counter) {
    var counter = " Filtered: 0";
    $(label).append(counter);
  }

  return label;
}

// Listeners
function disableVoteMsgsListener(event) {
  if (event !== undefined) {
    disableVoteMsgs = $(event.target).is(":checked");
  }
}

function autoVoteListener(event) {
  if (event !== undefined) {
    autoVote = $(event.target).is(":checked");
  }
}

function filterSpamListener(event) {
  if (event !== undefined) {
    filterSpam = $(event.target).is(":checked");
  }
}

function filterNonAsciiListener(event) {
  if (event !== undefined) {
    filterNonAscii = $(event.target).is(":checked");
  }
}

function addMins(date, mins) {
  var newDateObj = new Date(date.getTime() + mins * 60000);
  return newDateObj;
}

function howLongLeft() { // mostly from /u/Yantrio
  var soonMessageArray = $(".robin-message--message:contains('soon')");
  if (soonMessageArray.length > 0) {
    // for cases where it says "soon" instead of a time on page load
    return "Soon";
  }

  var remainingMessageArray = $(".robin-message--message:contains('approx')");

  if (remainingMessageArray.length == 0) {
    //This shouldn't happen
    return "Unknown";
  }

  var message = remainingMessageArray.text();
  var time = new Date(jQuery(
    ".robin--user-class--system:contains('approx') .robin-message--timestamp"
  ).attr("datetime"));
  try {
    var endTime = addMins(time, message.match(/\d+/)[0]);
    var fraction = Math.floor((endTime - new Date()) / 60 / 1000 * 10) / 10;
    var min = Math.floor(fraction);
    var sec = Math.round((fraction - min) * 60);
    return min + " m " + sec + " s";
  } catch (e) {
    return "Fail";
  }

  //grab the timestamp from the first post and then calc the difference using the estimate it gives you on boot
}

function updateCounter(id, value) {
  $("#" + id).text(value);
}

// Spam Filter
function checkSpam(message) {
  // Check for 6 or more repetitions of the same character
  if (message.search(/(.)\1{5,}/) != -1) {
    filteredSpamCount += 1;
    updateCounter("filter-spam-counter", filteredSpamCount);
    console.log("Blocked spam message (Repetition): " + message);
    return true;
  }

  if(filterNonAscii){
    if(message.match(nonEnglishSpamRegex)){
      filteredNonAsciiCount += 1;
      updateCounter("filter-nonascii-counter", filteredNonAsciiCount);
      console.log("Blocked spam message (non-ASCII): " + message);
      return true;
    }
  }

  for (o = 0; o < spamBlacklist.length; o++) {
    if (message.toLowerCase().search(spamBlacklist[o]) != -1) {
      filteredSpamCount += 1;
      updateCounter("filter-spam-counter", filteredSpamCount);
      console.log("Blocked spam message (Blacklist): " + message);
      return true;
    }
  }
  return false;
}

// Generic updates
function update() {
  updateCounter("time-left", howLongLeft());

  // update vote counters
  updateCounter("vote-grow", votes.grow);
  updateCounter("vote-stay", votes.stay);
  updateCounter("vote-abandon", votes.abandon);
  updateCounter("vote-abstain", votes.abstain);

  userCount = votes.grow + votes.stay + votes.abandon + votes.abstain;
  updateCounter("user-count", userCount);

  updateCounter("next-action", "Next round we will " + votes.action);
}

// Triggered whenever someone votes
function updateVotes() {
  // Cancel if updated during last 10 seconds
  if (Date.now() - votesLastUpdated < 10000) {
    return false;
  }

  console.log("Updating vote tally...");
  jQuery.get("/robin/", function(a) {
    var start = "{" + a.substring(a.indexOf("\"robin_user_list\": ["));
    var end = start.substring(0, start.indexOf("}]") + 2) + "}";
    list = JSON.parse(end).robin_user_list;
    votes.grow = list.filter(function(voter) {
      return voter.vote === "INCREASE"
    }).length;
    votes.stay = list.filter(function(voter) {
      return voter.vote === "CONTINUE"
    }).length;
    votes.abandon = list.filter(function(voter) {
      return voter.vote === "ABANDON"
    }).length;
    votes.abstain = list.filter(function(voter) {
      return voter.vote === "NOVOTE"
    }).length;

    var majority = userCount / 2;
    if (votes.grow > majority) {
      votes.action = "Grow";
    } else if (votes.stay > majority) {
      votes.action = "Stay";
    } else if (votes.abandon > majority) {
      votes.action = "Abandon";
    } else if (votes.abstain > majority) {
      votes.action = "Abstain";
    } else {
      vote.action = "No majority";
    }
  });

  votesLastUpdated = Date.now();
  return true;
}

// Mutation observer for new messages
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    var added = mutation.addedNodes[0];

    // Filters all new messages
    if ($(added).hasClass("robin-message")) {
      var msg = added;
      var msgText = $(msg).find(".robin-message--message").text();
      //console.log(msgText)

      // Highlight messages containing own user name
      var re = new RegExp(ownName, "i");
      if (msgText.match(re)) {
        $(msg).css({
          background: 'rgba(255, 0, 0, 0.3)',
          color: '#242424'
        });
      }

      // Filter vote messages
      if ($(msg).hasClass("robin--message-class--action") && msgText.startsWith(
          "voted to ")) {
        updateVotes();
        if (disableVoteMsgs) {
          $(msg).remove();
          console.log("Blocked spam message (Voting): " + message);
          filteredVoteCount += 1;
          updateCounter("filter-votes-counter", filteredVoteCount);
        }
        updateVotes();
      }

      // Filter spam
      if (filterSpam) {
        if (checkSpam(msgText)) {
          $(msg).remove();
        }
      }
    }
  });
});
observer.observe($("#robinChatMessageList").get(0), {
  childList: true
});

// Main run
console.log("Robin-Assistant " + version + " enabled!");

rewriteCSS();
addOptions();
updateVotes();
update();

//Check for startup messages for timing
function fetchTimeIntervals() {
  var minArray = $(".robin-message--message:contains('approx')").text().match(
    "\\d+");
}

// Auto-grow
setTimeout(function() {
  if (autoVote) {
    $(".robin--vote-class--increase")[0].click();
    console.log("Voting grow!");
  }
}, 10000);

// Update every 3 seconds
setInterval(function() {
  update();
  // Update votes at least every 30 seconds
  if (Date.now - votesLastUpdated > 30000) {
    updateVotes();
  }
}, 3000);

// Try to join robin if not in a chat once a minute
setInterval(function() {
  if ($("#joinRobinContainer".length)) {
    $("#joinRobinContainer.click()");
    setTimeout(function() {
      jQuery("#joinRobin").click();
    }, 1000);
  }
}, 60000);
<commit_msg>Update blacklist<commit_after>// ==UserScript==
// @name        Robin Assistant
// @description Growth in peace
// @namespace   com.github.leoverto
// @include     https://www.reddit.com/robin/
// @include     https://www.reddit.com/robin
// @version     1.10
// @author      LeoVerto, Wiiplay123, Getnamo, K2L8M11N2
// @grant       none
// ==/UserScript==

var autoVote = true;
var disableVoteMsgs = true;
var filterSpam = true;
var filterNonAscii = true;
var version = "1.10";

var ownName = $('.user a').text();
var filteredSpamCount = 0;
var filteredVoteCount = 0;
var filteredNonAsciiCount = 0;
var userCount = 0;

var votes = {
  grow: 0,
  stay: 0,
  abandon: 0,
  abstain: 0,
  action: 'Unknown'
}

var votesLastUpdated = 0;

var startTime = new Date();

var manualThaiList = ["̍", "̎", "̄", "̅", "̿", "̑", "̆", "̐", "͒", "͗", "\
", "͑", "̇", "̈", "̊", "͂", "̓", "̈́", "͊", "͋", "͌", "\
", "̃", "̂", "̌", "͐", "̀", "́", "̋", "̏", "̒", "̓", "\
", "̔", "̽", "̉", "ͣ", "ͤ", "ͥ", "ͦ", "ͧ", "ͨ", "ͩ", "\
", "ͪ", "ͫ", "ͬ", "ͭ", "ͮ", "ͯ", "̾", "͛", "͆", "̚", "\
", "̕", "̛", "̀", "́", "͘", "̡", "̢", "̧", "̨", "̴", "\
", "̵", "̶", "", "͜", "͝", "͞", "͟", "͠", "͢", "̸", "\
", "̷", "͡", "҉", "\
", "̖", "̗", "̘", "̙", "̜", "̝", "̞", "̟", "̠", "̤", "\
", "̥", "̦", "̩", "̪", "̫", "̬", "̭", "̮", "̯", "̰", "\
", "̱", "̲", "̳", "̹", "̺", "̻", "̼", "ͅ", "͇", "͈", "\
", "͉", "͍", "͎", "͓", "͔", "͕", "͖", "͙", "͚", "̣", "\
"];

var spamBlacklist = ["spam the most used",
  "ຈل͜ຈ", "hail", "autovoter", "﷽", "group to stay", "pasta", "robinplus",
  "automatically voted", "stayers are betrayers", "stayers aint players",
  "mins remaining. status", ">>>>", "trump", "#420", "้", "็", "◕_◕",
  "<<<<", "growing is all we know", "f it ends on you", "heil", "hitler",
  "timecube", "\( ͡° ͜ʖ ͡°\)", "◕", "guys can you please not spam the chat",
  "ｍｅｍｅｓ ｏｆ ｃａｐｉｔａｌｉｓｍ", "𝐁𝐄𝐑𝐍𝐈𝐄 𝐒𝐀𝐍𝐃𝐌𝐀𝐍", "█▄█▄",  "卐",
  "spam the most used phrase", "moob hunter", "someone in chat annoying",
  "cool ppl list", "can't beat me", "smexy", "my ruler", "bean",
  "voting will end", "current standings", "numbers and tits", "nigglets"
];

var nonEnglishSpamRegex = "[^\x00-\x7F]+";

function rewriteCSS() {
  $(".robin-chat--body").css({
    "height": "80vh"
  });
}

function sendMessage(msg) {
  $(".text-counter-input")[0].value = msg;
  $(".text-counter-input")[0].nextSibling.click();
}

// Custom options
function addOptions() {
  // Remove possible existing custom options
  $("#customOptions").remove();

  var customOptions = document.createElement("div");
  customOptions.id = "customOptions";
  customOptions.className =
    "robin-chat--sidebar-widget robin-chat--notification-widget";

  var header = "Robin-Assistant " + version +
    " Configuration"

  var autoVoteOption = createCheckbox("auto-vote",
    "Automatically vote Grow", autoVote, autoVoteListener, false);

  var filters = "Filters"

  var filterVotesOption = createCheckbox("filter-votes",
    "Vote Messages", disableVoteMsgs, disableVoteMsgsListener, true);
  var filterSpamOption = createCheckbox("filter-spam",
    "Common spam", filterSpam, filterSpamListener, true);
  var filterNonAsciiOption = createCheckbox("filter-nonascii",
    "Non-ascii", filterNonAscii, filterNonAsciiListener, true);

  var userCounter =
    "
Users here: 0";
  var voteGrow =
    "
Grow: 0";
  var voteStay =
    "
Stay: 0";
  var voteAbandon =
    "
Abandon: 0";
  var voteAbstain =
    "
Abstain: 0";
  var timer =
    "
Time Left: 0";
  var nextAction =
    "
Unknown";

  $(customOptions).insertAfter("#robinDesktopNotifier");
  $(customOptions).append(header);
  $(customOptions).append(autoVoteOption);
  $(customOptions).append(filters);
  $(customOptions).append(filterVotesOption);
  $(customOptions).append(filterSpamOption);
  $(customOptions).append(filterNonAsciiOption);
  $(customOptions).append(userCounter);
  $(customOptions).append(voteGrow);
  $(customOptions).append(voteStay);
  $(customOptions).append(voteAbandon);
  $(customOptions).append(voteAbstain);
  $(customOptions).append(nextAction);
  $(customOptions).append(timer);
}

function createCheckbox(name, description, checked, listener, counter) {
  var label = document.createElement("label");

  var checkbox = document.createElement("input");
  checkbox.name = name;
  checkbox.type = "checkbox";
  checkbox.onclick = listener;
  $(checkbox).prop("checked", checked);

  var description = document.createTextNode(description);

  label.appendChild(checkbox);
  label.appendChild(description);

  if (counter) {
    var counter = " Filtered: 0";
    $(label).append(counter);
  }

  return label;
}

// Listeners
function disableVoteMsgsListener(event) {
  if (event !== undefined) {
    disableVoteMsgs = $(event.target).is(":checked");
  }
}

function autoVoteListener(event) {
  if (event !== undefined) {
    autoVote = $(event.target).is(":checked");
  }
}

function filterSpamListener(event) {
  if (event !== undefined) {
    filterSpam = $(event.target).is(":checked");
  }
}

function filterNonAsciiListener(event) {
  if (event !== undefined) {
    filterNonAscii = $(event.target).is(":checked");
  }
}

function addMins(date, mins) {
  var newDateObj = new Date(date.getTime() + mins * 60000);
  return newDateObj;
}

function howLongLeft() { // mostly from /u/Yantrio
  var soonMessageArray = $(".robin-message--message:contains('soon')");
  if (soonMessageArray.length > 0) {
    // for cases where it says "soon" instead of a time on page load
    return "Soon";
  }

  var remainingMessageArray = $(".robin-message--message:contains('approx')");

  if (remainingMessageArray.length == 0) {
    //This shouldn't happen
    return "Unknown";
  }

  var message = remainingMessageArray.text();
  var time = new Date(jQuery(
    ".robin--user-class--system:contains('approx') .robin-message--timestamp"
  ).attr("datetime"));
  try {
    var endTime = addMins(time, message.match(/\d+/)[0]);
    var fraction = Math.floor((endTime - new Date()) / 60 / 1000 * 10) / 10;
    var min = Math.floor(fraction);
    var sec = Math.round((fraction - min) * 60);
    return min + " m " + sec + " s";
  } catch (e) {
    return "Fail";
  }

  //grab the timestamp from the first post and then calc the difference using the estimate it gives you on boot
}

function updateCounter(id, value) {
  $("#" + id).text(value);
}

// Spam Filter
function checkSpam(message) {
  // Check for 6 or more repetitions of the same character
  if (message.search(/(.)\1{5,}/) != -1) {
    filteredSpamCount += 1;
    updateCounter("filter-spam-counter", filteredSpamCount);
    console.log("Blocked spam message (Repetition): " + message);
    return true;
  }

  if(filterNonAscii){
    if(message.match(nonEnglishSpamRegex)){
      filteredNonAsciiCount += 1;
      updateCounter("filter-nonascii-counter", filteredNonAsciiCount);
      console.log("Blocked spam message (non-ASCII): " + message);
      return true;
    }
  }

  for (o = 0; o < spamBlacklist.length; o++) {
    if (message.toLowerCase().search(spamBlacklist[o]) != -1) {
      filteredSpamCount += 1;
      updateCounter("filter-spam-counter", filteredSpamCount);
      console.log("Blocked spam message (Blacklist): " + message);
      return true;
    }
  }
  return false;
}

// Generic updates
function update() {
  updateCounter("time-left", howLongLeft());

  // update vote counters
  updateCounter("vote-grow", votes.grow);
  updateCounter("vote-stay", votes.stay);
  updateCounter("vote-abandon", votes.abandon);
  updateCounter("vote-abstain", votes.abstain);

  userCount = votes.grow + votes.stay + votes.abandon + votes.abstain;
  updateCounter("user-count", userCount);

  updateCounter("next-action", "Next round we will " + votes.action);
}

// Triggered whenever someone votes
function updateVotes() {
  // Cancel if updated during last 10 seconds
  if (Date.now() - votesLastUpdated < 10000) {
    return false;
  }

  console.log("Updating vote tally...");
  jQuery.get("/robin/", function(a) {
    var start = "{" + a.substring(a.indexOf("\"robin_user_list\": ["));
    var end = start.substring(0, start.indexOf("}]") + 2) + "}";
    list = JSON.parse(end).robin_user_list;
    votes.grow = list.filter(function(voter) {
      return voter.vote === "INCREASE"
    }).length;
    votes.stay = list.filter(function(voter) {
      return voter.vote === "CONTINUE"
    }).length;
    votes.abandon = list.filter(function(voter) {
      return voter.vote === "ABANDON"
    }).length;
    votes.abstain = list.filter(function(voter) {
      return voter.vote === "NOVOTE"
    }).length;

    var majority = userCount / 2;
    if (votes.grow > majority) {
      votes.action = "Grow";
    } else if (votes.stay > majority) {
      votes.action = "Stay";
    } else if (votes.abandon > majority) {
      votes.action = "Abandon";
    } else if (votes.abstain > majority) {
      votes.action = "Abstain";
    } else {
      vote.action = "No majority";
    }
  });

  votesLastUpdated = Date.now();
  return true;
}

// Mutation observer for new messages
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    var added = mutation.addedNodes[0];

    // Filters all new messages
    if ($(added).hasClass("robin-message")) {
      var msg = added;
      var msgText = $(msg).find(".robin-message--message").text();
      //console.log(msgText)

      // Highlight messages containing own user name
      var re = new RegExp(ownName, "i");
      if (msgText.match(re)) {
        $(msg).css({
          background: 'rgba(255, 0, 0, 0.3)',
          color: '#242424'
        });
      }

      // Filter vote messages
      if ($(msg).hasClass("robin--message-class--action") && msgText.startsWith(
          "voted to ")) {
        updateVotes();
        if (disableVoteMsgs) {
          $(msg).remove();
          console.log("Blocked spam message (Voting): " + message);
          filteredVoteCount += 1;
          updateCounter("filter-votes-counter", filteredVoteCount);
        }
        updateVotes();
      }

      // Filter spam
      if (filterSpam) {
        if (checkSpam(msgText)) {
          $(msg).remove();
        }
      }
    }
  });
});
observer.observe($("#robinChatMessageList").get(0), {
  childList: true
});

// Main run
console.log("Robin-Assistant " + version + " enabled!");

rewriteCSS();
addOptions();
updateVotes();
update();

//Check for startup messages for timing
function fetchTimeIntervals() {
  var minArray = $(".robin-message--message:contains('approx')").text().match(
    "\\d+");
}

// Auto-grow
setTimeout(function() {
  if (autoVote) {
    $(".robin--vote-class--increase")[0].click();
    console.log("Voting grow!");
  }
}, 10000);

// Update every 3 seconds
setInterval(function() {
  update();
  // Update votes at least every 30 seconds
  if (Date.now - votesLastUpdated > 30000) {
    updateVotes();
  }
}, 3000);

// Try to join robin if not in a chat once a minute
setInterval(function() {
  if ($("#joinRobinContainer".length)) {
    $("#joinRobinContainer.click()");
    setTimeout(function() {
      jQuery("#joinRobin").click();
    }, 1000);
  }
}, 60000);
<|endoftext|>


<commit_before>"use strict";

var processSchema = require("./processSchema");

var slice = Array.prototype.slice;

function Schema() {
    Schema.prototype.constructor.apply(this, arguments);
}

/**
 * Creates a new Schema
 *
 * @param {String|Object} name
 * @param {String|Object=} schemaDefinition
 * @constructor
 */
Schema.prototype.constructor = function (name, schemaDefinition) {
    var schema;

    if (arguments.length === 1) {
        name = "Anonymous";
        schemaDefinition = arguments[0];
    }

    this.name = name;

    //data we always extract
    schema = processSchema(schemaDefinition);

    this.keys = schema.keys;
    this.types = schema.types;
};

/**
 * Returns a subset with the given keys of the current schema. You may pass an array with keys
 * or just the keys as arguments.
 *
 * @param {String|Array} key1
 * @param {String} key2
 * @param {String} key3
 * @returns {Schema}
 */
Schema.prototype.fields = function (key1, key2, key3) {
    var subset = Object.create(this),
        keys;

    if (arguments.length === 1 && Array.isArray(key1)) {
        keys = key1;
    } else {
        keys = slice.call(arguments);
    }

    subset.keys = keys;

    return subset;
};

Schema.use = function (plugin, config) {
    this._plugins = this._plugins || [];

    if (this._plugins.indexOf(plugin) === -1) {
        plugin(this, config);
        this._plugins.push(plugin);
    }

    return this;
};

module.exports = Schema;<commit_msg>Added docs to Schema.use()<commit_after>"use strict";

var processSchema = require("./processSchema");

var slice = Array.prototype.slice;

function Schema() {
    Schema.prototype.constructor.apply(this, arguments);
}

/**
 * Creates a new Schema
 *
 * @param {String|Object} name
 * @param {String|Object=} schemaDefinition
 * @constructor
 */
Schema.prototype.constructor = function (name, schemaDefinition) {
    var schema;

    if (arguments.length === 1) {
        name = "Anonymous";
        schemaDefinition = arguments[0];
    }

    this.name = name;

    //data we always extract
    schema = processSchema(schemaDefinition);

    this.keys = schema.keys;
    this.types = schema.types;
};

/**
 * Returns a subset with the given keys of the current schema. You may pass an array with keys
 * or just the keys as arguments.
 *
 * @param {String|Array} key1
 * @param {String} key2
 * @param {String} key3
 * @returns {Schema}
 */
Schema.prototype.fields = function (key1, key2, key3) {
    var subset = Object.create(this),
        keys;

    if (arguments.length === 1 && Array.isArray(key1)) {
        keys = key1;
    } else {
        keys = slice.call(arguments);
    }

    subset.keys = keys;

    return subset;
};

/**
 * Calls the given function with the Schema as first argument and the given config (optionally). Plugins can be used
 * to hook into class methods by overriding them.
 *
 * You may call this function multiple times with the same plugin, the plugin will only be applied once.
 *
 * @param {Function} plugin
 * @param {Object=} config
 * @returns {Function}
 */
Schema.use = function (plugin, config) {
    this._plugins = this._plugins || [];

    if (this._plugins.indexOf(plugin) === -1) {
        plugin(this, config);
        this._plugins.push(plugin);
    }

    return this;
};

module.exports = Schema;<|endoftext|>


<commit_before>/**
 * Semicircle extension for L.Circle.
 * Jan Pieter Waagmeester <jieter@jieter.nl>
 *
 * This version is tested with leaflet 1.0.2
 */
(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['leaflet'], factory);
    } else if (typeof module !== 'undefined' && typeof require !== 'undefined') {
        // Node/CommonJS
        module.exports = factory(require('leaflet'));
    } else {
        // Browser globals
        if (typeof window.L === 'undefined') {
            throw 'Leaflet must be loaded first';
        }
        factory(window.L);
    }
})(function (L) {
    var DEG_TO_RAD = Math.PI / 180;

    // make sure 0 degrees is up (North) and convert to radians.
    function fixAngle (angle) {
        return (angle - 90) * DEG_TO_RAD;
    }

    // rotate point [x + r, y+r] around [x, y] by `angle` radians.
    function rotated (p, angle, r) {
        return p.add(
            L.point(Math.cos(angle), Math.sin(angle)).multiplyBy(r)
        );
    }

    L.Point.prototype.rotated = function (angle, r) {
        return rotated(this, angle, r);
    };

    var semicircle = {
        options: {
            startAngle: 0,
            stopAngle: 359.9999
        },

        startAngle: function () {
            if (this.options.startAngle < this.options.stopAngle) {
                return fixAngle(this.options.startAngle);
            } else {
                return fixAngle(this.options.stopAngle);
            }
        },
        stopAngle: function () {
            if (this.options.startAngle < this.options.stopAngle) {
                return fixAngle(this.options.stopAngle);
            } else {
                return fixAngle(this.options.startAngle);
            }
        },

        setStartAngle: function (angle) {
            this.options.startAngle = angle;
            return this.redraw();
        },

        setStopAngle: function (angle) {
            this.options.stopAngle = angle;
            return this.redraw();
        },

        setDirection: function (direction, degrees) {
            if (degrees === undefined) {
                degrees = 10;
            }
            this.options.startAngle = direction - (degrees / 2);
            this.options.stopAngle = direction + (degrees / 2);

            return this.redraw();
        },
        getDirection: function () {
            return this.stopAngle() - (this.stopAngle() - this.startAngle()) / 2;
        },

        isSemicircle: function () {
            var startAngle = this.options.startAngle,
                stopAngle = this.options.stopAngle;

            return (
                !(startAngle === 0 && stopAngle > 359) &&
                !(startAngle == stopAngle)
            );
        },
        _containsPoint: function (p) {
            function normalize (angle) {
                while (angle <= -Math.PI) {
                    angle += 2.0 * Math.PI;
                }
                while (angle > Math.PI) {
                    angle -= 2.0 * Math.PI;
                }
                return angle;
            }
            var angle = Math.atan2(p.y - this._point.y, p.x - this._point.x);
            var nStart = normalize(this.startAngle());
            var nStop = normalize(this.stopAngle());
            if (nStop <= nStart) {
                nStop += 2.0 * Math.PI;
            }
            if (angle <= nStart) {
                angle += 2.0 * Math.PI;
            }
            return (
                nStart < angle && angle <= nStop &&
                p.distanceTo(this._point) <= this._radius + this._clickTolerance()
            );
        }
    };

    L.SemiCircle = L.Circle.extend(semicircle);
    L.SemiCircleMarker = L.CircleMarker.extend(semicircle);

    L.semiCircle = function (latlng, options) {
        return new L.SemiCircle(latlng, options);
    };
    L.semiCircleMarker = function (latlng, options) {
        return new L.SemiCircleMarker(latlng, options);
    };

    var _updateCircleSVG = L.SVG.prototype._updateCircle;
    var _updateCircleCanvas = L.Canvas.prototype._updateCircle;

    L.SVG.include({
        _updateCircle: function (layer) {
            // If we want a circle, we use the original function
            if (!(layer instanceof L.SemiCircle || layer instanceof L.SemiCircleMarker) ||
                !layer.isSemicircle()) {
                return _updateCircleSVG.call(this, layer);
            }
            if (layer._empty()) {
                return this._setPath(layer, 'M0 0');
            }

            var p = layer._map.latLngToLayerPoint(layer._latlng),
                r = layer._radius,
                r2 = Math.round(layer._radiusY || r),
                start = p.rotated(layer.startAngle(), r),
                end = p.rotated(layer.stopAngle(), r);

            var largeArc = (layer.options.stopAngle - layer.options.startAngle >= 180) ? '1' : '0';

            var d = 'M' + p.x + ',' + p.y +
                // line to first start point
                'L' + start.x + ',' + start.y +
                'A ' + r + ',' + r2 + ',0,' + largeArc + ',1,' + end.x + ',' + end.y +
                ' z';

            this._setPath(layer, d);
        }
    });

    L.Canvas.include({
        _updateCircle: function (layer) {
            // If we want a circle, we use the original function
            if (!(layer instanceof L.SemiCircle || layer instanceof L.SemiCircleMarker) ||
                !layer.isSemicircle()) {
                return _updateCircleCanvas.call(this, layer);
            }

            var p = layer._point,
                ctx = this._ctx,
                r = layer._radius,
                s = (layer._radiusY || r) / r,
                start = p.rotated(layer.startAngle(), r);

            this._drawnLayers[layer._leaflet_id] = layer;

            if (s !== 1) {
                ctx.save();
                ctx.scale(1, s);
            }

            ctx.beginPath();
            ctx.moveTo(p.x, p.y);
            ctx.lineTo(start.x, start.y);
            ctx.arc(p.x, p.y, r, layer.startAngle(), layer.stopAngle());
            ctx.lineTo(p.x, p.y);

            if (s !== 1) {
                ctx.restore();
            }

            this._fillStroke(ctx, layer);
        }
    });
});
<commit_msg>canvas fix (#29)<commit_after>/**
 * Semicircle extension for L.Circle.
 * Jan Pieter Waagmeester <jieter@jieter.nl>
 *
 * This version is tested with leaflet 1.0.2
 */
(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['leaflet'], factory);
    } else if (typeof module !== 'undefined' && typeof require !== 'undefined') {
        // Node/CommonJS
        module.exports = factory(require('leaflet'));
    } else {
        // Browser globals
        if (typeof window.L === 'undefined') {
            throw 'Leaflet must be loaded first';
        }
        factory(window.L);
    }
})(function (L) {
    var DEG_TO_RAD = Math.PI / 180;

    // make sure 0 degrees is up (North) and convert to radians.
    function fixAngle (angle) {
        return (angle - 90) * DEG_TO_RAD;
    }

    // rotate point [x + r, y+r] around [x, y] by `angle` radians.
    function rotated (p, angle, r) {
        return p.add(
            L.point(Math.cos(angle), Math.sin(angle)).multiplyBy(r)
        );
    }

    L.Point.prototype.rotated = function (angle, r) {
        return rotated(this, angle, r);
    };

    var semicircle = {
        options: {
            startAngle: 0,
            stopAngle: 359.9999
        },

        startAngle: function () {
            if (this.options.startAngle < this.options.stopAngle) {
                return fixAngle(this.options.startAngle);
            } else {
                return fixAngle(this.options.stopAngle);
            }
        },
        stopAngle: function () {
            if (this.options.startAngle < this.options.stopAngle) {
                return fixAngle(this.options.stopAngle);
            } else {
                return fixAngle(this.options.startAngle);
            }
        },

        setStartAngle: function (angle) {
            this.options.startAngle = angle;
            return this.redraw();
        },

        setStopAngle: function (angle) {
            this.options.stopAngle = angle;
            return this.redraw();
        },

        setDirection: function (direction, degrees) {
            if (degrees === undefined) {
                degrees = 10;
            }
            this.options.startAngle = direction - (degrees / 2);
            this.options.stopAngle = direction + (degrees / 2);

            return this.redraw();
        },
        getDirection: function () {
            return this.stopAngle() - (this.stopAngle() - this.startAngle()) / 2;
        },

        isSemicircle: function () {
            var startAngle = this.options.startAngle,
                stopAngle = this.options.stopAngle;

            return (
                !(startAngle === 0 && stopAngle > 359) &&
                !(startAngle == stopAngle)
            );
        },
        _containsPoint: function (p) {
            function normalize (angle) {
                while (angle <= -Math.PI) {
                    angle += 2.0 * Math.PI;
                }
                while (angle > Math.PI) {
                    angle -= 2.0 * Math.PI;
                }
                return angle;
            }
            var angle = Math.atan2(p.y - this._point.y, p.x - this._point.x);
            var nStart = normalize(this.startAngle());
            var nStop = normalize(this.stopAngle());
            if (nStop <= nStart) {
                nStop += 2.0 * Math.PI;
            }
            if (angle <= nStart) {
                angle += 2.0 * Math.PI;
            }
            return (
                nStart < angle && angle <= nStop &&
                p.distanceTo(this._point) <= this._radius + this._clickTolerance()
            );
        }
    };

    L.SemiCircle = L.Circle.extend(semicircle);
    L.SemiCircleMarker = L.CircleMarker.extend(semicircle);

    L.semiCircle = function (latlng, options) {
        return new L.SemiCircle(latlng, options);
    };
    L.semiCircleMarker = function (latlng, options) {
        return new L.SemiCircleMarker(latlng, options);
    };

    var _updateCircleSVG = L.SVG.prototype._updateCircle;
    var _updateCircleCanvas = L.Canvas.prototype._updateCircle;

    L.SVG.include({
        _updateCircle: function (layer) {
            // If we want a circle, we use the original function
            if (!(layer instanceof L.SemiCircle || layer instanceof L.SemiCircleMarker) ||
                !layer.isSemicircle()) {
                return _updateCircleSVG.call(this, layer);
            }
            if (layer._empty()) {
                return this._setPath(layer, 'M0 0');
            }

            var p = layer._map.latLngToLayerPoint(layer._latlng),
                r = layer._radius,
                r2 = Math.round(layer._radiusY || r),
                start = p.rotated(layer.startAngle(), r),
                end = p.rotated(layer.stopAngle(), r);

            var largeArc = (layer.options.stopAngle - layer.options.startAngle >= 180) ? '1' : '0';

            var d = 'M' + p.x + ',' + p.y +
                // line to first start point
                'L' + start.x + ',' + start.y +
                'A ' + r + ',' + r2 + ',0,' + largeArc + ',1,' + end.x + ',' + end.y +
                ' z';

            this._setPath(layer, d);
        }
    });

    L.Canvas.include({
        _updateCircle: function (layer) {
            // If we want a circle, we use the original function
            if (!(layer instanceof L.SemiCircle || layer instanceof L.SemiCircleMarker) ||
                !layer.isSemicircle()) {
                return _updateCircleCanvas.call(this, layer);
            }

            if (!this._drawing || layer._empty()) { return; }

            var p = layer._point,
                ctx = this._ctx,
                r = layer._radius,
                s = (layer._radiusY || r) / r,
                start = p.rotated(layer.startAngle(), r);

            this._drawnLayers[layer._leaflet_id] = layer;

            if (s !== 1) {
                ctx.save();
                ctx.scale(1, s);
            }

            ctx.beginPath();
            ctx.moveTo(p.x, p.y);
            ctx.lineTo(start.x, start.y);
            ctx.arc(p.x, p.y, r, layer.startAngle(), layer.stopAngle());
            ctx.lineTo(p.x, p.y);

            if (s !== 1) {
                ctx.restore();
            }

            this._fillStroke(ctx, layer);
        }
    });
});
<|endoftext|>


<commit_before>/*! NProgress (c) 2013, Rico Sta. Cruz
 *  http://ricostacruz.com/nprogress */

;(function(NProgress) {

  this.NProgress = NProgress;

})(function($) {
  var NProgress = {};

  NProgress.version = '0.1.0';

  var Settings = NProgress.settings = {
    minimum: 0.08,
    easing: 'ease',
    speed: 300,
    trickleSpeed: 0.008,
    template: '

'
  };

  /**
   * Updates configuration.
   *
   *     NProgress.configure({
   *       minimum: 0.1
   *     });
   */
  NProgress.configure = function(options) {
    $.extend(Settings, options);
    return this;
  };

  /**
   * Last number.
   */

  NProgress.status = null;

  /**
   * Sets the progress bar status, where `n` is a number from `0.0` to `1.0`.
   *
   *     NProgress.set(0.4);
   *     NProgress.set(1.0);
   */

  NProgress.set = function(n) {
    n = clamp(n, Settings.minimum, 1);
    NProgress.status = (n === 1 ? null : n);

    var $progress = NProgress.render(),
        $bar      = $progress.find('[role~="bar"]'),
        speed     = Settings.speed,
        ease      = Settings.easing;

    $progress[0].offsetWidth; /* Repaint */

    $progress.queue(function(next) {
      var perc = -1 + n; /* -1.0 ... 0.0 */

      $bar.css({
        transition: 'all '+speed+'ms '+ease,
        transform: 'translate3d('+(perc*100)+'%,0,0)'
      });

      if (n === 1) {
        // Fade out
        $progress.css({ transition: 'none', opacity: 1 });
        $progress[0].offsetWidth; /* Repaint */

        setTimeout(function() {
          $progress.css({ transition: 'all '+speed+'ms linear', opacity: 0 });
          setTimeout(function() {
            NProgress.remove();
            next();
          }, speed);
        }, speed);
      } else {
        setTimeout(next, speed);
      }
    });

    return this;
  };

  /**
   * Shows the progress bar.
   * This is the same as setting the status to 0%, except that it doesn't go backwards.
   *
   *     NProgress.start();
   *
   */
  NProgress.start = function() {
    if (!NProgress.status) NProgress.set(0);

    var work = function() {
      setTimeout(function() {
        if (!NProgress.status) return;
        NProgress.trickle();
        work();
      }, Settings.speed * 4);
    };

    if (Settings.trickleSpeed) work();

    return this;
  };

  /**
   * Hides the progress bar.
   * This is the *sort of* the same as setting the status to 100%, with the
   * difference being `done()` makes some placebo effect of some realistic motion.
   *
   *     NProgress.done();
   *
   * If `true` is passed, it will show the progress bar even if its hidden.
   *
   *     NProgress.done(true);
   */

  NProgress.done = function(force) {
    if (!force && !NProgress.status) return this;

    return NProgress.inc(0.3 + 0.5 * Math.random()).set(1);
  };

  /**
   * Increments by a random amount.
   */

  NProgress.inc = function(amount) {
    var n = NProgress.status;

    if (!n) {
      return NProgress.start();
    } else {
      if (typeof amount !== 'number') {
        amount = (1 - n) * clamp(Math.random() * n, 0.1, 0.95);
      }

      n = clamp(n + amount, 0, 0.994);
      return NProgress.set(n);
    }
  };

  NProgress.trickle = function() {
    return NProgress.inc(Math.random() * Settings.trickleSpeed);
  };

  /**
   * (Internal) renders the progress bar markup based on the `template`
   * setting.
   */

  NProgress.render = function() {
    if (NProgress.isRendered()) return $("#nprogress");
    $('html').addClass('nprogress-busy');

    var $el = $("
")
      .html(Settings.template);

    $el.find('[role~="bar"]').css({
      transition: 'all 0 linear',
      transform: 'translate3d(-100%,0,0)'
    });

    $el.appendTo(document.body);

    return $el;
  };

  /**
   * (Internal) Removes the element. Opposite of render().
   */

  NProgress.remove = function() {
    $('html').removeClass('nprogress-busy');
    $('#nprogress').remove();
  };

  /**
   * Checks if the progress bar is rendered.
   */

  NProgress.isRendered = function() {
    return ($("#nprogress").length > 0);
  };

  /**
   * Helpers
   */

  function clamp(n, min, max) {
    if (n < min) return min;
    if (n > max) return max;
    return n;
  }

  return NProgress;
}(jQuery));

<commit_msg>Faster default trickle.<commit_after>/*! NProgress (c) 2013, Rico Sta. Cruz
 *  http://ricostacruz.com/nprogress */

;(function(NProgress) {

  this.NProgress = NProgress;

})(function($) {
  var NProgress = {};

  NProgress.version = '0.1.0';

  var Settings = NProgress.settings = {
    minimum: 0.08,
    easing: 'ease',
    speed: 300,
    trickleSpeed: 0.015,
    template: '

'
  };

  /**
   * Updates configuration.
   *
   *     NProgress.configure({
   *       minimum: 0.1
   *     });
   */
  NProgress.configure = function(options) {
    $.extend(Settings, options);
    return this;
  };

  /**
   * Last number.
   */

  NProgress.status = null;

  /**
   * Sets the progress bar status, where `n` is a number from `0.0` to `1.0`.
   *
   *     NProgress.set(0.4);
   *     NProgress.set(1.0);
   */

  NProgress.set = function(n) {
    n = clamp(n, Settings.minimum, 1);
    NProgress.status = (n === 1 ? null : n);

    var $progress = NProgress.render(),
        $bar      = $progress.find('[role~="bar"]'),
        speed     = Settings.speed,
        ease      = Settings.easing;

    $progress[0].offsetWidth; /* Repaint */

    $progress.queue(function(next) {
      var perc = -1 + n; /* -1.0 ... 0.0 */

      $bar.css({
        transition: 'all '+speed+'ms '+ease,
        transform: 'translate3d('+(perc*100)+'%,0,0)'
      });

      if (n === 1) {
        // Fade out
        $progress.css({ transition: 'none', opacity: 1 });
        $progress[0].offsetWidth; /* Repaint */

        setTimeout(function() {
          $progress.css({ transition: 'all '+speed+'ms linear', opacity: 0 });
          setTimeout(function() {
            NProgress.remove();
            next();
          }, speed);
        }, speed);
      } else {
        setTimeout(next, speed);
      }
    });

    return this;
  };

  /**
   * Shows the progress bar.
   * This is the same as setting the status to 0%, except that it doesn't go backwards.
   *
   *     NProgress.start();
   *
   */
  NProgress.start = function() {
    if (!NProgress.status) NProgress.set(0);

    var work = function() {
      setTimeout(function() {
        if (!NProgress.status) return;
        NProgress.trickle();
        work();
      }, Settings.speed * 4);
    };

    if (Settings.trickleSpeed) work();

    return this;
  };

  /**
   * Hides the progress bar.
   * This is the *sort of* the same as setting the status to 100%, with the
   * difference being `done()` makes some placebo effect of some realistic motion.
   *
   *     NProgress.done();
   *
   * If `true` is passed, it will show the progress bar even if its hidden.
   *
   *     NProgress.done(true);
   */

  NProgress.done = function(force) {
    if (!force && !NProgress.status) return this;

    return NProgress.inc(0.3 + 0.5 * Math.random()).set(1);
  };

  /**
   * Increments by a random amount.
   */

  NProgress.inc = function(amount) {
    var n = NProgress.status;

    if (!n) {
      return NProgress.start();
    } else {
      if (typeof amount !== 'number') {
        amount = (1 - n) * clamp(Math.random() * n, 0.1, 0.95);
      }

      n = clamp(n + amount, 0, 0.994);
      return NProgress.set(n);
    }
  };

  NProgress.trickle = function() {
    return NProgress.inc(Math.random() * Settings.trickleSpeed);
  };

  /**
   * (Internal) renders the progress bar markup based on the `template`
   * setting.
   */

  NProgress.render = function() {
    if (NProgress.isRendered()) return $("#nprogress");
    $('html').addClass('nprogress-busy');

    var $el = $("
")
      .html(Settings.template);

    $el.find('[role~="bar"]').css({
      transition: 'all 0 linear',
      transform: 'translate3d(-100%,0,0)'
    });

    $el.appendTo(document.body);

    return $el;
  };

  /**
   * (Internal) Removes the element. Opposite of render().
   */

  NProgress.remove = function() {
    $('html').removeClass('nprogress-busy');
    $('#nprogress').remove();
  };

  /**
   * Checks if the progress bar is rendered.
   */

  NProgress.isRendered = function() {
    return ($("#nprogress").length > 0);
  };

  /**
   * Helpers
   */

  function clamp(n, min, max) {
    if (n < min) return min;
    if (n > max) return max;
    return n;
  }

  return NProgress;
}(jQuery));

<|endoftext|>


<commit_before>/*
/*    This program is free software: you can redistribute it and/or  modify
/*    it under the terms of the GNU Affero General Public License, version 3,
/*    as published by the Free Software Foundation.
/*
/*    This program is distributed in the hope that it will be useful,
/*    but WITHOUT ANY WARRANTY; without even the implied warranty of
/*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
/*    GNU Affero General Public License for more details.
/*
/*    You should have received a copy of the GNU Affero General Public License
/*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
/*
/*    The Original Code is "Pillars.js Modelator" aka "Modelator"
/*
/*    The Initial Developer of the Original Code is Francisco Javier Gallego Martín <bifuer.at.gmail.com>.
/*    Copyright (c) 2014-2017 Francisco Javier Gallego Martín.
/*/

/* jslint node: true, esnext: true */
"use strict";

const Chain = require("./Chain");
const {Tree, TreeArray} = require("./Tree");
const Controllable = require("./Controllable");

const Schema = module.exports = function Schema(config = {}){
  const schema = this;
  Controllable.call(this, config);

  schema.driver = config.driver;

  schema.schema = config.schema || [];
  schema.schema.splice(0, 0, ...[
    new Controllable({
      id : "_id",
      on : {
        /*
        get : [function getId(context, done){
          let id;
          if(context.parents.length === 1){ // entity root level
            id = context.entity._id;
          } else {
            id = context.parents.slice(-2,-1)[0];
            id = Array.isArray(id)? id[0] : id;
          }
          context.result = id;
          done();
        }]
        */
      }
    }),
    new Controllable({
      id : "_owner",
      internal : true,
      on : {
        insert : [function setOwner(context, done){
          context.result = context.uid;
          done();
        }]
      }
    }),
    new Controllable({
      id : "_guests",
      values : ["userA","userB","userC"]
    }),
    new Controllable({
      id : "_ctime",
      internal : true,
      on : {
        insert : function timeStamp(context, done){
          context.result = Date.now();
          done();
        }
      }
    }),
    new Controllable({
      id : "_mtime",
      internal : true,
      on : {
        insert : [function timeStamp(context, done){
          context.result = Date.now();
          done();
        }],
        update : [function timeStamp(context, done){
          context.result = Date.now();
          done();
        }]
      }
    })
  ]);
};
  Schema.prototype = Object.create(Controllable.prototype);
  Schema.prototype.constructor = Schema;
  Schema.prototype.controller = function schemaController(context, done){
    const schema = this;
    const indexes = Object.keys(context.result || {});
    // debug("SCHEMA".black.bgWhite, dotPath(context.parents), context.result, indexes);

    const result = context.result;
    context.result = new Tree();

    const chain = new Chain();

    for(const field of schema.schema){
      const index = field.id;
      const checkIndex = indexes.indexOf(index);
      if(checkIndex >= 0){
        indexes.splice(checkIndex,1);
      }
      // debug("FIELD".bgWhite, dotPath(context.parents), index);

      const subcontext = Object.assign({}, context, {
        index,
        input : context.input? context.input[field.id] : undefined,
        result: undefined,
        parents : context.parents.concat([field.id]),
        parent : context
      });
      chain.add(field.fire.bind(field, subcontext));
    }
    for(const index of indexes){
      if(index !== "__id"){
        context.errors.set(context.parents.concat([index]), 'unknow-input-field');
      }
    }
    chain.add(done, undefined).pull();
  };

  const Modelator = require("./Modelator");
  const RelationalSchema = require("./RelationalSchema");
  Schema.prototype.walker = function schemaWalker(parent = this, childList = []){
    if(parent && parent.constructor && (parent.constructor === Modelator || parent.constructor === RelationalSchema) ){
      childList.splice(0,0,parent);
      for(const controllable of parent.schema){
        schemaWalker(controllable, childList);
      }
    }
    return childList;
  };

<commit_msg>Update Schema.js<commit_after>/* jslint node: true, esnext: true */
"use strict";

const Chain = require("./Chain");
const {Tree, TreeArray} = require("./Tree");
const Controllable = require("./Controllable");

const Schema = module.exports = function Schema(config = {}){
  const schema = this;
  Controllable.call(this, config);

  schema.driver = config.driver;

  schema.schema = config.schema || [];
  schema.schema.splice(0, 0, ...[
    new Controllable({
      id : "_id",
      on : {
        /*
        get : [function getId(context, done){
          let id;
          if(context.parents.length === 1){ // entity root level
            id = context.entity._id;
          } else {
            id = context.parents.slice(-2,-1)[0];
            id = Array.isArray(id)? id[0] : id;
          }
          context.result = id;
          done();
        }]
        */
      }
    }),
    new Controllable({
      id : "_owner",
      internal : true,
      on : {
        insert : [function setOwner(context, done){
          context.result = context.uid;
          done();
        }]
      }
    }),
    new Controllable({
      id : "_guests",
      values : ["userA","userB","userC"]
    }),
    new Controllable({
      id : "_ctime",
      internal : true,
      on : {
        insert : function timeStamp(context, done){
          context.result = Date.now();
          done();
        }
      }
    }),
    new Controllable({
      id : "_mtime",
      internal : true,
      on : {
        insert : [function timeStamp(context, done){
          context.result = Date.now();
          done();
        }],
        update : [function timeStamp(context, done){
          context.result = Date.now();
          done();
        }]
      }
    })
  ]);
};
  Schema.prototype = Object.create(Controllable.prototype);
  Schema.prototype.constructor = Schema;
  Schema.prototype.controller = function schemaController(context, done){
    const schema = this;
    const indexes = Object.keys(context.result || {});
    // debug("SCHEMA".black.bgWhite, dotPath(context.parents), context.result, indexes);

    const result = context.result;
    context.result = new Tree();

    const chain = new Chain();

    for(const field of schema.schema){
      const index = field.id;
      const checkIndex = indexes.indexOf(index);
      if(checkIndex >= 0){
        indexes.splice(checkIndex,1);
      }
      // debug("FIELD".bgWhite, dotPath(context.parents), index);

      const subcontext = Object.assign({}, context, {
        index,
        input : context.input? context.input[field.id] : undefined,
        result: undefined,
        parents : context.parents.concat([field.id]),
        parent : context
      });
      chain.add(field.fire.bind(field, subcontext));
    }
    for(const index of indexes){
      if(index !== "__id"){
        context.errors.set(context.parents.concat([index]), 'unknow-input-field');
      }
    }
    chain.add(done, undefined).pull();
  };

  const Modelator = require("./Modelator");
  const RelationalSchema = require("./RelationalSchema");
  Schema.prototype.walker = function schemaWalker(parent = this, childList = []){
    if(parent && parent.constructor && (parent.constructor === Modelator || parent.constructor === RelationalSchema) ){
      childList.splice(0,0,parent);
      for(const controllable of parent.schema){
        schemaWalker(controllable, childList);
      }
    }
    return childList;
  };

<|endoftext|>


<commit_before>/*!
    Copyright (C) 2014 Google Inc., authors, and contributors <see AUTHORS file>
    Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
    Created By: brad@reciprocitylabs.com
    Maintained By: brad@reciprocitylabs.com
*/
;(function(cam, $, GGRC) {

GGRC.Controllers.Modals("GGRC.Controllers.QuickForm", {
  defaults : {
    model : null
    , instance : null
  }
}, {

  init : function() {
    if(this.options.instance && !this.options.model) {
      this.options.model = this.options.instance.constructor;
    }
    this.options.$content = this.element;
    if (this.element.data('force-refresh')) {
      this.options.instance.refresh();
    }
  }

  , "input, textarea, select change" : function(el, ev) {
    var that = this;
    if(!el.is("[data-lookup]")) {
      this.set_value_from_element(el);
      setTimeout(function() {
        that.options.instance.save();
      }, 100);
    }
  }

  , autocomplete_select : function(el, event, ui) {
    var that = this;
    var prop = el.attr("name").split(".").slice(0, -1).join(".");
    if(this._super.apply(this, arguments) !== false) {
      setTimeout(function() {
        that.options.instance.save().then(function() {
          var obj = that.options.instance.attr(prop);
          if(obj.attr) {
            obj.attr("saved", true);
          }
        });
      }, 100);
    } else {
      return false;
    }
  }

  , "input, select, textarea click" : function(el, ev) {
    this._super && this._super.apply(this, arguments);
    ev.stopPropagation();
  }

  , ".dropdown-menu > li click" : function(el, ev){
    ev.stopPropagation();
    var that = this;
    this.set_value({ name: el.data('name'), value: el.data('value') });
    setTimeout(function() {
      that.options.instance.save();
      $(el).closest('.open').removeClass('open');
    }, 100);
  }

  , "button[data-name][data-value]:not(.disabled), a.undoable[data-toggle*=modal] click": function(el, ev) {

    var that = this
      , name = el.data('name')
      , old_value = {};

    if(el.data('openclose')){
      var action = el.data('openclose'),
          main = el.closest('.item-main'),
          openclose = main.find('.openclose'),
          isOpened = openclose.hasClass('active');

      // We can't use main.openclose(action) here because content may not be loaded yet
      if(action === 'trigger'){
        openclose.trigger('click');
      }
      else if(action === 'close' && isOpened){
        openclose.trigger('click');
      }
      else if(action === 'open' && !isOpened){
        openclose.trigger('click');
      }
    }

    old_value[el.data("name")] = this.options.instance.attr(el.data("name"));
    if(el.data("also-undo")) {
      can.each(el.data("also-undo").split(","), function(attrname) {
        attrname = attrname.trim();
        old_value[attrname] = that.options.instance.attr(attrname);
      });
    }

    // Check if the undo button was clicked:
    this.options.instance.attr('_undo') || that.options.instance.attr('_undo', []);

    if(el.is("[data-toggle*=modal")) {
      setTimeout(function() {
        $(".modal:visible").one("modal:success", function() {
          that.options.instance.attr('_undo').unshift(old_value);
        });
      }, 100);
    } else {
      ev.stopPropagation();
      that.options.instance.attr('_undo').unshift(old_value);

    that.options.instance.attr('_disabled', 'disabled');
    that.options.instance.refresh().then(function(instance){
      that.set_value({ name: el.data('name'), value: el.data('value') });
      return instance.save();
    }).then(function(){
      that.options.instance.attr('_disabled', '');
    });
  }
  }

  , "a.undo click" : function(el, ev){
    ev.stopPropagation();
    var that = this
      , name = el.data('name')
      , old_value = this.options.instance.attr(name) || "";

    new_value = that.options.instance.attr('_undo').shift();
    that.options.instance.attr('_disabled', 'disabled');
    that.options.instance.refresh().then(function(instance){
      can.each(new_value, function(value, name) {
        that.set_value({ name: name, value: value });
      });
      return instance.save();
    }).then(function(){
      that.options.instance.attr('_disabled', '');
    });
  }

});

/*
  Below this line we're defining a few can.Components, which are in this file
  because they work similarly to the quick form controller (in fact, you should
  expect the quick form controller to be refactored into a component in the
  future) but they share no code with the quick form controller.

  the first component is quick add.  It is meant to have one or more form elements
  and a data-toggle="submit" link which will create a new join object between
  the parent instance and some selected option instance (likely picked through an
  autocomplete dropdown).

  Technically you can choose your instance however you want, as long as you find
  some way of getting its value into the component scope.  Extending this component
  with other methods to do that is fine.  You can also just pass it in when
  instantiating the component.
*/
can.Component.extend({
  tag: "ggrc-quick-add",
  // <content> in a component template will be replaced with whatever is contained
  //  within the component tag.  Since the views for the original uses of these components
  //  were already created with content, we just used <content> instead of making
  //  new view template files.
  template: "<content/>",
  scope: {
    parent_instance: null,
    source_mapping: null,
    deferred: "@",
    join_model: '@',
    model: null,
    delay: '@',
    quick_create: '@',
    attributes: {},
    create_url: function() {
      var value = this.element.find("input[type='text']").val();
      var dfd =  new CMS.Models.Document({
        link: value,
        title: value,
        context: this.scope.parent_instance.context || new CMS.Models.Context({id : null}),
      });
      return dfd;
    },
  },
  events: {
    init: function() {
      this.scope.attr("controller", this);
    },
    // The inserted event fires when the component content is added to the DOM.
    //  At this time, live bound rendering should be resolved, which is not the
    //  case during init.
    inserted: function(el) {
      this.element.find("input:not([data-mapping], [data-lookup])").each(function(i, el) {
        this.scope.attributes.attr($(el).attr("name"), $(el).val());
      }.bind(this));
    },
    "a[data-toggle=submit]:not(.disabled) click": function(el){
      var join_model_class, join_object, quick_create, created_dfd;

      if (this.scope.quick_create && this.scope.quick_create !== "@") {
        quick_create = this.scope[this.scope.quick_create].bind(this);
        if (quick_create) {
          created_dfd = quick_create();
        }
      }
      if (!created_dfd) {
        created_dfd = new $.Deferred().resolve();
      }

      if (this.scope.deferred) {
        this.scope.parent_instance.mark_for_addition("related_objects_as_source", created_dfd);
        return;
      }

      created_dfd.then(function() {
        if (this.scope.join_model && this.scope.join_model !== "@") {
          join_model_class = CMS.Models[this.scope.join_model] || CMS.ModelHelpers[this.scope.join_model];
          join_object = {};
          if (this.scope.join_model === "Relationship") {
            join_object["source"] = this.scope.parent_instance;
            join_object["destination"] = this.scope.instance;
          } else {
            join_object[this.scope.instance.constructor.table_singular] = this.scope.instance;
          }
          join_object = new join_model_class($.extend(
            join_object,
            {
              context: this.scope.parent_instance.context
                          || new CMS.Models.Context({id : null}),
            },
            this.scope.attributes.serialize()
          ));
        } else {
          join_object = GGRC.Mappings.make_join_object(
            this.scope.parent_instance,
            this.scope.instance || this.scope.attributes.instance,
            $.extend({
              context : this.scope.parent_instance.context
                        || new CMS.Models.Context({id : null})
                      },
                      this.scope.attributes.serialize())
          );
        }
        this.bindXHRToButton(
          join_object.save().done(function() {
            el.trigger("modal:success", join_object);
          }),
          el
          );
      }.bind(this));
    },
    // this works like autocomplete_select on all modal forms and
    //  descendant class objects.
    autocomplete_select : function(el, event, ui) {
      var that = this;
      setTimeout(function() {
        that.scope.attr(el.attr("name"), ui.item);
      });
    },
    "input[null-if-empty] change" : function(el) {
      if (!el.val()) {
        this.scope.attributes.attr(el.attr("name"), null);
      }
    },
    "input:not([data-mapping], [data-lookup]) change" : function(el) {
      this.scope.attributes.attr(el.attr("name"), el.val());
    },
    ".ui-autocomplete-input modal:success" : function(el, ev, data, options) {
      var that = this,
        multi_map = data.multi_map,
        join_model_class,
        join_object;

      if(multi_map){
        var length = data.arr.length,
            my_data;

        if (length == 1){
          my_data = data.arr[0];

          GGRC.Mappings.make_join_object(
            this.scope.parent_instance,
            my_data,
            $.extend({
              context : this.scope.parent_instance.context
                      || new CMS.Models.Context({id : null})
                      },
                      this.scope.attributes.serialize())
          ).save().done(function() {
            that.element.find("a[data-toggle=submit]").trigger("modal:success");
          });
        }

        else{
          for(var i = 0; i < length-1; i++){
            my_data = data.arr[i];

            GGRC.Mappings.make_join_object(
              this.scope.parent_instance,
              my_data,
              $.extend({
                context : this.scope.parent_instance.context
                        || new CMS.Models.Context({id : null})
                        },
                        this.scope.attributes.serialize())
            ).save().done(function(){});
          }
          my_data = data.arr[length-1];
          GGRC.Mappings.make_join_object(
            this.scope.parent_instance,
            my_data,
            $.extend({
              context : this.scope.parent_instance.context
                      || new CMS.Models.Context({id : null})
                      },
                      this.scope.attributes.serialize())
          ).save().done(function() {
            that.element.find("a[data-toggle=submit]").trigger("modal:success");
          });
        }
        //end multi-map
      } else {

        if (this.scope.join_model && this.scope.join_model !== "@") {
          join_model_class = CMS.Models[this.scope.join_model] || CMS.ModelHelpers[this.scope.join_model];
          join_object = new join_model_class(this.scope.attributes.serialize());
        } else {
          join_object = GGRC.Mappings.make_join_object(
            this.scope.parent_instance,
            data,
            $.extend({
              context : this.scope.parent_instance.context
                        || new CMS.Models.Context({id : null})
                      },
                      this.scope.attributes.serialize())
          );
        }
        join_object.save().done(function() {
           that.element.find("a[data-toggle=submit]").trigger("modal:success");
        });
      }
    }
  },
  helpers: {
    // Mapping-based autocomplete selectors use this helper to
    //  attach the mapping autocomplete ui widget.  These elements should
    //  be decorated with data-mapping attributes.
    mapping_autocomplete : function(options) {
      return function(el) {
        var $el = $(el);
        $el.ggrc_mapping_autocomplete({
          controller : options.contexts.attr("controller"),
          model : $el.data("model"),
          mapping : false
        });
      };
    }
  },
});

/*
  This component is for quickly updating the properties of an object through form fields.
  It works similar to GGRC.Controllers.QuickForm but has an extra feature: if the instance
  we're working with is a join object, and the option type is changed, it will work around
  the lack of support in proxy mappers for join objects being changed like that, and
  destroy the join object while creating a new one.

  Field updates trigger updates to the model automatically, even on the server.  This differs
  from the quick-add component above, in that it is not waiting for a submit trigger.
*/
can.Component.extend({
  tag: "ggrc-quick-update",
  template: "<content/>",
  scope: {
    instance: null,
    source_mapping: null,
    model: null,
    attributes: {}
  },
  events: {
    init: function() {
      this.scope.attr("controller", this);
      this.scope.attr("model", this.scope.model || this.scope.instance.constructor);
      if(!this.scope.instance._transient) {
        //only refresh if there's not currently an edit modal spawned.
        this.scope.instance.refresh();
      }
    },
    //currently we don't support proxy object updates in mappings, so for now a change
    //  to a connected object (assuming we are operating on a proxy object) will trigger
    //  a deletion of the proxy object and creation of a new one.
    autocomplete_select : function(el, event, ui) {
      var that = this;
      setTimeout(function() {
        var serial = that.scope.instance.serialize();
        delete serial[el.attr("name")];
        delete serial[el.attr("name") + "_id"];
        delete serial[el.attr("name") + "_type"];
        delete serial.id;
        delete serial.href;
        delete serial.selfLink;
        delete serial.created_at;
        delete serial.updated_at;
        delete serial.provisional_id;
        serial[el.attr("name")] = ui.item.stub();
        that.scope.instance.destroy().then(function() {
          new that.scope.model(serial).save();
        });
      });
    },
    // null-if-empty attributes are a pattern carried over from GGRC.Controllers.Modals
    //  Useful for connected objects.
    "input[null-if-empty] change" : function(el) {
      if (!el.val()) {
        this.scope.instance.attr(el.attr("name"), null);
      }
    },
    // data-mapping is the element decoration that triggers an autocomplete based on a
    //  mapping to a parent instance.  The mapping_autocomplete helper defined below is
    //  generally for these.
    "input:not([data-mapping]), select change" : function(el) {
      var isCheckbox = el.is("[type=checkbox][multiple]");
      if (isCheckbox) {
        if(!this.scope.instance[el.attr("name")]) {
          this.scope.instance.attr(el.attr("name"), new can.List());
        }
        this.scope.instance
        .attr(el.attr("name"))
        .replace(
          can.map(
            this.element.find("input[name='" + el.attr("name") + "']:checked"),
            function(el) {
              return $(el).val();
            }
          )
        );
        this.element.find("input:checkbox").prop("disabled", true);
      } else {
        this.scope.instance.attr(el.attr("name"), el.val());
      }
      this.scope.instance.save().then(function () {
        if (isCheckbox) {
          this.element.find("input:checkbox").prop("disabled", false);
        }
      }.bind(this));
    },
  },
  helpers: {
    mapping_autocomplete : function(options) {
      return function(el) {
        $(el).ggrc_mapping_autocomplete({ controller : options.contexts.attr("controller") });
      };
    }
  },
});

})(this.can, this.can.$, this.GGRC);
<commit_msg>Fix adding urls without a deferred<commit_after>/*!
    Copyright (C) 2014 Google Inc., authors, and contributors <see AUTHORS file>
    Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
    Created By: brad@reciprocitylabs.com
    Maintained By: brad@reciprocitylabs.com
*/
;(function(cam, $, GGRC) {

GGRC.Controllers.Modals("GGRC.Controllers.QuickForm", {
  defaults : {
    model : null
    , instance : null
  }
}, {

  init : function() {
    if(this.options.instance && !this.options.model) {
      this.options.model = this.options.instance.constructor;
    }
    this.options.$content = this.element;
    if (this.element.data('force-refresh')) {
      this.options.instance.refresh();
    }
  }

  , "input, textarea, select change" : function(el, ev) {
    var that = this;
    if(!el.is("[data-lookup]")) {
      this.set_value_from_element(el);
      setTimeout(function() {
        that.options.instance.save();
      }, 100);
    }
  }

  , autocomplete_select : function(el, event, ui) {
    var that = this;
    var prop = el.attr("name").split(".").slice(0, -1).join(".");
    if(this._super.apply(this, arguments) !== false) {
      setTimeout(function() {
        that.options.instance.save().then(function() {
          var obj = that.options.instance.attr(prop);
          if(obj.attr) {
            obj.attr("saved", true);
          }
        });
      }, 100);
    } else {
      return false;
    }
  }

  , "input, select, textarea click" : function(el, ev) {
    this._super && this._super.apply(this, arguments);
    ev.stopPropagation();
  }

  , ".dropdown-menu > li click" : function(el, ev){
    ev.stopPropagation();
    var that = this;
    this.set_value({ name: el.data('name'), value: el.data('value') });
    setTimeout(function() {
      that.options.instance.save();
      $(el).closest('.open').removeClass('open');
    }, 100);
  }

  , "button[data-name][data-value]:not(.disabled), a.undoable[data-toggle*=modal] click": function(el, ev) {

    var that = this
      , name = el.data('name')
      , old_value = {};

    if(el.data('openclose')){
      var action = el.data('openclose'),
          main = el.closest('.item-main'),
          openclose = main.find('.openclose'),
          isOpened = openclose.hasClass('active');

      // We can't use main.openclose(action) here because content may not be loaded yet
      if(action === 'trigger'){
        openclose.trigger('click');
      }
      else if(action === 'close' && isOpened){
        openclose.trigger('click');
      }
      else if(action === 'open' && !isOpened){
        openclose.trigger('click');
      }
    }

    old_value[el.data("name")] = this.options.instance.attr(el.data("name"));
    if(el.data("also-undo")) {
      can.each(el.data("also-undo").split(","), function(attrname) {
        attrname = attrname.trim();
        old_value[attrname] = that.options.instance.attr(attrname);
      });
    }

    // Check if the undo button was clicked:
    this.options.instance.attr('_undo') || that.options.instance.attr('_undo', []);

    if(el.is("[data-toggle*=modal")) {
      setTimeout(function() {
        $(".modal:visible").one("modal:success", function() {
          that.options.instance.attr('_undo').unshift(old_value);
        });
      }, 100);
    } else {
      ev.stopPropagation();
      that.options.instance.attr('_undo').unshift(old_value);

    that.options.instance.attr('_disabled', 'disabled');
    that.options.instance.refresh().then(function(instance){
      that.set_value({ name: el.data('name'), value: el.data('value') });
      return instance.save();
    }).then(function(){
      that.options.instance.attr('_disabled', '');
    });
  }
  }

  , "a.undo click" : function(el, ev){
    ev.stopPropagation();
    var that = this
      , name = el.data('name')
      , old_value = this.options.instance.attr(name) || "";

    new_value = that.options.instance.attr('_undo').shift();
    that.options.instance.attr('_disabled', 'disabled');
    that.options.instance.refresh().then(function(instance){
      can.each(new_value, function(value, name) {
        that.set_value({ name: name, value: value });
      });
      return instance.save();
    }).then(function(){
      that.options.instance.attr('_disabled', '');
    });
  }

});

/*
  Below this line we're defining a few can.Components, which are in this file
  because they work similarly to the quick form controller (in fact, you should
  expect the quick form controller to be refactored into a component in the
  future) but they share no code with the quick form controller.

  the first component is quick add.  It is meant to have one or more form elements
  and a data-toggle="submit" link which will create a new join object between
  the parent instance and some selected option instance (likely picked through an
  autocomplete dropdown).

  Technically you can choose your instance however you want, as long as you find
  some way of getting its value into the component scope.  Extending this component
  with other methods to do that is fine.  You can also just pass it in when
  instantiating the component.
*/
can.Component.extend({
  tag: "ggrc-quick-add",
  // <content> in a component template will be replaced with whatever is contained
  //  within the component tag.  Since the views for the original uses of these components
  //  were already created with content, we just used <content> instead of making
  //  new view template files.
  template: "<content/>",
  scope: {
    parent_instance: null,
    source_mapping: null,
    deferred: "@",
    join_model: '@',
    model: null,
    delay: '@',
    quick_create: '@',
    attributes: {},
    create_url: function() {
      var value = this.element.find("input[type='text']").val();
      return new CMS.Models.Document({
        link: value,
        title: value,
        context: this.scope.parent_instance.context || new CMS.Models.Context({id : null}),
      });
    },
  },
  events: {
    init: function() {
      this.scope.attr("controller", this);
    },
    // The inserted event fires when the component content is added to the DOM.
    //  At this time, live bound rendering should be resolved, which is not the
    //  case during init.
    inserted: function(el) {
      this.element.find("input:not([data-mapping], [data-lookup])").each(function(i, el) {
        this.scope.attributes.attr($(el).attr("name"), $(el).val());
      }.bind(this));
    },
    "a[data-toggle=submit]:not(.disabled) click": function(el){
      var join_model_class, join_object, quick_create, created_dfd;

      if (this.scope.quick_create && this.scope.quick_create !== "@") {
        quick_create = this.scope[this.scope.quick_create].bind(this);
        if (quick_create) {
          created_dfd = quick_create();
          if (!this.scope.deferred) {
            created_dfd = created_dfd.save().then(function(data){
              this.scope.attr('instance', data);
            }.bind(this));
          }
        }
      }
      if (!created_dfd) {
        created_dfd = new $.Deferred().resolve();
      }

      if (this.scope.deferred) {
        this.scope.parent_instance.mark_for_addition("related_objects_as_source", created_dfd);
        return;
      }

      created_dfd.then(function() {
        if (this.scope.join_model && this.scope.join_model !== "@") {
          join_model_class = CMS.Models[this.scope.join_model] || CMS.ModelHelpers[this.scope.join_model];
          join_object = {};
          if (this.scope.join_model === "Relationship") {
            join_object["source"] = this.scope.parent_instance;
            join_object["destination"] = this.scope.instance;
          } else {
            join_object[this.scope.instance.constructor.table_singular] = this.scope.instance;
          }
          join_object = new join_model_class($.extend(
            join_object,
            {
              context: this.scope.parent_instance.context
                          || new CMS.Models.Context({id : null}),
            },
            this.scope.attributes.serialize()
          ));
        } else {
          join_object = GGRC.Mappings.make_join_object(
            this.scope.parent_instance,
            this.scope.instance || this.scope.attributes.instance,
            $.extend({
              context : this.scope.parent_instance.context
                        || new CMS.Models.Context({id : null})
                      },
                      this.scope.attributes.serialize())
          );
        }
        this.bindXHRToButton(
          join_object.save().done(function() {
            el.trigger("modal:success", join_object);
          }),
          el
          );
      }.bind(this));
    },
    // this works like autocomplete_select on all modal forms and
    //  descendant class objects.
    autocomplete_select : function(el, event, ui) {
      var that = this;
      setTimeout(function() {
        that.scope.attr(el.attr("name"), ui.item);
      });
    },
    "input[null-if-empty] change" : function(el) {
      if (!el.val()) {
        this.scope.attributes.attr(el.attr("name"), null);
      }
    },
    "input:not([data-mapping], [data-lookup]) change" : function(el) {
      this.scope.attributes.attr(el.attr("name"), el.val());
    },
    ".ui-autocomplete-input modal:success" : function(el, ev, data, options) {
      var that = this,
        multi_map = data.multi_map,
        join_model_class,
        join_object;

      if(multi_map){
        var length = data.arr.length,
            my_data;

        if (length == 1){
          my_data = data.arr[0];

          GGRC.Mappings.make_join_object(
            this.scope.parent_instance,
            my_data,
            $.extend({
              context : this.scope.parent_instance.context
                      || new CMS.Models.Context({id : null})
                      },
                      this.scope.attributes.serialize())
          ).save().done(function() {
            that.element.find("a[data-toggle=submit]").trigger("modal:success");
          });
        }

        else{
          for(var i = 0; i < length-1; i++){
            my_data = data.arr[i];

            GGRC.Mappings.make_join_object(
              this.scope.parent_instance,
              my_data,
              $.extend({
                context : this.scope.parent_instance.context
                        || new CMS.Models.Context({id : null})
                        },
                        this.scope.attributes.serialize())
            ).save().done(function(){});
          }
          my_data = data.arr[length-1];
          GGRC.Mappings.make_join_object(
            this.scope.parent_instance,
            my_data,
            $.extend({
              context : this.scope.parent_instance.context
                      || new CMS.Models.Context({id : null})
                      },
                      this.scope.attributes.serialize())
          ).save().done(function() {
            that.element.find("a[data-toggle=submit]").trigger("modal:success");
          });
        }
        //end multi-map
      } else {

        if (this.scope.join_model && this.scope.join_model !== "@") {
          join_model_class = CMS.Models[this.scope.join_model] || CMS.ModelHelpers[this.scope.join_model];
          join_object = new join_model_class(this.scope.attributes.serialize());
        } else {
          join_object = GGRC.Mappings.make_join_object(
            this.scope.parent_instance,
            data,
            $.extend({
              context : this.scope.parent_instance.context
                        || new CMS.Models.Context({id : null})
                      },
                      this.scope.attributes.serialize())
          );
        }
        join_object.save().done(function() {
           that.element.find("a[data-toggle=submit]").trigger("modal:success");
        });
      }
    }
  },
  helpers: {
    // Mapping-based autocomplete selectors use this helper to
    //  attach the mapping autocomplete ui widget.  These elements should
    //  be decorated with data-mapping attributes.
    mapping_autocomplete : function(options) {
      return function(el) {
        var $el = $(el);
        $el.ggrc_mapping_autocomplete({
          controller : options.contexts.attr("controller"),
          model : $el.data("model"),
          mapping : false
        });
      };
    }
  },
});

/*
  This component is for quickly updating the properties of an object through form fields.
  It works similar to GGRC.Controllers.QuickForm but has an extra feature: if the instance
  we're working with is a join object, and the option type is changed, it will work around
  the lack of support in proxy mappers for join objects being changed like that, and
  destroy the join object while creating a new one.

  Field updates trigger updates to the model automatically, even on the server.  This differs
  from the quick-add component above, in that it is not waiting for a submit trigger.
*/
can.Component.extend({
  tag: "ggrc-quick-update",
  template: "<content/>",
  scope: {
    instance: null,
    source_mapping: null,
    model: null,
    attributes: {}
  },
  events: {
    init: function() {
      this.scope.attr("controller", this);
      this.scope.attr("model", this.scope.model || this.scope.instance.constructor);
      if(!this.scope.instance._transient) {
        //only refresh if there's not currently an edit modal spawned.
        this.scope.instance.refresh();
      }
    },
    //currently we don't support proxy object updates in mappings, so for now a change
    //  to a connected object (assuming we are operating on a proxy object) will trigger
    //  a deletion of the proxy object and creation of a new one.
    autocomplete_select : function(el, event, ui) {
      var that = this;
      setTimeout(function() {
        var serial = that.scope.instance.serialize();
        delete serial[el.attr("name")];
        delete serial[el.attr("name") + "_id"];
        delete serial[el.attr("name") + "_type"];
        delete serial.id;
        delete serial.href;
        delete serial.selfLink;
        delete serial.created_at;
        delete serial.updated_at;
        delete serial.provisional_id;
        serial[el.attr("name")] = ui.item.stub();
        that.scope.instance.destroy().then(function() {
          new that.scope.model(serial).save();
        });
      });
    },
    // null-if-empty attributes are a pattern carried over from GGRC.Controllers.Modals
    //  Useful for connected objects.
    "input[null-if-empty] change" : function(el) {
      if (!el.val()) {
        this.scope.instance.attr(el.attr("name"), null);
      }
    },
    // data-mapping is the element decoration that triggers an autocomplete based on a
    //  mapping to a parent instance.  The mapping_autocomplete helper defined below is
    //  generally for these.
    "input:not([data-mapping]), select change" : function(el) {
      var isCheckbox = el.is("[type=checkbox][multiple]");
      if (isCheckbox) {
        if(!this.scope.instance[el.attr("name")]) {
          this.scope.instance.attr(el.attr("name"), new can.List());
        }
        this.scope.instance
        .attr(el.attr("name"))
        .replace(
          can.map(
            this.element.find("input[name='" + el.attr("name") + "']:checked"),
            function(el) {
              return $(el).val();
            }
          )
        );
        this.element.find("input:checkbox").prop("disabled", true);
      } else {
        this.scope.instance.attr(el.attr("name"), el.val());
      }
      this.scope.instance.save().then(function () {
        if (isCheckbox) {
          this.element.find("input:checkbox").prop("disabled", false);
        }
      }.bind(this));
    },
  },
  helpers: {
    mapping_autocomplete : function(options) {
      return function(el) {
        $(el).ggrc_mapping_autocomplete({ controller : options.contexts.attr("controller") });
      };
    }
  },
});

})(this.can, this.can.$, this.GGRC);
<|endoftext|>