<commit_before>import Ember from 'ember';
import Component from '@ember/component';
import {inject as service} from '@ember/service';
import {later} from '@ember/runloop';

export default Component.extend({
  store: service(),
  router: service(),

  title: "",
  imageId: "",
  dataSet: [],
  config: {},

  createAndLaunch: true,
  createButtonText: Ember.computed('createAndLaunch', function() {
    return this.get('createAndLaunch') ? 'Create New Tale and Launch' : 'Create New Tale';
  }),

  disabled: Ember.computed('title', 'imageId', function() {
    return (this.get('title') && this.get('imageId')) ? '' : 'disabled';
  }),

  defaultErrorMessage: "There was an error while creating your Tale.",

  // ---------------------------------------------------------------------------------
  // ACTIONS TOC:
  //   createNewTaleButton() = Event: onClick "Create New Tale" button
  //   clearModal()          = Event: onHide modal
  //   setModalFromQueryParams() = Event: onShow modal
  //   setDefaultAction()    = Event: onSelect "Create New Tale" dropdown
  // ---------------------------------------------------------------------------------
  actions: {
    // ---------------------------------------------------------------------------------
    // Either imports tale from a dataset or creates a new tale
    // ---------------------------------------------------------------------------------
    async createNewTaleButton() {
      let {dataSet, config, title, imageId, datasetAPI} = this;

      title = title.trim();

      if (this.importing) {
        this.importTaleFromDataset(title, imageId, dataSet, datasetAPI)
      } else {
        this.createNewTale(title, imageId, dataSet, config);
      }
    },

    // ---------------------------------------------------------------------------------
    // clears all text fields
    // ---------------------------------------------------------------------------------
    clearModal() {
      this.set('importing', false);
      this.set('datasetAPI', null);
      this.set('imageId', null);
      this.set('dataSet', []);
      this.set('title', null);
      $('.ui.dropdown.compute-environment').dropdown('clear');
    },

    // ---------------------------------------------------------------------------------
    // If being referred to from a third party, then prepopulate the modal from query params
    // ---------------------------------------------------------------------------------
    setModalFromQueryParams() {
      try {
        if (this.queryParams.uri) {
          this.set('importing', true);
          let {api, name, uri, environment} = this.queryParams;

          this.set('dataSet', uri);
          this.set('title', name || uri);

          let {official, nonOfficial} = this.computeEnvironments;
          let image = official.findBy('name', environment) || nonOfficial.findBy('name', environment);
          if (!image) throw new Error('Could not find specified environment: '+environment);

          this.set('imageId', image.id);

          $('.ui.dropdown.compute-environment').dropdown('set selected', image.id);

          this.set('datasetAPI', api);
        }
      } catch(e) {
        later(()=>$('.ui.modal.create-tale').modal('hide'), 50);
        this.handleError({responseJSON:{message:e+""}});
      }
    },

    // ---------------------------------------------------------------------------------
    // Sets the default action to take when clicking on "Create New Tale" button
    // Choices are: Create Tale   ,    Create Tale and Launch
    // ---------------------------------------------------------------------------------
    setDefaultAction(createAndLaunch) {
      this.set('createAndLaunch', createAndLaunch);
    }
  },

  // ---------------------------------------------------------------------------------
  // Display error message
  // ---------------------------------------------------------------------------------
  handleError(e) {
    let errorMessage = (e.responseJSON ? e.responseJSON.message : this.get('defaultErrorMessage'));
    this.set('errorMessage', errorMessage);
    later(()=>$('.ui.compose-error.modal').modal('show'), 100);
  },

  // ---------------------------------------------------------------------------------
  // Calls POST /tale
  // if launch is false: transitions to the "tale view" pane on success
  // if launch is true:  launches the tale and transitions to the "run" pane on success
  // ---------------------------------------------------------------------------------
  async createNewTale(title, imageId, dataSet, config) {
    const store = this.get('store');
    let newTale = store.createRecord('tale', {title, imageId, dataSet, config});

    try {
      let item = await newTale.save();
      let taleId = item.id;

      if (this.createAndLaunch) {
        let newInstance = this.get('store').createRecord('instance');
        newInstance.save({adapterOptions:{queryParams:{imageId, taleId}}});
      }

      // later(this.router.transitionTo.bind(this.router, 'tale.view', taleId), 100);
    } catch(e) {
      this.handleError(e);
    }
  },

  // ---------------------------------------------------------------------------------
  // Calls POST /tale/import
  // Does nothing with the job that is returned (see comment below)
  // ---------------------------------------------------------------------------------
  async importTaleFromDataset(title, imageId, url, datasetAPI) {
    let taleKwargs = {title};

    let lookupKwargs = {};
    if (datasetAPI) {
      lookupKwargs.base_url = datasetAPI;
    }

    let appendPath = 'import';
    let newTaleImport = this.get('store').createRecord('tale');

    let queryParams = {taleKwargs:JSON.stringify(taleKwargs), lookupKwargs:JSON.stringify(lookupKwargs), imageId, url, spawn:true};
    let adapterOptions = {appendPath, queryParams};

    try {
      let job = await newTaleImport.save({adapterOptions});
      // TODO(Adam): Per discussion with Kacper https://github.com/whole-tale/dashboard/issues/452#issuecomment-485838913
      //             I will not implement a tale import tracker on the browse page. I will await decisions from the UI/UX team.
    } catch(e) {
      this.handleError({responseJSON:{message:e+""}});
    }
    this.router.transitionTo({queryParams: {environment: null, name: null, uri: null, api: null}});
  }
});<commit_msg>resolved all linter errors with the create-tale-modal<commit_after>import Component from '@ember/component';
import { computed } from '@ember/object';
import {inject as service} from '@ember/service';
import {later} from '@ember/runloop';
import $ from 'jquery';

export default Component.extend({
  store: service(),
  router: service(),

  title: "",
  imageId: "",
  dataSet: null,
  config: null,

  createAndLaunch: true,
  createButtonText: computed('createAndLaunch', function() {
    return this.get('createAndLaunch') ? 'Create New Tale and Launch' : 'Create New Tale';
  }),

  disabled: computed('title', 'imageId', function() {
    return (this.get('title') && this.get('imageId')) ? '' : 'disabled';
  }),

  defaultErrorMessage: "There was an error while creating your Tale.",

  // ---------------------------------------------------------------------------------
  // ACTIONS TOC:
  //   createNewTaleButton() = Event: onClick "Create New Tale" button
  //   clearModal()          = Event: onHide modal
  //   setModalFromQueryParams() = Event: onShow modal
  //   setDefaultAction()    = Event: onSelect "Create New Tale" dropdown
  // ---------------------------------------------------------------------------------
  actions: {
    // ---------------------------------------------------------------------------------
    // Either imports tale from a dataset or creates a new tale
    // ---------------------------------------------------------------------------------
    async createNewTaleButton() {
      let {dataSet, config, title, imageId, datasetAPI} = this;

      title = title.trim();

      if (this.importing) {
        this.importTaleFromDataset(title, imageId, dataSet||[], datasetAPI)
      } else {
        this.createNewTale(title, imageId, dataSet||[], config||{});
      }
    },

    // ---------------------------------------------------------------------------------
    // clears all text fields
    // ---------------------------------------------------------------------------------
    clearModal() {
      this.set('importing', false);
      this.set('datasetAPI', null);
      this.set('imageId', null);
      this.set('dataSet', null);
      this.set('title', null);
      $('.ui.dropdown.compute-environment').dropdown('clear');
    },

    // ---------------------------------------------------------------------------------
    // If being referred to from a third party, then prepopulate the modal from query params
    // ---------------------------------------------------------------------------------
    setModalFromQueryParams() {
      try {
        if (this.queryParams.uri) {
          this.set('importing', true);
          let {api, name, uri, environment} = this.queryParams;

          this.set('dataSet', uri);
          this.set('title', name || uri);

          let {official, nonOfficial} = this.computeEnvironments;
          let image = official.findBy('name', environment) || nonOfficial.findBy('name', environment);
          if (!image) throw new Error('Could not find specified environment: '+environment);

          this.set('imageId', image.id);

          $('.ui.dropdown.compute-environment').dropdown('set selected', image.id);

          this.set('datasetAPI', api);
        }
      } catch(e) {
        later(()=>$('.ui.modal.create-tale').modal('hide'), 50);
        this.handleError({responseJSON:{message:e+""}});
      }
    },

    // ---------------------------------------------------------------------------------
    // Sets the default action to take when clicking on "Create New Tale" button
    // Choices are: Create Tale   ,    Create Tale and Launch
    // ---------------------------------------------------------------------------------
    setDefaultAction(createAndLaunch) {
      this.set('createAndLaunch', createAndLaunch);
    }
  },

  // ---------------------------------------------------------------------------------
  // Display error message
  // ---------------------------------------------------------------------------------
  handleError(e) {
    let errorMessage = (e.responseJSON ? e.responseJSON.message : this.get('defaultErrorMessage'));
    this.set('errorMessage', errorMessage);
    later(()=>$('.ui.compose-error.modal').modal('show'), 100);
  },

  // ---------------------------------------------------------------------------------
  // Calls POST /tale
  // if launch is false: transitions to the "tale view" pane on success
  // if launch is true:  launches the tale and transitions to the "run" pane on success
  // ---------------------------------------------------------------------------------
  async createNewTale(title, imageId, dataSet, config) {
    const store = this.get('store');
    dataSet = dataSet || [];
    config = config || {};
    let newTale = store.createRecord('tale', {title, imageId, dataSet, config});

    try {
      let item = await newTale.save();
      let taleId = item.id;

      if (this.createAndLaunch) {
        let newInstance = this.get('store').createRecord('instance');
        newInstance.save({adapterOptions:{queryParams:{imageId, taleId}}});
      }

      // later(this.router.transitionTo.bind(this.router, 'tale.view', taleId), 100);
    } catch(e) {
      this.handleError(e);
    }
  },

  // ---------------------------------------------------------------------------------
  // Calls POST /tale/import
  // Does nothing with the job that is returned (see comment below)
  // ---------------------------------------------------------------------------------
  async importTaleFromDataset(title, imageId, url, datasetAPI) {
    let taleKwargs = {title};

    let lookupKwargs = {};
    if (datasetAPI) {
      lookupKwargs.base_url = datasetAPI;
    }

    let appendPath = 'import';
    let newTaleImport = this.get('store').createRecord('tale');

    let queryParams = {taleKwargs:JSON.stringify(taleKwargs), lookupKwargs:JSON.stringify(lookupKwargs), imageId, url, spawn:true};
    let adapterOptions = {appendPath, queryParams};

    try {
      let job = await newTaleImport.save({adapterOptions});
      job;
      // TODO(Adam): Per discussion with Kacper https://github.com/whole-tale/dashboard/issues/452#issuecomment-485838913
      //             I will not implement a tale import tracker on the browse page. I will await decisions from the UI/UX team.
    } catch(e) {
      this.handleError({responseJSON:{message:e+""}});
    }
    this.router.transitionTo({queryParams: {environment: null, name: null, uri: null, api: null}});
  }
});<|endoftext|>


