<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { uniqBy } from 'lodash';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import cx from 'classnames';

import * as actions from '../../actions';
import { Sets, db, sync } from 'database';
import KeySelector from 'app/common/KeySelector';
import getKeyDiff from 'app/common/getKeyDiff';
import ChordLine from "./lines/ChordLine.js";
import ChordPair from "./lines/ChordPair.js";
import Line from "./lines/Line.js";
import transposeChord from '../common/transpose-chord';
import transposeLines from '../common/transpose-lines';

import './SongViewer.scss';

class SongViewer extends Component {
	static propTypes = {
		setKey: PropTypes.string
	};

	state = {
		capoAmount: 0,
		isSetListDropdownVisible: false,
		key: '',
		setList: [],
	};

	componentDidMount() {

		// Update an initial list when the component mounts.
		this.updateListOfSets();

		//Set the page title to the song title
		document.title = this.props.song.title;

		// Listen for any changes on the database.
		sync.on( "change", this.updateListOfSets.bind( this ) );

		this.handleProps( this.props );

		const songId = this.props.song._id;
		const setId = this.props.currentSet._id;
		console.log( 'songId', songId );
		console.log( 'setId', setId );

	}

	componentWillReceiveProps( nextProps ) {
		this.handleProps( nextProps );
	}

	componentWillUnmount() {
		sync.cancel();
	}

	addToSet = set => {

		const { song } = this.props;

		db.get( set._id ).then( doc => {

			const data = { ...doc };

			data.songs = data.songs || [];
			data.songs.push( {
				_id: song._id,
				key: song.key
			} );
			data.songs = uniqBy( data.songs, '_id' );

			db.put( data ).then( () => {

				if ( this.props.history ) {

					const location = {
						pathname: `/sets/${doc._id}`
					};

					this.props.history.push( location );
				}

			} ).catch( err => {

				if ( err.name === 'conflict' ) {

					console.error( 'SongList.addToSet: conflict -', err );

				} else {

					console.error( 'SongList.addToSet -', err );

				}

			} );

		} ).catch( err => {
			console.error( err );
		} );

	};

	handleProps = props => {

		const songUser = props.song.users && props.song.users.find( u => u.id === props.user.id ) || {};

		console.log( 'song key', props.song.key );
		console.log( 'set key', props.setKey );
		console.log( 'user key', songUser.key );

		const key = songUser.key || props.setKey || props.song.key;

		this.setState( { key } );

	};

	scrollToSection( section ) {

		let totalVertPadding = 32;
		let headerHeight = 92;

		location.href = '#';
		location.href = '#section-' + section.index;

		let scrollBottom = window.innerHeight - document.body.scrollTop + totalVertPadding;

		if ( headerHeight < scrollBottom ) {

			// Go back 92 pixels to offset the header.
			window.scrollBy( 0, -headerHeight );

		}

	}

	changeKey = amount => {
		const key = transposeChord( this.state.key, amount );
		this.props.setCurrentSongUserKey( key );
	};

	setListDropdownHide = () => this.setState( { isSetListDropdownVisible: false } );
	setListDropdownShow = () => this.setState( { isSetListDropdownVisible: true } );
	setListDropdownToggle = () => this.state.isSetListDropdownVisible ?
		this.setListDropdownHide() : this.setListDropdownShow();

	transposeDown = () => this.changeKey( -1 );
	transposeUp = () => this.changeKey( 1 );

	updateListOfSets = () => Sets.getAll().then( setList => this.setState( { setList } ) );

	render() {

		const { isSetListDropdownVisible, key, setList } = this.state;

		const song = { ...this.props.song };
		const setKey = this.props.setKey || song.key;

		const capo = getKeyDiff( setKey, key ); //this is only for display purposes, telling the user where to put the capo
		const transposeAmount = getKeyDiff( song.key, key ); //this is how much to transpose by

		const lines = song.lines ? transposeLines( song.lines, transposeAmount ) : [];

		let sections = [];

		return (
			song ?

											{song.title}

											{song.author}

										{/*Key of {song.key}*/}

												<KeySelector
													onSelect={( key, amount ) => this.changeKey( amount )}
													value={key}/>

												<Link className="button"
												      to={`/songs/${song._id}/edit`}>

													Edit Song
												</Link>

														<button className="button"
														        aria-haspopup="true"
														        aria-controls="dropdown-menu">

															Add to set

														</button>

															{setList.map( set => (
																 this.addToSet( set )}>
																	{set.title}

															) )}

Original Song key: {this.props.song.key}
Set key: {this.props.setKey}
Capo: {capo}

								{parseSong( lines, sections )}

				: null
		);

	}
}

const mapStateToProps = state => ({
	currentSet: state.currentSet,
	song: state.currentSong,
	user: state.user
});

export default connect( mapStateToProps, actions )( SongViewer );

//-----------------------------------------------------

export function parseSong( lines, sections ) {

	let children = [];
	let result = [];
	let section = '';
	let sectionIndex = 0;

	for ( let i = 0; i < lines.length; i++ ) {

		let line = lines[ i ];

		switch ( lines[ i ].type ) {

			case 'chord-line':
				children.push(
					<ChordLine key={i} chords={line.chords}/> );
				break;

			case 'chord-pair':
				children.push(
					<ChordPair key={i} chords={line.chords} text={line.text}/> );
				break;

			case 'empty':
				children.push(

 );
				break;

			case 'line':
				children.push(
					<Line key={i} text={line.text}/> );
				break;

			case 'section':

				if ( section ) {

					// Finish off last section
					result.push(
						<section
							id={`section-${sectionIndex}`}
							key={`section-${sectionIndex}`}
							className="song-viewer__section"
							data-section={section}
						>{children}

					);
					children = [];

				} else {

					result = result.concat( children );

				}

				section = line.text;
				sections.push( {
					title: line.text,
					index: sectionIndex
				} );

				sectionIndex++;

				break;

		}

	} //end of loop through lines

	if ( section ) {

		result.push( <section id={`section-${sectionIndex}`}
		                      key={`section-${sectionIndex}`}
		                      className="song-viewer__section"
		                      data-section={section}>{children}
 );

	}

	if ( children.length && !section ) {

		result = result.concat( children );

	}

	return result;

}
<commit_msg>decouple song lines from the Song object<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { uniqBy } from 'lodash';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import cx from 'classnames';

import * as actions from '../../actions';
import { Sets, db, sync } from 'database';
import KeySelector from 'app/common/KeySelector';
import getKeyDiff from 'app/common/getKeyDiff';
import ChordLine from "./lines/ChordLine.js";
import ChordPair from "./lines/ChordPair.js";
import Line from "./lines/Line.js";
import Parser from 'app/parsers/song-parser.js';
import transposeChord from '../common/transpose-chord';
import transposeLines from '../common/transpose-lines';

import './SongViewer.scss';

class SongViewer extends Component {
	static propTypes = {
		setKey: PropTypes.string
	};

	state = {
		capoAmount: 0,
		isSetListDropdownVisible: false,
		key: '',
		lines: [],
		setList: [],
	};

	componentDidMount() {

		// Update an initial list when the component mounts.
		this.updateListOfSets();

		//Set the page title to the song title
		document.title = this.props.song.title;

		// Listen for any changes on the database.
		sync.on( "change", this.updateListOfSets.bind( this ) );

		this.handleProps( this.props );

		const songId = this.props.song._id;
		const setId = this.props.currentSet._id;
		console.log( 'songId', songId );
		console.log( 'setId', setId );

	}

	componentWillReceiveProps( nextProps ) {
		this.handleProps( nextProps );
	}

	componentWillUnmount() {
		sync.cancel();
	}

	addToSet = set => {

		const { song } = this.props;

		db.get( set._id ).then( doc => {

			const data = { ...doc };

			data.songs = data.songs || [];
			data.songs.push( {
				_id: song._id,
				key: song.key
			} );
			data.songs = uniqBy( data.songs, '_id' );

			db.put( data ).then( () => {

				if ( this.props.history ) {

					const location = {
						pathname: `/sets/${doc._id}`
					};

					this.props.history.push( location );
				}

			} ).catch( err => {

				if ( err.name === 'conflict' ) {

					console.error( 'SongList.addToSet: conflict -', err );

				} else {

					console.error( 'SongList.addToSet -', err );

				}

			} );

		} ).catch( err => {
			console.error( err );
		} );

	};

	handleProps = props => {

		const songUser = props.song.users && props.song.users.find( u => u.id === props.user.id ) || {};

		console.log( 'song key', props.song.key );
		console.log( 'set key', props.setKey );
		console.log( 'user key', songUser.key );

		const key = songUser.key || props.setKey || props.song.key;

		const parser = new Parser();
		const lines = parser.parse( props.song.content );

		this.setState( { key, lines } );

	};

	scrollToSection( section ) {

		let totalVertPadding = 32;
		let headerHeight = 92;

		location.href = '#';
		location.href = '#section-' + section.index;

		let scrollBottom = window.innerHeight - document.body.scrollTop + totalVertPadding;

		if ( headerHeight < scrollBottom ) {

			// Go back 92 pixels to offset the header.
			window.scrollBy( 0, -headerHeight );

		}

	}

	changeKey = amount => {
		const key = transposeChord( this.state.key, amount );
		this.props.setCurrentSongUserKey( key );
	};

	setListDropdownHide = () => this.setState( { isSetListDropdownVisible: false } );
	setListDropdownShow = () => this.setState( { isSetListDropdownVisible: true } );
	setListDropdownToggle = () => this.state.isSetListDropdownVisible ?
		this.setListDropdownHide() : this.setListDropdownShow();

	transposeDown = () => this.changeKey( -1 );
	transposeUp = () => this.changeKey( 1 );

	updateListOfSets = () => Sets.getAll().then( setList => this.setState( { setList } ) );

	render() {

		const { isSetListDropdownVisible, key, lines: linesState, setList } = this.state;

		const song = { ...this.props.song };
		const setKey = this.props.setKey || song.key;

		const capo = getKeyDiff( setKey, key ); //this is only for display purposes, telling the user where to put the capo
		const transposeAmount = getKeyDiff( song.key, key ); //this is how much to transpose by

		const lines = transposeLines( linesState, transposeAmount );

		let sections = [];

		return (
			song ?

											{song.title}

											{song.author}

										{/*Key of {song.key}*/}

												<KeySelector
													onSelect={( key, amount ) => this.changeKey( amount )}
													value={key}/>

												<Link className="button"
												      to={`/songs/${song._id}/edit`}>

													Edit Song
												</Link>

														<button className="button"
														        aria-haspopup="true"
														        aria-controls="dropdown-menu">

															Add to set

														</button>

															{setList.map( set => (
																 this.addToSet( set )}>
																	{set.title}

															) )}

Original Song key: {this.props.song.key}
Set
													key: {this.props.setKey}
Capo: {capo}

								{parseSong( lines, sections )}

				: null
		);

	}
}

