<commit_before>/**
 * Copyright 2014 Ian Davies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
define(["../../honest/responsive_video", "/partials/content/QuestionTabs.html"], function(rv, templateUrl) {
	return ["$location", "$filter", "$state", "api", "questionActions", "QUESTION_TYPES", function($location, $filter, $state, api, questionActions, QUESTION_TYPES) {
		return {
			restrict: 'A',
			scope: true,
			templateUrl: templateUrl,
			link: function(scope, _element, _attrs, _ctrls, _transclude) {
				if (scope.accordionChildMetrics) {
					scope.accordionChildMetrics.questionCount++;
				}

				let emptyListIfUndefined = function(originalResult) {
					let result = originalResult ? originalResult : [];
					return result;
				};

				// An object to hold a load of state for this particular question. Keep it together like this
				// so that child scopes can read/write values safely without shadowing.
				scope.question = {
					validationResponse: null,
					selectedChoice: null,
					gameBoardCompletedPassed: false,
					gameBoardCompletedPerfect: false,
					pageCompleted: false,
					id: scope.doc.id,
					type: scope.doc.type,
					relatedConcepts: emptyListIfUndefined($filter('filter')(scope.doc.relatedContent, {type: "isaacConceptPage"})),
					relatedUnansweredEasierQuestions: emptyListIfUndefined($filter('filter')(scope.doc.relatedContent, function(relatedContent){
						let isQuestionPage = ["isaacQuestionPage", "isaacFastTrackQuestionPage"].indexOf(relatedContent.type) >= 0;
						let isEasier = relatedContent.level < scope.page.level;
						let isUnanswered = !relatedContent.correct;
						return isQuestionPage && isEasier && isUnanswered;
					})),
					relatedUnansweredSupportingQuestions: emptyListIfUndefined($filter('filter')(scope.doc.relatedContent, function(relatedContent){
						let isQuestionPage = ["isaacQuestionPage", "isaacFastTrackQuestionPage"].indexOf(relatedContent.type) >=0;
						let isEqualOrHarder = relatedContent.level >= scope.page.level;
						let isUnanswered = !relatedContent.correct;
						return isQuestionPage && isEqualOrHarder && isUnanswered;
					}))
				};
				if (scope.doc.bestAttempt) {
					scope.question.validationResponse = scope.doc.bestAttempt;
					scope.question.selectedChoice = scope.question.validationResponse.answer;
				}

				scope.activateTab = function(i) {
					scope.activeTab = i;
					rv.updateAll();

					if (i > -1) {
						api.logger.log({
							type : "VIEW_HINT",
							questionId : scope.doc.id,
							hintIndex : i,
						})
					}
				}

				let checkGamebaordProgress = function() {
					let initialGameBoardPercent = scope.gameBoard.percentageCompleted;
					let gameBoardCompletedPassed =  true;
					let gameBoardCompletedPerfect =  true;

					// Re-load the game board to check for updated progress
					api.gameBoards.get({id: scope.gameBoard.id}).$promise.then(function(board) {
						scope.question.gameBoardPercentComplete = board.percentageCompleted;

						//We want to know if they have (a) completed the gameboard, (b) passed the gameboard
						for(let i = 0; i < board.questions.length; i++){
							// page progress
							if (board.questions[i].state != "PERFECT"){
								gameBoardCompletedPerfect = false;
							}
							if (board.questions[i].state != "PASSED" && board.questions[i].state != "PERFECT"){
								gameBoardCompletedPassed = false;
							}
						}
						// If things have changed, and the answer is correct, show the modal
						if ((gameBoardCompletedPassed != !!scope.question.gameBoardCompletedPassed ||
							gameBoardCompletedPerfect != !!scope.question.gameBoardCompletedPerfect ||
							initialGameBoardPercent < board.percentageCompleted) && r.correct) {
							scope.question.gameBoardCompletedPassed = gameBoardCompletedPassed;
							scope.question.gameBoardCompletedPerfect = gameBoardCompletedPerfect;
							scope.$emit('gameBoardCompletedPassed', scope.question.gameBoardCompletedPassed);
							scope.$emit('gameBoardCompletedPerfect', scope.question.gameBoardCompletedPerfect);

							if(!scope.modalPassedDisplayed && scope.question.gameBoardCompletedPassed) {
								scope.modals["congrats"].show();
								scope.$emit("modalPassedDisplayed", true);
							}

							if(!scope.modalPerfectDisplayed && scope.question.gameBoardCompletedPerfect) {
								scope.modals["congrats"].show();
								scope.$emit("modalPerfectDisplayed", true);
							}
						}

						// if(board.percentageCompleted == '100' && !scope.modalDisplayed && validationResponse.correct) {
						//     scope.modals["congrats"].show();
						//     scope.$emit("modalCompleteDisplayed", true);
						// }
						// NOTE: We can't just rely on percentageCompleted as it gives us 100% when there is one
						// question for a gameboard and the question has been passed, not completed. See issue #419
					});
				};
				void checkGamebaordProgress;
				// FIXME ^ This looks too complicated for something we can just delete. Better check before nuking it.

				let applyValidationResponseToQuestionPart = function(content, validationResponse) {
					if (QUESTION_TYPES.indexOf(content.type) >= 0 && content.id == validationResponse.questionId &&	content.bestAttempt != true) {
						content.bestAttempt = validationResponse;
					}
					if (content.children) {
						for (let i=0; i < content.children.length; i++) {
							let child = content.children[i];
							applyValidationResponseToQuestionPart(child, validationResponse);
						}
					}
				};

				let isPageCompleted = function(questionPage) {
					let hasIncorrectOrUnansweredQuestion = function(content) {
						let foundIncorrectQuestionPart = false;
						if (QUESTION_TYPES.indexOf(content.type) >= 0) {
							if (!content.bestAttempt || !content.bestAttempt.correct) {
								foundIncorrectQuestionPart = true;
							}
						}
						if (content.children) {
							for (let i=0; i < content.children.length; i++) {
								let child = content.children[i];
								foundIncorrectQuestionPart |= hasIncorrectOrUnansweredQuestion(child);
							}
						}
						return foundIncorrectQuestionPart
					}
					let pageCompleted = !hasIncorrectOrUnansweredQuestion(questionPage);
					return pageCompleted;
				}

				let determineFastTrackPrimaryAction = function(questionPart, questionPage, questionHistory, gameboardId) {
					let questionPartAnsweredCorrectly = questionPart.validationResponse && questionPart.validationResponse.correct;
					if (questionPartAnsweredCorrectly) {
						if (questionPart.pageCompleted) {
							if (questionHistory.length) {
								return questionActions.retryPreviousQuestion(questionHistory, gameboardId);
							} else {
								if (gameboardId  && !questionPart.gameBoardCompletedPerfect) {
									return questionActions.backToBoard(gameboardId);
								} else {
									return null; // Gameboard completed
								}
							}
						} else {
							return null; // questionActions.goToNextQuestionPart();
						}
					} else  {
						return questionActions.checkMyAnswer(scope, api);
					}
				}

				let determineFastTrackSecondaryAction = function(questionPart, questionPage, questionHistory, gameboardId) {
					let questionPartNotAnsweredCorrectly = !(questionPart.validationResponse && questionPart.validationResponse.correct);
					if (questionPartNotAnsweredCorrectly && questionPart.relatedUnansweredEasierQuestions.length) {
						let easierQuestion = questionPart.relatedUnansweredEasierQuestions[0];
						return questionActions.tryEasierQuestion(easierQuestion, questionPage.id, questionPart.pageCompleted, questionHistory, gameboardId);
					} else if (questionPart.relatedUnansweredSupportingQuestions.length) {
						let supportingQuestion = questionPart.relatedUnansweredSupportingQuestions[0];
						return questionActions.trySupportingQuestion(supportingQuestion, questionPage.id, questionPart.pageCompleted, questionHistory, gameboardId);
					} else if (questionPart.relatedConcepts.length) {
						let relatedConcept = questionPart.relatedConcepts[0];
						return questionActions.showRelatedConceptPage(relatedConcept);
					} else {
						return null;
					}
				}

				let determinePrimaryAction = function(validationResponse) {
					let action = null;
					if (!validationResponse || !validationResponse.correct) {
						action = questionActions.checkMyAnswer(scope, api);
					}
					return action;
				}

				let determineActions = function() {
					if (scope.page.type != 'isaacFastTrackQuestionPage') {
						scope.primaryAction = determinePrimaryAction(scope.question.validationResponse);
					} else {
						scope.primaryAction = determineFastTrackPrimaryAction(scope.question, scope.page, scope.questionHistory.slice(), scope.gameboardId);
						scope.secondaryAction = determineFastTrackSecondaryAction(scope.question, scope.page, scope.questionHistory.slice(), scope.gameboardId);
					}
				}

				scope.$watch("question.selectedChoice", function(newVal, oldVal) {
					// (Show some help text. Quietly though!)
					scope.hlp = newVal && newVal.value && newVal.value.toLowerCase().match('(^h[ae]lp|"help").*');

					if (newVal === oldVal)
						return; // Init

					scope.canSubmit = true;
					scope.question.validationResponse = null;
				}, true);

				scope.$on("ensureVisible", function(e) {
					if (e.targetScope == scope)
						return;

					e.stopPropagation();

					let i = e.targetScope.questionTabIndex;

					scope.activateTab(i);

					scope.$emit("ensureVisible");
				});

				scope.$watchGroup(["canSubmit", "question.selectedChoice"], function(newVal, oldVal) {
					if (newVal !== oldVal) {
						determineActions();
					}
				});

				scope.$watch("question.validationResponse", function(newVal, oldVal) {
					if (newVal !== oldVal) {
						if (scope.question.validationResponse) {
							applyValidationResponseToQuestionPart(scope.page, scope.question.validationResponse);
							scope.question.pageCompleted = isPageCompleted(scope.page);
							if (scope.question.pageCompleted) {
								scope.$emit('pageCompleted');
							}
							determineActions();
						}
					}
				});

				scope.$watch("question.pageCompleted", function(newVal, oldVal) {
					if (scope.gameboard && newVal !== oldVal) {
						// checkGameboardProgress();
					}
				});

				scope.incorrectSigFigs = function(validationResponse) {
					let explanationPresent = validationResponse && validationResponse.explanation;
					return explanationPresent && validationResponse.explanation.tags && validationResponse.explanation.tags.indexOf('sig_figs') >= 0;
				}

				scope.question.pageCompleted = isPageCompleted(scope.page);
				scope.canSubmit = false; // A flag to prevent someone clicking submit multiple times without changing their answer.
				scope.activateTab(-1); // Activate "Answer now" tab by default.
				determineActions();
			}
		};
	}];
});
<commit_msg>Possible fix for an issue with custom feedback getting merged weirdly and breaking rendering code.<commit_after>/**
 * Copyright 2014 Ian Davies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
define(["../../honest/responsive_video", "/partials/content/QuestionTabs.html"], function(rv, templateUrl) {
	return ["$location", "$filter", "$state", "api", "questionActions", "QUESTION_TYPES", function($location, $filter, $state, api, questionActions, QUESTION_TYPES) {
		return {
			restrict: 'A',
			scope: true,
			templateUrl: templateUrl,
			link: function(scope, _element, _attrs, _ctrls, _transclude) {
				if (scope.accordionChildMetrics) {
					scope.accordionChildMetrics.questionCount++;
				}

				let emptyListIfUndefined = function(originalResult) {
					let result = originalResult ? originalResult : [];
					return result;
				};

				// An object to hold a load of state for this particular question. Keep it together like this
				// so that child scopes can read/write values safely without shadowing.
				scope.question = {
					validationResponse: null,
					selectedChoice: null,
					gameBoardCompletedPassed: false,
					gameBoardCompletedPerfect: false,
					pageCompleted: false,
					id: scope.doc.id,
					type: scope.doc.type,
					relatedConcepts: emptyListIfUndefined($filter('filter')(scope.doc.relatedContent, {type: "isaacConceptPage"})),
					relatedUnansweredEasierQuestions: emptyListIfUndefined($filter('filter')(scope.doc.relatedContent, function(relatedContent){
						let isQuestionPage = ["isaacQuestionPage", "isaacFastTrackQuestionPage"].indexOf(relatedContent.type) >= 0;
						let isEasier = relatedContent.level < scope.page.level;
						let isUnanswered = !relatedContent.correct;
						return isQuestionPage && isEasier && isUnanswered;
					})),
					relatedUnansweredSupportingQuestions: emptyListIfUndefined($filter('filter')(scope.doc.relatedContent, function(relatedContent){
						let isQuestionPage = ["isaacQuestionPage", "isaacFastTrackQuestionPage"].indexOf(relatedContent.type) >=0;
						let isEqualOrHarder = relatedContent.level >= scope.page.level;
						let isUnanswered = !relatedContent.correct;
						return isQuestionPage && isEqualOrHarder && isUnanswered;
					}))
				};
				if (scope.doc.bestAttempt) {
					scope.question.validationResponse = scope.doc.bestAttempt;
					scope.question.selectedChoice = scope.question.validationResponse.answer;
				}

				scope.activateTab = function(i) {
					scope.activeTab = i;
					rv.updateAll();

					if (i > -1) {
						api.logger.log({
							type : "VIEW_HINT",
							questionId : scope.doc.id,
							hintIndex : i,
						})
					}
				}

				let checkGamebaordProgress = function() {
					let initialGameBoardPercent = scope.gameBoard.percentageCompleted;
					let gameBoardCompletedPassed =  true;
					let gameBoardCompletedPerfect =  true;

					// Re-load the game board to check for updated progress
					api.gameBoards.get({id: scope.gameBoard.id}).$promise.then(function(board) {
						scope.question.gameBoardPercentComplete = board.percentageCompleted;

						//We want to know if they have (a) completed the gameboard, (b) passed the gameboard
						for(let i = 0; i < board.questions.length; i++){
							// page progress
							if (board.questions[i].state != "PERFECT"){
								gameBoardCompletedPerfect = false;
							}
							if (board.questions[i].state != "PASSED" && board.questions[i].state != "PERFECT"){
								gameBoardCompletedPassed = false;
							}
						}
						// If things have changed, and the answer is correct, show the modal
						if ((gameBoardCompletedPassed != !!scope.question.gameBoardCompletedPassed ||
							gameBoardCompletedPerfect != !!scope.question.gameBoardCompletedPerfect ||
							initialGameBoardPercent < board.percentageCompleted) && r.correct) {
							scope.question.gameBoardCompletedPassed = gameBoardCompletedPassed;
							scope.question.gameBoardCompletedPerfect = gameBoardCompletedPerfect;
							scope.$emit('gameBoardCompletedPassed', scope.question.gameBoardCompletedPassed);
							scope.$emit('gameBoardCompletedPerfect', scope.question.gameBoardCompletedPerfect);

							if(!scope.modalPassedDisplayed && scope.question.gameBoardCompletedPassed) {
								scope.modals["congrats"].show();
								scope.$emit("modalPassedDisplayed", true);
							}

							if(!scope.modalPerfectDisplayed && scope.question.gameBoardCompletedPerfect) {
								scope.modals["congrats"].show();
								scope.$emit("modalPerfectDisplayed", true);
							}
						}

						// if(board.percentageCompleted == '100' && !scope.modalDisplayed && validationResponse.correct) {
						//     scope.modals["congrats"].show();
						//     scope.$emit("modalCompleteDisplayed", true);
						// }
						// NOTE: We can't just rely on percentageCompleted as it gives us 100% when there is one
						// question for a gameboard and the question has been passed, not completed. See issue #419
					});
				};
				void checkGamebaordProgress;
				// FIXME ^ This looks too complicated for something we can just delete. Better check before nuking it.

				let applyValidationResponseToQuestionPart = function(content, validationResponse) {
					if (QUESTION_TYPES.indexOf(content.type) >= 0 && content.id == validationResponse.questionId &&	content.bestAttempt != true) {
						content.bestAttempt = validationResponse;
					}
					if (content.children) {
						for (let i=0; i < content.children.length; i++) {
							let child = content.children[i];
							applyValidationResponseToQuestionPart(child, validationResponse);
						}
					}
				};

				let isPageCompleted = function(questionPage) {
					let hasIncorrectOrUnansweredQuestion = function(content) {
						let foundIncorrectQuestionPart = false;
						if (QUESTION_TYPES.indexOf(content.type) >= 0) {
							if (!content.bestAttempt || !content.bestAttempt.correct) {
								foundIncorrectQuestionPart = true;
							}
						}
						if (content.children) {
							for (let i=0; i < content.children.length; i++) {
								let child = content.children[i];
								foundIncorrectQuestionPart |= hasIncorrectOrUnansweredQuestion(child);
							}
						}
						return foundIncorrectQuestionPart
					}
					let pageCompleted = !hasIncorrectOrUnansweredQuestion(questionPage);
					return pageCompleted;
				}

				let determineFastTrackPrimaryAction = function(questionPart, questionPage, questionHistory, gameboardId) {
					let questionPartAnsweredCorrectly = questionPart.validationResponse && questionPart.validationResponse.correct;
					if (questionPartAnsweredCorrectly) {
						if (questionPart.pageCompleted) {
							if (questionHistory.length) {
								return questionActions.retryPreviousQuestion(questionHistory, gameboardId);
							} else {
								if (gameboardId  && !questionPart.gameBoardCompletedPerfect) {
									return questionActions.backToBoard(gameboardId);
								} else {
									return null; // Gameboard completed
								}
							}
						} else {
							return null; // questionActions.goToNextQuestionPart();
						}
					} else  {
						return questionActions.checkMyAnswer(scope, api);
					}
				}

				let determineFastTrackSecondaryAction = function(questionPart, questionPage, questionHistory, gameboardId) {
					let questionPartNotAnsweredCorrectly = !(questionPart.validationResponse && questionPart.validationResponse.correct);
					if (questionPartNotAnsweredCorrectly && questionPart.relatedUnansweredEasierQuestions.length) {
						let easierQuestion = questionPart.relatedUnansweredEasierQuestions[0];
						return questionActions.tryEasierQuestion(easierQuestion, questionPage.id, questionPart.pageCompleted, questionHistory, gameboardId);
					} else if (questionPart.relatedUnansweredSupportingQuestions.length) {
						let supportingQuestion = questionPart.relatedUnansweredSupportingQuestions[0];
						return questionActions.trySupportingQuestion(supportingQuestion, questionPage.id, questionPart.pageCompleted, questionHistory, gameboardId);
					} else if (questionPart.relatedConcepts.length) {
						let relatedConcept = questionPart.relatedConcepts[0];
						return questionActions.showRelatedConceptPage(relatedConcept);
					} else {
						return null;
					}
				}

				let determinePrimaryAction = function(validationResponse) {
					let action = null;
					if (!validationResponse || !validationResponse.correct) {
						action = questionActions.checkMyAnswer(scope, api);
					}
					return action;
				}

				let determineActions = function() {
					if (scope.page.type != 'isaacFastTrackQuestionPage') {
						scope.primaryAction = determinePrimaryAction(scope.question.validationResponse);
					} else {
						scope.primaryAction = determineFastTrackPrimaryAction(scope.question, scope.page, scope.questionHistory.slice(), scope.gameboardId);
						scope.secondaryAction = determineFastTrackSecondaryAction(scope.question, scope.page, scope.questionHistory.slice(), scope.gameboardId);
					}
				}

				scope.$watch("question.selectedChoice", function(newVal, oldVal) {
					// (Show some help text. Quietly though!)
					scope.hlp = newVal && newVal.value && newVal.value.toLowerCase().match('(^h[ae]lp|"help").*');

					if (newVal === oldVal)
						return; // Init

					scope.canSubmit = true;
					scope.question.validationResponse = null;
				}, true);

				scope.$on("ensureVisible", function(e) {
					if (e.targetScope == scope)
						return;

					e.stopPropagation();

					let i = e.targetScope.questionTabIndex;

					scope.activateTab(i);

					scope.$emit("ensureVisible");
				});

				scope.$watchGroup(["canSubmit", "question.selectedChoice"], function(newVal, oldVal) {
					if (newVal !== oldVal) {
						determineActions();
					}
				});

				scope.$watch("question.validationResponse", function(newVal, oldVal) {
					if (newVal !== oldVal) {
						if (scope.question.validationResponse) {
							scope.question.validationResponse.explanation = scope.question.validationResponse.explanation || null;
							applyValidationResponseToQuestionPart(scope.page, scope.question.validationResponse);
							scope.question.pageCompleted = isPageCompleted(scope.page);
							if (scope.question.pageCompleted) {
								scope.$emit('pageCompleted');
							}
							determineActions();
						}
					}
				});

				scope.$watch("question.pageCompleted", function(newVal, oldVal) {
					if (scope.gameboard && newVal !== oldVal) {
						// checkGameboardProgress();
					}
				});

				scope.incorrectSigFigs = function(validationResponse) {
					let explanationPresent = validationResponse && validationResponse.explanation;
					return explanationPresent && validationResponse.explanation.tags && validationResponse.explanation.tags.indexOf('sig_figs') >= 0;
				}

				scope.question.pageCompleted = isPageCompleted(scope.page);
				scope.canSubmit = false; // A flag to prevent someone clicking submit multiple times without changing their answer.
				scope.activateTab(-1); // Activate "Answer now" tab by default.
				determineActions();
			}
		};
	}];
});
<|endoftext|>


<commit_before>/* @flow */
import {hasCommandModifier} from 'draft-js/lib/KeyBindingUtil';