<commit_before>define(['text!./results-list.html', 'app', 'lodash'], function(template, app, _) { 'use strict';

app.directive('resultsList',['$timeout', function ($timeout) {
    return {
        restrict: 'EAC',
        template: template,
        replace: true,
        require : '^reportingDisplay',
        scope: {
          show: '=show',
          items:       '=ngModel',
          numRecords: '=count' // total count of all children subquires needed for 0 result combinations
        },
          link : function ($scope, $element, $attr, reportingDisplayCtrl)
        {
            $scope.expanded = false;
            $scope.schemaNameMap={

                'nationalAssessment':'National Assessments',
                'nationalReport':'National Reports'

            };
            $scope.$watch('items',function(){init();});
            $scope.$watch('show',function(){showCountry($scope.show);});

            //=======================================================================
    				//
    				//=======================================================================
    				function init() {
                  $scope.numCountries=_.size($scope.items);
                  if(!$scope.numRecords)$scope.numRecords=0;
    				}//

            //=======================================================================
    				//
    				//=======================================================================
    				function showCountry(showCountry) {

                  if(showCountry==='show') return showAllCountry();

                  _.each($scope.items,function(country){
                      if(country.identifier.toUpperCase()==showCountry.toUpperCase())
                          country.hidden=false;
                      else
                          country.hidden=true;
                  });

    				}//
            //=======================================================================
            //
            //=======================================================================
            function showAllCountry() {
                  _.each($scope.items,function(country){
                          country.hidden=false;
                  });

            }//
              //=======================================================================
              //
              //=======================================================================
              $scope.zoomToCountry =function (id) {
//console.log(uri);
                    reportingDisplayCtrl.zoomToCountry(id);

              }//

            //=======================================================================
            //
            //=======================================================================
            $scope.closeAllCountries =function (keepOpen) {

                  _.each($scope.items,function(country){

                    if(country.identifier==keepOpen)
                        country.expanded=true;
                    else
                        country.expanded=false;
                  });

            }//

            //=======================================================================
    				//
    				//=======================================================================
    				$scope.toggleHide =function (show) {

                  if(show)
                        show=false;
                  else
                        show=true;
                  return show;

    				}//
            // //=======================================================================
    				// //
    				// //=======================================================================
    				// function initJQ() {
            //     $(function () {
            //
            //         $('.tree li:has(ul)').addClass('parent_li').find(' > span').attr('title', 'Collapse this country');
            //         $('.tree li.parent_li > span').on('click', function (e) {
            //             var children = $(this).parent('li.parent_li').find(' > ul > li');
            //             if (children.is(":visible")) {
            //
            //                 children.hide('fast');
            //                 $(this).attr('title', 'Expand this branch').find(' > i').addClass('icon-plus-sign').removeClass('icon-minus-sign');
            //             } else {
            //                 children.show('fast');
            //                 $(this).attr('title', 'Collapse this branch').find(' > i').addClass('icon-minus-sign').removeClass('icon-plus-sign');
            //             }
            //             e.stopPropagation();
            //         });
            //     });
            // }//initJQ()

        }//link
    }; // return
  }]);  //app.directive('searchFilterCountries
});// define
<commit_msg>fix branches not opening and closing on country and home select<commit_after>define(['text!./results-list.html', 'app', 'lodash'], function(template, app, _) { 'use strict';

app.directive('resultsList',['$timeout', function ($timeout) {
    return {
        restrict: 'EAC',
        template: template,
        replace: true,
        require : '^reportingDisplay',
        scope: {
          show: '=show',
          items:       '=ngModel',
          numRecords: '=count' // total count of all children subquires needed for 0 result combinations
        },
          link : function ($scope, $element, $attr, reportingDisplayCtrl)
        {
            $scope.expanded = false;
            $scope.schemaNameMap={

                'nationalAssessment':'National Assessments',
                'nationalReport':'National Reports'

            };
            $scope.$watch('items',function(){init();});
            $scope.$watch('show',function(){showCountry($scope.show);});

            //=======================================================================
    				//
    				//=======================================================================
    				function init() {
                  $scope.numCountries=_.size($scope.items);
                  if(!$scope.numRecords)$scope.numRecords=0;
    				}//

            //=======================================================================
    				//
    				//=======================================================================
    				function showCountry(showCountry) {

                  if(showCountry==='show') return showAllCountry();

                  _.each($scope.items,function(country){
                      if(country.identifier.toUpperCase()===showCountry.toUpperCase()){
                          country.hidden=0;
                          country.expanded=1;
                          _.each(country.docs, function(schema){
                                  schema.expandedDoc=1;
                          });
                      }
                      else
                          country.hidden=1;
                  });
    				}//

            //=======================================================================
            //
            //=======================================================================
            function showAllCountry() {
                  _.each($scope.items,function(country){
                          country.hidden=false;
                          country.expanded=0;
                          _.each(country.docs, function(schema){
                                  schema.expandedDoc=0;
                          });
                  });

            }//
              //=======================================================================
              //
              //=======================================================================
              $scope.zoomToCountry =function (id) {
//console.log(uri);
                    reportingDisplayCtrl.zoomToCountry(id);

              }//

            //=======================================================================
            //
            //=======================================================================
            $scope.closeAllCountries =function (keepOpen) {

                  _.each($scope.items,function(country){

                    if(country.identifier==keepOpen)
                        country.expanded=true;
                    else
                        country.expanded=false;
                  });

            }//

            //=======================================================================
    				//
    				//=======================================================================
    				$scope.toggleHide =function (show) {

                  if(show)
                        show=false;
                  else
                        show=true;
                  return show;

    				}//
            // //=======================================================================
    				// //
    				// //=======================================================================
    				// function initJQ() {
            //     $(function () {
            //
            //         $('.tree li:has(ul)').addClass('parent_li').find(' > span').attr('title', 'Collapse this country');
            //         $('.tree li.parent_li > span').on('click', function (e) {
            //             var children = $(this).parent('li.parent_li').find(' > ul > li');
            //             if (children.is(":visible")) {
            //
            //                 children.hide('fast');
            //                 $(this).attr('title', 'Expand this branch').find(' > i').addClass('icon-plus-sign').removeClass('icon-minus-sign');
            //             } else {
            //                 children.show('fast');
            //                 $(this).attr('title', 'Collapse this branch').find(' > i').addClass('icon-minus-sign').removeClass('icon-plus-sign');
            //             }
            //             e.stopPropagation();
            //         });
            //     });
            // }//initJQ()

        }//link
    }; // return
  }]);  //app.directive('searchFilterCountries
});// define
<|endoftext|>


<commit_before>// @flow
import React, { Component } from 'react';
import SweetInput from '../SweetInput/SweetInput';
import SelectCountry from '../SelectCountry';
import SweetSelect from '../SweetSelect/SweetSelect';
import Checkbox from '../Checkbox/Checkbox';
import type { Element } from 'react';

type Field = {
  data_type: string;
  name: string;
  label: string;
  default_value?: string;
  required?: boolean;
  disabled?: boolean;
  choices: any;
};

export default class FieldShape extends Component {
  props: {
    field: Field;
    value?: any;
    errorMessage?: string;
    onChange?: (v: ?SyntheticEvent | ?string) => void;
  };

  checkboxToggle(event) {
    this.props.onChange(event.target.checked ? '1' : '0');
  }

  fieldProps() {
    const { field, value } = this.props;
    return {
      name: field.name,
      label: field.label,
      disabled: field.disabled,
      required: field.required,
      value: value,
      errorMessage: this.props.errorMessage,
      onChange: this.props.onChange,
    };
  }

  renderField(type: string): Element<any> {
    const fieldProps = this.fieldProps();
    const { field: { default_value, name } } = this.props;

    switch (type) {
      case 'email':
        return <SweetInput type="email" {...fieldProps} />;
      case 'phone':
      case 'numeric':
        return <SweetInput type="tel" {...fieldProps} />;
      case 'country':
        return <SelectCountry {...fieldProps} />;
      case 'dropdown':
      case 'select':
        return <SweetSelect {...fieldProps} options={this.props.field.choices} />;
      case 'hidden':
        return <input type="hidden" name={name} value={default_value} />;
      case 'checkbox':
        fieldProps.value = (fieldProps.value || '0').toString();
        const checked = fieldProps.value === '1' || fieldProps.value === 'checked' || fieldProps.value === 'true';
        return (<Checkbox checked={checked} onChange={this.checkboxToggle.bind(this)}>
                  {fieldProps.label}
                </Checkbox>);
      case 'choice':
        return (

{ fieldProps.label }

                  {this.props.field.choices.map( choice =>

                      <input id={choice.id} name={fieldProps.name}
                        type='radio' value={choice.value} checked={choice.value === fieldProps.value}
                        onChange={event => this.props.onChange(event.target.value)} />
                      { choice.label }

                  )}

);
      case 'instruction':
        return
{ fieldProps.label }
;
      case 'text':
      case 'postal':
      default:
        return <SweetInput type="text" {...fieldProps} />;
    }
  }