const mapStateToProps = state => ({
	currentSet: state.currentSet,
	song: state.currentSong,
	user: state.user
});

export default connect( mapStateToProps, actions )( SongViewer );

//-----------------------------------------------------

export function parseSong( lines, sections ) {

	let children = [];
	let result = [];
	let section = '';
	let sectionIndex = 0;

	for ( let i = 0; i < lines.length; i++ ) {

		let line = lines[ i ];

		switch ( lines[ i ].type ) {

			case 'chord-line':
				children.push(
					<ChordLine key={i} chords={line.chords}/> );
				break;

			case 'chord-pair':
				children.push(
					<ChordPair key={i} chords={line.chords} text={line.text}/> );
				break;

			case 'empty':
				children.push(

 );
				break;

			case 'line':
				children.push(
					<Line key={i} text={line.text}/> );
				break;

			case 'section':

				if ( section ) {

					// Finish off last section
					result.push(
						<section
							id={`section-${sectionIndex}`}
							key={`section-${sectionIndex}`}
							className="song-viewer__section"
							data-section={section}
						>{children}

					);
					children = [];

				} else {

					result = result.concat( children );

				}

				section = line.text;
				sections.push( {
					title: line.text,
					index: sectionIndex
				} );

				sectionIndex++;

				break;

		}

	} //end of loop through lines

	if ( section ) {

		result.push( <section id={`section-${sectionIndex}`}
		                      key={`section-${sectionIndex}`}
		                      className="song-viewer__section"
		                      data-section={section}>{children}
 );

	}

	if ( children.length && !section ) {

		result = result.concat( children );

	}

	return result;

}
<|endoftext|>


<commit_before>/** @flow */

import type {MatchMediaType} from '../config';
import type {PluginConfig, PluginResult} from './index';

let _windowMatchMedia;
const _getWindowMatchMedia = function(ExecutionEnvironment) {
  if (_windowMatchMedia === undefined) {
    _windowMatchMedia = !!ExecutionEnvironment.canUseDOM &&
      !!window &&
      !!window.matchMedia &&
      (mediaQueryString => window.matchMedia(mediaQueryString)) ||
      null;
  }
  return _windowMatchMedia;
};

export default function resolveMediaQueries({
  ExecutionEnvironment,
  getComponentField,
  getGlobalState,
  config,
  mergeStyles,
  setState,
  style
}: PluginConfig): PluginResult { // eslint-disable-line no-shadow
  // Remove media queries
  let newStyle = Object.keys(style).reduce(
    (styleWithoutMedia, key) => {
      if (key.indexOf('@media') !== 0) {
        styleWithoutMedia[key] = style[key];
      }
      return styleWithoutMedia;
    },
    {}
  );

  const matchMedia: ?MatchMediaType = config.matchMedia ||
    _getWindowMatchMedia(ExecutionEnvironment);

  if (!matchMedia) {
    return newStyle;
  }

  const newComponentFields = {};
  const mediaQueryListByQueryString =
    getGlobalState('mediaQueryListByQueryString') || {};

  Object.keys(style)
  .filter(name => name.indexOf('@media') === 0)
  .map(query => {
    const mediaQueryStyles = style[query];
    query = query.replace('@media ', '');

    // Create a global MediaQueryList if one doesn't already exist
    let mql = mediaQueryListByQueryString[query];
    if (!mql && matchMedia) {
      mediaQueryListByQueryString[query] = mql = matchMedia(query);
    }

    const listenersByQuery =
      getComponentField('_radiumMediaQueryListenersByQuery');

    if (!listenersByQuery || !listenersByQuery[query]) {
      const listener = () => setState(query, mql.matches, '_all');
      mql.addListener(listener);
      newComponentFields._radiumMediaQueryListenersByQuery = {
        ...listenersByQuery
      };
      newComponentFields._radiumMediaQueryListenersByQuery[query] = {
        remove() { mql.removeListener(listener); }
      };
    }

    // Apply media query states
    if (mql.matches) {
      newStyle = mergeStyles([newStyle, mediaQueryStyles]);
    }
  });

  // Remove media queries
  newStyle = Object.keys(newStyle).reduce(
    (styleWithoutMedia, key) => {
      if (key.indexOf('@media') !== 0) {
        styleWithoutMedia[key] = newStyle[key];
      }
      return styleWithoutMedia;
    },
    {}
  );

  return {
    componentFields: newComponentFields,
    globalState: {mediaQueryListByQueryString},
    style: newStyle
  };
}
<commit_msg>Don't add extra media query listeners<commit_after>/** @flow */

import type {MatchMediaType} from '../config';
import type {PluginConfig, PluginResult} from './index';

let _windowMatchMedia;
const _getWindowMatchMedia = function(ExecutionEnvironment) {
  if (_windowMatchMedia === undefined) {
    _windowMatchMedia = !!ExecutionEnvironment.canUseDOM &&
      !!window &&
      !!window.matchMedia &&
      (mediaQueryString => window.matchMedia(mediaQueryString)) ||
      null;
  }
  return _windowMatchMedia;
};

export default function resolveMediaQueries({
  ExecutionEnvironment,
  getComponentField,
  getGlobalState,
  config,
  mergeStyles,
  setState,
  style
}: PluginConfig): PluginResult { // eslint-disable-line no-shadow
  // Remove media queries
  let newStyle = Object.keys(style).reduce(
    (styleWithoutMedia, key) => {
      if (key.indexOf('@media') !== 0) {
        styleWithoutMedia[key] = style[key];
      }
      return styleWithoutMedia;
    },
    {}
  );

  const matchMedia: ?MatchMediaType = config.matchMedia ||
    _getWindowMatchMedia(ExecutionEnvironment);

  if (!matchMedia) {
    return newStyle;
  }

  const listenersByQuery = {
    ...getComponentField('_radiumMediaQueryListenersByQuery')
  };
  const newComponentFields = {
    _radiumMediaQueryListenersByQuery: listenersByQuery
  };
  const mediaQueryListByQueryString =
    getGlobalState('mediaQueryListByQueryString') || {};

  Object.keys(style)
  .filter(name => name.indexOf('@media') === 0)
  .map(query => {
    const mediaQueryStyles = style[query];
    query = query.replace('@media ', '');

    // Create a global MediaQueryList if one doesn't already exist
    let mql = mediaQueryListByQueryString[query];
    if (!mql && matchMedia) {
      mediaQueryListByQueryString[query] = mql = matchMedia(query);
    }

    if (!listenersByQuery || !listenersByQuery[query]) {
      const listener = () => setState(query, mql.matches, '_all');
      mql.addListener(listener);

      listenersByQuery[query] = {
        remove() {
          mql.removeListener(listener);
        }
      };
    }

    // Apply media query states
    if (mql.matches) {
      newStyle = mergeStyles([newStyle, mediaQueryStyles]);
    }
  });

  // Remove media queries
  newStyle = Object.keys(newStyle).reduce(
    (styleWithoutMedia, key) => {
      if (key.indexOf('@media') !== 0) {
        styleWithoutMedia[key] = newStyle[key];
      }
      return styleWithoutMedia;
    },
    {}
  );

  return {
    componentFields: newComponentFields,
    globalState: {mediaQueryListByQueryString},
    style: newStyle
  };
}
<|endoftext|>


<commit_before>user_pref("app.normandy.enabled", false);
user_pref("beacon.enabled", false);
user_pref("browser.backspace_action", 0);
user_pref("browser.discovery.enabled", false);
user_pref("browser.library.activity-stream.enabled", false);
user_pref("browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", false);
user_pref("browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", false);
user_pref("browser.newtabpage.enabled", false);
user_pref("browser.safebrowsing.downloads.enabled", false);
user_pref("browser.safebrowsing.malware.enabled", false);
user_pref("browser.safebrowsing.phishing.enabled", false);
user_pref("browser.startup.page", 3);
user_pref("browser.tabs.drawInTitlebar", true);
user_pref("browser.urlbar.matchBuckets", "general:5,suggestion:Infinity");
user_pref("dom.forms.autocomplete.formautofill", true);
user_pref("dom.ipc.processCount", 128);
user_pref("extensions.formautofill.creditCards.available", true);
user_pref("extensions.pocket.enabled", false);
user_pref("general.autoScroll", true);
user_pref("general.smoothScroll", false);
user_pref("gfx.webrender.all", true);
user_pref("javascript.options.asyncstack", false);
user_pref("layers.acceleration.force-enabled", true);
user_pref("layers.omtp.paint-workers", 4);
user_pref("media.eme.enabled", true);
user_pref("network.IDN_show_punycode", true);
user_pref("network.security.esni.enabled", true);
user_pref("network.trr.bootstrapAddress", "1.1.1.1");
user_pref("network.trr.mode", 2);
user_pref("privacy.donottrackheader.enabled", true);
<commit_msg>Add intl.accept_languages<commit_after>user_pref("app.normandy.enabled", false);
user_pref("beacon.enabled", false);
user_pref("browser.backspace_action", 0);
user_pref("browser.discovery.enabled", false);
user_pref("browser.library.activity-stream.enabled", false);
user_pref("browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", false);
user_pref("browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", false);
user_pref("browser.newtabpage.enabled", false);
user_pref("browser.safebrowsing.downloads.enabled", false);
user_pref("browser.safebrowsing.malware.enabled", false);
user_pref("browser.safebrowsing.phishing.enabled", false);
user_pref("browser.startup.page", 3);
user_pref("browser.tabs.drawInTitlebar", true);
user_pref("browser.urlbar.matchBuckets", "general:5,suggestion:Infinity");
user_pref("dom.forms.autocomplete.formautofill", true);
user_pref("dom.ipc.processCount", 128);
user_pref("extensions.formautofill.creditCards.available", true);
user_pref("extensions.pocket.enabled", false);
user_pref("general.autoScroll", true);
user_pref("general.smoothScroll", false);
user_pref("gfx.webrender.all", true);
user_pref("intl.accept_languages", "en,ro");
user_pref("javascript.options.asyncstack", false);
user_pref("layers.acceleration.force-enabled", true);
user_pref("layers.omtp.paint-workers", 4);
user_pref("media.eme.enabled", true);
user_pref("network.IDN_show_punycode", true);
user_pref("network.security.esni.enabled", true);
user_pref("network.trr.bootstrapAddress", "1.1.1.1");
user_pref("network.trr.mode", 2);
user_pref("privacy.donottrackheader.enabled", true);
<|endoftext|>