import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {EditorState, Entity, RichUtils} from 'draft-js';
import {ENTITY_TYPE} from 'draft-js-utils';
import DefaultToolbarConfig from './EditorToolbarConfig';
import StyleButton from './StyleButton';
import PopoverIconButton from '../ui/PopoverIconButton';
import ButtonGroup from '../ui/ButtonGroup';
import Dropdown from '../ui/Dropdown';
import IconButton from '../ui/IconButton';
import getEntityAtCursor from './getEntityAtCursor';
import clearEntityForRange from './clearEntityForRange';
import autobind from 'class-autobind';
import cx from 'classnames';

import styles from './EditorToolbar.css';

import type EventEmitter from 'events';
import type {ToolbarConfig} from './EditorToolbarConfig';

type ChangeHandler = (state: EditorState) => any;

type Props = {
  className?: string;
  editorState: EditorState;
  keyEmitter: EventEmitter;
  onChange: ChangeHandler;
  focusEditor: Function;
  toolbarConfig: ToolbarConfig;
};

type State = {
  showLinkInput: boolean;
};

export default class EditorToolbar extends Component {
  props: Props;
  state: State;

  constructor() {
    super(...arguments);
    autobind(this);
    this.state = {
      showLinkInput: false,
    };
  }

  componentWillMount() {
    // Technically, we should also attach/detach event listeners when the
    // `keyEmitter` prop changes.
    this.props.keyEmitter.on('keypress', this._onKeypress);
  }