  render() {
    return (

        {this.renderField(this.props.field.data_type)}

    );
  }
}
<commit_msg>Only call onChange if it exists (flagged by flow as possible bug)<commit_after>// @flow
import React, { Component } from 'react';
import SweetInput from '../SweetInput/SweetInput';
import SelectCountry from '../SelectCountry';
import SweetSelect from '../SweetSelect/SweetSelect';
import Checkbox from '../Checkbox/Checkbox';
import type { Element } from 'react';

type Field = {
  data_type: string;
  name: string;
  label: string;
  default_value?: string;
  required?: boolean;
  disabled?: boolean;
  choices: any;
};

export default class FieldShape extends Component {
  props: {
    field: Field;
    value?: any;
    errorMessage?: string;
    onChange?: (v: ?SyntheticEvent | ?string) => void;
  };

  checkboxToggle(event: SyntheticInputEvent) {
    this.props.onChange && this.props.onChange(event.target.checked ? '1' : '0');
  }

  fieldProps() {
    const { field, value } = this.props;
    return {
      name: field.name,
      label: field.label,
      disabled: field.disabled,
      required: field.required,
      value: value,
      errorMessage: this.props.errorMessage,
      onChange: this.props.onChange,
    };
  }

  renderField(type: string): Element<any> {
    const fieldProps = this.fieldProps();
    const { field: { default_value, name } } = this.props;

    switch (type) {
      case 'email':
        return <SweetInput type="email" {...fieldProps} />;
      case 'phone':
      case 'numeric':
        return <SweetInput type="tel" {...fieldProps} />;
      case 'country':
        return <SelectCountry {...fieldProps} />;
      case 'dropdown':
      case 'select':
        return <SweetSelect {...fieldProps} options={this.props.field.choices} />;
      case 'hidden':
        return <input type="hidden" name={name} value={default_value} />;
      case 'checkbox':
        fieldProps.value = (fieldProps.value || '0').toString();
        const checked = fieldProps.value === '1' || fieldProps.value === 'checked' || fieldProps.value === 'true';
        return (<Checkbox checked={checked} onChange={this.checkboxToggle.bind(this)}>
                  {fieldProps.label}
                </Checkbox>);
      case 'choice':
        return (

{ fieldProps.label }

                  {this.props.field.choices.map( choice =>

                      <input id={choice.id} name={fieldProps.name}
                        type='radio' value={choice.value} checked={choice.value === fieldProps.value}
                        onChange={(event: SyntheticInputEvent) => this.props.onChange && this.props.onChange(event.target.value)} />
                      { choice.label }

                  )}

);
      case 'instruction':
        return
{ fieldProps.label }
;
      case 'text':
      case 'postal':
      default:
        return <SweetInput type="text" {...fieldProps} />;
    }
  }

  render() {
    return (

        {this.renderField(this.props.field.data_type)}

    );
  }
}
<|endoftext|>


<commit_before>(function() {
  'use strict';

  angular.module('character-tracker.charactersheet')
  .controller('ClassController', ClassController);

  function ClassController() {
    var vm = this;

    vm.classes = [
      {name: 'Fighter'},
      {name: 'Cleric'},
      {name: 'Druid'},
      {name: 'Wizard'},
      {name: 'Rogue'},
      {name: 'Barbarian'},
      {name: 'Ranger'}
    ]

    vm.characterClasses = [
      {name: '', level: 0}
    ]

    init();

    function init() {
      $.material.init();
    }

    return vm;
  }
}());<commit_msg>getting services in order<commit_after>(function() {
  'use strict';

  angular.module('character-tracker.charactersheet')
  .controller('ClassController', ClassController);

  ClassController.$inject = ['ClassService']

  function ClassController(ClassService) {
    var vm = this;

    vm.classes = ClassService.getClasses();

    vm.characterClasses = [
      {name: '', level: 0}
    ]

    init();

    function init() {
      $.material.init();
    }

    return vm;
  }
}());<|endoftext|>


<commit_before>
import StellarSdk from 'stellar-sdk';
import multisig from '@futuretense/stellar-multisig';

import horizon from '../../core/services/horizon';
import storage from '../../core/services/storage';
import selectFunderModal from './select-funder.html';

const range = (l, r) => new Array(r - l).fill().map((_, k) => k + l);

export default class CreateSharedController {

	/* @ngInject */
	constructor($location, Modal, Reviewer, Wallet) {
		this.$location = $location;
		this.Modal = Modal;
		this.Reviewer = Reviewer;
		this.Wallet = Wallet;

		this.account = {
			alias:	getAccountName(),
			weight: 1
		};

		this.advanced = storage.getItem('advanced');
		this.signers = [];
		this.state = 0;

		this.currentSignerIndex = 1;
		this.numCosigners = 2;
		this.threshold = 1;

		function getAccountName() {
			const accountNum = getNextSharedAccountNumber();
			return `Shared Account #${accountNum}`;
		}

		function getNextSharedAccountNumber() {
			return Wallet.accountList.filter(item => item.isMultiSig()).length + 1;
		}
	}

	onCosignerCountChange() {
		this.threshold = Math.min(this.threshold, this.numCosigners);
	}

	getCosignerOptions() {
		return range(2, 11);
	}

	getThresholdOptions() {
		return range(1, this.numCosigners + 1);
	}

	next() {
		if (this.state === 0) {
			this.state = 1;
		}

		else if (this.state === 1) {
			this.addSigner();
			if (this.signers.length === this.numCosigners) {

				const fees = horizon.getFees(this.account.network);
				this.minimum = fees.baseReserve * (2 + this.numCosigners);
				this.account.amount = this.minimum;
				this.state = 2;
			}
		}

		else if (this.state === 2) {
			this.createAccount();
		}
	}

	addSigner() {
		this.signers.push({
			address: this.account.signer,
			id: this.account.destInfo.id,
			weight: this.account.weight
		});
		this.account.signer = '';
		this.account.weight = 1;
		this.currentSignerIndex += 1;
	}

	createAccount() {
		const network = this.account.network;
		const accounts = {};

		Object.keys(this.Wallet.accounts).forEach((key) => {
			const account = this.Wallet.accounts[key];
			if (account.network === network) {
				accounts[account.alias] = account;
			}
		});

		const funderName = this.account.funder;
		if (funderName in accounts) {

			const newAccount	= StellarSdk.Keypair.random();
			const funder		= accounts[funderName];
			const newAccountId	= newAccount.publicKey();

			funder.horizon()
			.loadAccount(funder.id)
			.then((account) => {

				const builder = new StellarSdk.TransactionBuilder(account)
				.addOperation(StellarSdk.Operation.createAccount({
					destination: newAccount.publicKey(),
					startingBalance: this.account.amount.toString()
				}))
				.setTimeout(0);

				this.signers.forEach((signer) => {
					const op = StellarSdk.Operation.setOptions({
						source: newAccountId,
						signer: {
							ed25519PublicKey: signer.id,
							weight: signer.weight
						}
					});

					builder.addOperation(op);
				});

				const threshold = this.threshold;
				builder.addOperation(StellarSdk.Operation.setOptions({
					source: newAccountId,
					lowThreshold: threshold,
					medThreshold: threshold,
					highThreshold: threshold,
					masterWeight: 0
				}));

				/*	This signs for the new account */

				const tx = builder.build();
				const networkId = horizon.getNetworkId(network);
				const hash = multisig.getTransactionHash(tx, networkId);
				const sig = newAccount.signDecorated(hash);
				tx.signatures.push(sig);

				return {
					tx: tx,
					network: network
				};
			})
			.then(this.Reviewer.review)
			.then(() => {
				this.Wallet.importAccount(
					newAccountId,
					newAccount.secret(),
					this.account.alias,
					network
				);
				this.$location.path('/');
			});
		}
	}

	onValidAddress(destInfo) {
		this.account.destInfo = destInfo;
	}

	isSigner(name) {
		return this.signers.map(item => item.address).includes(name);
	}

	accountFilter(account) {
		return !this.isSigner(account);
	}

	/* filter out contacts that have been added already, or has a memo set  */
	contactFilter(contact) {
		const [name, item] = contact;
		return !('memo' in item) && (!this.isSigner(name));
	}

	selectFunder() {
		const data = {
			network: this.account.network,
			minimum: this.minimum,
			numOps: 2 + this.signers.length
		};

		this.Modal.show(selectFunderModal, data)
		.then((res) => {
			this.account.funder = res;
		});
	}
}
<commit_msg>Sign the raw hash<commit_after>
import StellarSdk from 'stellar-sdk';
import * as multisig from '@futuretense/stellar-multisig';

import horizon from '../../core/services/horizon';
import storage from '../../core/services/storage';
import selectFunderModal from './select-funder.html';

const range = (l, r) => new Array(r - l).fill().map((_, k) => k + l);

export default class CreateSharedController {

	/* @ngInject */
	constructor($location, Modal, Reviewer, Wallet) {
		this.$location = $location;
		this.Modal = Modal;
		this.Reviewer = Reviewer;
		this.Wallet = Wallet;

		this.account = {
			alias:	getAccountName(),
			weight: 1
		};

		this.advanced = storage.getItem('advanced');
		this.signers = [];
		this.state = 0;

		this.currentSignerIndex = 1;
		this.numCosigners = 2;
		this.threshold = 1;

		function getAccountName() {
			const accountNum = getNextSharedAccountNumber();
			return `Shared Account #${accountNum}`;
		}

		function getNextSharedAccountNumber() {
			return Wallet.accountList.filter(item => item.isMultiSig()).length + 1;
		}
	}

	onCosignerCountChange() {
		this.threshold = Math.min(this.threshold, this.numCosigners);
	}

	getCosignerOptions() {
		return range(2, 11);
	}

	getThresholdOptions() {
		return range(1, this.numCosigners + 1);
	}