<commit_before>/**
 * Tatoeba Project, free collaborative creation of multilingual corpuses project
 * Copyright (C) 2014 Gilles Bedel
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

$(document).ready(function() {
    var rootUrl = get_tatoeba_root_url();

    // Show the transcribe buttons if there are some
    // hidden transcriptions
    $('.needsReview:hidden').each(function(index) {
        /* Move the show button in the menu */
        transcr = $(this);
        menu = transcr.closest(".sentences_set").find('.transcribe-buttons');
        button = transcr.find('.transcribe.option');
        button.click(function(event) {
            button.remove();
            transcr.toggle(true);
        });
        button.toggle(true);
        menu.append(button);
    });

    function markupToStored(lang, text) {
        if (lang == 'ja-Hrkt') {
            text = text.replace(
                // Converts the kanji｛reading｝ notation into [kanji|reading]
                // \p{Hiragana}: \u3041-\u3096\u309d\u309e
                // \p{Katakana}: \u30a1-\u30fa\u30fd\u30fe
                // ー: \u30fc
                // ｛: \uff5b
                // ｝: \uff5d
                // 〈〉《》「」『』: \u3008-\u300f
                /([^\u3041-\u3096\u309d\u309e\u30a1-\u30fa\u30fd\u30fe\u30fc\u3008-\u300f]*)\uff5b([^\uff5d]*)\uff5d/g,
                '[$1|$2]'
            );
        }
        return text;
    }

    $('.editable.transcription').each(function() {
        var div = $(this);
        var previousValue = {};

        sentenceId = div.parent().parent().attr('data-sentence-id');
        script = div.attr('data-script');
        saveUrl = rootUrl + '/transcriptions/save/' + sentenceId + '/' + script;

        $(this).editable(saveUrl, {
            type      : 'textarea',
            cancel    : div.attr('data-cancel'),
            submit    : div.attr('data-submit'),
            event     : 'edit_transcription',
            id        : 'divId',
            height    : false, // disable autoheight, we'll set it in onedit()
            onedit    : function(settings, self) {
                $(self).find("rt").hide();
                settings.height = $(self).height();
                $(self).find("rt").show();
            },
            data : function(value, settings) {
                var contents = $('').html(value);
                return contents.find('.markup').text() || contents.text();
            },
            ajaxoptions : {
                success : function(result, status) {
                    div.editing = false;
                    div.parent().replaceWith(result);
                }
            },
            onsubmit  : function(settings, self) {
                // Save the submitted value to restore it on error
                $(self).find("textarea").each(function(idx) {
                    previousValue[idx] = $(this).val();
                });

                // Format entered transcription
                var input = $(this).find("textarea");
                var entered = input.val();
                var storedFormat = markupToStored($(self).attr('lang'), entered);
                input.val(storedFormat);

                // Handle reset button
                if (self.resetClicked) {
                    settings.target = settings.target.replace(/\/save\//, '/reset/');
                }
                return true;
            },
            onerror   : function (settings, self, xhr) {
                // Go back to the previous editing state
                $(self).html(self.revert);
                self.editing = false;
                $(self).trigger(settings.event);

                // Restore the previous value
                $(self).find("textarea").each(function(idx) {
                    $(this).val(previousValue[idx]);
                });
                return false; // don't reset the form
            },
            indicator : '',
            tooltip   : div.attr('data-tooltip'),
            cssclass  : 'editInPlaceForm',
            onblur    : 'ignore'
        }).bind('edit_transcription', function(e) {
            $(this).find('textarea').keydown(function(event) {
                if (event.which == 13)
                    $(this).closest('form').submit();
            });
            // Add third reset button
            if ($(this).find('button').length == 2) {
                var editable = this;
                var reset = $('<button type="submit"/>');
                reset.html(div.attr('data-reset'));
                reset.click(function(event) {
                    // hack to distinguish submit event
                    // triggered from this button
                    editable.resetClicked = true;
                });
                $(editable).find('form').append(reset);
            }
        });

        $(".edit_transcription").bind("click", function() {
            $(this).closest('.transcriptionContainer').find('.transcription').trigger("edit_transcription");
        });
    });
});
<commit_msg>Keep transcription buttons of translations hidden<commit_after>/**
 * Tatoeba Project, free collaborative creation of multilingual corpuses project
 * Copyright (C) 2014 Gilles Bedel
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

$(document).ready(function() {
    var rootUrl = get_tatoeba_root_url();

    // Show the transcribe buttons if there are some
    // hidden transcriptions
    $('.needsReview:hidden').each(function(index) {
        var transcr = $(this);
        if (transcr.closest('.translations').length == 0) {
            /* Transcription of the main sentence, not a translation */
            /* Move the show button in the menu */
            var menu = transcr.closest('.sentences_set').find('.transcribe-buttons');
            var button = transcr.find('.transcribe.option');
            button.click(function(event) {
                button.remove();
                transcr.toggle(true);
            });
            button.toggle(true);
            menu.append(button);
        }
    });

    function markupToStored(lang, text) {
        if (lang == 'ja-Hrkt') {
            text = text.replace(
                // Converts the kanji｛reading｝ notation into [kanji|reading]
                // \p{Hiragana}: \u3041-\u3096\u309d\u309e
                // \p{Katakana}: \u30a1-\u30fa\u30fd\u30fe
                // ー: \u30fc
                // ｛: \uff5b
                // ｝: \uff5d
                // 〈〉《》「」『』: \u3008-\u300f
                /([^\u3041-\u3096\u309d\u309e\u30a1-\u30fa\u30fd\u30fe\u30fc\u3008-\u300f]*)\uff5b([^\uff5d]*)\uff5d/g,
                '[$1|$2]'
            );
        }
        return text;
    }

    $('.editable.transcription').each(function() {
        var div = $(this);
        var previousValue = {};

        sentenceId = div.parent().parent().attr('data-sentence-id');
        script = div.attr('data-script');
        saveUrl = rootUrl + '/transcriptions/save/' + sentenceId + '/' + script;

        $(this).editable(saveUrl, {
            type      : 'textarea',
            cancel    : div.attr('data-cancel'),
            submit    : div.attr('data-submit'),
            event     : 'edit_transcription',
            id        : 'divId',
            height    : false, // disable autoheight, we'll set it in onedit()
            onedit    : function(settings, self) {
                $(self).find("rt").hide();
                settings.height = $(self).height();
                $(self).find("rt").show();
            },
            data : function(value, settings) {
                var contents = $('').html(value);
                return contents.find('.markup').text() || contents.text();
            },
            ajaxoptions : {
                success : function(result, status) {
                    div.editing = false;
                    div.parent().replaceWith(result);
                }
            },
            onsubmit  : function(settings, self) {
                // Save the submitted value to restore it on error
                $(self).find("textarea").each(function(idx) {
                    previousValue[idx] = $(this).val();
                });

                // Format entered transcription
                var input = $(this).find("textarea");
                var entered = input.val();
                var storedFormat = markupToStored($(self).attr('lang'), entered);
                input.val(storedFormat);

                // Handle reset button
                if (self.resetClicked) {
                    settings.target = settings.target.replace(/\/save\//, '/reset/');
                }
                return true;
            },
            onerror   : function (settings, self, xhr) {
                // Go back to the previous editing state
                $(self).html(self.revert);
                self.editing = false;
                $(self).trigger(settings.event);

                // Restore the previous value
                $(self).find("textarea").each(function(idx) {
                    $(this).val(previousValue[idx]);
                });
                return false; // don't reset the form
            },
            indicator : '',
            tooltip   : div.attr('data-tooltip'),
            cssclass  : 'editInPlaceForm',
            onblur    : 'ignore'
        }).bind('edit_transcription', function(e) {
            $(this).find('textarea').keydown(function(event) {
                if (event.which == 13)
                    $(this).closest('form').submit();
            });
            // Add third reset button
            if ($(this).find('button').length == 2) {
                var editable = this;
                var reset = $('<button type="submit"/>');
                reset.html(div.attr('data-reset'));
                reset.click(function(event) {
                    // hack to distinguish submit event
                    // triggered from this button
                    editable.resetClicked = true;
                });
                $(editable).find('form').append(reset);
            }
        });

        $(".edit_transcription").bind("click", function() {
            $(this).closest('.transcriptionContainer').find('.transcription').trigger("edit_transcription");
        });
    });
});
<|endoftext|>


<commit_before>const _ = require('lodash');
const AccessDeniedError = require('./errors/AccessDeniedError');

/**
 * Merges local and global JWT settings
* @function mergeJwt
* @param  {object} local  local settings
* @param  {object} global global settings
* @return {object} merged settings
*/
function mergeJwt(local, global) {
    if (!local && !global) return undefined;
    if (!local) return global;
    if (!global) return local;
    return {
        key: local.key || global.key,
        optional: (typeof local.optional == 'undefined') ? global.optional : local.optional
    };
}

/**
 * Creates an internal JWT settings object
* @function getJwt
* @param  {(object|string)} o the user setting to transform.
                            If it is a string, it is treated as a key and JWT is not optional.
* @param  {string} [o.key] the jwt key
* @param  {boolean} [o.optional] if true the JWT is optional for the operation
* @return {object} an JWT settings object
*/
function getJwt(o) {
    if (o) {
        if (typeof o === 'string')
            return {key: o};
        else
            return _.pick(o, ['key', 'optional']);
    } else {
        return {};
    }
}

/**
* When passed a function, calls it with other arguments, else returns the object.
* Promises are resolved automatically.
* @function resolveFn
* @param  {(object|function)} o The function or object to resolve
* @return {any} The object or the result of calling the function
*/
async function resolveFn(o) {
    if (typeof o !== 'function')
        return o;
    var args = Array.prototype.slice.call(arguments, 1);
    return await o.apply(null, args);
}

/**
 * Retrieves hash and range key from a model or index.
* @function getKeys
* @param  {object} model   The model to retrieve the hash and range key from.
* @param  {object} options The options used for building the router. If a property "gsi" is present and a string,
                            hash and range key of that index are retrieved.
* @return {object} An object with properties hashKey and rangeKey, containing the respective property name
*/
function getKeys(model, options) {
    let keys = {};
    // If this is for an index, use it
    if (options.gsi && typeof options.gsi === 'string') {
        let index = model.$__.schema.indexes.global[options.gsi];
        // TODO: Maybe hashKey must be set to the index name (dynamoose/lib/Query.js:85)
        keys.hashKey = index.name;
        if (index.indexes[options.gsi].rangeKey) {
            keys.rangeKey = index.indexes[options.gsi].rangeKey;
        }
    } else {
        keys.hashKey = model.$__.schema.hashKey.name;
        if (model.$__.schema.rangeKey) {
            keys.rangeKey = model.$__.schema.rangeKey.name;
        }
    }
    return keys;
}

/**
 * Merges local and global options for methods
* @function mergeOptions
* @param  {object} local  Local options
* @param  {object} global Global options
* @return {object} Merged options
*/
function mergeOptions(local, global) {
    return Object.assign({}, global, local,
            {jwt: mergeJwt(local.jwt, global ? global.jwt : null)});
}