  componentWillUnmount() {
    this.props.keyEmitter.removeListener('keypress', this._onKeypress);
  }

  render() {
    let {className, toolbarConfig} = this.props;
    if (toolbarConfig == null) {
      toolbarConfig = DefaultToolbarConfig;
    }
    let display = toolbarConfig.display || DefaultToolbarConfig.display;
    let buttonsGroups = display.map((groupName) => {
      switch (groupName) {
        case 'INLINE_STYLE_BUTTONS': {
          return this._renderInlineStyleButtons(toolbarConfig);
        }
        case 'BLOCK_TYPE_BUTTONS': {
          return this._renderBlockTypeButtons(toolbarConfig);
        }
        case 'LINK_BUTTONS': {
          return this._renderLinkButtons();
        }
        case 'BLOCK_TYPE_DROPDOWN': {
          return this._renderBlockTypeDropdown(toolbarConfig);
        }
        case 'HISTORY_BUTTONS': {
          return this._renderUndoRedo();
        }
      }
    });
    return (

        {buttonsGroups}

    );
  }

  _renderBlockTypeDropdown(toolbarConfig: ToolbarConfig) {
    let blockType = this._getCurrentBlockType();
    let choices = new Map(
      (toolbarConfig.BLOCK_TYPE_DROPDOWN || []).map((type) => [type.style, {label: type.label, className: type.className}])
    );
    if (!choices.has(blockType)) {
      blockType = Array.from(choices.keys())[0];
    }
    return (
      <ButtonGroup>
        <Dropdown
          choices={choices}
          selectedKey={blockType}
          onChange={this._selectBlockType}
        />
      </ButtonGroup>
    );
  }