	next() {
		if (this.state === 0) {
			this.state = 1;
		}

		else if (this.state === 1) {
			this.addSigner();
			if (this.signers.length === this.numCosigners) {

				const fees = horizon.getFees(this.account.network);
				this.minimum = fees.baseReserve * (2 + this.numCosigners);
				this.account.amount = this.minimum;
				this.state = 2;
			}
		}

		else if (this.state === 2) {
			this.createAccount();
		}
	}

	addSigner() {
		this.signers.push({
			address: this.account.signer,
			id: this.account.destInfo.id,
			weight: this.account.weight
		});
		this.account.signer = '';
		this.account.weight = 1;
		this.currentSignerIndex += 1;
	}

	createAccount() {
		const network = this.account.network;
		const accounts = {};

		Object.keys(this.Wallet.accounts).forEach((key) => {
			const account = this.Wallet.accounts[key];
			if (account.network === network) {
				accounts[account.alias] = account;
			}
		});

		const funderName = this.account.funder;
		if (funderName in accounts) {

			const newAccount	= StellarSdk.Keypair.random();
			const funder		= accounts[funderName];
			const newAccountId	= newAccount.publicKey();

			funder.horizon()
			.loadAccount(funder.id)
			.then((account) => {

				const builder = new StellarSdk.TransactionBuilder(account)
				.addOperation(StellarSdk.Operation.createAccount({
					destination: newAccount.publicKey(),
					startingBalance: this.account.amount.toString()
				}))
				.setTimeout(0);

				this.signers.forEach((signer) => {
					const op = StellarSdk.Operation.setOptions({
						source: newAccountId,
						signer: {
							ed25519PublicKey: signer.id,
							weight: signer.weight
						}
					});

					builder.addOperation(op);
				});

				const threshold = this.threshold;
				builder.addOperation(StellarSdk.Operation.setOptions({
					source: newAccountId,
					lowThreshold: threshold,
					medThreshold: threshold,
					highThreshold: threshold,
					masterWeight: 0
				}));

				/*	This signs for the new account */

				const tx = builder.build();
				const networkId = horizon.getNetworkId(network);
				const hash = multisig.getTransactionHashRaw(tx, networkId);
				const sig = newAccount.signDecorated(hash);
				tx.signatures.push(sig);

				return {
					tx: tx,
					network: network
				};
			})
			.then(this.Reviewer.review)
			.then(() => {
				this.Wallet.importAccount(
					newAccountId,
					newAccount.secret(),
					this.account.alias,
					network
				);
				this.$location.path('/');
			});
		}
	}

	onValidAddress(destInfo) {
		this.account.destInfo = destInfo;
	}

	isSigner(name) {
		return this.signers.map(item => item.address).includes(name);
	}

	accountFilter(account) {
		return !this.isSigner(account);
	}

	/* filter out contacts that have been added already, or has a memo set  */
	contactFilter(contact) {
		const [name, item] = contact;
		return !('memo' in item) && (!this.isSigner(name));
	}

	selectFunder() {
		const data = {
			network: this.account.network,
			minimum: this.minimum,
			numOps: 2 + this.signers.length
		};

		this.Modal.show(selectFunderModal, data)
		.then((res) => {
			this.account.funder = res;
		});
	}
}
<|endoftext|>


<commit_before>'use strict';

module.exports = function(kbox) {

  // Modules
  var path = require('path');
  var fs = require('fs');
  var _ = require('lodash');

  // Nobody expects the Spanish Inquisition!
  var inquirer = require('inquirer');

  // "Constants"
  var PLUGIN_NAME = 'kalabox-plugin-pantheon';

  kbox.ifApp(function(app) {

    // Get our push and pull stuff
    var puller = require('./pull.js')(kbox, app);
    var pusher = require('./push.js')(kbox, app);

    // Grab the needed clients
    var Terminus = require('./terminus.js');
    var terminus = new Terminus(kbox, app);
    var pathToRoot = path.resolve(__dirname, '..', '..', '..');
    var pathToNode = path.join(pathToRoot, 'node_modules');
    var Drush = require(pathToNode + '/kalabox-plugin-drush/lib/drush.js');
    var drush = new Drush(kbox, app, 'terminus', PLUGIN_NAME);

    /*
     * Filter out questions that have been answered interactively
     */
    var filterQuestions = function(questions, options) {

      return _.filter(questions, function(question) {

        var option = options[question.name];

        if (question.filter) {
          options[question.name] = question.filter(options[question.name]);
        }
        if (option === false || option === undefined) {
          return true;
        }
        else {
          return !_.includes(Object.keys(options), question.name);
        }

      });
    };

    // Tasks
    // kbox terminus COMMAND
    kbox.tasks.add(function(task) {
      task.path = [app.name, 'terminus'];
      task.category = 'appCmd';
      task.description = 'Run terminus commands.';
      task.kind = 'delegate';
      task.func = function(done) {
        var opts = terminus.getOpts(this.options);
        var cmd = this.payload;
        terminus.cmd(cmd, opts, done);
      };
    });

    // kbox drush COMMAND
    kbox.tasks.add(function(task) {
      task.path = [app.name, 'drush'];
      task.category = 'appCmd';
      task.description = 'Run drush commands.';
      task.kind = 'delegate';
      task.options.push({
        name: 'drush-version',
        kind: 'string',
        description: 'The version of drush that you want.'
      });
      task.func = function(done) {
        var opts = drush.getOpts(this.options);
        var cmd = this.payload;
        cmd.unshift('@dev');
        cmd.push('--strict=0');
        drush.cmd(cmd, opts, done);
      };
    });

    // kbox pull
    kbox.tasks.add(function(task) {

      // Grab pantheon config so we can mix in interactives
      var pantheonConf = app.config.pluginConf['kalabox-plugin-pantheon'];

      // Define our task metadata
      task.path = [app.name, 'pull'];
      task.category = 'appAction';
      task.description = 'Pull down new code and optionally data and files.';
      task.kind = 'delegate';
      task.options.push({
        name: 'database',
        kind: 'boolean',
        description: 'Import latest database backup.'
      });
      task.options.push({
        name: 'files',
        kind: 'boolean',
        description: 'Import latest files.'
      });

      // This is what we run yo!
      task.func = function(done) {

        // Grab the CLI options that are available
        var options = this.options;
        var questions = [
          {
            type: 'confirm',
            name: 'database',
            message: 'Also refresh from latest database backup?',
          },
          {
            type: 'confirm',
            name: 'files',
            message: 'Also grab latest files?',
          },
        ];

        // Filter out interactive questions based on passed in options
        questions = filterQuestions(questions, options);

        // Launch the inquiry
        inquirer.prompt(questions, function(answers) {

          // Collect our answers
          var choices = _.merge({}, options, answers);

          // Grab pantheon aliases
          return terminus.getSiteAliases()

          // Pull our code
          .then(function() {
            return puller.pullCode(pantheonConf.site, pantheonConf.env);
          })

          // Pull our DB if selected
          .then(function() {
            if (choices.database) {
              return puller.pullDB(pantheonConf.site, pantheonConf.env);
            }
          })

          // Pull our files if selected
          .then(function() {
            if (choices.files) {
              return puller.pullFiles(pantheonConf.site, pantheonConf.env);
            }
          })

          .nodeify(done);

        });

      };
    });

    // kbox appname push
    kbox.tasks.add(function(task) {

      // Grab pantheon conf
      var pantheonConf = app.config.pluginConf[PLUGIN_NAME];

      // Task metadata
      task.path = [app.name, 'push'];
      task.category = 'appAction';
      task.description = 'Push up new code and optionally data and files.';
      task.kind = 'delegate';
      task.options.push({
        name: 'message',
        alias: 'm',
        kind: 'string',
        description: 'Tell us about your change'
      });
      task.options.push({
        name: 'database',
        kind: 'boolean',
        description: 'Push local database up.'
      });
      task.options.push({
        name: 'files',
        kind: 'boolean',
        description: 'Push local files up.'
      });

      // This is how we do it
      // https://www.youtube.com/watch?v=0hiUuL5uTKc
      task.func = function(done) {

        // Grab the CLI options that are available
        var options = this.options;
        var questions = [
          {
            type: 'input',
            name: 'message',
            message: 'Tell us about these changes.',
            default: 'Best changes ever!'
          },
          {
            type: 'confirm',
            name: 'database',
            message: 'Also push up your local database?',
          },
          {
            type: 'confirm',
            name: 'files',
            message: 'Also push up your local files directory?',
          },
        ];

        // Filter out interactive questions based on passed in options
        questions = filterQuestions(questions, options);

        // Launch the inquiry
        inquirer.prompt(questions, function(answers) {

          // Collect our choices
          var choices = _.merge({}, options, answers);

          // Grab pantheon site aliases
          return terminus.getSiteAliases()

          // Push our code
          .then(function() {
            return pusher.pushCode(
              pantheonConf.site,
              pantheonConf.env,
              choices.message
            );
          })

          // Push our DB is selected
          .then(function() {
            if (choices.database) {
              return pusher.pushDB(pantheonConf.site, pantheonConf.env);
            }
          })

          // Push our files if selected
          .then(function() {
            if (choices.files) {
              return pusher.pushFiles(pantheonConf.site, pantheonConf.env);
            }
          })

          .nodeify(done);

        });

      };
    });

    // Events
    // Build the site after post-create happens
    kbox.core.events.on('post-create-app', function(app, done) {

      // Our pantheon config for later on
      var pantheonConf = app.config.pluginConf[PLUGIN_NAME];

      // Get our pantheon site aliases
      return terminus.getSiteAliases()

      // Pull our code for the first time
      .then(function() {
        return puller.pullCode(pantheonConf.site, pantheonConf.env);
      })

      // Pull our DB
      .then(function() {
        return puller.pullDB(pantheonConf.site, pantheonConf.env);
      })

      // Get our files
      .then(function() {
        return puller.pullFiles(pantheonConf.site, pantheonConf.env);
      })

      .nodeify(done);

    });

  });

};
<commit_msg>#516: Refactor files and db options to allow pull from other places<commit_after>'use strict';