/**
 * Asynchronously executes an action and, if present, other actions in parallel and merges their results.
* @function execParallel
* @param  {type} ctx      the koa context
* @param  {function} action   the main action
* @param  {object} [parallel] an object containing the information for parallel actions
* @param  {function[]} [parallel.actions]   the actions to execute in parallel
* @param  {function} [parallel.merge]   the function to merge the results
* @return {any} the result of the merge operation or of the main action if no parallel actions are given
*/
async function execParallel(ctx, action, parallel) {
    let actions = [action];
    if (parallel) {
        actions = [...actions, ...parallel.actions.map(a => a(ctx))];
    }
    let results = await Promise.all(actions);
    return parallel ? parallel.merge(ctx, results) : results[0];
}

function handleAuthorize(auth) {
    if (!auth) throw new AccessDeniedError();
    else if (auth instanceof Error) {
        throw new AccessDeniedError(auth.message, auth);
    } else if (typeof auth === 'object' && !auth.pass) {
        throw new AccessDeniedError(auth.message);
    }
}

/**
 * Authorizes a user based on the koa context.
 * Throws an AccessDeniedError when function returns false,
 * an error or a special object.
* @function handleAuthorize
* @param  {object} ctx the koa context
* @param  {function} authorize the user-defined function to use.
* @return {undefined}
*/
async function authorize(ctx, authorize) {
    let auth = await authorize(ctx);
    handleAuthorize(auth);
}

/**
 * Authorizes a user based on the koa context and some data.
 * Throws an AccessDeniedError when function returns false,
 * an error or a special object.
* @function postAuthorize
* @param  {object} ctx the koa context
* @param  {any} data    the data
* @param  {object} options the options object to check for a postAuthorize property
* @return {undefined}
*/
async function postAuthorize(ctx, data, options) {
    if (options.postAuthorize) {
        let auth = await options.postAuthorize(ctx, data);
        handleAuthorize(auth);
    }
}

/**
 * Applies a mapping function to either the object itself or,
 * if it contains DynamoDB updates to properties, to the individual updates ($PUT, $ADD, $DELETE).
* @function applyProps
* @param  {object} d  The update object
* @param  {function} fn The mapping function
* @return {object} The transformed object
*/
function applyProps(d, fn) {
    if (d.$PUT || d.$ADD || d.$DELETE) {
        d = _.pick(d, '$PUT', '$DELETE', '$ADD');
        _.forOwn(d, (v, k) => d[k] = fn(v));
        return d;
    } else {
        return fn(d);
    }
}

module.exports = {
    getJwt: getJwt,
    resolveFn: resolveFn,
    getKeys: getKeys,
    mergeOptions: mergeOptions,
    execParallel: execParallel,
    authorize: authorize,
    postAuthorize: postAuthorize,
    applyProps: applyProps
};<commit_msg>Rename authorize function parameter<commit_after>const _ = require('lodash');
const AccessDeniedError = require('./errors/AccessDeniedError');

/**
 * Merges local and global JWT settings
* @function mergeJwt
* @param  {object} local  local settings
* @param  {object} global global settings
* @return {object} merged settings
*/
function mergeJwt(local, global) {
    if (!local && !global) return undefined;
    if (!local) return global;
    if (!global) return local;
    return {
        key: local.key || global.key,
        optional: (typeof local.optional == 'undefined') ? global.optional : local.optional
    };
}

/**
 * Creates an internal JWT settings object
* @function getJwt
* @param  {(object|string)} o the user setting to transform.
                            If it is a string, it is treated as a key and JWT is not optional.
* @param  {string} [o.key] the jwt key
* @param  {boolean} [o.optional] if true the JWT is optional for the operation
* @return {object} an JWT settings object
*/
function getJwt(o) {
    if (o) {
        if (typeof o === 'string')
            return {key: o};
        else
            return _.pick(o, ['key', 'optional']);
    } else {
        return {};
    }
}

/**
* When passed a function, calls it with other arguments, else returns the object.
* Promises are resolved automatically.
* @function resolveFn
* @param  {(object|function)} o The function or object to resolve
* @return {any} The object or the result of calling the function
*/
async function resolveFn(o) {
    if (typeof o !== 'function')
        return o;
    var args = Array.prototype.slice.call(arguments, 1);
    return await o.apply(null, args);
}

/**
 * Retrieves hash and range key from a model or index.
* @function getKeys
* @param  {object} model   The model to retrieve the hash and range key from.
* @param  {object} options The options used for building the router. If a property "gsi" is present and a string,
                            hash and range key of that index are retrieved.
* @return {object} An object with properties hashKey and rangeKey, containing the respective property name
*/
function getKeys(model, options) {
    let keys = {};
    // If this is for an index, use it
    if (options.gsi && typeof options.gsi === 'string') {
        let index = model.$__.schema.indexes.global[options.gsi];
        // TODO: Maybe hashKey must be set to the index name (dynamoose/lib/Query.js:85)
        keys.hashKey = index.name;
        if (index.indexes[options.gsi].rangeKey) {
            keys.rangeKey = index.indexes[options.gsi].rangeKey;
        }
    } else {
        keys.hashKey = model.$__.schema.hashKey.name;
        if (model.$__.schema.rangeKey) {
            keys.rangeKey = model.$__.schema.rangeKey.name;
        }
    }
    return keys;
}

/**
 * Merges local and global options for methods
* @function mergeOptions
* @param  {object} local  Local options
* @param  {object} global Global options
* @return {object} Merged options
*/
function mergeOptions(local, global) {
    return Object.assign({}, global, local,
            {jwt: mergeJwt(local.jwt, global ? global.jwt : null)});
}

/**
 * Asynchronously executes an action and, if present, other actions in parallel and merges their results.
* @function execParallel
* @param  {type} ctx      the koa context
* @param  {function} action   the main action
* @param  {object} [parallel] an object containing the information for parallel actions
* @param  {function[]} [parallel.actions]   the actions to execute in parallel
* @param  {function} [parallel.merge]   the function to merge the results
* @return {any} the result of the merge operation or of the main action if no parallel actions are given
*/
async function execParallel(ctx, action, parallel) {
    let actions = [action];
    if (parallel) {
        actions = [...actions, ...parallel.actions.map(a => a(ctx))];
    }
    let results = await Promise.all(actions);
    return parallel ? parallel.merge(ctx, results) : results[0];
}

function handleAuthorize(auth) {
    if (!auth) throw new AccessDeniedError();
    else if (auth instanceof Error) {
        throw new AccessDeniedError(auth.message, auth);
    } else if (typeof auth === 'object' && !auth.pass) {
        throw new AccessDeniedError(auth.message);
    }
}

/**
 * Authorizes a user based on the koa context.
 * Throws an AccessDeniedError when function returns false,
 * an error or a special object.
* @function handleAuthorize
* @param  {object} ctx the koa context
* @param  {function} authorize the user-defined function to use.
* @return {undefined}
*/
async function authorize(ctx, authorizeFn) {
    let auth = await authorizeFn(ctx);
    handleAuthorize(auth);
}

/**
 * Authorizes a user based on the koa context and some data.
 * Throws an AccessDeniedError when function returns false,
 * an error or a special object.
* @function postAuthorize
* @param  {object} ctx the koa context
* @param  {any} data    the data
* @param  {object} options the options object to check for a postAuthorize property
* @return {undefined}
*/
async function postAuthorize(ctx, data, options) {
    if (options.postAuthorize) {
        let auth = await options.postAuthorize(ctx, data);
        handleAuthorize(auth);
    }
}

/**
 * Applies a mapping function to either the object itself or,
 * if it contains DynamoDB updates to properties, to the individual updates ($PUT, $ADD, $DELETE).
* @function applyProps
* @param  {object} d  The update object
* @param  {function} fn The mapping function
* @return {object} The transformed object
*/
function applyProps(d, fn) {
    if (d.$PUT || d.$ADD || d.$DELETE) {
        d = _.pick(d, '$PUT', '$DELETE', '$ADD');
        _.forOwn(d, (v, k) => d[k] = fn(v));
        return d;
    } else {
        return fn(d);
    }
}

module.exports = {
    getJwt: getJwt,
    resolveFn: resolveFn,
    getKeys: getKeys,
    mergeOptions: mergeOptions,
    execParallel: execParallel,
    authorize: authorize,
    postAuthorize: postAuthorize,
    applyProps: applyProps
};<|endoftext|>


<commit_before>import _ from 'lodash';
import React from 'react';
import loanActionMap from './data/loanActionMap';

export function formatDate(dateStr, intl) {
  if (!dateStr) return dateStr;
  return intl.formatDate(dateStr);
}

export function formatDateTime(dateStr, intl) {
  if (!dateStr) return dateStr;
  return `${intl.formatDate(dateStr)} ${intl.formatTime(dateStr)}`;
}

export function futureDate(dateStr, locale, days) {
  if (!dateStr) return dateStr;
  const date = new Date(Date.parse(dateStr));
  date.setDate(date.getDate() + days);
  return date.toLocaleDateString(locale);
}

export function getFullName(user) {
  const lastName = _.get(user, ['personal', 'lastName'], '');
  const firstName = _.get(user, ['personal', 'firstName'], '');
  const middleName = _.get(user, ['personal', 'middleName'], '');

  return `${lastName}${firstName ? ', ' : ' '}${firstName} ${middleName}`;
}

export function getRowURL(user) {
  return `/users/view/${user.id}`;
}

export function getAnchoredRowFormatter(row) {
  return (
    <a
      href={getRowURL(row.rowData)} key={`row-${row.rowIndex}`}
      aria-label={row.labelStrings && row.labelStrings.join('...')}
      role="listitem"
      className={`${row.rowClass}`}
      {...row.rowProps}
    >
      {row.cells}
    </a>
  );
}
export function isSubstringsInString(listSubStrings, testString) {
  return new RegExp(listSubStrings.join('|')).test(testString);
}
export function getItemStatusFormatter(loan) {
  return isSubstringsInString(['renewed', 'recalled', 'requested'], loan.action) ?
    `${_.get(loan, ['item', 'status', 'name'], '')} - ${loanActionMap[loan.action]}` :
    `${_.get(loan, ['item', 'status', 'name'], '')}`;
}

export function eachPromise(arr, fn) {
  if (!Array.isArray(arr)) return Promise.reject(new Error('Array not found'));
  return arr.reduce((prev, cur) => (prev.then(() => fn(cur))), Promise.resolve());
}
<commit_msg>Cleanup<commit_after>import _ from 'lodash';
import React from 'react';
import loanActionMap from './data/loanActionMap';

export function formatDate(dateStr, intl) {
  if (!dateStr) return dateStr;
  return intl.formatDate(dateStr);
}

export function formatDateTime(dateStr, intl) {
  if (!dateStr) return dateStr;
  return `${intl.formatDate(dateStr)} ${intl.formatTime(dateStr)}`;
}

export function getFullName(user) {
  const lastName = _.get(user, ['personal', 'lastName'], '');
  const firstName = _.get(user, ['personal', 'firstName'], '');
  const middleName = _.get(user, ['personal', 'middleName'], '');

  return `${lastName}${firstName ? ', ' : ' '}${firstName} ${middleName}`;
}