  _renderBlockTypeButtons(toolbarConfig: ToolbarConfig) {
    let blockType = this._getCurrentBlockType();
    let buttons = (toolbarConfig.BLOCK_TYPE_BUTTONS || []).map((type, index) => (


<commit_before>import _ from 'underscore'
import $ from 'jquery'
import moment from 'moment';
import {CompositeView, ItemView} from 'backbone.marionette'
import {Model} from 'backbone'
import {localStorage} from '../../../utils'
import app from '../../../application'
import {props} from '../../decorators'

let autoplay = false;

class PlaylistItem extends ItemView {

    @props({
        className: 'playlist-item js-playlist-item',

        template: require('../templates/playlistItem.ejs'),

        ui: {
            link: '.js-link'
        },

        bindings: {
            ':el': {
                classes: {
                    'watched': '_watched'
                }
            }
        },

        triggers: {
            'click @ui.link': 'link:clicked'
        }
    })

    initialize() {
        const videoId = this.model.get('videoId');
        const watched = !!localStorage.get(`${videoId}.info`, 'watched');

        if (watched) {
            this.model.set('_watched', true);
        }
    }

    onRender() {
        this.$el.attr('data-videoid', this.model.get('videoId'));

        this.stickit();
    }
}

class PlaylistItems extends CompositeView {

    @props({
        className: 'layout-playlists-items row',

        model: new Model({
            _search: '',
            _searchDate: null,
            videoId: null,
            _loading: false
        }),

        ui: {
            search: '.js-search',
            datepicker: '.js-datepicker',
            loader: '.js-loader'
        },

        childView: PlaylistItem,

        childViewContainer: '.js-playlist-items',

        childEvents: {
            'link:clicked': '_onClickLink'
        },

        template: require('../templates/playlistItems.ejs')
    })

    set videoId(val) {
        this.model.set('videoId', val);
    }

    set loading(val) {
        this.model.set('_loading', val);
    }

    /**
     * @returns {{search: String}}
     */
    get playlistFilter() {
        return {
            search: this.model.get('_search'),
            date: this.model.get('_searchDate')
        }
    }

    bindings() {
        return {
            '.js-video-container': {
                observe: 'videoId',
                update: _.debounce(this._videoPlay, 500)
            },

            '@ui.search': '_search',

            '@ui.datepicker': {
                observe: '_searchDate',
                onSet: (val) => {
                    return val ? moment(val, 'DD.MM.YYYY') : null;
                }
            },

            '@ui.loader': {
                classes: {
                    show: '_loading'
                }
            }
        }
    }

    modelEvents() {
        return {
            'change:videoId': () => {
                this._highlightVideo();
                this._routeToVideo();
            },

            'change:_search change:_searchDate': _.debounce(() => {
                this._searchCollection();
                this._highlightVideo();
            }, 500)
        }
    }

    initialize() {
        this._playerInterval = 0;
    }

    onRender() {
        this.ui.datepicker.datepicker({
            format: 'dd.mm.yyyy',
            autoclose: true,
            weekStart: 1
        });

        this.listenTo(app.channel, 'resize', _.debounce(this._onResize, 100));

        this.stickit();
    }

    _searchCollection() {
        const filter = this.playlistFilter;

        this.collection.search(filter);
    }

    _onClickLink(playlistItem) {
        this.videoId = playlistItem.ui.link.data('videoid');
    }

    _highlightVideo() {
        this.$childViewContainer.find('.js-playlist-item').removeClass('active');

        const $videoId = this.$childViewContainer.find('[data-videoid="' + this.model.get('videoId') + '"]');

        $videoId.addClass('active');

        // Scroll
        this.$childViewContainer
            .animate({
                scrollTop: ($videoId.index() - 1) * 65
            }, 250);
    }

    _routeToVideo(replaceState = false) {
        const currentPlaylistItem = this.collection.getCurrentPlaylistItem(this.model.get('videoId'));

        app.navigate(
            `playlists/playlist/${currentPlaylistItem.get('playlistId')}/video/${currentPlaylistItem.get('videoId')}`,
            { replace: replaceState }
        );
    }

    _videoPlay() {
        clearInterval(this._playerInterval);

        const videoId = this.model.get('videoId');

        if (videoId) {
            if (this._player && this._player.loadVideoById) {
                this._player.loadVideoById(videoId, 0);
            } else {
                $('#yt-video-container').replaceWith('

');

                this._player = new YT.Player('yt-video-container', {
                    width: 200,
                    height: 200,
                    videoId: videoId,
                    events: {
                        'onReady': this._onVideoReady.bind(this),
                        'onStateChange': this._onVideoStateChange.bind(this)
                    }
                });

                this._videoSetSize();
            }
        }
    }

    _videoBeforePlay() {
        const videoId = this.model.get('videoId');

        // currentTime
        const videoInfo = localStorage.get(`${videoId}.info`);

        if (videoInfo && videoInfo.currentTime) {
            this._player.seekTo(videoInfo.currentTime);
        }
    }

    _videoPlaying() {
        const videoId = this.model.get('videoId');

        // Store player-status
        clearInterval(this._playerInterval);

        const updateCurrentTime = () => {
            const currentTime = Math.round(this._player.getCurrentTime());

            localStorage.update(`${videoId}.info`, { currentTime });
        };

        updateCurrentTime();
        this._playerInterval = setInterval(updateCurrentTime, 8000);
    }

    _videoEnded() {
        const videoId = this.model.get('videoId');

        // Mark as watched
        this.collection.getCurrentPlaylistItem(videoId).set('_watched', true);
        localStorage.update(`${videoId}.info`, { watched: true, currentTime: 0 });

        // Play next
        const nextPlaylistItem = this.collection.getNextPlaylistItem(videoId);

        if (nextPlaylistItem) {
            const nextVideoId = nextPlaylistItem.get('videoId');

            // Reset currentTime
            localStorage.update(`${nextVideoId}.info`, { currentTime: 0 });

            // Set new videoId
            this.videoId = nextVideoId;

            // Start video automatically
            autoplay = true;
        }
    }

    _onVideoReady(e) {
        if (_.isNull(e.data)) {
            // autoplay
            if (autoplay) {
                this._player.playVideo();

                autoplay = false;
            }

            this._onResize();
        }
    }

    _videoSetSize() {
        if (this._player) {
            let width  = 640;
            let height = width * 0.51;

            if (window.innerWidth <= 768) {
                width  = '100%';
                height = window.innerWidth * 0.51;
            }

            this._player.setSize(width, height);
        }
    }

    _onVideoStateChange(e) {
        const videoId = this.model.get('videoId');

        switch (e.data) {
            case YT.PlayerState.UNSTARTED:
                this._videoBeforePlay();
                break;
            case YT.PlayerState.PLAYING:
                this._videoPlaying();
                break;
            case YT.PlayerState.ENDED:
                this._videoEnded();
                break;
        }
    }

    _onResize() {
        this._videoSetSize();

        var playlistItems = this.$('.js-playlist-items');

        if (window.innerWidth <= 768) {
            var top = playlistItems.offset().top;

            playlistItems.css('height', window.innerHeight - top);
        } else {
            playlistItems.removeAttr('style');
        }
    }

}

export default PlaylistItems<commit_msg>Scroll up when initializing playlistItems<commit_after>import _ from 'underscore'
import $ from 'jquery'
import moment from 'moment';
import {CompositeView, ItemView} from 'backbone.marionette'
import {Model} from 'backbone'
import {localStorage} from '../../../utils'
import app from '../../../application'
import {props} from '../../decorators'

let autoplay = false;

class PlaylistItem extends ItemView {

    @props({
        className: 'playlist-item js-playlist-item',

        template: require('../templates/playlistItem.ejs'),

        ui: {
            link: '.js-link'
        },

        bindings: {
            ':el': {
                classes: {
                    'watched': '_watched'
                }
            }
        },

        triggers: {
            'click @ui.link': 'link:clicked'
        }
    })

    initialize() {
        const videoId = this.model.get('videoId');
        const watched = !!localStorage.get(`${videoId}.info`, 'watched');

        if (watched) {
            this.model.set('_watched', true);
        }
    }

    onRender() {
        this.$el.attr('data-videoid', this.model.get('videoId'));

        this.stickit();
    }
}

class PlaylistItems extends CompositeView {

    @props({
        className: 'layout-playlists-items row',

        model: new Model({
            _search: '',
            _searchDate: null,
            videoId: null,
            _loading: false
        }),

        ui: {
            search: '.js-search',
            datepicker: '.js-datepicker',
            loader: '.js-loader'
        },

        childView: PlaylistItem,

        childViewContainer: '.js-playlist-items',

        childEvents: {
            'link:clicked': '_onClickLink'
        },

        template: require('../templates/playlistItems.ejs')
    })

    set videoId(val) {
        this.model.set('videoId', val);
    }

    set loading(val) {
        this.model.set('_loading', val);
    }

    /**
     * @returns {{search: String}}
     */
    get playlistFilter() {
        return {
            search: this.model.get('_search'),
            date: this.model.get('_searchDate')
        }
    }

    bindings() {
        return {
            '.js-video-container': {
                observe: 'videoId',
                update: _.debounce(this._videoPlay, 500)
            },

            '@ui.search': '_search',

            '@ui.datepicker': {
                observe: '_searchDate',
                onSet: (val) => {
                    return val ? moment(val, 'DD.MM.YYYY') : null;
                }
            },

            '@ui.loader': {
                classes: {
                    show: '_loading'
                }
            }
        }
    }

    modelEvents() {
        return {
            'change:videoId': () => {
                this._highlightVideo();
                this._routeToVideo();
            },

            'change:_search change:_searchDate': _.debounce(() => {
                this._searchCollection();
                this._highlightVideo();
            }, 500)
        }
    }

    initialize() {
        this._playerInterval = 0;
    }

    onRender() {
        this.ui.datepicker.datepicker({
            format: 'dd.mm.yyyy',
            autoclose: true,
            weekStart: 1
        });

        this.listenTo(app.channel, 'resize', _.debounce(this._onResize, 100));

        this.stickit();

        scrollTo(0, window.innerWidth > 768 ? 300 : 0);
    }

    _searchCollection() {
        const filter = this.playlistFilter;

        this.collection.search(filter);
    }

    _onClickLink(playlistItem) {
        this.videoId = playlistItem.ui.link.data('videoid');
    }

    _highlightVideo() {
        this.$childViewContainer.find('.js-playlist-item').removeClass('active');

        const $videoId = this.$childViewContainer.find('[data-videoid="' + this.model.get('videoId') + '"]');

        $videoId.addClass('active');

        // Scroll
        this.$childViewContainer
            .animate({
                scrollTop: ($videoId.index() - 1) * 65
            }, 250);
    }

    _routeToVideo(replaceState = false) {
        const currentPlaylistItem = this.collection.getCurrentPlaylistItem(this.model.get('videoId'));

        app.navigate(
            `playlists/playlist/${currentPlaylistItem.get('playlistId')}/video/${currentPlaylistItem.get('videoId')}`,
            { replace: replaceState }
        );
    }

    _videoPlay() {
        clearInterval(this._playerInterval);

        const videoId = this.model.get('videoId');

        if (videoId) {
            if (this._player && this._player.loadVideoById) {
                this._player.loadVideoById(videoId, 0);
            } else {
                $('#yt-video-container').replaceWith('

');

                this._player = new YT.Player('yt-video-container', {
                    width: 200,
                    height: 200,
                    videoId: videoId,
                    events: {
                        'onReady': this._onVideoReady.bind(this),
                        'onStateChange': this._onVideoStateChange.bind(this)
                    }
                });

                this._videoSetSize();
            }
        }
    }

    _videoBeforePlay() {
        const videoId = this.model.get('videoId');

        // currentTime
        const videoInfo = localStorage.get(`${videoId}.info`);

        if (videoInfo && videoInfo.currentTime) {
            this._player.seekTo(videoInfo.currentTime);
        }
    }

    _videoPlaying() {
        const videoId = this.model.get('videoId');

        // Store player-status
        clearInterval(this._playerInterval);

        const updateCurrentTime = () => {
            const currentTime = Math.round(this._player.getCurrentTime());

            localStorage.update(`${videoId}.info`, { currentTime });
        };

        updateCurrentTime();
        this._playerInterval = setInterval(updateCurrentTime, 8000);
    }

    _videoEnded() {
        const videoId = this.model.get('videoId');

        // Mark as watched
        this.collection.getCurrentPlaylistItem(videoId).set('_watched', true);
        localStorage.update(`${videoId}.info`, { watched: true, currentTime: 0 });

        // Play next
        const nextPlaylistItem = this.collection.getNextPlaylistItem(videoId);

        if (nextPlaylistItem) {
            const nextVideoId = nextPlaylistItem.get('videoId');

            // Reset currentTime
            localStorage.update(`${nextVideoId}.info`, { currentTime: 0 });

            // Set new videoId
            this.videoId = nextVideoId;

            // Start video automatically
            autoplay = true;
        }
    }

    _onVideoReady(e) {
        if (_.isNull(e.data)) {
            // autoplay
            if (autoplay) {
                this._player.playVideo();

                autoplay = false;
            }

            this._onResize();
        }
    }

    _videoSetSize() {
        if (this._player) {
            let width  = 640;
            let height = width * 0.51;

            if (window.innerWidth <= 768) {
                width  = '100%';
                height = window.innerWidth * 0.51;
            }

            this._player.setSize(width, height);
        }
    }

    _onVideoStateChange(e) {
        const videoId = this.model.get('videoId');

        switch (e.data) {
            case YT.PlayerState.UNSTARTED:
                this._videoBeforePlay();
                break;
            case YT.PlayerState.PLAYING:
                this._videoPlaying();
                break;
            case YT.PlayerState.ENDED:
                this._videoEnded();
                break;
        }
    }

    _onResize() {
        this._videoSetSize();

        var playlistItems = this.$('.js-playlist-items');

        if (window.innerWidth <= 768) {
            var top = playlistItems.offset().top;

            playlistItems.css('height', window.innerHeight - top);
        } else {
            playlistItems.removeAttr('style');
        }
    }

}

export default PlaylistItems<|endoftext|>


<commit_before>/**
 * Send a message to the embed via postMessage
 *
 * @param {Embed} embed
 * @param {string} method The name of the method to call
 * @param {string} parameters The parameters to pass to the method
 */
export function postMessage(embed, method, parameters) {
  if (!embed.element.contentWindow || !embed.element.contentWindow.postMessage) {
    throw new Error('No `contentWindow` or `contentWindow.postMessage` avaialble on the element');
  }

  let message = {
    method,
    parameters
  };

  embed.element.contentWindow.postMessage(message, embed.origin);
}

/**
 * Parse a message received from postMessage
 *
 * @param {string|object} data The data received from postMessage
 * @return {object}
 */
export function parseMessage(data) {
  if (typeof data === 'string') {
    data = JSON.parse(data);
  }
  return data;
}
<commit_msg>fix: typo in exception message<commit_after>/**
 * Send a message to the embed via postMessage
 *
 * @param {Embed} embed
 * @param {string} method The name of the method to call
 * @param {string} parameters The parameters to pass to the method
 */
export function postMessage(embed, method, parameters) {
  if (!embed.element.contentWindow || !embed.element.contentWindow.postMessage) {
    throw new Error('No `contentWindow` or `contentWindow.postMessage` available on the element');
  }

  let message = {
    method,
    parameters
  };

  embed.element.contentWindow.postMessage(message, embed.origin);
}

/**
 * Parse a message received from postMessage
 *
 * @param {string|object} data The data received from postMessage
 * @return {object}
 */
export function parseMessage(data) {
  if (typeof data === 'string') {
    data = JSON.parse(data);
  }
  return data;
}
<|endoftext|>


<commit_before>var fluid = require("universal");

var loader = fluid.getLoader(__dirname);

loader.require("./src/RegistryResolver.js");

fluid.defaults("gpii.lifecycleManager.registryResolverConfig", {
    gradeNames: ["fluid.littleComponent", "autoInit"],
    resolvers: {
        registry: "gpii.lifecycleManager.registryResolver"
    }
});

fluid.demands("gpii.lifecycleManager.standardResolverConfig", "gpii.lifecycleManager.resolver", {
    options: {
        gradeNames: ["gpii.lifecycleManager.registryResolverConfig"]
    }
});<commit_msg>GPII-115: Updated index.js of registry resolver to use linkage instead of demand blocks<commit_after>var fluid = require("universal");

var loader = fluid.getLoader(__dirname);

loader.require("./src/RegistryResolver.js");

fluid.defaults("gpii.lifecycleManager.registryResolverConfig", {
    gradeNames: ["fluid.littleComponent", "autoInit"],
    resolvers: {
        registry: "gpii.lifecycleManager.registryResolver"
    }
});

fluid.defaults("gpii.lifecycleManager.resolverConfigWithRegistry", {
    gradeNames: ["autoInit", "fluid.gradeLinkageRecord"],
    contextGrades: ["gpii.lifecycleManager.resolver"],
    resultGrades: ["gii.lifecycleManager.registryResolverConfig"]
});

// fluid.demands("gpii.lifecycleManager.standardResolverConfig", "gpii.lifecycleManager.resolver", {
//     options: {
//         gradeNames: ["gpii.lifecycleManager.registryResolverConfig"]
//     }
// });<|endoftext|>


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

  angular
    .module('openSenseMapApp')
    .controller('SidebarBoxDetailsController', SidebarBoxDetailsController);

  SidebarBoxDetailsController.$inject = ['$scope', '$stateParams', 'moment', '$timeout', 'Box', 'OpenSenseMapAPI', 'osemMapData', 'Sidebar'];

  function SidebarBoxDetailsController ($scope, $stateParams, moment, $timeout, Box, OpenSenseMapAPI, osemMapData, Sidebar) {
    var vm = this;
    vm.box = {};
    vm.selectedSensor = null;
    vm.minimized = false;

    vm.selectSensor = selectSensor;
    vm.resetFilter = resetFilter;
    vm.performFilter = performFilter;
    vm.getTimeAgo = getTimeAgo;

    activate();

    ////

    function activate () {
      console.log("Activate Sidebar", moment);
      OpenSenseMapAPI.getBox($stateParams.id)
        .then(function (response) {
          vm.box = new Box(response);
          Sidebar.setTitle(vm.box.name);
          Sidebar.addAction({href: vm.box.getArchiveLink(), target: '_blank', icon: 'fa-archive', hideOnMinimized: true});
          Sidebar.addAction({handler: focusSelectedBox, icon: 'fa-thumb-tack', hideOnMinimized: false});
          focusSelectedBox();
          if (vm.box.exposure === 'mobile') getBoxTrajectory();
        })
        .catch(function (error) {
          vm.boxNotFound = true;
        })
        .finally(function () {
          $scope.$parent.$broadcast('boxSelected', vm.box);
          $timeout(function () {
            $scope.$broadcast('osemBadgeRefreshStartTimer');
          }, 1000)
        });
    }

    function getBoxTrajectory (options) {
      var options = Object.assign({ format: 'geojson' }, options);
      var data = { params: options };
      return OpenSenseMapAPI.getBoxLocations($stateParams.id, data)
        .then(function (response) {
          // save result in map.js scope, as it needs to be accessible for leaflet directive
          $scope.$parent.map.boxLocations = response;
          focusSelectedBox(response);
          return response;
        })
    }

    // focus current location of a box or its trajectory, if optional
    // `trajectory` GeoJSON linestring is provided
    function focusSelectedBox (trajectory) {
      var currentLoc = [
        vm.box.currentLocation.coordinates[1],
        vm.box.currentLocation.coordinates[0]
      ];

      var bounds = trajectory && trajectory.geometry.coordinates.length
        ? L.geoJSON(trajectory).getBounds()
        : [currentLoc, currentLoc];

      return centerBounds(bounds);
    }

    function centerBounds (bounds) {
      osemMapData.getMap('map_main').then(function(map) {
        var padding = 450; // sidebar width: 450px
        // consider smaller devices (250px min map-width + 450px sidebar-width)
        if (document.body.clientWidth <= 700) padding = 0;
        if (Sidebar.minimized) padding = 0;

        map.fitBounds(bounds, {
          paddingTopLeft: [0,0],
          paddingBottomRight: [padding, 0],
          maxZoom: 17,
          animate: false
        });
      });
    }

    $scope.$on('$destroy', function(ev) {
      $scope.$parent.$broadcast('boxDeselected', vm.box);
      // reset externally stored state
      $scope.$parent.map.legendInfo = {};
      $scope.$parent.map.boxLocations = {};
      $scope.$parent.map.selectedSensorMeasurements = [];

      Sidebar.removeActions();
      Sidebar.setTitle('');
    });

    $scope.$on('osemBadgeRefreshFinished', function () {
      vm.box.getLastMeasurement();

      // update trajectory and (map)measurements for mobile boxes
      // but only if there is no time filter selected in the chart
      if (
        vm.box.exposure === 'mobile' &&
        (!vm.selectedSensor || !vm.selectedSensor.chart.toDate)
      ) {
        if (vm.selectedSensor) getSensorData(vm.selectedSensor);
        getBoxTrajectory();
      }

      $scope.$broadcast('osemBadgeRefreshStartTimer');
    });

    // sync the selected measurement between chart & map
    // TODO: more elegant way of 2way binding?
    $scope.$on('osemChartsMouseOver', function (event, data) {
      $scope.$parent.map.highlightedMeasure = data;
    });
    $scope.$on('osemChartsMouseOut', function (event, data) {
      $scope.$parent.map.highlightedMeasure = undefined;
    });
    $scope.$on('osemMeasurementMouseOver.map_main', function (e, args) {
      vm.selectedSensor.chart.selectedMeasurement = args.target.options.measurement;
    });
    $scope.$on('osemMeasurementMouseOut.map_main', function (e, args) {
      vm.selectedSensor.chart.selectedMeasurement = undefined;
    });

    function selectSensor(sensor, event) {
      $scope.$parent.map.selectedSensorMeasurements = [];
      $scope.$parent.map.legendInfo = {};
      sensor.chart.fromDate = undefined;
      sensor.chart.toDate = undefined;
      sensor.chart.error = false;

      // if already selected sensor is selected again: clear selection
      if (sensor === vm.selectedSensor) {
        vm.selectedSensor = null;
        return;
      }
      vm.selectedSensor = sensor;

      // get chart data once and add measurements to the map
      // if we have them already, use the cached data
      if (!sensor.measurements) {
        var data = {
          params: {}
        };
        if (angular.isDefined(sensor.lastMeasurement)) {
          data.params['to-date'] = sensor.lastMeasurement.createdAt;
        }

        getSensorData(sensor, data);
      } else if (vm.box.exposure === 'mobile') {
        $scope.$parent.map.selectedSensorMeasurements = sensor.measurements;
      }

      sensor.chart.yAxisTitle = sensor.title + '('+sensor.unit+')';
      angular.extend($scope.$parent.map.legendInfo, { unit: sensor.unit });

      // dont close a chart if it is already open when being selected
      if (sensor.chart.open) {
        event.stopPropagation();
        event.preventDefault();
      }
    };

    function resetFilter (sensor) {
      var data = {
        params: {}
      };
      if (angular.isDefined(sensor.lastMeasurement)) {
        data.params['to-date'] = sensor.lastMeasurement.createdAt;
      }

      return getSensorData(sensor, data);
    }

    function performFilter (sensor) {
      var data = {
        params: {
          'from-date': sensor.chart.fromDate.toISOString(),
          'to-date': sensor.chart.toDate.toISOString()
        }
      }

      if (vm.box.exposure === 'mobile') {
        getBoxTrajectory(data.params) // might need an update for the new timeframe
      }

      return getSensorData(sensor, data);
    }

    function getSensorData (sensor, data) {
      return OpenSenseMapAPI.getSensorData(vm.box._id, sensor._id, data)
        .then(function (response) {
          sensor.chart.data = [];
          for (var j = 0; j < response.length; j++) {
            sensor.chart.data.push({
              id: response[j].id,
              value: parseFloat(response[j].value),
              date: new Date(response[j].createdAt),
              unit: sensor.unit,
            });
          }
          sensor.chart.done = true;
          return response;
        })
        .then(function (measurements) {
          if (vm.box.exposure === 'mobile') {
            sensor.measurements = measurements;
            $scope.$parent.map.selectedSensorMeasurements = measurements;
          }
          return measurements;
        })
        .catch(function (error) {
          sensor.chart.error = true;
        });
    }
  }
})();
<commit_msg>feat(sidebar.box): add edit icon<commit_after>(function () {
  'use strict';

  angular
    .module('openSenseMapApp')
    .controller('SidebarBoxDetailsController', SidebarBoxDetailsController);

  SidebarBoxDetailsController.$inject = ['$scope', '$state', '$stateParams', 'moment', '$timeout', 'Box', 'OpenSenseMapAPI', 'osemMapData', 'Sidebar', 'LocalStorageService'];

  function SidebarBoxDetailsController ($scope, $state, $stateParams, moment, $timeout, Box, OpenSenseMapAPI, osemMapData, Sidebar, LocalStorageService) {
    var vm = this;
    vm.box = {};
    vm.selectedSensor = null;
    vm.minimized = false;

    vm.selectSensor = selectSensor;
    vm.resetFilter = resetFilter;
    vm.performFilter = performFilter;

    activate();

    ////

    function activate () {
      OpenSenseMapAPI.getBox($stateParams.id)
        .then(function (response) {
          vm.box = new Box(response);
          Sidebar.setTitle(vm.box.name);
          Sidebar.addAction({href: vm.box.getArchiveLink(), target: '_blank', icon: 'fa-archive', hideOnMinimized: true});
          Sidebar.addAction({handler: focusSelectedBox, icon: 'fa-thumb-tack', hideOnMinimized: false});

          var account = LocalStorageService.getValue('osem.account');
          if (account) {
            var boxes = JSON.parse(account).boxes;
            if (boxes.indexOf($stateParams.id) > -1) {
              Sidebar.addAction({handler: editBox, icon: 'fa-pencil', hideOnMinimized: true});
            }
          }

          focusSelectedBox();
          if (vm.box.exposure === 'mobile') getBoxTrajectory();
        })
        .catch(function (error) {
          vm.boxNotFound = true;
        })
        .finally(function () {
          $scope.$parent.$broadcast('boxSelected', vm.box);
          $timeout(function () {
            $scope.$broadcast('osemBadgeRefreshStartTimer');
          }, 1000)
        });
    }

    function getBoxTrajectory (options) {
      var options = Object.assign({ format: 'geojson' }, options);
      var data = { params: options };
      return OpenSenseMapAPI.getBoxLocations($stateParams.id, data)
        .then(function (response) {
          // save result in map.js scope, as it needs to be accessible for leaflet directive
          $scope.$parent.map.boxLocations = response;
          focusSelectedBox(response);
          return response;
        })
    }

    function editBox () {
      $state.go('account.edit.general', { id: vm.box._id , box: vm.box });
    }

    // focus current location of a box or its trajectory, if optional
    // `trajectory` GeoJSON linestring is provided
    function focusSelectedBox (trajectory) {
      var currentLoc = [
        vm.box.currentLocation.coordinates[1],
        vm.box.currentLocation.coordinates[0]
      ];

      var bounds = trajectory && trajectory.geometry.coordinates.length
        ? L.geoJSON(trajectory).getBounds()
        : [currentLoc, currentLoc];

      return centerBounds(bounds);
    }

    function centerBounds (bounds) {
      osemMapData.getMap('map_main').then(function(map) {
        var padding = 450; // sidebar width: 450px
        // consider smaller devices (250px min map-width + 450px sidebar-width)
        if (document.body.clientWidth <= 700) padding = 0;
        if (Sidebar.minimized) padding = 0;

        map.fitBounds(bounds, {
          paddingTopLeft: [0,0],
          paddingBottomRight: [padding, 0],
          maxZoom: 17,
          animate: false
        });
      });
    }

    $scope.$on('$destroy', function(ev) {
      $scope.$parent.$broadcast('boxDeselected', vm.box);
      // reset externally stored state
      $scope.$parent.map.legendInfo = {};
      $scope.$parent.map.boxLocations = {};
      $scope.$parent.map.selectedSensorMeasurements = [];

      Sidebar.removeActions();
      Sidebar.setTitle('');
    });

    $scope.$on('osemBadgeRefreshFinished', function () {
      vm.box.getLastMeasurement();

      // update trajectory and (map)measurements for mobile boxes
      // but only if there is no time filter selected in the chart
      if (
        vm.box.exposure === 'mobile' &&
        (!vm.selectedSensor || !vm.selectedSensor.chart.toDate)
      ) {
        if (vm.selectedSensor) getSensorData(vm.selectedSensor);
        getBoxTrajectory();
      }

      $scope.$broadcast('osemBadgeRefreshStartTimer');
    });

    // sync the selected measurement between chart & map
    // TODO: more elegant way of 2way binding?
    $scope.$on('osemChartsMouseOver', function (event, data) {
      $scope.$parent.map.highlightedMeasure = data;
    });
    $scope.$on('osemChartsMouseOut', function (event, data) {
      $scope.$parent.map.highlightedMeasure = undefined;
    });
    $scope.$on('osemMeasurementMouseOver.map_main', function (e, args) {
      vm.selectedSensor.chart.selectedMeasurement = args.target.options.measurement;
    });
    $scope.$on('osemMeasurementMouseOut.map_main', function (e, args) {
      vm.selectedSensor.chart.selectedMeasurement = undefined;
    });

    function selectSensor(sensor, event) {
      $scope.$parent.map.selectedSensorMeasurements = [];
      $scope.$parent.map.legendInfo = {};
      sensor.chart.fromDate = undefined;
      sensor.chart.toDate = undefined;
      sensor.chart.error = false;

      // if already selected sensor is selected again: clear selection
      if (sensor === vm.selectedSensor) {
        vm.selectedSensor = null;
        return;
      }
      vm.selectedSensor = sensor;

      // get chart data once and add measurements to the map
      // if we have them already, use the cached data
      if (!sensor.measurements) {
        var data = {
          params: {}
        };
        if (angular.isDefined(sensor.lastMeasurement)) {
          data.params['to-date'] = sensor.lastMeasurement.createdAt;
        }

        getSensorData(sensor, data);
      } else if (vm.box.exposure === 'mobile') {
        $scope.$parent.map.selectedSensorMeasurements = sensor.measurements;
      }

      sensor.chart.yAxisTitle = sensor.title + '('+sensor.unit+')';
      angular.extend($scope.$parent.map.legendInfo, { unit: sensor.unit });

      // dont close a chart if it is already open when being selected
      if (sensor.chart.open) {
        event.stopPropagation();
        event.preventDefault();
      }
    };

    function resetFilter (sensor) {
      var data = {
        params: {}
      };
      if (angular.isDefined(sensor.lastMeasurement)) {
        data.params['to-date'] = sensor.lastMeasurement.createdAt;
      }

      return getSensorData(sensor, data);
    }

    function performFilter (sensor) {
      var data = {
        params: {
          'from-date': sensor.chart.fromDate.toISOString(),
          'to-date': sensor.chart.toDate.toISOString()
        }
      }

      if (vm.box.exposure === 'mobile') {
        getBoxTrajectory(data.params) // might need an update for the new timeframe
      }

      return getSensorData(sensor, data);
    }

    function getSensorData (sensor, data) {
      return OpenSenseMapAPI.getSensorData(vm.box._id, sensor._id, data)
        .then(function (response) {
          sensor.chart.data = [];
          for (var j = 0; j < response.length; j++) {
            sensor.chart.data.push({
              id: response[j].id,
              value: parseFloat(response[j].value),
              date: new Date(response[j].createdAt),
              unit: sensor.unit,
            });
          }
          sensor.chart.done = true;
          return response;
        })
        .then(function (measurements) {
          if (vm.box.exposure === 'mobile') {
            sensor.measurements = measurements;
            $scope.$parent.map.selectedSensorMeasurements = measurements;
          }
          return measurements;
        })
        .catch(function (error) {
          sensor.chart.error = true;
        });
    }
  }
})();
<|endoftext|>


<commit_before>import req from '../req.js';
import Event from '../Event';

export default function Ticker() {
  this.event = new Event();

  this.ready = false;
  this.data = {};

  this.load();
}

const MAX_ATTEMPTS = 120;

Ticker.prototype.load = function(attempt) {
  if (attempt >= MAX_ATTEMPTS) {
    return;
  }
  req.getJson('https://api.stellarterm.com/v1/ticker.json')
  .then(tickerData => {
    this.ready = true;
    this.data = tickerData;
    console.log('Loaded ticker. Data generated ' + Math.round((new Date() - this.data._meta.start * 1000)/1000) + ' seconds ago.')
    this.event.trigger();
    setTimeout(() => {this.load()}, 60*1000) // Refresh every 60 seconds
  })
  .catch(e => {
    console.log('Unable to load ticker', e);
    if (!attempt) {
      attempt = 0;
    }
    setTimeout(() => {
      this.load(attempt + 1);
    }, 1000)
  })
}
<commit_msg>Reduce ticker update frequency<commit_after>import req from '../req.js';
import Event from '../Event';

export default function Ticker() {
  this.event = new Event();

  this.ready = false;
  this.data = {};

  this.load();
}

const MAX_ATTEMPTS = 120;

Ticker.prototype.load = function(attempt) {
  if (attempt >= MAX_ATTEMPTS) {
    return;
  }
  req.getJson('https://api.stellarterm.com/v1/ticker.json')
  .then(tickerData => {
    this.ready = true;
    this.data = tickerData;
    console.log('Loaded ticker. Data generated ' + Math.round((new Date() - this.data._meta.start * 1000)/1000) + ' seconds ago.')
    this.event.trigger();
    setTimeout(() => {this.load()}, 61*5*1000) // Refresh every 5 minutes
  })
  .catch(e => {
    console.log('Unable to load ticker', e);
    if (!attempt) {
      attempt = 0;
    }
    setTimeout(() => {
      this.load(attempt + 1);
    }, 1000)
  })
}
<|endoftext|>


<commit_before>/**
 * Testing activity '_test-badge' to manually try badge components and visualization,
 * with dummy data.
 *
 * IMPORTANT: Any activity starting with underscore must not being published.
 */
'use strict';

import '../../kocomponents/badge/view';
import '../../kocomponents/badge/viewer';
import * as activities from '../index';
import * as badges from '../../data/badges';
import Activity from '../../components/Activity';
import flatArray from 'lodash/flatten';
import ko from 'knockout';
import template from './template.html';

const ROUTE_NAME = '_test-badge';

const dummyData = [
    {
      'userBadgeID': 2,
      'solutionID': 275,
      'jobTitleID': 106,
      'badgeURL': 'https://api.badgr.io/public/assertions/ZwxV7sqTTqa-r_vK51VsdA.json?v=2_0',
      'type': 'badge',
      'createdBy': 'user'
    },
    {
      'userBadgeID': 3,
      'solutionID': 275,
      'jobTitleID': 106,
      'badgeURL': 'https://api.badgr.io/public/collections/fcfad6d9e51bd635c9c88248587dc035?v=2_0',
      'type': 'collection',
      'createdBy': 'user'
    }
];

/**
 * @returns {Promise<Array<UserBadgeAssertion>>}
 */
const expandUserBadges = (userBadge) => {
    const userBadgeAndAssertion = (assertion) => ({
        userBadge,
        assertion
    });

    switch (userBadge.type) {
        case 'badge':
            return badges.getAssertion(userBadge.badgeURL).then(userBadgeAndAssertion);
        case 'collection':
            return badges
              .getCollectionAssertions(userBadge.badgeURL)
              .then((list) => list.map(userBadgeAndAssertion));
        default:
            throw new Error(`Unsupported user badge type ${userBadge.type}`);
    }
};

export default class _TestBadgeActivity extends Activity {

    static get template() { return template; }

    constructor($activity, app) {
        super($activity, app);
        this.accessLevel = null;
        this.title = 'Testing badges';

        this.userBadges = ko.observableArray([]);

        // Load all the assertions for the user badges, expanding collections
        // as individual assertions, and the result as a flat array
        const fetching = dummyData.map(expandUserBadges);
        Promise.all(fetching).then(flatArray).then(this.userBadges);
    }

    /**
     * Returns a URL to where to edit the badge assigned to the user, with a return
     * link to the listing editor.
     * @param {rest/UserBadge} userBadge record for a badge assigned to a user (AKA 'assertion' in OpenBadges naming)
     * @returns {string}
     */
    getBadgeEditURL(userBadge) {
        return `/badge-edit/${userBadge.userBadgeID}?mustReturn=listingEditor/${userBadge.jobTitleID}&returnText=${encodeURIComponent('Listing Editor')}`;
    }

    /**
     * Returns a URL to where to view details of the badge assigned to the user, with a return
     * link to the listing editor.
     * @param {rest/UserBadge} userBadge record for a badge assigned to a user (AKA 'assertion' in OpenBadges naming)
     * @returns {string}
     */
    getBadgeDetailsURL(userBadge) {
        return `/badge-view/${userBadge.userBadgeID}?mustReturn=listingEditor/${userBadge.jobTitleID}&returnText=${encodeURIComponent('Listing Editor')}`;
    }
}

activities.register(ROUTE_NAME, _TestBadgeActivity);
<commit_msg>Impl. Edit badge only on user generated badge, at test-badges #746<commit_after>/**
 * Testing activity '_test-badge' to manually try badge components and visualization,
 * with dummy data.
 *
 * IMPORTANT: Any activity starting with underscore must not being published.
 */
'use strict';

import '../../kocomponents/badge/view';
import '../../kocomponents/badge/viewer';
import * as activities from '../index';
import * as badges from '../../data/badges';
import Activity from '../../components/Activity';
import flatArray from 'lodash/flatten';
import ko from 'knockout';
import template from './template.html';

const ROUTE_NAME = '_test-badge';

const dummyData = [
    {
      'userBadgeID': 2,
      'solutionID': 275,
      'jobTitleID': 106,
      'badgeURL': 'https://api.badgr.io/public/assertions/ZwxV7sqTTqa-r_vK51VsdA.json?v=2_0',
      'type': 'badge',
      'createdBy': 'jd'
    },
    {
      'userBadgeID': 3,
      'solutionID': 275,
      'jobTitleID': 106,
      'badgeURL': 'https://api.badgr.io/public/collections/fcfad6d9e51bd635c9c88248587dc035?v=2_0',
      'type': 'collection',
      'createdBy': 'user'
    }
];

/**
 * @returns {Promise<Array<UserBadgeAssertion>>}
 */
const expandUserBadges = (userBadge) => {
    const userBadgeAndAssertion = (assertion) => ({
        userBadge,
        assertion
    });

    switch (userBadge.type) {
        case 'badge':
            return badges.getAssertion(userBadge.badgeURL).then(userBadgeAndAssertion);
        case 'collection':
            return badges
              .getCollectionAssertions(userBadge.badgeURL)
              .then((list) => list.map(userBadgeAndAssertion));
        default:
            throw new Error(`Unsupported user badge type ${userBadge.type}`);
    }
};

export default class _TestBadgeActivity extends Activity {

    static get template() { return template; }

    constructor($activity, app) {
        super($activity, app);
        this.accessLevel = null;
        this.title = 'Testing badges';

        this.userBadges = ko.observableArray([]);

        // Load all the assertions for the user badges, expanding collections
        // as individual assertions, and the result as a flat array
        const fetching = dummyData.map(expandUserBadges);
        Promise.all(fetching).then(flatArray).then(this.userBadges);
    }

    /**
     * Returns a URL to where to edit the badge assigned to the user, with a return
     * link to the listing editor.
     * @param {rest/UserBadge} userBadge record for a badge assigned to a user (AKA 'assertion' in OpenBadges naming)
     * @returns {string}
     */
    getBadgeEditURL(userBadge) {
        if (userBadge.createdBy !== 'user') return null;
        else return `/badge-edit/${userBadge.userBadgeID}?mustReturn=listingEditor/${userBadge.jobTitleID}&returnText=${encodeURIComponent('Listing Editor')}`;
    }

    /**
     * Returns a URL to where to view details of the badge assigned to the user, with a return
     * link to the listing editor.
     * @param {rest/UserBadge} userBadge record for a badge assigned to a user (AKA 'assertion' in OpenBadges naming)
     * @returns {string}
     */
    getBadgeDetailsURL(userBadge) {
        return `/badge-view/${userBadge.userBadgeID}?mustReturn=listingEditor/${userBadge.jobTitleID}&returnText=${encodeURIComponent('Listing Editor')}`;
    }
}

activities.register(ROUTE_NAME, _TestBadgeActivity);
<|endoftext|>


<commit_before><commit_msg>Mod: corrected text and added task switching to data<commit_after><|endoftext|>


<commit_before>//>>excludeStart("exclude", pragmas.exclude);
define([], function(){
	//>>excludeEnd("exclude");

	/**
	 * The shoestring object constructor.
	 *
	 * @param {string,object} prim The selector to find or element to wrap.
	 * @param {object} sec The context in which to match the `prim` selector.
	 * @returns shoestring
	 * @this window
	 */
	function shoestring( prim, sec ){
		var pType = typeof( prim ),
				ret = [],
				sel;

		// return an empty shoestring object
		if( !prim ){
			return new Shoestring( ret );
		}

		// ready calls
		if( prim.call ){
			return shoestring.ready( prim );
		}

		// handle re-wrapping shoestring objects
		if( prim.constructor === Shoestring && !sec ){
			return prim;
		}

		// if string starting with <, make html
		if( pType === "string" && prim.indexOf( "<" ) === 0 ){
			var dfrag = document.createElement( "div" );

			dfrag.innerHTML = prim;

			// TODO depends on children (circular)
			return shoestring( dfrag ).children().each(function(){
				dfrag.removeChild( this );
			});
		}

		// if string, it's a selector, use qsa
		if( pType === "string" ){
			if( sec ){
				return shoestring( sec ).find( prim );
			}

//>>includeStart("development", pragmas.development);
			try {
//>>includeEnd("development");
				sel = document.querySelectorAll( prim );
//>>includeStart("development", pragmas.development);
			} catch( e ) {
				shoestring.error( 'queryselector', prim );
			}
//>>includeEnd("development");

			return new Shoestring( sel, prim );
		}

		// array like objects or node lists
		if( Object.prototype.toString.call( pType ) === '[object Array]' ||
				prim instanceof window.NodeList ){

			return new Shoestring( prim, prim );
		}

		// if it's an array, use all the elements
		if( prim.constructor === Array ){
			return new Shoestring( prim, prim );
		}

		// otherwise assume it's an object the we want at an index
		return new Shoestring( [prim], prim );
	}

	var Shoestring = function( ret, prim ) {
		this.length = 0;
		this.selector = prim;
		shoestring.merge(this, ret);
	};

	// TODO only required for tests
	Shoestring.prototype.reverse = [].reverse;

	// For adding element set methods
	shoestring.fn = Shoestring.prototype;

	// expose for testing purposes only
	shoestring.Shoestring = Shoestring;

	// For extending objects
	// TODO move to separate module when we use prototypes
	shoestring.extend = function( first, second ){
		for( var i in second ){
			if( second.hasOwnProperty( i ) ){
				first[ i ] = second[ i ];
			}
		}

		return first;
	};

	// taken directly from jQuery
	shoestring.merge = function( first, second ) {
		var len, j, i;

		len = +second.length,
		j = 0,
		i = first.length;

		for ( ; j < len; j++ ) {
			first[ i++ ] = second[ j ];
		}

		first.length = i;

		return first;
	};

	// expose
	window.shoestring = shoestring;

//>>excludeStart("exclude", pragmas.exclude);
});
//>>excludeEnd("exclude");
<commit_msg>NodeList not supported in ie8<commit_after>//>>excludeStart("exclude", pragmas.exclude);
define([], function(){
	//>>excludeEnd("exclude");

	/**
	 * The shoestring object constructor.
	 *
	 * @param {string,object} prim The selector to find or element to wrap.
	 * @param {object} sec The context in which to match the `prim` selector.
	 * @returns shoestring
	 * @this window
	 */
	function shoestring( prim, sec ){
		var pType = typeof( prim ),
				ret = [],
				sel;

		// return an empty shoestring object
		if( !prim ){
			return new Shoestring( ret );
		}

		// ready calls
		if( prim.call ){
			return shoestring.ready( prim );
		}

		// handle re-wrapping shoestring objects
		if( prim.constructor === Shoestring && !sec ){
			return prim;
		}

		// if string starting with <, make html
		if( pType === "string" && prim.indexOf( "<" ) === 0 ){
			var dfrag = document.createElement( "div" );

			dfrag.innerHTML = prim;

			// TODO depends on children (circular)
			return shoestring( dfrag ).children().each(function(){
				dfrag.removeChild( this );
			});
		}

		// if string, it's a selector, use qsa
		if( pType === "string" ){
			if( sec ){
				return shoestring( sec ).find( prim );
			}

//>>includeStart("development", pragmas.development);
			try {
//>>includeEnd("development");
				sel = document.querySelectorAll( prim );
//>>includeStart("development", pragmas.development);
			} catch( e ) {
				shoestring.error( 'queryselector', prim );
			}
//>>includeEnd("development");

			return new Shoestring( sel, prim );
		}

		// array like objects or node lists
		if( Object.prototype.toString.call( pType ) === '[object Array]' ||
				(window.NodeList && prim instanceof window.NodeList) ){

			return new Shoestring( prim, prim );
		}

		// if it's an array, use all the elements
		if( prim.constructor === Array ){
			return new Shoestring( prim, prim );
		}

		// otherwise assume it's an object the we want at an index
		return new Shoestring( [prim], prim );
	}

	var Shoestring = function( ret, prim ) {
		this.length = 0;
		this.selector = prim;
		shoestring.merge(this, ret);
	};

	// TODO only required for tests
	Shoestring.prototype.reverse = [].reverse;

	// For adding element set methods
	shoestring.fn = Shoestring.prototype;

	// expose for testing purposes only
	shoestring.Shoestring = Shoestring;

	// For extending objects
	// TODO move to separate module when we use prototypes
	shoestring.extend = function( first, second ){
		for( var i in second ){
			if( second.hasOwnProperty( i ) ){
				first[ i ] = second[ i ];
			}
		}

		return first;
	};

	// taken directly from jQuery
	shoestring.merge = function( first, second ) {
		var len, j, i;

		len = +second.length,
		j = 0,
		i = first.length;

		for ( ; j < len; j++ ) {
			first[ i++ ] = second[ j ];
		}

		first.length = i;

		return first;
	};

	// expose
	window.shoestring = shoestring;

//>>excludeStart("exclude", pragmas.exclude);
});
//>>excludeEnd("exclude");
<|endoftext|>


<commit_before>/**
 * SimpleAjax
 *
 * @fileOverview
 *    Cross browser ajax object for creating ajax calls.
 *    Released under MIT license.
 * @version 0.3.1
 * @author Victor Villaverde Laan
 * @link http://www.freelancephp.net/simpleajax-small-ajax-javascript-object/
 * @link https://github.com/freelancephp/SimpleAJAX
 */

/**
 * @namespace SimpleAjax
 */
var SimpleAjax = {

	/**
	* @property {XMLHttpRequest|ActiveXObject}
	*/
	xhr: null,

	/**
	* @property {Object} Default ajax settings
	*/
	settings: {
		url: '',
		type: 'GET',
		dataType: 'text', // text, html, json or xml
		async: true,
		cache: true,
		data: null,
		contentType: 'application/x-www-form-urlencoded',
		success: null,
		error: null,
		complete: null,
		accepts: {
			text: 'text/plain',
			html: 'text/html',
			xml: 'application/xml, text/xml',
			json: 'application/json, text/javascript'
		}
	},

	/**
	* Ajax call
	* @param {Object} [options] Overwrite the default settings (see ajaxSettings)
	* @return {This}
	*/
	call: function (options) {
		var self = this,
			xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'),
			opts = (function (s, o) {
				var opts = {};

				for (var key in s)
					opts[key] = (typeof o[key] == 'undefined') ? s[key] : o[key];

				return opts;
			})(this.settings, options),
			ready = function () {
				if(xhr.readyState == 4){
					if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
						// set data
						var data = (opts.dataType == 'xml') ? xhr.responseXML : xhr.responseText;

						// parse json data
						if (opts.dataType == 'json')
							data = self.parseJSON(data);

						// success callback
						if (self.isFunction(opts.success))
							opts.success.call(opts, data, xhr.status, xhr);
					} else {
						// error callback
						if (self.isFunction(opts.error))
							opts.error.call(opts, xhr, xhr.status);
					}

					// complete callback
					if (self.isFunction(opts.complete))
						opts.complete.call(opts, xhr, xhr.status);
				}
			};

		this.xhr = xhr;

		// prepare options
		if (!opts.cache)
			opts.url += ((opts.url.indexOf('?') > -1) ? '&' : '?') + '_nocache='+ (new Date()).getTime();

		if (opts.data) {
			if (opts.type == 'GET') {
				opts.url += ((opts.url.indexOf('?') > -1) ? '&' : '?') + this.param(opts.data);
				opts.data = null;
			} else {
				opts.data = this.param(opts.data);
			}
		}

		// set request
		xhr.open(opts.type, opts.url, opts.async);
		xhr.setRequestHeader('Content-type', opts.contentType);

		if (opts.dataType && opts.accepts[opts.dataType])
			xhr.setRequestHeader('Accept', opts.accepts[opts.dataType]);

		if (opts.async) {
			xhr.onreadystatechange = ready;
			xhr.send(opts.data);
		} else {
			xhr.send(opts.data);
			ready();
		}

		return this;
	},

	/**
	* Ajax GET request
	* @param {String} url
	* @param {String|Object} [data] Containing GET values
	* @param {Function} [success] Callback when request was succesfull
	* @return {This}
	*/
	get: function (url, data, success) {
		if (this.isFunction(data)) {
			success = data;
			data = null;
		}

		return this.call({
			url: url,
			type: 'GET',
			data: data,
			success: success
		});
	},

	/**
	* Ajax POST request
	* @param {String} url
	* @param {String|Object} [data] Containing POST values
	* @param {Function} [success] Callback when request was succesfull
	* @return {This}
	*/
	post: function (url, data, success) {
		if (this.isFunction(data)) {
			success = data;
			data = null;
		}

		return this.call({
			url: url,
			type: 'POST',
			data: data,
			success: success
		});
	},

	/**
	* Set content loaded by an ajax call
	* @param {DOMElement|String} el Can contain an element or the id of the element
	* @param {String} url The url of the ajax call (include GET vars in querystring)
	* @param {String} [data] The POST data, when set method will be set to POST
	* @param {Function} [complete] Callback when loading is completed
	* @return {This}
	*/
	load: function (el, url, data, complete) {
		if (typeof el == 'string')
			el = document.getElementById(el);

		return this.call({
			url: url,
			type: data ? 'POST' : 'GET',
			data: data || null,
			complete: complete || null,
			success: function (html) {
				try {
					el.innerHTML = html;
				} catch (e) {
					var ph = document.createElement('div');
					ph.innerHTML = html;

					// empty element content
					while (el.firstChild)
						el.removeChild(el.firstChild);

					// set new html content
					for(var x = 0, max = ph.childNodes.length; x < max; x++)
						el.appendChild(ph.childNodes[x]);
				}
			}
		});
	},

	/**
	* Make querystring outof object or array of values
	* @param {Object|Array} obj Keys/values
	* @return {String} The querystring
	*/
	param: function (obj) {
		var s = [];

		for (var key in obj) {
			s.push(encodeURIComponent(key) +'='+ encodeURIComponent(obj[key]));
		}

		return s.join('&');
	},

	/**
	* Parse JSON string
	* @param {String} data
	* @return {Object} JSON object
	*/
	parseJSON: function (data) {
		if (typeof data !== 'string' || !data)
			return null;

		return eval('('+ this.trim(data) +')');
	},

	/**
	* Trim spaces
	* @param {String} str
	* @return {String}
	*/
	trim: function (str) {
		return str.replace(/^\s+/, '').replace(/\s+$/, '');
	},

	/**
	* Check if argument is function
	* @param {Mixed} obj
	* @return {Boolean}
	*/
	isFunction: function (obj) {
		return Object.prototype.toString.call( obj ) === '[object Function]';
	}

};

if (!window.Ajax) {
	/**
	* Alias for SimpleAjax
	* @namespace Ajax
	*/
	window.Ajax = SimpleAJAX;
}
<commit_msg>Correct outline of comments<commit_after>/**
 * SimpleAjax
 *
 * @fileOverview
 *    Cross browser ajax object for creating ajax calls.
 *    Released under MIT license.
 * @version 0.3.1
 * @author Victor Villaverde Laan
 * @link http://www.freelancephp.net/simpleajax-small-ajax-javascript-object/
 * @link https://github.com/freelancephp/SimpleAJAX
 */

/**
 * @namespace SimpleAjax
 */
var SimpleAjax = {

	/**
	* @property {XMLHttpRequest|ActiveXObject}
	*/
	xhr: null,

	/**
	 * @property {Object} Default ajax settings
	 */
	settings: {
		url: '',
		type: 'GET',
		dataType: 'text', // text, html, json or xml
		async: true,
		cache: true,
		data: null,
		contentType: 'application/x-www-form-urlencoded',
		success: null,
		error: null,
		complete: null,
		accepts: {
			text: 'text/plain',
			html: 'text/html',
			xml: 'application/xml, text/xml',
			json: 'application/json, text/javascript'
		}
	},

	/**
	 * Ajax call
	 * @param {Object} [options] Overwrite the default settings (see ajaxSettings)
	 * @return {This}
	 */
	call: function (options) {
		var self = this,
			xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'),
			opts = (function (s, o) {
				var opts = {};

				for (var key in s)
					opts[key] = (typeof o[key] == 'undefined') ? s[key] : o[key];

				return opts;
			})(this.settings, options),
			ready = function () {
				if(xhr.readyState == 4){
					if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
						// set data
						var data = (opts.dataType == 'xml') ? xhr.responseXML : xhr.responseText;

						// parse json data
						if (opts.dataType == 'json')
							data = self.parseJSON(data);

						// success callback
						if (self.isFunction(opts.success))
							opts.success.call(opts, data, xhr.status, xhr);
					} else {
						// error callback
						if (self.isFunction(opts.error))
							opts.error.call(opts, xhr, xhr.status);
					}

					// complete callback
					if (self.isFunction(opts.complete))
						opts.complete.call(opts, xhr, xhr.status);
				}
			};

		this.xhr = xhr;

		// prepare options
		if (!opts.cache)
			opts.url += ((opts.url.indexOf('?') > -1) ? '&' : '?') + '_nocache='+ (new Date()).getTime();

		if (opts.data) {
			if (opts.type == 'GET') {
				opts.url += ((opts.url.indexOf('?') > -1) ? '&' : '?') + this.param(opts.data);
				opts.data = null;
			} else {
				opts.data = this.param(opts.data);
			}
		}

		// set request
		xhr.open(opts.type, opts.url, opts.async);
		xhr.setRequestHeader('Content-type', opts.contentType);

		if (opts.dataType && opts.accepts[opts.dataType])
			xhr.setRequestHeader('Accept', opts.accepts[opts.dataType]);

		if (opts.async) {
			xhr.onreadystatechange = ready;
			xhr.send(opts.data);
		} else {
			xhr.send(opts.data);
			ready();
		}

		return this;
	},

	/**
	 * Ajax GET request
	 * @param {String} url
	 * @param {String|Object} [data] Containing GET values
	 * @param {Function} [success] Callback when request was succesfull
	 * @return {This}
	 */
	get: function (url, data, success) {
		if (this.isFunction(data)) {
			success = data;
			data = null;
		}

		return this.call({
			url: url,
			type: 'GET',
			data: data,
			success: success
		});
	},

	/**
	 * Ajax POST request
	 * @param {String} url
	 * @param {String|Object} [data] Containing POST values
	 * @param {Function} [success] Callback when request was succesfull
	 * @return {This}
	 */
	post: function (url, data, success) {
		if (this.isFunction(data)) {
			success = data;
			data = null;
		}

		return this.call({
			url: url,
			type: 'POST',
			data: data,
			success: success
		});
	},

	/**
	 * Set content loaded by an ajax call
	 * @param {DOMElement|String} el Can contain an element or the id of the element
	 * @param {String} url The url of the ajax call (include GET vars in querystring)
	 * @param {String} [data] The POST data, when set method will be set to POST
	 * @param {Function} [complete] Callback when loading is completed
	 * @return {This}
	 */
	load: function (el, url, data, complete) {
		if (typeof el == 'string')
			el = document.getElementById(el);

		return this.call({
			url: url,
			type: data ? 'POST' : 'GET',
			data: data || null,
			complete: complete || null,
			success: function (html) {
				try {
					el.innerHTML = html;
				} catch (e) {
					var ph = document.createElement('div');
					ph.innerHTML = html;

					// empty element content
					while (el.firstChild)
						el.removeChild(el.firstChild);

					// set new html content
					for(var x = 0, max = ph.childNodes.length; x < max; x++)
						el.appendChild(ph.childNodes[x]);
				}
			}
		});
	},

	/**
	 * Make querystring outof object or array of values
	 * @param {Object|Array} obj Keys/values
	 * @return {String} The querystring
	 */
	param: function (obj) {
		var s = [];

		for (var key in obj) {
			s.push(encodeURIComponent(key) +'='+ encodeURIComponent(obj[key]));
		}

		return s.join('&');
	},

	/**
	 * Parse JSON string
	 * @param {String} data
	 * @return {Object} JSON object
	 */
	parseJSON: function (data) {
		if (typeof data !== 'string' || !data)
			return null;

		return eval('('+ this.trim(data) +')');
	},

	/**
	 * Trim spaces
	 * @param {String} str
	 * @return {String}
	 */
	trim: function (str) {
		return str.replace(/^\s+/, '').replace(/\s+$/, '');
	},

	/**
	 * Check if argument is function
	 * @param {Mixed} obj
	 * @return {Boolean}
	 */
	isFunction: function (obj) {
		return Object.prototype.toString.call( obj ) === '[object Function]';
	}

};

if (!window.Ajax) {
	/**
	 * Alias for SimpleAjax
	 * @namespace Ajax
	 */
	window.Ajax = SimpleAJAX;
}
<|endoftext|>


<commit_before>import Observer from './Observer.js';

/**
 * Editable Observer
 */
export default class EditableObserver extends Observer {
    /**
     * @inheritDoc
     *
     * @param {Editor} editor
     */
    constructor(editor) {
        super(editor);

        /**
         * Editables
         *
         * @type {String[]}
         * @readonly
         */
        this.editables = [...this.editor.tags].reduce((result, item) => {
            if (item[1].editable) {
                result.push(item[1].name);
            }

            return result;
        }, []);
    }

    /**
     * @inheritDoc
     */
    observe(ev) {
        ev.forEach(item => item.addedNodes.forEach(node => {
            if (node instanceof HTMLElement && this.editables.includes(node.tagName.toLowerCase())) {
                this.toEditable(node);
            } else if (node instanceof HTMLElement) {
                node.querySelectorAll(this.editables.join(', ')).forEach(this.toEditable)
            }
        }));
    }

    /**
     * Editable element callback
     *
     * @private
     *
     * @param {HTMLElement} node
     */
    toEditable(node) {
        node.contentEditable = 'true';
        node.focus();
        node.addEventListener('keydown', ev => {
            if (ev.key === 'Enter' && (!ev.shiftKey || !this.editor.allowed('br', node.tagName))) {
                ev.preventDefault();
                ev.cancelBubble = true;
            }
        });
        node.addEventListener('keyup', ev => {
            let tag;

            if (ev.key === 'Enter' && !ev.shiftKey && (tag = this.editor.getTag(node.tagName)) && tag.enter) {
                let current = node;
                let parentName;

                ev.preventDefault();
                ev.cancelBubble = true;

                do {
                    parentName = this.editor.getTagName(current.parentElement);

                    if (this.editor.allowed(tag.enter, parentName)) {
                        const newElement = this.editor.document.createElement(tag.enter);
                        this.editor.insert(newElement, current);
                        break;
                    }
                } while ((current = current.parentElement) && this.editor.content.contains(current) && !this.editor.content.isSameNode(current));
            }
        });
    }
}
<commit_msg>Fix regression<commit_after>import Observer from './Observer.js';

/**
 * Editable Observer
 */
export default class EditableObserver extends Observer {
    /**
     * @inheritDoc
     *
     * @param {Editor} editor
     */
    constructor(editor) {
        super(editor);

        /**
         * Editables
         *
         * @type {String[]}
         * @readonly
         */
        this.editables = [...this.editor.tags].reduce((result, item) => {
            if (item[1].editable) {
                result.push(item[1].name);
            }

            return result;
        }, []);
    }

    /**
     * @inheritDoc
     */
    observe(ev) {
        ev.forEach(item => item.addedNodes.forEach(node => {
            if (node instanceof HTMLElement && this.editables.includes(node.tagName.toLowerCase())) {
                this.toEditable(node);
            } else if (node instanceof HTMLElement) {
                node.querySelectorAll(this.editables.join(', ')).forEach(ed => this.toEditable(ed))
            }
        }));
    }

    /**
     * Editable element callback
     *
     * @private
     *
     * @param {HTMLElement} node
     */
    toEditable(node) {
        node.contentEditable = 'true';
        node.focus();
        node.addEventListener('keydown', ev => {
            if (ev.key === 'Enter' && (!ev.shiftKey || !this.editor.allowed('br', node.tagName))) {
                ev.preventDefault();
                ev.cancelBubble = true;
            }
        });
        node.addEventListener('keyup', ev => {
            let tag;

            if (ev.key === 'Enter' && !ev.shiftKey && (tag = this.editor.getTag(node.tagName)) && tag.enter) {
                let current = node;
                let parentName;

                ev.preventDefault();
                ev.cancelBubble = true;

                do {
                    parentName = this.editor.getTagName(current.parentElement);

                    if (this.editor.allowed(tag.enter, parentName)) {
                        const newElement = this.editor.document.createElement(tag.enter);
                        this.editor.insert(newElement, current);
                        break;
                    }
                } while ((current = current.parentElement) && this.editor.content.contains(current) && !this.editor.content.isSameNode(current));
            }
        });
    }
}
<|endoftext|>