module.exports = function(kbox) {

  // Modules
  var path = require('path');
  var fs = require('fs');
  var _ = require('lodash');

  // Nobody expects the Spanish Inquisition!
  var inquirer = require('inquirer');

  // "Constants"
  var PLUGIN_NAME = 'kalabox-plugin-pantheon';

  kbox.ifApp(function(app) {

    // Get our push and pull stuff
    var puller = require('./pull.js')(kbox, app);
    var pusher = require('./push.js')(kbox, app);

    // Grab the needed clients
    var Terminus = require('./terminus.js');
    var terminus = new Terminus(kbox, app);
    var pathToRoot = path.resolve(__dirname, '..', '..', '..');
    var pathToNode = path.join(pathToRoot, 'node_modules');
    var Drush = require(pathToNode + '/kalabox-plugin-drush/lib/drush.js');
    var drush = new Drush(kbox, app, 'terminus', PLUGIN_NAME);

    // Get our config
    var pantheonConf = app.config.pluginConf['kalabox-plugin-pantheon'];

    // Supports pull env
    var supportedPullEnvs = [pantheonConf.env, 'test', 'live'];
    var supportedPushEnvs = [pantheonConf.env];

    /*
     * Filter out questions that have been answered interactively
     */
    var filterQuestions = function(questions, options) {

      return _.filter(questions, function(question) {

        var option = options[question.name];

        if (question.filter) {
          options[question.name] = question.filter(options[question.name]);
        }
        if (option === false || option === undefined) {
          return true;
        }
        else {
          return !_.includes(Object.keys(options), question.name);
        }

      });
    };

    /*
     * Get choices array for supported pull environments
     * @todo: allow multidev?
     */
    var getEnvPullChoices = function() {

      // Map to a choices object and return
      return _.map(supportedPullEnvs, function(env) {
        return {
          name: env,
          value: env
        };
      });

    };

    /*
     * Get choices array for supported pull environments
     * @todo: allow multidev?
     */
    var getEnvPushChoices = function() {

      // Map to a choices object and return
      return _.map(supportedPushEnvs, function(env) {
        return {
          name: env,
          value: env
        };
      });

    };

    // Tasks
    // kbox terminus COMMAND
    kbox.tasks.add(function(task) {
      task.path = [app.name, 'terminus'];
      task.category = 'appCmd';
      task.description = 'Run terminus commands.';
      task.kind = 'delegate';
      task.func = function(done) {
        var opts = terminus.getOpts(this.options);
        var cmd = this.payload;
        terminus.cmd(cmd, opts, done);
      };
    });

    // kbox drush COMMAND
    kbox.tasks.add(function(task) {
      task.path = [app.name, 'drush'];
      task.category = 'appCmd';
      task.description = 'Run drush commands.';
      task.kind = 'delegate';
      task.options.push({
        name: 'drush-version',
        kind: 'string',
        description: 'The version of drush that you want.'
      });
      task.func = function(done) {
        var opts = drush.getOpts(this.options);
        var cmd = this.payload;
        cmd.unshift('@dev');
        cmd.push('--strict=0');
        drush.cmd(cmd, opts, done);
      };
    });

    // kbox pull
    kbox.tasks.add(function(task) {

      // Define our task metadata
      task.path = [app.name, 'pull'];
      task.category = 'appAction';
      task.description = 'Pull down new code and optionally data and files.';
      task.kind = 'delegate';
      // Build list of options for desc
      var getOptions = 'Options are ' + supportedPullEnvs.join(', ');
      task.options.push({
        name: 'database',
        kind: 'string',
        description: 'Pull DB from an env. ' + getOptions + ' and none'
      });
      task.options.push({
        name: 'files',
        kind: 'string',
        description: 'Pull files from an env. ' + getOptions + ' and none'
      });

      // This is what we run yo!
      task.func = function(done) {

        // Grab the CLI options that are available
        var options = this.options;
        var questions = [
          {
            type: 'list',
            name: 'database',
            message: 'Which database do you want to use?',
            choices: function(answers) {

              // Get approved choices
              var choices = getEnvPullChoices();

              // Add none choice
              choices.push({
                name: 'Do not pull a database',
                value: 'none'
              });

              // Return our choices
              return choices;

            },
            default: function(answers) {
              return pantheonConf.env;
            }
          },
          {
            type: 'list',
            name: 'files',
            message: 'Which files do you want to use?',
            choices: function(answers) {

              // Get approved choices
              var choices = getEnvPullChoices();

              // Add none choice
              choices.push({
                name: 'Do not pull files',
                value: 'none'
              });

              // Return our choices
              return choices;

            },
            default: function(answers) {
              return answers.database || pantheonConf.env;
            }
          }
        ];

        // Filter out interactive questions based on passed in options
        questions = filterQuestions(questions, options);

        // Launch the inquiry
        inquirer.prompt(questions, function(answers) {

          // Collect our answers
          var choices = _.merge({}, options, answers);

          // Grab pantheon aliases
          return terminus.getSiteAliases()

          // Pull our code
          .then(function() {
            return puller.pullCode(pantheonConf.site, pantheonConf.env);
          })

          // Pull our DB if selected
          .then(function() {
            if (choices.database !== 'none') {
              return puller.pullDB(pantheonConf.site, choices.database);
            }
          })

          // Pull our files if selected
          .then(function() {
            if (choices.files !== 'none') {
              return puller.pullFiles(pantheonConf.site, choices.database);
            }
          })

          .nodeify(done);

        });

      };
    });

    // kbox appname push
    kbox.tasks.add(function(task) {

      // Grab pantheon conf
      var pantheonConf = app.config.pluginConf[PLUGIN_NAME];

      // Task metadata
      task.path = [app.name, 'push'];
      task.category = 'appAction';
      task.description = 'Push up new code and optionally data and files.';
      task.kind = 'delegate';
      task.options.push({
        name: 'message',
        alias: 'm',
        kind: 'string',
        description: 'Tell us about your change'
      });
      // Build list of options for desc
      var getOptions = 'Options are ' + supportedPushEnvs.join(', ');
      task.options.push({
        name: 'database',
        kind: 'string',
        description: 'Push DB to specific env. ' + getOptions + ' and none'
      });
      task.options.push({
        name: 'files',
        kind: 'string',
        description: 'Push files to a spefic env. ' + getOptions + ' and none'
      });

      // This is how we do it
      // https://www.youtube.com/watch?v=0hiUuL5uTKc
      task.func = function(done) {

        // Grab the CLI options that are available
        var options = this.options;
        var questions = [
          {
            type: 'list',
            name: 'database',
            message: 'Which env do you want to push the DB to?',
            choices: function(answers) {

              // Get approved choices
              var choices = getEnvPushChoices();

              // Add none choice
              choices.push({
                name: 'Do not push the database',
                value: 'none'
              });

              // Return our choices
              return choices;

            },
            default: function(answers) {
              return pantheonConf.env;
            }
          },
          {
            type: 'list',
            name: 'files',
            message: 'Which env do you want to push the files to?',
            choices: function(answers) {

              // Get approved choices
              var choices = getEnvPushChoices();

              // Add none choice
              choices.push({
                name: 'Do not push the files',
                value: 'none'
              });

              // Return our choices
              return choices;

            },
            default: function(answers) {
              return answers.database || pantheonConf.env;
            }
          }
        ];

        // Filter out interactive questions based on passed in options
        questions = filterQuestions(questions, options);

        // Launch the inquiry
        inquirer.prompt(questions, function(answers) {

          // Collect our choices
          var choices = _.merge({}, options, answers);

          // Grab pantheon site aliases
          return terminus.getSiteAliases()

          // Push our code
          .then(function() {
            return pusher.pushCode(
              pantheonConf.site,
              pantheonConf.env,
              choices.message
            );
          })

          // Push our DB is selected
          .then(function() {
            if (choices.database !== 'none') {
              return pusher.pushDB(pantheonConf.site, choices.database);
            }
          })

          // Push our files if selected
          .then(function() {
            if (choices.files !== 'none') {
              return pusher.pushFiles(pantheonConf.site, choices.files);
            }
          })

          .nodeify(done);

        });

      };
    });

    // Events
    // Build the site after post-create happens
    kbox.core.events.on('post-create-app', function(app, done) {

      // Our pantheon config for later on
      var pantheonConf = app.config.pluginConf[PLUGIN_NAME];

      // Get our pantheon site aliases
      return terminus.getSiteAliases()

      // Pull our code for the first time
      .then(function() {
        return puller.pullCode(pantheonConf.site, pantheonConf.env);
      })

      // Pull our DB
      .then(function() {
        return puller.pullDB(pantheonConf.site, pantheonConf.env);
      })

      // Get our files
      .then(function() {
        return puller.pullFiles(pantheonConf.site, pantheonConf.env);
      })

      .nodeify(done);

    });

  });

};
<|endoftext|>


<commit_before>'use strict';
const helpers = require('../../../shared/helpers');

const Snackbar = require('../snackbar');
const contributionCounter = require('./contribution-counter');

// TODO: Re-introduce the use of Google Analytics events
// TODO: Add pretty errors instead of silent fails
// TODO: Use the "require('./contribution-counter')"

// Precondition on the availability of the helpers

if(!helpers.motifTagging ||
   typeof(helpers.motifTagging.addTag) !== 'function' ||
   typeof(helpers.motifTagging.removeTag) !== 'function' ||
   typeof(helpers.motifTagging.removeVisionTag) !== 'function') {
  throw new Error('Missing helpers.motifTagging.* helpers');
}

const ADD_MOTIF_TAG_SELECTOR = '[data-action="add-motif-tag"]';
const CANCEL_MOTIF_TAGS_SELECTOR = '[data-action="cancel-motif-tags"]';
const EDIT_MOTIF_TAGS_SELECTOR = '[data-action="edit-motif-tags"]';
const SAVE_MOTIF_TAGS_SELECTOR = '[data-action="save-motif-tags"]';
const TRANSFER_TAG_INPUT_SELECTOR = '[data-action="transfer-motif-tag"]';
const REMOVE_TAG_INPUT_SELECTOR = '[data-action="remove-motif-tag"]';
const DYNAMIC_TEMPLATE_SELECTOR = '[data-motif-template]';