export function getRowURL(user) {
  return `/users/view/${user.id}`;
}

export function getAnchoredRowFormatter(row) {
  return (
    <a
      href={getRowURL(row.rowData)} key={`row-${row.rowIndex}`}
      aria-label={row.labelStrings && row.labelStrings.join('...')}
      role="listitem"
      className={`${row.rowClass}`}
      {...row.rowProps}
    >
      {row.cells}
    </a>
  );
}
export function isSubstringsInString(listSubStrings, testString) {
  return new RegExp(listSubStrings.join('|')).test(testString);
}
export function getItemStatusFormatter(loan) {
  return isSubstringsInString(['renewed', 'recalled', 'requested'], loan.action) ?
    `${_.get(loan, ['item', 'status', 'name'], '')} - ${loanActionMap[loan.action]}` :
    `${_.get(loan, ['item', 'status', 'name'], '')}`;
}

export function eachPromise(arr, fn) {
  if (!Array.isArray(arr)) return Promise.reject(new Error('Array not found'));
  return arr.reduce((prev, cur) => (prev.then(() => fn(cur))), Promise.resolve());
}
<|endoftext|>


<commit_before>'use strict'

var cc = require('./core');

exports.timeout = function(milliseconds) {
  var ch = cc.chan(0);
  var t = setTimeout(function() { clearTimeout(t); ch.close(); }, milliseconds);
  return ch;
};

exports.source = function(gen, ctrl) {
  var ch = cc.chan();

  cc.go(function*() {
    for (;;) {
      if (ctrl && (yield cc.select([ctrl], null)))
        break;
      var next = gen.next();
      if (next.done)
        break;
      if (!(yield ch.push(next.value)))
        break;
    }
    ch.close();
  });

  return ch;
};

exports.each = function(fn, ch) {
  var done = cc.chan(0);

  cc.go(function*() {
    var val;
    while((val = yield ch.pull()) !== undefined)
      fn(val);
    done.close();
  });

  return done;
};

exports.map = function(fn, ch, closeInput) {
  var outch = cc.chan();

  cc.go(function*() {
    var val;
    while((val = yield ch.pull()) !== undefined)
      if (!(yield outch.push(fn(val))))
        break;
    if (closeInput)
      ch.close();
    outch.close();
  });

  return outch;
};

exports.filter = function(pred, ch, closeInput) {
  var outch = cc.chan();

  cc.go(function*() {
    var val;
    while((val = yield ch.pull()) !== undefined)
      if (pred(val))
        if (!(yield outch.push(val)))
          break;
    if (closeInput)
      ch.close();
    outch.close();
  });

  return outch;
};

exports.take = function(n, ch, closeInput) {
  var outch = cc.chan();

  cc.go(function*() {
    var val, i;
    for (i = 0; i < n; ++i) {
      val = yield ch.pull();
      if (val === undefined || !(yield outch.push(val)))
        break;
    }
    if (closeInput)
      ch.close();
    outch.close();
  });

  return outch;
};

exports.merge = function(inchs, closeInputs) {
  var outch = cc.chan();
  var active = inchs.slice();

  cc.go(function*() {
    while (active.length > 0) {
      var res = yield cc.select(active);
      if (res.value === undefined)
        active.splice(res.index, 1);
      else
        if (!(yield outch.push(res.value)))
          break;
    }

    if (closeInputs)
      for (ch of active.values())
        ch.close();
    outch.close();
  });

  return outch;
};

exports.zip = function(inchs, closeInputs) {
  var outch = cc.chan();

  cc.go(function*() {
    var results, active, indices, i, res;

    results = new Array(inchs.length);

    while (true) {
      active  = inchs.slice();
      indices = []
      for (i = 0; i < inchs.length; ++i)
        indices.push(i);

      while (active.length > 0) {
        res = yield cc.select(active);

        if (res.value === undefined) {
          outch.close();
          break;
        }

        i = res.index;
        results[indices[i]] = res.value;
        active.splice(i, 1);
        indices.splice(i, 1);
      }

      if (!(yield outch.push(results)))
        break;
    }
    if (closeInputs)
      for (ch of inchs.values())
        ch.close();
    outch.close();
  });

  return outch;
};
<commit_msg>fixed problems found via 'use strict'<commit_after>'use strict'

var cc = require('./core');

exports.timeout = function(milliseconds) {
  var ch = cc.chan(0);
  var t = setTimeout(function() { clearTimeout(t); ch.close(); }, milliseconds);
  return ch;
};

exports.source = function(gen, ctrl) {
  var ch = cc.chan();

  cc.go(function*() {
    for (var x of gen) {
      if (ctrl && (yield cc.select([ctrl], null)))
        break;
      if (!(yield ch.push(x)))
        break;
    }
    ch.close();
  });

  return ch;
};

exports.each = function(fn, ch) {
  var done = cc.chan(0);

  cc.go(function*() {
    var val;
    while((val = yield ch.pull()) !== undefined)
      fn(val);
    done.close();
  });

  return done;
};

exports.map = function(fn, ch, closeInput) {
  var outch = cc.chan();

  cc.go(function*() {
    var val;
    while((val = yield ch.pull()) !== undefined)
      if (!(yield outch.push(fn(val))))
        break;
    if (closeInput)
      ch.close();
    outch.close();
  });

  return outch;
};

exports.filter = function(pred, ch, closeInput) {
  var outch = cc.chan();

  cc.go(function*() {
    var val;
    while((val = yield ch.pull()) !== undefined)
      if (pred(val))
        if (!(yield outch.push(val)))
          break;
    if (closeInput)
      ch.close();
    outch.close();
  });

  return outch;
};

exports.take = function(n, ch, closeInput) {
  var outch = cc.chan();

  cc.go(function*() {
    var val, i;
    for (i = 0; i < n; ++i) {
      val = yield ch.pull();
      if (val === undefined || !(yield outch.push(val)))
        break;
    }
    if (closeInput)
      ch.close();
    outch.close();
  });

  return outch;
};

exports.merge = function(inchs, closeInputs) {
  var outch = cc.chan();
  var active = inchs.slice();

  cc.go(function*() {
    while (active.length > 0) {
      var res = yield cc.select(active);
      if (res.value === undefined)
        active.splice(res.index, 1);
      else
        if (!(yield outch.push(res.value)))
          break;
    }

    if (closeInputs)
      for (var ch of active.values())
        ch.close();
    outch.close();
  });

  return outch;
};

exports.zip = function(inchs, closeInputs) {
  var outch = cc.chan();

  cc.go(function*() {
    var results, active, indices, i, res;

    results = new Array(inchs.length);

    while (true) {
      active  = inchs.slice();
      indices = []
      for (i = 0; i < inchs.length; ++i)
        indices.push(i);

      while (active.length > 0) {
        res = yield cc.select(active);

        if (res.value === undefined) {
          outch.close();
          break;
        }

        i = res.index;
        results[indices[i]] = res.value;
        active.splice(i, 1);
        indices.splice(i, 1);
      }

      if (!(yield outch.push(results)))
        break;
    }
    if (closeInputs)
      for (var ch of inchs.values())
        ch.close();
    outch.close();
  });

  return outch;
};
<|endoftext|>


<commit_before>var validator = require('validator/lib/validators');

var field = module.exports = {};

/**
 * The type() hook.
 */
field.type = function(types, callback) {
  var newTypes = {};

  newTypes['field'] = {
    title: 'Field',
    description: 'Fields add schema to types and provide validation and output sanitizing.',
    access: {
      'list': true,
      'load': true,
      'add': false,
      'edit': false,
      'delete': false
    },
  };

  callback(null, newTypes);
};

/**
 * The field() hook.
 */
field.field = function(fields, callback) {
  var newFields = {};

  newFields['text'] = {
    title: 'Text',
    element: 'text',
    validate: function(settings, item, next) {
      // Default minLenght to 1.
      settings.minLength = settings.minLength || 1;

      // Default maxLenght to 256.
      settings.maxLength = settings.maxLength || 256;

      next(null, !validator.notEmpty(item[settings.name]) || validator.len(item[settings.name].toString(), settings.minLength, settings.maxLength) || 'Value must have from ' + settings.minLength + ' to ' + settings.maxLength + ' characters.');
    }
  };
  newFields['number'] = {
    title: 'Number',
    element: 'number',
    validate: function(settings, item, next) {
      next(null, validator.isNumeric(item[settings.name].toString()) || 'Invalid number.');
    }
  };
  newFields['date'] = {
    title: 'Date',
    element: 'date',
    validate: function(settings, item, next) {
      next(null, validator.isDate(item[settings.name].toString()) || 'Invalid date.');
    }
  };
  newFields['email'] = {
    title: 'Email',
    element: 'email',
    validate: function(settings, item, next) {
      // Email validator oddly returns the email itself, so need to convert to
      // boolean.
      next(null, new Boolean(validator.isEmail(item[settings.name].toString())) || 'Invalid email.');
    }
  };
  newFields['password'] = {
    title: 'Password',
    element: 'password',
    validate: function(settings, item, next) {
      var minLength = settings.minLength || 6;
      next(null, validator.len(item[settings.name].toString(), settings.minLength || 6) || 'Password must have at least ' + minLength + ' characters.');
    }
  };
  newFields['url'] = {
    title: 'URL',
    element: 'url',
    validate: function(settings, item, next) {
      next(null, validator.isUrl(item[settings.name].toString()) || 'Invalid URL.');
    }
  };
  newFields['submit'] = {
    title: 'Submit',
    element: 'submit'
  };

  callback(null, newFields);
};

/**
 * The preSave() hook.
 */
field.preSave = function(type, data, callback) {
  var application = this.application;

  if (type.settings.fields) {
    // Validate type fields.
    async.each(Object.keys(type.settings.fields), function(fieldName, next) {
      var fieldSettings = type.settings.fields[fieldName];

      // Add fieldName to fieldSettings.
      fieldSettings.name = fieldName;

      var Field = application.type('field');
      Field.load(fieldSettings.type, function(error, field) {
        if (error) {
          // Application error.
          return next(error);
        }
        if (!field || !field.preSave) {
          // Field is of an unrecognized type or there's not a preSave()
          // callback.
          return next();
        }

        field.preSave(fieldSettings, data, function(error, result) {
          if (error) {
            // Application error.
            return next(error);
          }
          next();
        });
      });
    },
    function(error) {
      if (error) {
        // Application error.
        return callback(error);
      }
      callback();
    });
  }
  else {
    callback();
  }
};
<commit_msg>Issue #103: Adding missing async dependency.<commit_after>var async = require('async');
var validator = require('validator/lib/validators');

var field = module.exports = {};

/**
 * The type() hook.
 */
field.type = function(types, callback) {
  var newTypes = {};

  newTypes['field'] = {
    title: 'Field',
    description: 'Fields add schema to types and provide validation and output sanitizing.',
    access: {
      'list': true,
      'load': true,
      'add': false,
      'edit': false,
      'delete': false
    },
  };

  callback(null, newTypes);
};