const ADD_TAG_INPUT_SELECTOR = '.motif-tagging__add-input input';
const EDITNG_CONTAINER_SELECTOR = '.motif-tagging__editing-container';

const IS_EDITING_CLASS = 'motif-tagging__editing-container--editing';

const motifTaggingTemplate = require('views/includes/motif-tagging');
const tagTemplate = require('views/includes/motif-tagging/tags');
const tagSuggestionsTemplate = require('views/includes/motif-tagging/tag-suggestions');
const saveButtonTemplate = require('views/includes/motif-tagging/save-buttons');

const templates = {
  'tags': tagTemplate,
  'tag-suggestions': tagSuggestionsTemplate,
  'save-buttons': saveButtonTemplate
};

(function($) {

  class MotifTaggingController {

    constructor($motifTagging) {
      this.$motifTagging = $motifTagging;
      this.templateSelectors = $motifTagging.find(DYNAMIC_TEMPLATE_SELECTOR);
      this.$editingContainer = $($motifTagging.find(EDITNG_CONTAINER_SELECTOR)[0]);

      this.state = {
        editing: false,
        saving: false,
        metadata: {}
      };
      // Register the listeners
      this.registerListeners();
    }

    render() {
      this.$editingContainer.toggleClass(IS_EDITING_CLASS, this.state.editing);

      this.templateSelectors.each((i, template) => {
        let $template = $(template);
        let name = $template.data('motif-template');
        let markup = $(templates[name](this.state));

        $template.html(markup.html());
      });
    }

    toggleEditing(editing) {
      // Fetch the current metadata when editing
      $.getJSON(location.pathname + '/json', (metadata) => {
        // Fetch and override the metadata
        this.state.metadata = metadata;
        // Mutate the state
        this.state.editing = editing;
        // Re-render the motif-tagging template
        this.render();
      });
    }

    edit() {
      this.toggleEditing(true);
    }

    cancel() {
      this.toggleEditing(false);
    }

    addTagFromInput() {
      const $input = this.$motifTagging.find(ADD_TAG_INPUT_SELECTOR);
      // Read the value of the input field
      const tag = $input.val();
      // Then clear it
      $input.val('');
      // Add it
      this.addTag(tag);
    }

    addTag(tag) {
      // Don't do anything with an undefined, null or empty tag
      if(!tag || tag === '') {
        return;
      }
      // Add it to the metadata
      helpers.motifTagging.addTag(this.state.metadata, tag);
      // Re-render the motif-tagging template
      this.render();
    }

    removeTag(tag) {
      // Remove it from the metadata
      helpers.motifTagging.removeTag(this.state.metadata, tag);
      helpers.motifTagging.removeVisionTag(this.state.metadata, tag);
      // Re-render the motif-tagging template
      this.render();
    }

    save() {
      // Don't save twice
      if(this.state.saving) {
        return;
      }
      // Add any tag which has not been added
      this.addTagFromInput();
      this.state.saving = true;
      this.render();
      const url = location.pathname + '/save-motif-tags';
      $.post(url, this.state.metadata, (metadata) => {
        // Once the metadata is back, override it and stop saving
        this.state.metadata = metadata;
        this.state.saving = false;
        this.state.editing = false;
        this.render();

        Snackbar.info('Gemt! Tak for dit bidrag!');
        contributionCounter.contributionAdded();
      }, 'json');
    }

    registerListeners() {
      // When clicking the edit button
      this.$motifTagging.on('click', EDIT_MOTIF_TAGS_SELECTOR, () => {
        this.edit();
      });
      // When clicking the edit button, motif-tagging template is re-rendered
      this.$motifTagging.on('click', ADD_MOTIF_TAG_SELECTOR, () => {
        this.addTagFromInput();
      });
      // When clicking the edit button, motif-tagging template is re-rendered
      this.$motifTagging.on('click', CANCEL_MOTIF_TAGS_SELECTOR, () => {
        this.cancel();
      });
      // When clicking the edit button, motif-tagging template is re-rendered
      this.$motifTagging.on('click', SAVE_MOTIF_TAGS_SELECTOR, () => {
        this.save();
      });
      // When hitting enter in the input field, we add the tag
      this.$motifTagging.on('keyup', ADD_TAG_INPUT_SELECTOR, (event) => {
        if (event.keyCode === 13) {
          this.addTagFromInput();
          // Put the focus back into the input
          this.$motifTagging.find(ADD_TAG_INPUT_SELECTOR).focus();
        }
      });
      // When clicking the thumbs up on a vision tag, add it
      this.$motifTagging.on('click', TRANSFER_TAG_INPUT_SELECTOR, (event) => {
        const tag = $(event.target).closest('[data-tag]').data('tag');
        this.addTag(tag);
        // Let's not make this a click on the actual tag
        event.preventDefault();
      });
      // When clicking the cross on a vision tag, remove it
      this.$motifTagging.on('click', REMOVE_TAG_INPUT_SELECTOR, (event) => {
        const tag = $(event.target).closest('[data-tag]').data('tag');
        this.removeTag(tag);
        // Let's not make this a click on the actual tag
        event.preventDefault();
      });
    }

    bindTypeahead($input) {
      // Construct a source for tags
      const tagsSource = new Bloodhound({
        datumTokenizer: function(tags) {
          return Bloodhound.tokenizers.whitespace(tags);
        },
        queryTokenizer: Bloodhound.tokenizers.whitespace,
        remote: {
          url: '/motif-tag-suggestions?text=%QUERY',
          wildcard: '%QUERY',
          cache: false
        }
      });

      // Initialize the source of tags
      tagsSource.initialize();

      // And bind it on the input field
      $input.typeahead({
        hint: false,
        highlight: true,
        minLength: 1
      }, {
        name: 'tags',
        source: tagsSource
      });

      // When the input is focussed, add a class to the parent
      $input.on('focus', function() {
        $input.parent('.twitter-typeahead').addClass('focused');
      });

      // When the input is empty and blurred, remove the class again
      $input.on('blur', function() {
        if($input.val() === '') {
          $input.parent('.twitter-typeahead').removeClass('focused');
        }
      });
    }
  }

  // Initialize motif tagging for every .motif-tagging element on the page
  $(() => {
    $('.motif-tagging').each((index, motifTagging) => {
      const $motifTagging = $(motifTagging);
      // Initialize a controller
      const controller = new MotifTaggingController($motifTagging);
    });
  });
})(jQuery);
<commit_msg>We're using that<commit_after>'use strict';
const helpers = require('../../../shared/helpers');

const Snackbar = require('../snackbar');
const contributionCounter = require('./contribution-counter');

// TODO: Re-introduce the use of Google Analytics events
// TODO: Add pretty errors instead of silent fails

// Precondition on the availability of the helpers

if(!helpers.motifTagging ||
   typeof(helpers.motifTagging.addTag) !== 'function' ||
   typeof(helpers.motifTagging.removeTag) !== 'function' ||
   typeof(helpers.motifTagging.removeVisionTag) !== 'function') {
  throw new Error('Missing helpers.motifTagging.* helpers');
}

const ADD_MOTIF_TAG_SELECTOR = '[data-action="add-motif-tag"]';
const CANCEL_MOTIF_TAGS_SELECTOR = '[data-action="cancel-motif-tags"]';
const EDIT_MOTIF_TAGS_SELECTOR = '[data-action="edit-motif-tags"]';
const SAVE_MOTIF_TAGS_SELECTOR = '[data-action="save-motif-tags"]';
const TRANSFER_TAG_INPUT_SELECTOR = '[data-action="transfer-motif-tag"]';
const REMOVE_TAG_INPUT_SELECTOR = '[data-action="remove-motif-tag"]';
const DYNAMIC_TEMPLATE_SELECTOR = '[data-motif-template]';

const ADD_TAG_INPUT_SELECTOR = '.motif-tagging__add-input input';
const EDITNG_CONTAINER_SELECTOR = '.motif-tagging__editing-container';

const IS_EDITING_CLASS = 'motif-tagging__editing-container--editing';

const motifTaggingTemplate = require('views/includes/motif-tagging');
const tagTemplate = require('views/includes/motif-tagging/tags');
const tagSuggestionsTemplate = require('views/includes/motif-tagging/tag-suggestions');
const saveButtonTemplate = require('views/includes/motif-tagging/save-buttons');

const templates = {
  'tags': tagTemplate,
  'tag-suggestions': tagSuggestionsTemplate,
  'save-buttons': saveButtonTemplate
};

(function($) {

  class MotifTaggingController {

    constructor($motifTagging) {
      this.$motifTagging = $motifTagging;
      this.templateSelectors = $motifTagging.find(DYNAMIC_TEMPLATE_SELECTOR);
      this.$editingContainer = $($motifTagging.find(EDITNG_CONTAINER_SELECTOR)[0]);

      this.state = {
        editing: false,
        saving: false,
        metadata: {}
      };
      // Register the listeners
      this.registerListeners();
    }

    render() {
      this.$editingContainer.toggleClass(IS_EDITING_CLASS, this.state.editing);

      this.templateSelectors.each((i, template) => {
        let $template = $(template);
        let name = $template.data('motif-template');
        let markup = $(templates[name](this.state));

        $template.html(markup.html());
      });
    }

    toggleEditing(editing) {
      // Fetch the current metadata when editing
      $.getJSON(location.pathname + '/json', (metadata) => {
        // Fetch and override the metadata
        this.state.metadata = metadata;
        // Mutate the state
        this.state.editing = editing;
        // Re-render the motif-tagging template
        this.render();
      });
    }

    edit() {
      this.toggleEditing(true);
    }

    cancel() {
      this.toggleEditing(false);
    }

    addTagFromInput() {
      const $input = this.$motifTagging.find(ADD_TAG_INPUT_SELECTOR);
      // Read the value of the input field
      const tag = $input.val();
      // Then clear it
      $input.val('');
      // Add it
      this.addTag(tag);
    }

    addTag(tag) {
      // Don't do anything with an undefined, null or empty tag
      if(!tag || tag === '') {
        return;
      }
      // Add it to the metadata
      helpers.motifTagging.addTag(this.state.metadata, tag);
      // Re-render the motif-tagging template
      this.render();
    }

    removeTag(tag) {
      // Remove it from the metadata
      helpers.motifTagging.removeTag(this.state.metadata, tag);
      helpers.motifTagging.removeVisionTag(this.state.metadata, tag);
      // Re-render the motif-tagging template
      this.render();
    }

    save() {
      // Don't save twice
      if(this.state.saving) {
        return;
      }
      // Add any tag which has not been added
      this.addTagFromInput();
      this.state.saving = true;
      this.render();
      const url = location.pathname + '/save-motif-tags';
      $.post(url, this.state.metadata, (metadata) => {
        // Once the metadata is back, override it and stop saving
        this.state.metadata = metadata;
        this.state.saving = false;
        this.state.editing = false;
        this.render();

        Snackbar.info('Gemt! Tak for dit bidrag!');
        contributionCounter.contributionAdded();
      }, 'json');
    }

    registerListeners() {
      // When clicking the edit button
      this.$motifTagging.on('click', EDIT_MOTIF_TAGS_SELECTOR, () => {
        this.edit();
      });
      // When clicking the edit button, motif-tagging template is re-rendered
      this.$motifTagging.on('click', ADD_MOTIF_TAG_SELECTOR, () => {
        this.addTagFromInput();
      });
      // When clicking the edit button, motif-tagging template is re-rendered
      this.$motifTagging.on('click', CANCEL_MOTIF_TAGS_SELECTOR, () => {
        this.cancel();
      });
      // When clicking the edit button, motif-tagging template is re-rendered
      this.$motifTagging.on('click', SAVE_MOTIF_TAGS_SELECTOR, () => {
        this.save();
      });
      // When hitting enter in the input field, we add the tag
      this.$motifTagging.on('keyup', ADD_TAG_INPUT_SELECTOR, (event) => {
        if (event.keyCode === 13) {
          this.addTagFromInput();
          // Put the focus back into the input
          this.$motifTagging.find(ADD_TAG_INPUT_SELECTOR).focus();
        }
      });
      // When clicking the thumbs up on a vision tag, add it
      this.$motifTagging.on('click', TRANSFER_TAG_INPUT_SELECTOR, (event) => {
        const tag = $(event.target).closest('[data-tag]').data('tag');
        this.addTag(tag);
        // Let's not make this a click on the actual tag
        event.preventDefault();
      });
      // When clicking the cross on a vision tag, remove it
      this.$motifTagging.on('click', REMOVE_TAG_INPUT_SELECTOR, (event) => {
        const tag = $(event.target).closest('[data-tag]').data('tag');
        this.removeTag(tag);
        // Let's not make this a click on the actual tag
        event.preventDefault();
      });
    }

    bindTypeahead($input) {
      // Construct a source for tags
      const tagsSource = new Bloodhound({
        datumTokenizer: function(tags) {
          return Bloodhound.tokenizers.whitespace(tags);
        },
        queryTokenizer: Bloodhound.tokenizers.whitespace,
        remote: {
          url: '/motif-tag-suggestions?text=%QUERY',
          wildcard: '%QUERY',
          cache: false
        }
      });

      // Initialize the source of tags
      tagsSource.initialize();

      // And bind it on the input field
      $input.typeahead({
        hint: false,
        highlight: true,
        minLength: 1
      }, {
        name: 'tags',
        source: tagsSource
      });

      // When the input is focussed, add a class to the parent
      $input.on('focus', function() {
        $input.parent('.twitter-typeahead').addClass('focused');
      });

      // When the input is empty and blurred, remove the class again
      $input.on('blur', function() {
        if($input.val() === '') {
          $input.parent('.twitter-typeahead').removeClass('focused');
        }
      });
    }
  }

  // Initialize motif tagging for every .motif-tagging element on the page
  $(() => {
    $('.motif-tagging').each((index, motifTagging) => {
      const $motifTagging = $(motifTagging);
      // Initialize a controller
      const controller = new MotifTaggingController($motifTagging);
    });
  });
})(jQuery);
<|endoftext|>


<commit_before>/*
Generates a div-based table from an array of strings.

Example:
  // Inserts a 2 row, 2 column table into the top of $('id')
  Tbl('className', [ 'A', 'B', 'C', 'D' ], '%', 50, 50).inject($('id'), 'top');

Requires the following CSS to be present:
  .row
    :overflow hidden
    .parent
      :float left
      :display block

*/
var Tbl = function() {
  args = $A(arguments);

  var cname  = args.shift();
  var data   = $A(args.shift());
  var unit   = args.shift();
  var widths = $type(args[0]) == 'array' ? args[0] : args;

  var container = new Element('div', {
    'class':   cname + ' row',
    styles : { width: widths.sum() + unit }
  });

  var margin;
  (data.length).times(function(x) {
    var col = x % widths.length;
    if (col == 0) {
      margin  = widths.sum();
      margin -= widths[col];
    }
    var first_last = (col == 0 ? ' first' : col == widths.length -1 ? ' last' : '');
    var div = new Element('div', {
      'class': cname + col + ' ' + cname + '_parent parent' + first_last,
      styles: {
        'margin-right': col == 0 ? margin + unit       : null,
        'margin-left' : col != 0 ? '-' + margin + unit : null,
        'clear'       : col == 0 ? 'left'              : null,
        'width'       : widths[col] + unit
      }
    });
    var child = new Element('div', {
      'class': cname + '_cell cell' + first_last
    });
    child.innerHTML = data[x] == '' ? ' ' : data[x];
    div.adopt(child);
    container.adopt(div);
    if (col != 0)
      margin -= widths[col];
  });

  container.adopt(new Element('div', {
    styles: { clear: 'both' }
  }));
  return container;
};

Array.prototype.sum = function(){
	for(var i=0, sum=0; i<this.length; sum+=this[i++]);
	return sum;
};<commit_msg>tbl.js clear fix (for IE)<commit_after>/*
Generates a div-based table from an array of strings.

Example:
  // Inserts a 2 row, 2 column table into the top of $('id')
  Tbl('className', [ 'A', 'B', 'C', 'D' ], '%', 50, 50).inject($('id'), 'top');

Requires the following CSS to be present:
  .row
    :overflow hidden
    .parent
      :float left
      :display block

*/
var Tbl = function() {
  args = $A(arguments);

  var cname  = args.shift();
  var data   = $A(args.shift());
  var unit   = args.shift();
  var widths = $type(args[0]) == 'array' ? args[0] : args;

  var container = new Element('div', {
    'class':   cname + ' row',
    styles : { width: widths.sum() + unit }
  });

  var margin;
  (data.length).times(function(x) {
    var col = x % widths.length;
    if (col == 0) {
      margin  = widths.sum();
      margin -= widths[col];
      if (x != 0)
        container.adopt(new Element('div', {
          styles: { clear: 'both' }
        }));
    }
    var first_last = (col == 0 ? ' first' : col == widths.length -1 ? ' last' : '');
    var div = new Element('div', {
      'class': cname + col + ' ' + cname + '_parent parent' + first_last,
      styles: {
        'margin-right': col == 0 ? margin + unit       : null,
        'margin-left' : col != 0 ? '-' + margin + unit : null,
        'clear'       : col == 0 ? 'left'              : null,
        'width'       : widths[col] + unit
      }
    });
    var child = new Element('div', {
      'class': cname + '_cell cell' + first_last
    });
    child.innerHTML = data[x] == '' ? ' ' : data[x];
    div.adopt(child);
    container.adopt(div);
    if (col != 0)
      margin -= widths[col];
  });
  container.adopt(new Element('div', {
    styles: { clear: 'both' }
  }));
  return container;
};

Array.prototype.sum = function(){
	for(var i=0, sum=0; i<this.length; sum+=this[i++]);
	return sum;
};<|endoftext|>