/**
 * The field() hook.
 */
field.field = function(fields, callback) {
  var newFields = {};

  newFields['text'] = {
    title: 'Text',
    element: 'text',
    validate: function(settings, item, next) {
      // Default minLenght to 1.
      settings.minLength = settings.minLength || 1;

      // Default maxLenght to 256.
      settings.maxLength = settings.maxLength || 256;

      next(null, !validator.notEmpty(item[settings.name]) || validator.len(item[settings.name].toString(), settings.minLength, settings.maxLength) || 'Value must have from ' + settings.minLength + ' to ' + settings.maxLength + ' characters.');
    }
  };
  newFields['number'] = {
    title: 'Number',
    element: 'number',
    validate: function(settings, item, next) {
      next(null, validator.isNumeric(item[settings.name].toString()) || 'Invalid number.');
    }
  };
  newFields['date'] = {
    title: 'Date',
    element: 'date',
    validate: function(settings, item, next) {
      next(null, validator.isDate(item[settings.name].toString()) || 'Invalid date.');
    }
  };
  newFields['email'] = {
    title: 'Email',
    element: 'email',
    validate: function(settings, item, next) {
      // Email validator oddly returns the email itself, so need to convert to
      // boolean.
      next(null, new Boolean(validator.isEmail(item[settings.name].toString())) || 'Invalid email.');
    }
  };
  newFields['password'] = {
    title: 'Password',
    element: 'password',
    validate: function(settings, item, next) {
      var minLength = settings.minLength || 6;
      next(null, validator.len(item[settings.name].toString(), settings.minLength || 6) || 'Password must have at least ' + minLength + ' characters.');
    }
  };
  newFields['url'] = {
    title: 'URL',
    element: 'url',
    validate: function(settings, item, next) {
      next(null, validator.isUrl(item[settings.name].toString()) || 'Invalid URL.');
    }
  };
  newFields['submit'] = {
    title: 'Submit',
    element: 'submit'
  };

  callback(null, newFields);
};

/**
 * The preSave() hook.
 */
field.preSave = function(type, data, callback) {
  var application = this.application;

  if (type.settings.fields) {
    // Validate type fields.
    async.each(Object.keys(type.settings.fields), function(fieldName, next) {
      var fieldSettings = type.settings.fields[fieldName];

      // Add fieldName to fieldSettings.
      fieldSettings.name = fieldName;

      var Field = application.type('field');
      Field.load(fieldSettings.type, function(error, field) {
        if (error) {
          // Application error.
          return next(error);
        }
        if (!field || !field.preSave) {
          // Field is of an unrecognized type or there's not a preSave()
          // callback.
          return next();
        }

        field.preSave(fieldSettings, data, function(error, result) {
          if (error) {
            // Application error.
            return next(error);
          }
          next();
        });
      });
    },
    function(error) {
      if (error) {
        // Application error.
        return callback(error);
      }
      callback();
    });
  }
  else {
    callback();
  }
};
<|endoftext|>


<commit_before>import Reac		from 'react/addons';
import Dialo		from '../../components/dialog';
import UserStore	from '../../stores/user';
import api		from '../../utils/api';

export default React.createClass({
    propTypes: {
        onDismiss: React.PropTypes.func.isRequired
    },

    getInitialState() {
	return {
	    infoReceived:   false,
	    clientVersion:  process.env.VERSION || 'unknown',
    	    apiVersion:     null,
    	    imgVersion:     null
    	    }
    },

    componentWillMount(){
    	let token = UserStore.getToken();

    	api.queryApiVersion({ token }).then((res) => {
            this.state.apiVersion = res.version  || 'unknown';
            this.setState({infoReceived : true});
    	}, (err) => {
            this.state.apiVersion = 'error';
            this.setState({infoReceived : true});
        });

    	api.queryImgVersion({ token }).then((res) => {
	    this.state.imgVersion = res.version || 'unknown';
            this.setState({infoReceived : true});
    	}, (err) => {
            this.state.imgVersion = 'error';
            this.setState({infoReceived : true});
        });
    },

    shouldComponentUpdate(){
        if(!this.state.apiVersion || !this.state.imgVersion){
            return false;
        }
        return true;
    },

    close(event) {
        event.preventDefault();
        return this.props.onDismiss();
    },

    render() {
       	return(
            <Dialog className="dialog-about"
                    onDismiss={this.props.onDismiss}>

                    About Contriboard

                        Fast open-source brainstorming tool for everyone.

Developers:

                                n4sjamk.github.io

Contriboard homepage:

                                n4sjamk.github.io/contriboard

Client version:

{this.state.clientVersion}

API version:

{this.state.apiVersion}

IMG version:

{this.state.imgVersion}

                	<button className="btn-primary" onClick={this.close}>
						Close
					</button>

            </Dialog>
        );
    }
});
<commit_msg>Fixed more missing letters<commit_after>import React		from 'react/addons';
import Dialog		from '../../components/dialog';
import UserStore	from '../../stores/user';
import api		from '../../utils/api';

export default React.createClass({
    propTypes: {
        onDismiss: React.PropTypes.func.isRequired
    },

    getInitialState() {
	return {
	    infoReceived:   false,
	    clientVersion:  process.env.VERSION || 'unknown',
    	    apiVersion:     null,
    	    imgVersion:     null
    	    }
    },

    componentWillMount(){
    	let token = UserStore.getToken();

    	api.queryApiVersion({ token }).then((res) => {
            this.state.apiVersion = res.version  || 'unknown';
            this.setState({infoReceived : true});
    	}, (err) => {
            this.state.apiVersion = 'error';
            this.setState({infoReceived : true});
        });

    	api.queryImgVersion({ token }).then((res) => {
	    this.state.imgVersion = res.version || 'unknown';
            this.setState({infoReceived : true});
    	}, (err) => {
            this.state.imgVersion = 'error';
            this.setState({infoReceived : true});
        });
    },

    shouldComponentUpdate(){
        if(!this.state.apiVersion || !this.state.imgVersion){
            return false;
        }
        return true;
    },

    close(event) {
        event.preventDefault();
        return this.props.onDismiss();
    },

    render() {
       	return(
            <Dialog className="dialog-about"
                    onDismiss={this.props.onDismiss}>

                    About Contriboard

                        Fast open-source brainstorming tool for everyone.

Developers:

                                n4sjamk.github.io

Contriboard homepage:

                                n4sjamk.github.io/contriboard

Client version:

{this.state.clientVersion}

API version:

{this.state.apiVersion}

IMG version:

{this.state.imgVersion}

                	<button className="btn-primary" onClick={this.close}>
						Close
					</button>

            </Dialog>
        );
    }
});
<|endoftext|>


<commit_before>const hooks = require('feathers-hooks-common');
const { authenticate } = require('@feathersjs/authentication');
const { BadRequest, Forbidden } = require('@feathersjs/errors');
const { Configuration } = require('@schul-cloud/commons');
const { NODE_ENV, ENVIRONMENTS, SC_TITLE, SC_SHORT_TITLE } = require('../../../../config/globals');
const globalHooks = require('../../../hooks');
const pinModel = require('../model').registrationPinModel;
const { getRandomInt } = require('../../../utils/randomNumberGenerator');

const removeOldPins = (hook) => pinModel.deleteMany({ email: hook.data.email }).then(() => Promise.resolve(hook));

const generatePin = (hook) => {
	const pin = getRandomInt(9999, 1000);
	hook.data.pin = pin.toString();
	return Promise.resolve(hook);
};

function createinfoText(hook) {
	const role = hook.data.mailTextForRole;
	const { pin } = hook.data;
	if (!pin) {
		throw new BadRequest('Fehler beim Erstellen der Pin.');
	}

	if (role === 'parent') {
		let consentWords = 'durch Ihr Einverständnis ';

		const skipConsentRoles = Configuration.get('SKIP_CONDITIONS_CONSENT');
		if (skipConsentRoles !== '' && skipConsentRoles.length > 1) {
			consentWords = '';
		}

		return `Vielen Dank, dass Sie Ihrem Kind ${consentWords}die Nutzung der ${SC_TITLE} ermöglichen.
Bitte geben Sie den folgenden Bestätigungscode im Registrierungsprozess ein, um die Registrierung abzuschließen:

PIN: ${pin}

Mit Freundlichen Grüßen
Ihr ${SC_SHORT_TITLE}-Team`;
	}
	if (role === 'student' || role === 'employee' || role === 'expert') {
		return `Vielen Dank, dass du die ${SC_TITLE} nutzen möchtest.
Bitte gib den folgenden Bestätigungscode im Registrierungsprozess ein,
um deine Registrierung bei der ${SC_TITLE} abzuschließen:

PIN: ${pin}

Mit freundlichen Grüßen
Dein ${SC_SHORT_TITLE}-Team`;
	}
	throw new BadRequest('Die angegebene Rolle ist ungültig.', { role });
}

const checkAndVerifyPin = (hook) => {
	if (hook.result.data.length === 0) {
		return hook;
	}
	if (hook.result.data.length === 1) {
		const firstDataItem = hook.result.data[0];
		// check generation age
		const now = Date.now();
		if (firstDataItem.updatedAt.getTime() + Configuration.get('PIN_MAX_AGE_SECONDS') * 1000 < now) {
			throw new Forbidden('Der eingegebene Code ist nicht mehr gültig. Bitte fordere einen neuen Code an.');
		}
		if (firstDataItem.verified === true) {
			// already verified
			return hook;
		}
		if (firstDataItem.pin) {
			if (firstDataItem.pin === hook.params.query.pin) {
				return hook.app
					.service('registrationPins')
					.patch(firstDataItem._id, { verified: true })
					.then((result) => {
						hook.result.data = [result];
						return hook;
					});
			}
			throw new BadRequest(
				'Der eingegebene Code ist ungültig oder konnte nicht bestätigt werden. Bitte versuche es erneut.'
			);
		}
		return hook;
	}
	throw new BadRequest('Only one result allowed here');
};

const mailPin = (hook) => {
	if (!(hook.data || {}).silent) {
		globalHooks.sendEmail(hook, {
			subject: `${SC_SHORT_TITLE}: Registrierung mit PIN verifizieren`,
			emails: (hook.data || {}).email,
			content: {
				text: createinfoText(hook),
				// TODO: implement html mails later
			},
		});
	}
	return Promise.resolve(hook);
};

const returnPinOnlyToSuperHero = async (hook) => {
	if (NODE_ENV === ENVIRONMENTS.TEST) {
		return Promise.resolve(hook);
	}

	if (((hook.params || {}).account || {}).userId) {
		const userService = hook.app.service('/users/');
		const currentUser = await userService.get(hook.params.account.userId, { query: { $populate: 'roles' } });
		const userRoles = currentUser.roles.map((role) => role.name);
		if (userRoles.includes('superhero')) {
			return Promise.resolve(hook);
		}
	}

	globalHooks.removeResponse()(hook);
	return Promise.resolve(hook);
};

const validateEmailAndPin = (hook) => {
	const { email, pin } = hook.params.query;
	if (!hook.params.query || !email) {
		throw new BadRequest('email required');
	}
	if (
		email &&
		typeof email === 'string' &&
		email.length &&
		(!pin || (pin && typeof pin === 'string' && pin.length === 4))
	) {
		return hook;
	}
	throw new BadRequest('pin or email invalid', { email, pin });
};

exports.before = {
	all: [globalHooks.forceHookResolve(authenticate('jwt'))],
	find: [hooks.disallow('external'), validateEmailAndPin],
	get: hooks.disallow('external'),
	create: [globalHooks.blockDisposableEmail('email'), removeOldPins, generatePin, mailPin],
	update: hooks.disallow('external'),
	patch: hooks.disallow('external'),
	remove: hooks.disallow('external'),
};

exports.after = {
	all: [globalHooks.removeResponse(['get', 'find', 'create'])],
	find: [checkAndVerifyPin],
	get: [],
	create: [returnPinOnlyToSuperHero],
	update: [],
	patch: [],
	remove: [],
};
<commit_msg>send mail after adding pin to db<commit_after>const hooks = require('feathers-hooks-common');
const { authenticate } = require('@feathersjs/authentication');
const { BadRequest, Forbidden } = require('@feathersjs/errors');
const { Configuration } = require('@schul-cloud/commons');
const { NODE_ENV, ENVIRONMENTS, SC_TITLE, SC_SHORT_TITLE } = require('../../../../config/globals');
const globalHooks = require('../../../hooks');
const pinModel = require('../model').registrationPinModel;
const { getRandomInt } = require('../../../utils/randomNumberGenerator');

const removeOldPins = (hook) => pinModel.deleteMany({ email: hook.data.email }).then(() => Promise.resolve(hook));

const generatePin = (hook) => {
	const pin = getRandomInt(9999, 1000);
	hook.data.pin = pin.toString();
	return Promise.resolve(hook);
};

function createinfoText(hook) {
	const role = hook.data.mailTextForRole;
	const { pin } = hook.data;
	if (!pin) {
		throw new BadRequest('Fehler beim Erstellen der Pin.');
	}

	if (role === 'parent') {
		let consentWords = 'durch Ihr Einverständnis ';

		const skipConsentRoles = Configuration.get('SKIP_CONDITIONS_CONSENT');
		if (skipConsentRoles !== '' && skipConsentRoles.length > 1) {
			consentWords = '';
		}

		return `Vielen Dank, dass Sie Ihrem Kind ${consentWords}die Nutzung der ${SC_TITLE} ermöglichen.
Bitte geben Sie den folgenden Bestätigungscode im Registrierungsprozess ein, um die Registrierung abzuschließen:

PIN: ${pin}

Mit Freundlichen Grüßen
Ihr ${SC_SHORT_TITLE}-Team`;
	}
	if (role === 'student' || role === 'employee' || role === 'expert') {
		return `Vielen Dank, dass du die ${SC_TITLE} nutzen möchtest.
Bitte gib den folgenden Bestätigungscode im Registrierungsprozess ein,
um deine Registrierung bei der ${SC_TITLE} abzuschließen:

PIN: ${pin}

Mit freundlichen Grüßen
Dein ${SC_SHORT_TITLE}-Team`;
	}
	throw new BadRequest('Die angegebene Rolle ist ungültig.', { role });
}

const checkAndVerifyPin = (hook) => {
	if (hook.result.data.length === 0) {
		return hook;
	}
	if (hook.result.data.length === 1) {
		const firstDataItem = hook.result.data[0];
		// check generation age
		const now = Date.now();
		if (firstDataItem.updatedAt.getTime() + Configuration.get('PIN_MAX_AGE_SECONDS') * 1000 < now) {
			throw new Forbidden('Der eingegebene Code ist nicht mehr gültig. Bitte fordere einen neuen Code an.');
		}
		if (firstDataItem.verified === true) {
			// already verified
			return hook;
		}
		if (firstDataItem.pin) {
			if (firstDataItem.pin === hook.params.query.pin) {
				return hook.app
					.service('registrationPins')
					.patch(firstDataItem._id, { verified: true })
					.then((result) => {
						hook.result.data = [result];
						return hook;
					});
			}
			throw new BadRequest(
				'Der eingegebene Code ist ungültig oder konnte nicht bestätigt werden. Bitte versuche es erneut.'
			);
		}
		return hook;
	}
	throw new BadRequest('Only one result allowed here');
};

const mailPin = (hook) => {
	if (!(hook.data || {}).silent) {
		globalHooks.sendEmail(hook, {
			subject: `${SC_SHORT_TITLE}: Registrierung mit PIN verifizieren`,
			emails: (hook.data || {}).email,
			content: {
				text: createinfoText(hook),
				// TODO: implement html mails later
			},
		});
	}
	return Promise.resolve(hook);
};

const returnPinOnlyToSuperHero = async (hook) => {
	if (NODE_ENV === ENVIRONMENTS.TEST) {
		return Promise.resolve(hook);
	}

	if (((hook.params || {}).account || {}).userId) {
		const userService = hook.app.service('/users/');
		const currentUser = await userService.get(hook.params.account.userId, { query: { $populate: 'roles' } });
		const userRoles = currentUser.roles.map((role) => role.name);
		if (userRoles.includes('superhero')) {
			return Promise.resolve(hook);
		}
	}

	globalHooks.removeResponse()(hook);
	return Promise.resolve(hook);
};

const validateEmailAndPin = (hook) => {
	const { email, pin } = hook.params.query;
	if (!hook.params.query || !email) {
		throw new BadRequest('email required');
	}
	if (
		email &&
		typeof email === 'string' &&
		email.length &&
		(!pin || (pin && typeof pin === 'string' && pin.length === 4))
	) {
		return hook;
	}
	throw new BadRequest('pin or email invalid', { email, pin });
};

exports.before = {
	all: [globalHooks.forceHookResolve(authenticate('jwt'))],
	find: [hooks.disallow('external'), validateEmailAndPin],
	get: hooks.disallow('external'),
	create: [globalHooks.blockDisposableEmail('email'), removeOldPins, generatePin],
	update: hooks.disallow('external'),
	patch: hooks.disallow('external'),
	remove: hooks.disallow('external'),
};

exports.after = {
	all: [globalHooks.removeResponse(['get', 'find', 'create'])],
	find: [checkAndVerifyPin],
	get: [],
	create: [mailPin, returnPinOnlyToSuperHero],
	update: [],
	patch: [],
	remove: [],
};
<|endoftext|>


<commit_before>'use strict';

describe('angular-notifications-icon', function() {
  beforeEach(module('angular-notification-icons'));

  describe('NotificationDirectiveController', function() {
    var $controller;
    var $rootScope;
    var $animate;

    beforeEach(inject(function(_$controller_, _$rootScope_, _$animate_) {
      $controller = _$controller_;
      $rootScope = _$rootScope_;
      $animate = _$animate_;
    }));

    it('should pass', function(done) {
      $controller('NotificationDirectiveController', {$scope: $rootScope.$new()});
      done();
    });
  });
});<commit_msg>Add a few tests and stub out a lot more.<commit_after>'use strict';

describe('angular-notifications-icon', function() {
  beforeEach(module('angular-notification-icons'));

  describe('NotificationDirectiveController', function() {
    var $controller;
    var scope;
    var $animate;
    var $q;

    var sandbox;
    var element;
    var angularElement;

    beforeEach(function() {
      sandbox = sinon.sandbox.create();

      element = { name: 'foo' };
      angularElement = {
        find: function() {
          return element;
        }
      };
    });

    afterEach(function() {
      sandbox.restore();
    });

    beforeEach(inject(function(_$controller_, $rootScope, _$animate_, _$q_) {
      $controller = _$controller_;
      scope = {$scope: $rootScope.$new()};
      $animate = _$animate_;
      $q = _$q_;
    }));

    var spyOn = function(object, functionName) {
      if (!object[functionName]) {
        var spy = sandbox.spy();
        object[functionName] = spy;
        return spy;
      }

      return sandbox.spy(object, functionName);
    };

    it('should start with reasonable defaults', function() {
      var ctrl = $controller('NotificationDirectiveController', scope);
      ctrl.visible.should.be.false;
      ctrl.wideThreshold.should.eql(100);
    });

    it('should initialize with an element', function() {
      var findSpy = spyOn(angularElement, 'find');

      var ctrl = $controller('NotificationDirectiveController', scope);
      ctrl.init(angularElement);
      findSpy.getCall(0).args[0].should.equal('.angular-notifications-icon');
    });

    it('should set a clear trigger if specified', function() {
      var onSpy = spyOn(angularElement, 'on');

      var ctrl = $controller('NotificationDirectiveController', scope, {clearTrigger: 'mouseover'});
      ctrl.init(angularElement);
      onSpy.getCall(0).args[0].should.eql('mouseover');
    });

    it('should appear if not visible and count goes above 0', function() {

    });

    it('should play appear animation if set', function() {

    });

    it('should use a default appear animation', function() {

    });

    it('should update if visible and the count changes', function() {

    });

    it('should play update animation if set', function() {

    });

    it('should use a default update animation', function() {

    });

    it('should set the appear animation based on the \'animation\' property', function() {

    });

    it('should set the update animation based on the \'animation\' property', function() {

    });

    it('should disappear if visible and the count goes to or below 0', function() {

    });

    it('should play disappear animation if set', function() {

    });

    it('should interrupt an aniamtion that is already playing', function() {

    });

    it('should add .wide-icon if the count is higher than the wide threshold', function() {

    });

    it('should remove .wide-icon if the count is lower than the wide threshold', function() {

    });
  });
});<|endoftext|>


<commit_before>import {dialog} from 'electron';
import {isEmpty, first} from 'lodash';
import csv from 'fast-csv';
import fs from 'fs';
import {basename} from 'path';
import log from 'karmapa-log';
import sleep from 'sleep-promise';
import shortid from 'shortid';

import csvProcessor, {FIELD_PAGE_NUM} from './../../utils/csvProcessor';
import pageNumToFloat from './../../utils/pageNumToFloat';
import FRACTION_LENGTH from './../../constants/fractionLength';
import now from './../../utils/now';
import Reporter from './../../utils/Reporter';

const reportDuration = 1000;
const reporter = new Reporter();

const numberConcurrentWrite = 50000;
const dialogOptions = {
  properties: ['openFile'],
  filters: [
    {name: 'Csv Files', extensions: ['csv']}
  ]
};