<commit_before>// This is called with the results from from FB.getLoginStatus().
  function statusChangeCallback(response) {
    console.log('statusChangeCallback');
    console.log(response);
    // The response object is returned with a status field that lets the
    // app know the current login status of the person.
    // Full docs on the response object can be found in the documentation
    // for FB.getLoginStatus().
    if (response.status === 'connected') {
      // Logged into your app and Facebook.
      FB.logout(function(response){
        alert('Logged out');
      });
    } else if (response.status === 'not_authorized') {
      // The person is logged into Facebook, but not your app.

    } else {
      // The person is not logged into Facebook, so we're not sure if
      // they are logged into this app or not.

    }
  }

  // This function is called when someone finishes with the Login
  // Button.  See the onlogin handler attached to it in the sample
  // code below.
  function checkLoginState() {
    FB.getLoginStatus(function(response) {
      statusChangeCallback(response);
    });
  }

  window.fbAsyncInit = function() {
  FB.init({
    appId      : '255439164661872',
    cookie     : true,  // enable cookies to allow the server to access
                        // the session
    xfbml      : true,  // parse social plugins on this page
    version    : 'v2.0' // use version 2.0
  });

  // Now that we've initialized the JavaScript SDK, we call
  // FB.getLoginStatus().  This function gets the state of the
  // person visiting this page and can return one of three states to
  // the callback you provide.  They can be:
  //
  // 1. Logged into your app ('connected')
  // 2. Logged into Facebook, but not your app ('not_authorized')
  // 3. Not logged into Facebook and can't tell if they are logged into
  //    your app or not.
  //
  // These three cases are handled in the callback function.

  FB.getLoginStatus(function(response) {
    statusChangeCallback(response);
  });

  };

  // Load the SDK asynchronously
  (function(d, s, id) {
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) return;
    js = d.createElement(s); js.id = id;
    js.src = "https://connect.facebook.net/en_US/sdk.js";
    fjs.parentNode.insertBefore(js, fjs);
  }(document, 'script', 'facebook-jssdk'));

  // Here we run a very simple test of the Graph API after login is
  // successful.  See statusChangeCallback() for when this call is made.
  function testAPI() {
    console.log('Welcome!  Fetching your information.... ');
    FB.api('/me', function(response) {
      console.log('Successful login for: ' + response.name);
      document.getElementById('status').innerHTML =
        'Thanks for logging in, ' + response.name + '!';
    });
  }

  function log_in(){
    FB.login(function(response) {
     console.log(response);
   }, {scope: 'user_friends', 'email'});
  }<commit_msg>Fix bug<commit_after>// This is called with the results from from FB.getLoginStatus().
  function statusChangeCallback(response) {
    console.log('statusChangeCallback');
    console.log(response);
    // The response object is returned with a status field that lets the
    // app know the current login status of the person.
    // Full docs on the response object can be found in the documentation
    // for FB.getLoginStatus().
    if (response.status === 'connected') {
      // Logged into your app and Facebook.
      FB.logout(function(response){
        alert('Logged out');
      });
    } else if (response.status === 'not_authorized') {
      // The person is logged into Facebook, but not your app.

    } else {
      // The person is not logged into Facebook, so we're not sure if
      // they are logged into this app or not.

    }
  }

  // This function is called when someone finishes with the Login
  // Button.  See the onlogin handler attached to it in the sample
  // code below.
  function checkLoginState() {
    FB.getLoginStatus(function(response) {
      statusChangeCallback(response);
    });
  }

  window.fbAsyncInit = function() {
  FB.init({
    appId      : '255439164661872',
    cookie     : true,  // enable cookies to allow the server to access
                        // the session
    xfbml      : true,  // parse social plugins on this page
    version    : 'v2.0' // use version 2.0
  });

  // Now that we've initialized the JavaScript SDK, we call
  // FB.getLoginStatus().  This function gets the state of the
  // person visiting this page and can return one of three states to
  // the callback you provide.  They can be:
  //
  // 1. Logged into your app ('connected')
  // 2. Logged into Facebook, but not your app ('not_authorized')
  // 3. Not logged into Facebook and can't tell if they are logged into
  //    your app or not.
  //
  // These three cases are handled in the callback function.

  FB.getLoginStatus(function(response) {
    statusChangeCallback(response);
  });

  };

  // Load the SDK asynchronously
  (function(d, s, id) {
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) return;
    js = d.createElement(s); js.id = id;
    js.src = "https://connect.facebook.net/en_US/sdk.js";
    fjs.parentNode.insertBefore(js, fjs);
  }(document, 'script', 'facebook-jssdk'));

  // Here we run a very simple test of the Graph API after login is
  // successful.  See statusChangeCallback() for when this call is made.
  function testAPI() {
    console.log('Welcome!  Fetching your information.... ');
    FB.api('/me', function(response) {
      console.log('Successful login for: ' + response.name);
      document.getElementById('status').innerHTML =
        'Thanks for logging in, ' + response.name + '!';
    });
  }

  function log_in(){
    FB.login(function(response) {
     console.log(response);
   }, {scope: 'user_friends'});
  }<|endoftext|>


<commit_before>angular.module("umbraco")
    .controller("Umbraco.PropertyEditors.RTEController",
    function ($rootScope, $element, $scope, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper) {

        tinyMceService.configuration().then(function(tinyMceConfig){

            //config value from general tinymce.config file
            var validElements = tinyMceConfig.validElements;
            var invalidElements = tinyMceConfig.inValidElements;
            var plugins = _.map(tinyMceConfig.plugins, function(plugin){
                                            if(plugin.useOnFrontend){
                                                return plugin.name;
                                            }
                                        }).join(" ");

            var editorConfig = $scope.model.config.editor;
            if(!editorConfig || angular.isString(editorConfig)){
                editorConfig = tinyMceService.defaultPrevalues();
            }

            //config value on the data type
            var toolbar = editorConfig.toolbar.join(" | ");

            assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope).then(function () {

                /** Loads in the editor */
                function loadTinyMce() {

                    //valid_elements: validElements,

                    //we need to add a timeout here, to force a redraw so TinyMCE can find
                    //the elements needed
                    $timeout(function () {
                        tinymce.DOM.events.domLoaded = true;
                        tinymce.init({
                            mode: "exact",
                            elements: $scope.model.alias + "_rte",
                            skin: "umbraco",
                            plugins: plugins,
                            menubar: false,
                            statusbar: false,
                            height: editorConfig.dimensions.height,
                            toolbar: toolbar,
                            setup: function (editor) {

                                //We need to listen on multiple things here because of the nature of tinymce, it doesn't
                                //fire events when you think!
                                //The change event doesn't fire when content changes, only when cursor points are changed and undo points
                                //are created. the blur event doesn't fire if you insert content into the editor with a button and then
                                //press save.
                                //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can
                                //listen to both change and blur and also on our own 'saving' event. I think this will be best because a
                                //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked
                                //save before the timeout elapsed.
                                editor.on('change', function (e) {
                                    angularHelper.safeApply($scope, function () {
                                        $scope.model.value = editor.getContent();
                                    });
                                });
                                editor.on('blur', function (e) {
                                    angularHelper.safeApply($scope, function () {
                                        $scope.model.value = editor.getContent();
                                    });
                                });
                                var unsubscribe = $scope.$on("formSubmitting", function () {
                                    $scope.model.value = editor.getContent();
                                });

                                //when the element is disposed we need to unsubscribe!
                                // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom
                                // element might still be there even after the modal has been hidden.
                                $element.bind('$destroy', function () {
                                    unsubscribe();
                                });

                                //Create the insert media plugin
                                tinyMceService.createMediaPicker(editor, $scope);

                                //Create the embedded plugin
                                tinyMceService.createInsertEmbeddedMedia(editor, $scope);

                                //Create the insert link plugin
                                tinyMceService.createLinkPicker(editor, $scope);

                                //Create the insert macro plugin
                                tinyMceService.createInsertMacro(editor, $scope);
                            }
                        });
                    }, 1);
                }

                loadTinyMce();

                //here we declare a special method which will be called whenever the value has changed from the server
                //this is instead of doing a watch on the model.value = faster
                $scope.model.onValueChanged = function (newVal, oldVal) {
                    //update the display val again if it has changed from the server
                    loadTinyMce();
                };
            });
        });

    });<commit_msg>Fixes: U4-3093 RTE doesnt pre-render macros<commit_after>angular.module("umbraco")
    .controller("Umbraco.PropertyEditors.RTEController",
    function ($rootScope, $element, $scope, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper) {

        tinyMceService.configuration().then(function(tinyMceConfig){

            //config value from general tinymce.config file
            var validElements = tinyMceConfig.validElements;

            //These are absolutely required in order for the macros to render inline
            //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce
            var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style]";

            var invalidElements = tinyMceConfig.inValidElements;
            var plugins = _.map(tinyMceConfig.plugins, function(plugin){
                                            if(plugin.useOnFrontend){
                                                return plugin.name;
                                            }
                                        }).join(" ");

            var editorConfig = $scope.model.config.editor;
            if(!editorConfig || angular.isString(editorConfig)){
                editorConfig = tinyMceService.defaultPrevalues();
            }

            //config value on the data type
            var toolbar = editorConfig.toolbar.join(" | ");

            assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope).then(function () {

                /** Loads in the editor */
                function loadTinyMce() {

                    //we need to add a timeout here, to force a redraw so TinyMCE can find
                    //the elements needed
                    $timeout(function () {
                        tinymce.DOM.events.domLoaded = true;
                        tinymce.init({
                            mode: "exact",
                            elements: $scope.model.alias + "_rte",
                            skin: "umbraco",
                            plugins: plugins,
                            valid_elements: validElements,
                            invalid_elements: invalidElements,
                            extended_valid_elements: extendedValidElements,
                            menubar: false,
                            statusbar: false,
                            height: editorConfig.dimensions.height,
                            toolbar: toolbar,
                            setup: function (editor) {

                                //We need to listen on multiple things here because of the nature of tinymce, it doesn't
                                //fire events when you think!
                                //The change event doesn't fire when content changes, only when cursor points are changed and undo points
                                //are created. the blur event doesn't fire if you insert content into the editor with a button and then
                                //press save.
                                //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can
                                //listen to both change and blur and also on our own 'saving' event. I think this will be best because a
                                //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked
                                //save before the timeout elapsed.
                                editor.on('change', function (e) {
                                    angularHelper.safeApply($scope, function () {
                                        $scope.model.value = editor.getContent();
                                    });
                                });
                                editor.on('blur', function (e) {
                                    angularHelper.safeApply($scope, function () {
                                        $scope.model.value = editor.getContent();
                                    });
                                });
                                var unsubscribe = $scope.$on("formSubmitting", function () {
                                    $scope.model.value = editor.getContent();
                                });

                                //when the element is disposed we need to unsubscribe!
                                // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom
                                // element might still be there even after the modal has been hidden.
                                $element.bind('$destroy', function () {
                                    unsubscribe();
                                });

                                //Create the insert media plugin
                                tinyMceService.createMediaPicker(editor, $scope);

                                //Create the embedded plugin
                                tinyMceService.createInsertEmbeddedMedia(editor, $scope);

                                //Create the insert link plugin
                                tinyMceService.createLinkPicker(editor, $scope);

                                //Create the insert macro plugin
                                tinyMceService.createInsertMacro(editor, $scope);
                            }
                        });
                    }, 1);
                }

                loadTinyMce();

                //here we declare a special method which will be called whenever the value has changed from the server
                //this is instead of doing a watch on the model.value = faster
                $scope.model.onValueChanged = function (newVal, oldVal) {
                    //update the display val again if it has changed from the server
                    loadTinyMce();
                };
            });
        });

    });<|endoftext|>