export default async function addFolderByCsv(event, data) {

  const {models, importEmitter, db} = this.params;
  const {Folder} = models;
  const {broadcast, resolve, reject} = this;

  dialog.showOpenDialog(dialogOptions, handleDialogOpen);

  function processCsv(csvFilePath) {

    let cancelImporting = false;
    importEmitter.removeAllListeners();

    importEmitter.once('cancel-importing', () => {
      cancelImporting = true;
    });

    const filename = basename(csvFilePath);
    const stream = fs.createReadStream(csvFilePath);

    let completedLines = 0;
    let hasColumnRow = false;
    let folder = null;
    let fields = [];
    let currentEntryDatum = null;
    let entryData = [];

    async function handleData(data) {

      let createdEntryDatum = null;

      if (cancelImporting) {
        return Promise.reject('User stopped importing');
      }

      if (isEmpty(folder) && csvProcessor.isColumnRow(data)) {
        const columnData = csvProcessor.getColumnData(data);
        folder = await Folder.create({
          name: `${filename} - ${shortid.generate()}`,
          data: columnData
        });
        broadcast('csv-folder-created', {folderId: folder.id});
        fields = csvProcessor.getFields(data);
        hasColumnRow = true;
      }
      else if (folder) {

        const rowData = csvProcessor.getRowDataByFields(data, fields);
        const {sourceEntry} = rowData;
        const pageNum = pageNumToFloat(rowData[FIELD_PAGE_NUM], FRACTION_LENGTH) || '';

        if (sourceEntry) {

          delete rowData.sourceEntry;
          delete rowData[FIELD_PAGE_NUM];

          currentEntryDatum = {
            folderId: folder.id,
            sourceEntry,
            pageNum,
            data: rowData
          };
          createdEntryDatum = currentEntryDatum;
        }
        else if (currentEntryDatum) {
          currentEntryDatum.data = csvProcessor.appendData(data, currentEntryDatum.data, fields);
        }
      }
      ++completedLines;
      reporter.report(() => {
        broadcast('csv-processing-status', {completedLines});
      }, reportDuration);

      if (createdEntryDatum) {
        return createdEntryDatum;
      }
    }

    async function batchInsertIfNeeded(entryDatum) {

      if (entryDatum) {
        entryData.push(entryDatum);

        if (entryData.length > (numberConcurrentWrite + 1)) {

          const insertQuery = getEntryBatchInsertQuery(db, entryData.slice(0, entryData.length - 1));

          return db.raw(insertQuery)
            .then(() => {
              // put the last one to new array
              entryData = [entryData.pop()];
            });
        }
      }
    }

    function done() {
      broadcast('csv-processing-done', {filename});
      resolve({message: 'done'});
    }

    csv.fromStream(stream)
      .validate((data, next) => {

        handleData(data)
          .then((entryDatum) => batchInsertIfNeeded(entryDatum))
          .then(() => next(null, true))
          .catch((err) => next(err));
      })
      .on('data', () => {})
      .on('error', (err) => {
        log.error(err);
        reject({message: `${err}`});
      })
      .on('end', () => {

        if (! hasColumnRow) {
          reject({messageId: 'no-column-row-detected', filename});
          return;
        }

        // finish the rest
        if (entryData.length > 0) {
          const insertQuery = getEntryBatchInsertQuery(db, entryData);
          db.raw(insertQuery)
            .then(done);
        }
        else {
          done();
        }
      });
  }

  function handleDialogOpen(paths) {

    if (isEmpty(paths)) {
      resolve({message: 'User did not choose csv file'});
      return;
    }
    broadcast('csv-processing-start');
    const csvFilePath = first(paths);
    processCsv(csvFilePath);
  }
}

function getEntryBatchInsertQuery(db, entryData) {
  const formattedData = entryData.map((row) => ({...row, data: JSON.stringify(row.data)}));
  return db.knex.batchInsert('Entry', formattedData, 200);
}
<commit_msg>add remove '.csv' when get file name<commit_after>import {dialog} from 'electron';
import {isEmpty, first} from 'lodash';
import csv from 'fast-csv';
import fs from 'fs';
import {basename} from 'path';
import log from 'karmapa-log';
import sleep from 'sleep-promise';
import shortid from 'shortid';

import csvProcessor, {FIELD_PAGE_NUM} from './../../utils/csvProcessor';
import pageNumToFloat from './../../utils/pageNumToFloat';
import FRACTION_LENGTH from './../../constants/fractionLength';
import now from './../../utils/now';
import Reporter from './../../utils/Reporter';

const reportDuration = 1000;
const reporter = new Reporter();

const numberConcurrentWrite = 50000;
const dialogOptions = {
  properties: ['openFile'],
  filters: [
    {name: 'Csv Files', extensions: ['csv']}
  ]
};

export default async function addFolderByCsv(event, data) {

  const {models, importEmitter, db} = this.params;
  const {Folder} = models;
  const {broadcast, resolve, reject} = this;

  dialog.showOpenDialog(dialogOptions, handleDialogOpen);

  function processCsv(csvFilePath) {

    let cancelImporting = false;
    importEmitter.removeAllListeners();

    importEmitter.once('cancel-importing', () => {
      cancelImporting = true;
    });

    const filename = basename(csvFilePath).replace('.csv', '');
    const stream = fs.createReadStream(csvFilePath);

    let completedLines = 0;
    let hasColumnRow = false;
    let folder = null;
    let fields = [];
    let currentEntryDatum = null;
    let entryData = [];

    async function handleData(data) {

      let createdEntryDatum = null;

      if (cancelImporting) {
        return Promise.reject('User stopped importing');
      }

      if (isEmpty(folder) && csvProcessor.isColumnRow(data)) {
        const columnData = csvProcessor.getColumnData(data);
        folder = await Folder.create({
          name: `${filename} - ${shortid.generate()}`,
          data: columnData
        });
        broadcast('csv-folder-created', {folderId: folder.id});
        fields = csvProcessor.getFields(data);
        hasColumnRow = true;
      }
      else if (folder) {

        const rowData = csvProcessor.getRowDataByFields(data, fields);
        const {sourceEntry} = rowData;
        const pageNum = pageNumToFloat(rowData[FIELD_PAGE_NUM], FRACTION_LENGTH) || '';

        if (sourceEntry) {

          delete rowData.sourceEntry;
          delete rowData[FIELD_PAGE_NUM];

          currentEntryDatum = {
            folderId: folder.id,
            sourceEntry,
            pageNum,
            data: rowData
          };
          createdEntryDatum = currentEntryDatum;
        }
        else if (currentEntryDatum) {
          currentEntryDatum.data = csvProcessor.appendData(data, currentEntryDatum.data, fields);
        }
      }
      ++completedLines;
      reporter.report(() => {
        broadcast('csv-processing-status', {completedLines});
      }, reportDuration);

      if (createdEntryDatum) {
        return createdEntryDatum;
      }
    }

    async function batchInsertIfNeeded(entryDatum) {

      if (entryDatum) {
        entryData.push(entryDatum);

        if (entryData.length > (numberConcurrentWrite + 1)) {

          const insertQuery = getEntryBatchInsertQuery(db, entryData.slice(0, entryData.length - 1));

          return db.raw(insertQuery)
            .then(() => {
              // put the last one to new array
              entryData = [entryData.pop()];
            });
        }
      }
    }

    function done() {
      broadcast('csv-processing-done', {filename});
      resolve({message: 'done'});
    }

    csv.fromStream(stream)
      .validate((data, next) => {

        handleData(data)
          .then((entryDatum) => batchInsertIfNeeded(entryDatum))
          .then(() => next(null, true))
          .catch((err) => next(err));
      })
      .on('data', () => {})
      .on('error', (err) => {
        log.error(err);
        reject({message: `${err}`});
      })
      .on('end', () => {

        if (! hasColumnRow) {
          reject({messageId: 'no-column-row-detected', filename});
          return;
        }

        // finish the rest
        if (entryData.length > 0) {
          const insertQuery = getEntryBatchInsertQuery(db, entryData);
          db.raw(insertQuery)
            .then(done);
        }
        else {
          done();
        }
      });
  }

  function handleDialogOpen(paths) {

    if (isEmpty(paths)) {
      resolve({message: 'User did not choose csv file'});
      return;
    }
    broadcast('csv-processing-start');
    const csvFilePath = first(paths);
    processCsv(csvFilePath);
  }
}

function getEntryBatchInsertQuery(db, entryData) {
  const formattedData = entryData.map((row) => ({...row, data: JSON.stringify(row.data)}));
  return db.knex.batchInsert('Entry', formattedData, 200);
}
<|endoftext|>


<commit_before>Xflow.registerOperator("createIGIndex", {
    outputs: [  {type: 'int', name: 'index', customAlloc: true },
                {type: 'float2', name: 'igTexCoord'}
             ],
    params:  [  {type: 'int', source: 'vertexCount', optional: false},
                {type: 'texture', source: 'tex'}],
    evaluate: function(index, vertexCount, image, info) {
        console.log(arguments);
        return true;
    }
});<commit_msg>Working version of createIGIndex<commit_after>Xflow.registerOperator("createIGIndex", {
    outputs:[
        {type:'int', name:'index', customAlloc:true },
        {type:'float2', name:'texcoord', customAlloc:true }
    ],
    params:[
        {type:'int', source:'vertexCount', optional:false},
        {type:'texture', source:'positionTex', optional: false}
    ],
    alloc:function (sizes, vertexCount, image) {
        sizes['texcoord'] = image.width * image.height;
        sizes['index'] = vertexCount[0];
    },
    evaluate:function (index, texcoord, vertexCount, image, info) {
        // tex coords
        var halfPixel = {
            x: 0.5 / image.width,
            y: 0.5 / image.height
        };
        var i = 0;
        for (var y = 0, ylength = image.height; y < ylength; y++)
        {
            for (var x = 0, xlength = image.width; x < xlength; x++)
            {
                texcoord[i++] = (x / xlength) + halfPixel.x;
                texcoord[i++] = 1 - ((y / ylength) + halfPixel.y);
            }
        }

        // index creation
        for(var i = 0; i < vertexCount[0]; i++) {
            index[i] = i;
        }
        return true;
    }
});<|endoftext|>


<commit_before>var path = require('path')
function View(name, options){

	options.path = path.join(options.root, name)
	//this.path = options.path
        //this.root = options.root
	//this.defaultEngine = options.defaultEngine
	//this.engines = options.engines
	Object.assign(this, options)
//	return options
}
View.prototype.render = function(options, callback){
	this.engines[this.defaultEngine](this.path, options, callback)
}
module.exports = View
<commit_msg>stupid period<commit_after>var path = require('path')
function View(name, options){

	options.path = path.join(options.root, name)
	//this.path = options.path
        //this.root = options.root
	//this.defaultEngine = options.defaultEngine
	//this.engines = options.engines
	Object.assign(this, options)
//	return options
}
View.prototype.render = function(options, callback){
	this.engines['.'+this.defaultEngine](this.path, options, callback)
}
module.exports = View
<|endoftext|>