<commit_before>import Ember from 'ember';
import config from 'ember-get-config';
import Analytics from '../mixins/analytics';

import { elasticEscape } from '../utils/elastic-query';

var getProvidersPayload = '{"from": 0,"query": {"bool": {"must": {"query_string": {"query": "*"}}, "filter": [{"term": {"types": "preprint"}}]}},"aggregations": {"sources": {"terms": {"field": "sources","size": 200}}}}';

const filterMap = {
    providers: 'sources',
    subjects: 'subjects'
};

/**
 * @module ember-preprints
 * @submodule controllers
 */

/**
 * @class Discover Controller
 */
export default Ember.Controller.extend(Analytics, {
    theme: Ember.inject.service(), // jshint ignore:line
    // TODO: either remove or add functionality to info icon on "Refine your search panel"

    // Many pieces taken from: https://github.com/CenterForOpenScience/ember-share/blob/develop/app/controllers/discover.js
    queryParams: {
        page: 'page',
        queryString: 'q',
        subjectFilter: 'subject',
        providerFilter: 'provider',
    },

    activeFilters: { providers: [], subjects: [] },

    osfProviders: [
        'OSF',
        'PsyArXiv',
        'SocArXiv',
        'engrXiv'
    ],

    whiteListedProviders: [
        'OSF',
        'arXiv',
        'bioRxiv',
        'Cogprints',
        'engrXiv',
        'PeerJ',
        'PsyArXiv',
        'Research Papers in Economics',
        'SocArXiv'
    ].map(item => item.toLowerCase()),

    page: 1,
    size: 10,
    numberOfResults: 0,
    queryString: '',
    subjectFilter: null,
    queryBody: {},
    providersPassed: false,

    i18n: Ember.inject.service(),

    sortByOptions: Ember.computed('i18n', function() {
        const i18n = this.get('i18n');
        return [i18n.t('discover.relevance'), i18n.t('discover.sort_oldest_newest'), i18n.t('discover.sort_newest_oldest')];
    }),

    treeSubjects: Ember.computed('activeFilters', function() {
        return this.get('activeFilters.subjects').slice();
    }),
    // chosenOption is always the first element in the list
    chosenSortByOption: Ember.computed('sortByOptions', function() {
        return this.get('sortByOptions')[0];
    }),

    showActiveFilters: true, //should always have a provider, don't want to mix osfProviders and non-osf
    showPrev: Ember.computed.gt('page', 1),
    showNext: Ember.computed('page', 'size', 'numberOfResults', function() {
        return this.get('page') * this.get('size') < this.get('numberOfResults');
    }),

    results: Ember.ArrayProxy.create({ content: [] }),

    searchUrl: config.SHARE.searchUrl,

    init() {
        this._super(...arguments);
        this.set('facetFilters', Ember.Object.create());

        Ember.$.ajax({
            type: 'POST',
            url: this.get('searchUrl'),
            data: getProvidersPayload,
            contentType: 'application/json',
            crossDomain: true,
        }).then(results => {
            const hits = results.aggregations.sources.buckets;
            const whiteList = this.get('whiteListedProviders');
            const providers = hits
                .filter(hit => whiteList.includes(hit.key.toLowerCase()));

            providers.push(
                ...this.get('osfProviders')
                .filter(key => !providers
                    .find(hit => hit.key.toLowerCase() === key.toLowerCase())
                )
                .map(key => ({
                    key,
                    doc_count: 0
                }))
            );

            providers
                .sort((a, b) => a.key.toLowerCase() < b.key.toLowerCase() ? -1 : 1)
                .unshift(
                    ...providers.splice(
                        providers.findIndex(item => item.key === 'OSF'),
                        1
                    )
                );

            if (!this.get('theme.isProvider')) {
                this.set('otherProviders', providers);
            } else {
                const filtered = providers.filter(
                    item => item.key.toLowerCase() === this.get('theme.id').toLowerCase()
                );

                this.set('otherProviders', filtered);
                this.get('activeFilters.providers').pushObject(filtered[0].key);
            }

            this.notifyPropertyChange('otherProviders');
        });

        this.loadPage();
    },
    subjectChanged: Ember.observer('subjectFilter', function() {
        Ember.run.once(() => {
            let filter = this.get('subjectFilter');
            if (!filter || filter === 'true') return;
            this.set('activeFilters.subjects', filter.split('AND'));
            this.notifyPropertyChange('activeFilters');
            this.loadPage();
        });
    }),
    providerChanged: Ember.observer('providerFilter', function() {
        if (!this.get('theme.isProvider')) {
            Ember.run.once(() => {
                let filter = this.get('providerFilter');
                if (!filter || filter === 'true') return;
                this.set('activeFilters.providers', filter.split('AND'));
                this.notifyPropertyChange('activeFilters');
                this.set('providersPassed', true);
                this.loadPage();
            });
        }
    }),
    loadPage() {
        this.set('loading', true);
        Ember.run.debounce(this, this._loadPage, 500);
    },
    _loadPage() {
        let queryBody = JSON.stringify(this.getQueryBody());

        return Ember.$.ajax({
            url: this.get('searchUrl'),
            crossDomain: true,
            type: 'POST',
            contentType: 'application/json',
            data: queryBody
        }).then(json => {
            if (this.isDestroyed || this.isDestroying) return;

            this.set('numberOfResults', json.hits.total);

            let results = json.hits.hits.map(hit => {
                // HACK: Make share data look like apiv2 preprints data
                let result = Ember.merge(hit._source, {
                    id: hit._id,
                    type: 'elastic-search-result',
                    workType: hit._source['@type'],
                    abstract: hit._source.description,
                    subjects: hit._source.subjects.map(each => ({text: each})),
                    providers: hit._source.sources.map(item => ({name: item})),
                    osfProvider: hit._source.sources.reduce((acc, source) => (acc || this.get('osfProviders').includes(source)), false),
                    hyperLinks: [// Links that are hyperlinks from hit._source.lists.links
                        {
                            type: 'share',
                            url: config.SHARE.baseUrl + 'preprint/' + hit._id
                        }
                    ],
                    infoLinks: [] // Links that are not hyperlinks  hit._source.lists.links
                });

                hit._source.identifiers.forEach(function(identifier) {
                    if (identifier.startsWith('http://')) {
                        result.hyperLinks.push({url: identifier});
                    } else {
                        const spl = identifier.split('://');
                        const [type, uri, ..._] = spl; // jshint ignore:line
                        result.infoLinks.push({type, uri});
                    }
                });

                result.contributors = result.lists.contributors
                  .sort((b, a) => (b.order_cited || -1) - (a.order_cited || -1))
                  .map(contributor => ({
                        users: Object.keys(contributor)
                          .reduce(
                              (acc, key) => Ember.merge(acc, {[key.camelize()]: contributor[key]}),
                              {bibliographic: contributor.relation !== 'contributor'}
                          )
                    }));

                // Temporary fix to handle half way migrated SHARE ES
                // Only false will result in a false here.
                result.contributors.map(contributor => contributor.users.bibliographic = !(contributor.users.bibliographic === false));  // jshint ignore:line

                return result;
            });

            this.set('loading', false);
            return this.set('results', results);
        });
    },
    maxPages: Ember.computed('numberOfResults', function() {
        return ((this.get('numberOfResults') / this.get('size')) | 0) + (this.get('numberOfResults') % 10 === 0 ? 0 : 1);
    }),
    getQueryBody() {
        const facetFilters = this.get('activeFilters');

        this.set('subjectFilter', facetFilters.subjects.join('AND'));

        if (!this.get('theme.isProvider'))
            this.set('providerFilter', facetFilters.providers.join('AND'));

        const filter = [
            {
                terms: {
                    type: [
                        'preprint'
                    ]
                }
            }
        ];

        // TODO set up ember to transpile Object.entries
        for (const key in filterMap) {
            const val = filterMap[key];
            const filterList = facetFilters[key];

            if (!filterList.length || (key === 'providers' && this.get('theme.isProvider')))
                continue;

            filter.push({
                terms: {
                    [val]: filterList
                }
            });
        }

        if (this.get('theme.isProvider')) {
            filter.push({
                terms: {
                    sources: [this.get('theme.provider.name')]
                }
            });
        }

        const sortByOption = this.get('chosenSortByOption');
        const sort = {};
        const i18n = this.get('i18n');
        if (sortByOption.toString() === i18n.t('discover.sort_oldest_newest').toString()) {
            sort.date_updated = 'asc';
        } else if (sortByOption.toString() === i18n.t('discover.sort_newest_oldest').toString()) {
            sort.date_updated = 'desc';
        }

        return this.set('queryBody', {
            query: {
                bool: {
                    must: {
                        query_string: {
                            query: elasticEscape(this.get('queryString')) || '*'
                        }
                    },
                    filter
                }
            },
            sort,
            from: (this.get('page') - 1) * this.get('size'),
        });
    },

    reloadSearch: Ember.observer('activeFilters', function() {
        this.set('page', 1);
        this.loadPage();
    }),

    _clearFilters() {
        this.set('activeFilters', {
            providers: this.get('theme.isProvider') ? this.get('activeFilters.providers') : [],
            subjects: []
        });
    },

    otherProviders: [],
    actions: {
        search(val, event) {
            if (event &&
                (
                    event.keyCode < 49 ||
                    [91, 92, 93].includes(event.keyCode)
                ) &&
                ![8, 32, 48].includes(event.keyCode)
            )
                return;

            this.set('page', 1);
            this.loadPage();

            Ember.get(this, 'metrics')
                .trackEvent({
                    category: `${event && event.type === 'keyup' ? 'input' : 'button'}`,
                    action: `${event && event.type === 'keyup' ? 'onkeyup' : 'click'}`,
                    label: 'Preprints - Discover - Search'
                });
        },

        previous() {
            if (this.get('page') > 1) {
                this.decrementProperty('page');
                this.loadPage();
            }
        },

        next() {
            if (this.get('page') * this.get('size') <= this.get('numberOfResults')) {
                this.incrementProperty('page');
                this.loadPage();
            }
        },

        clearFilters() {
            this._clearFilters();

            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'button',
                    action: 'click',
                    label: 'Preprints - Discover - Clear Filters'
                });
        },

        sortBySelect(index) {
            // Selecting an option just swaps it with whichever option is first
            let copy = this.get('sortByOptions').slice(0);
            let temp = copy[0];
            copy[0] = copy[index];
            copy[index] = temp;
            this.set('sortByOptions', copy);
            this.set('page', 1);
            this.loadPage();

            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'dropdown',
                    action: 'select',
                    label: `Preprints - Discover - Sort by: ${copy[index]}`
                });
        },

        updateFilters(filterType, item) {
            item = typeof item === 'object' ? item.text : item;
            const filters = this.get(`activeFilters.${filterType}`);
            const hasItem = filters.includes(item);
            const action = hasItem ? 'remove' : 'push';
            filters[`${action}Object`](item);
            this.notifyPropertyChange('activeFilters');

            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'filter',
                    action: hasItem ? 'remove' : 'add',
                    label: `Preprints - Discover - ${filterType} ${item}`
                });
        },
    },
});
<commit_msg>Change to i18n.locale<commit_after>import Ember from 'ember';
import config from 'ember-get-config';
import Analytics from '../mixins/analytics';

import { elasticEscape } from '../utils/elastic-query';

var getProvidersPayload = '{"from": 0,"query": {"bool": {"must": {"query_string": {"query": "*"}}, "filter": [{"term": {"types": "preprint"}}]}},"aggregations": {"sources": {"terms": {"field": "sources","size": 200}}}}';

const filterMap = {
    providers: 'sources',
    subjects: 'subjects'
};

/**
 * @module ember-preprints
 * @submodule controllers
 */

/**
 * @class Discover Controller
 */
export default Ember.Controller.extend(Analytics, {
    theme: Ember.inject.service(), // jshint ignore:line
    // TODO: either remove or add functionality to info icon on "Refine your search panel"

    // Many pieces taken from: https://github.com/CenterForOpenScience/ember-share/blob/develop/app/controllers/discover.js
    queryParams: {
        page: 'page',
        queryString: 'q',
        subjectFilter: 'subject',
        providerFilter: 'provider',
    },

    activeFilters: { providers: [], subjects: [] },

    osfProviders: [
        'OSF',
        'PsyArXiv',
        'SocArXiv',
        'engrXiv'
    ],

    whiteListedProviders: [
        'OSF',
        'arXiv',
        'bioRxiv',
        'Cogprints',
        'engrXiv',
        'PeerJ',
        'PsyArXiv',
        'Research Papers in Economics',
        'SocArXiv'
    ].map(item => item.toLowerCase()),

    page: 1,
    size: 10,
    numberOfResults: 0,
    queryString: '',
    subjectFilter: null,
    queryBody: {},
    providersPassed: false,

    i18n: Ember.inject.service(),

    sortByOptions: Ember.computed('i18n.locale', function() {
        const i18n = this.get('i18n');
        return [i18n.t('discover.relevance'), i18n.t('discover.sort_oldest_newest'), i18n.t('discover.sort_newest_oldest')];
    }),

    treeSubjects: Ember.computed('activeFilters', function() {
        return this.get('activeFilters.subjects').slice();
    }),
    // chosenOption is always the first element in the list
    chosenSortByOption: Ember.computed('sortByOptions', function() {
        return this.get('sortByOptions')[0];
    }),

    showActiveFilters: true, //should always have a provider, don't want to mix osfProviders and non-osf
    showPrev: Ember.computed.gt('page', 1),
    showNext: Ember.computed('page', 'size', 'numberOfResults', function() {
        return this.get('page') * this.get('size') < this.get('numberOfResults');
    }),

    results: Ember.ArrayProxy.create({ content: [] }),

    searchUrl: config.SHARE.searchUrl,

    init() {
        this._super(...arguments);
        this.set('facetFilters', Ember.Object.create());

        Ember.$.ajax({
            type: 'POST',
            url: this.get('searchUrl'),
            data: getProvidersPayload,
            contentType: 'application/json',
            crossDomain: true,
        }).then(results => {
            const hits = results.aggregations.sources.buckets;
            const whiteList = this.get('whiteListedProviders');
            const providers = hits
                .filter(hit => whiteList.includes(hit.key.toLowerCase()));

            providers.push(
                ...this.get('osfProviders')
                .filter(key => !providers
                    .find(hit => hit.key.toLowerCase() === key.toLowerCase())
                )
                .map(key => ({
                    key,
                    doc_count: 0
                }))
            );

            providers
                .sort((a, b) => a.key.toLowerCase() < b.key.toLowerCase() ? -1 : 1)
                .unshift(
                    ...providers.splice(
                        providers.findIndex(item => item.key === 'OSF'),
                        1
                    )
                );

            if (!this.get('theme.isProvider')) {
                this.set('otherProviders', providers);
            } else {
                const filtered = providers.filter(
                    item => item.key.toLowerCase() === this.get('theme.id').toLowerCase()
                );

                this.set('otherProviders', filtered);
                this.get('activeFilters.providers').pushObject(filtered[0].key);
            }

            this.notifyPropertyChange('otherProviders');
        });

        this.loadPage();
    },
    subjectChanged: Ember.observer('subjectFilter', function() {
        Ember.run.once(() => {
            let filter = this.get('subjectFilter');
            if (!filter || filter === 'true') return;
            this.set('activeFilters.subjects', filter.split('AND'));
            this.notifyPropertyChange('activeFilters');
            this.loadPage();
        });
    }),
    providerChanged: Ember.observer('providerFilter', function() {
        if (!this.get('theme.isProvider')) {
            Ember.run.once(() => {
                let filter = this.get('providerFilter');
                if (!filter || filter === 'true') return;
                this.set('activeFilters.providers', filter.split('AND'));
                this.notifyPropertyChange('activeFilters');
                this.set('providersPassed', true);
                this.loadPage();
            });
        }
    }),
    loadPage() {
        this.set('loading', true);
        Ember.run.debounce(this, this._loadPage, 500);
    },
    _loadPage() {
        let queryBody = JSON.stringify(this.getQueryBody());

        return Ember.$.ajax({
            url: this.get('searchUrl'),
            crossDomain: true,
            type: 'POST',
            contentType: 'application/json',
            data: queryBody
        }).then(json => {
            if (this.isDestroyed || this.isDestroying) return;

            this.set('numberOfResults', json.hits.total);

            let results = json.hits.hits.map(hit => {
                // HACK: Make share data look like apiv2 preprints data
                let result = Ember.merge(hit._source, {
                    id: hit._id,
                    type: 'elastic-search-result',
                    workType: hit._source['@type'],
                    abstract: hit._source.description,
                    subjects: hit._source.subjects.map(each => ({text: each})),
                    providers: hit._source.sources.map(item => ({name: item})),
                    osfProvider: hit._source.sources.reduce((acc, source) => (acc || this.get('osfProviders').includes(source)), false),
                    hyperLinks: [// Links that are hyperlinks from hit._source.lists.links
                        {
                            type: 'share',
                            url: config.SHARE.baseUrl + 'preprint/' + hit._id
                        }
                    ],
                    infoLinks: [] // Links that are not hyperlinks  hit._source.lists.links
                });

                hit._source.identifiers.forEach(function(identifier) {
                    if (identifier.startsWith('http://')) {
                        result.hyperLinks.push({url: identifier});
                    } else {
                        const spl = identifier.split('://');
                        const [type, uri, ..._] = spl; // jshint ignore:line
                        result.infoLinks.push({type, uri});
                    }
                });

                result.contributors = result.lists.contributors
                  .sort((b, a) => (b.order_cited || -1) - (a.order_cited || -1))
                  .map(contributor => ({
                        users: Object.keys(contributor)
                          .reduce(
                              (acc, key) => Ember.merge(acc, {[key.camelize()]: contributor[key]}),
                              {bibliographic: contributor.relation !== 'contributor'}
                          )
                    }));

                // Temporary fix to handle half way migrated SHARE ES
                // Only false will result in a false here.
                result.contributors.map(contributor => contributor.users.bibliographic = !(contributor.users.bibliographic === false));  // jshint ignore:line

                return result;
            });

            this.set('loading', false);
            return this.set('results', results);
        });
    },
    maxPages: Ember.computed('numberOfResults', function() {
        return ((this.get('numberOfResults') / this.get('size')) | 0) + (this.get('numberOfResults') % 10 === 0 ? 0 : 1);
    }),
    getQueryBody() {
        const facetFilters = this.get('activeFilters');

        this.set('subjectFilter', facetFilters.subjects.join('AND'));

        if (!this.get('theme.isProvider'))
            this.set('providerFilter', facetFilters.providers.join('AND'));

        const filter = [
            {
                terms: {
                    type: [
                        'preprint'
                    ]
                }
            }
        ];

        // TODO set up ember to transpile Object.entries
        for (const key in filterMap) {
            const val = filterMap[key];
            const filterList = facetFilters[key];

            if (!filterList.length || (key === 'providers' && this.get('theme.isProvider')))
                continue;

            filter.push({
                terms: {
                    [val]: filterList
                }
            });
        }

        if (this.get('theme.isProvider')) {
            filter.push({
                terms: {
                    sources: [this.get('theme.provider.name')]
                }
            });
        }

        const sortByOption = this.get('chosenSortByOption');
        const sort = {};
        const i18n = this.get('i18n');
        if (sortByOption.toString() === i18n.t('discover.sort_oldest_newest').toString()) {
            sort.date_updated = 'asc';
        } else if (sortByOption.toString() === i18n.t('discover.sort_newest_oldest').toString()) {
            sort.date_updated = 'desc';
        }

        return this.set('queryBody', {
            query: {
                bool: {
                    must: {
                        query_string: {
                            query: elasticEscape(this.get('queryString')) || '*'
                        }
                    },
                    filter
                }
            },
            sort,
            from: (this.get('page') - 1) * this.get('size'),
        });
    },

    reloadSearch: Ember.observer('activeFilters', function() {
        this.set('page', 1);
        this.loadPage();
    }),

    _clearFilters() {
        this.set('activeFilters', {
            providers: this.get('theme.isProvider') ? this.get('activeFilters.providers') : [],
            subjects: []
        });
    },

    otherProviders: [],
    actions: {
        search(val, event) {
            if (event &&
                (
                    event.keyCode < 49 ||
                    [91, 92, 93].includes(event.keyCode)
                ) &&
                ![8, 32, 48].includes(event.keyCode)
            )
                return;

            this.set('page', 1);
            this.loadPage();

            Ember.get(this, 'metrics')
                .trackEvent({
                    category: `${event && event.type === 'keyup' ? 'input' : 'button'}`,
                    action: `${event && event.type === 'keyup' ? 'onkeyup' : 'click'}`,
                    label: 'Preprints - Discover - Search'
                });
        },

        previous() {
            if (this.get('page') > 1) {
                this.decrementProperty('page');
                this.loadPage();
            }
        },

        next() {
            if (this.get('page') * this.get('size') <= this.get('numberOfResults')) {
                this.incrementProperty('page');
                this.loadPage();
            }
        },

        clearFilters() {
            this._clearFilters();

            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'button',
                    action: 'click',
                    label: 'Preprints - Discover - Clear Filters'
                });
        },

        sortBySelect(index) {
            // Selecting an option just swaps it with whichever option is first
            let copy = this.get('sortByOptions').slice(0);
            let temp = copy[0];
            copy[0] = copy[index];
            copy[index] = temp;
            this.set('sortByOptions', copy);
            this.set('page', 1);
            this.loadPage();

            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'dropdown',
                    action: 'select',
                    label: `Preprints - Discover - Sort by: ${copy[index]}`
                });
        },

        updateFilters(filterType, item) {
            item = typeof item === 'object' ? item.text : item;
            const filters = this.get(`activeFilters.${filterType}`);
            const hasItem = filters.includes(item);
            const action = hasItem ? 'remove' : 'push';
            filters[`${action}Object`](item);
            this.notifyPropertyChange('activeFilters');

            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'filter',
                    action: hasItem ? 'remove' : 'add',
                    label: `Preprints - Discover - ${filterType} ${item}`
                });
        },
    },
});
<|endoftext|>


<commit_before>/**
 * @jsx React.DOM
 */
var React = require('react');
var bus = require('../bus');

var url = require('url');

var Tag = require('./tag');

var Router = require('react-router');
var Link = Router.Link;

function canShow(items, className){
    if (!items.length) {
        className += ' hidden';
    }
    return className;
}

var ReleaseDetail = React.createClass({
    componentWillMount: function(){
        var cat = this.props.params.cat;
        this.props.release = this.props.data.findWhere({cat:cat});
        var data = this.props.release.toJSON();

        // fix for В style unicode entities
        var s = document.createElement('span');
        s.innerHTML = '' + data.album + ' - ' + data.artist + '  | ' + data.cat.toUpperCase();
        document.title = s.innerHTML;
    },
    componentDidMount: function(){
        window.scrollTo(0,0);
    },
    getDownloadLinks: function(){

        var sources = ["fma", "archiveorg", "scene_org", "clearbits", "sonicsquirrel", "soundshiva", "dogmazic", "jamendo", "bandcamp", "soundcloud", "mixcloud", "lastfm", "itunes", "amazon", "googleplay", "spotify", "discogs", "rym", "musicbrainz"];
        var release = this.props.release.toJSON();

        var downloadLinks = sources.map(function(source){
            if (release[source]) {
                return ;
            };
        });
        return downloadLinks;

    },

    buildLink: function(release){
        return (
            <Link className="mini-link" to={"/release/" + release.cat}>

            </Link>);
    },
    getTags: function(tags){

        return tags.map(function(tag){
            return (<Tag tag={tag}>{tag}</Tag>)
        });

    },
    getCountries: function(countries){
        return countries.map(function(country){
            var country = country.replace('.','');
            return ();
        });
    },
    getRelatedLinks: function(data){
        var relatedRelease = this.props.data.getMayAlsoLike({
            tags: data.tags,
            artist: data.artist,
            ignore: data.cat
        });

        var links = relatedRelease.map(function(model){
            var data = model.toJSON();
            return this.buildLink(data);
        }.bind(this));

        return links;

    },
    getBySameArtist: function(data){
        var bySameArtist = this.props.data.getByArtist(data.artist, data.cat);

        var links = bySameArtist.map(function(model){
            var data = model.toJSON();
            return this.buildLink(data);
        }.bind(this));

        return links;
    },
    startPlaying: function(){
        // push this release to the playlist queue
        bus.emit('queue', this.props.release);
    },
    render: function(){

        var data = this.props.release.toJSON();
        var playable = data.tracks && data.tracks.length;
        var coverPath = url.parse(data.cover);
        var formattedDate = data.momented.format('MMMM Do, YYYY');

        var relatedLinks = this.getRelatedLinks(data);
        var bySameArtist = this.getBySameArtist(data);

        var tags = this.getTags(data.tags);
        var countries = this.getCountries(data.country);
        var downloadLinks = this.getDownloadLinks();

        return (

{data.album} {data.artist}

Released on {formattedDate}

                        Tags:

                        {tags}

Nationality:

                        {countries}

                        Download links:

                        {downloadLinks}

By same artist:

                        {bySameArtist}

You may also like:

                        {relatedLinks}

        )
    }
})

/*

{
    "cat": "enrmp001",
    "artist": "ps",
    "album": "from my own little corner",
    "release_date": "2002-01-14",
    "info_en": "3 track dark ambient ep from our staff member ps<\/a>. Photo artwork by Joana Queir\u00f3z.",
    "info_pt": "EP de dark ambient de 3 temas feito pelo nosso membro de staff ps<\/a>. Fotografia de Joana Queir\u00f3z.",
    "discogs": "http:\/\/www.discogs.com\/ps-From-My-Own-Little-Corner\/release\/606784",
    "jamendo": null,
    "sonicsquirrel": null,
    "scene_org": "http:\/\/scene.org\/file.php?file=%2Fmusic%2Fgroups%2Fenough_records%2Fenrmp001_ps_-_from_my_own_little_corner.zip&fileinfo",
    "bandcamp": "http:\/\/enoughrec.bandcamp.com\/album\/from-my-own-little-corner",
    "soundcloud": null,
    "itunes": null,
    "spotify": null,
    "mixcloud": null,
    "fma": null,
    "rym": "http:\/\/rateyourmusic.com\/release\/ep\/ps_f1\/from_my_own_little_corner\/",
    "musicbrainz": null,
    "dogmazic": null,
    "lastfm": "http:\/\/www.last.fm\/music\/ps\/from+my+own+little+corner",
    "tags": ["laidback", "dark", "ambient", ".pt"],
    "archiveorg": "http:\/\/www.archive.org\/details\/enrmp001_ps_-_from_my_own_little_corner",
    "cover": "http:\/\/enoughrecords.scene.org\/covers\/enrmp001.jpg",
    "cc": "http:\/\/creativecommons.org\/licenses\/by-nc-sa\/3.0\/",
    "cc_img": "ccbyncsa.png",
    "tracks": ["http:\/\/http.de.scene.org\/pub\/music\/groups\/enough_records\/enrmp001_ps_-_from_my_own_little_corner\/01_ps_-_lost_in_familiar_alleys.mp3", "http:\/\/http.de.scene.org\/pub\/music\/groups\/enough_records\/enrmp001_ps_-_from_my_own_little_corner\/02_ps_-_while_she_is_not_here.mp3", "http:\/\/http.de.scene.org\/pub\/music\/groups\/enough_records\/enrmp001_ps_-_from_my_own_little_corner\/03_xhale_vs_ps_-_two_seconds_of_time.mp3"]
}

*/

module.exports = ReleaseDetail;
<commit_msg>improve text on detail page for same artist heading<commit_after>/**
 * @jsx React.DOM
 */
var React = require('react');
var bus = require('../bus');

var url = require('url');

var Tag = require('./tag');

var Router = require('react-router');
var Link = Router.Link;

function canShow(items, className){
    if (!items.length) {
        className += ' hidden';
    }
    return className;
}

var ReleaseDetail = React.createClass({
    componentWillMount: function(){
        var cat = this.props.params.cat;
        this.props.release = this.props.data.findWhere({cat:cat});
        var data = this.props.release.toJSON();

        // fix for В style unicode entities
        var s = document.createElement('span');
        s.innerHTML = '' + data.album + ' - ' + data.artist + '  | ' + data.cat.toUpperCase();
        document.title = s.innerHTML;
    },
    componentDidMount: function(){
        window.scrollTo(0,0);
    },
    getDownloadLinks: function(){

        var sources = ["fma", "archiveorg", "scene_org", "clearbits", "sonicsquirrel", "soundshiva", "dogmazic", "jamendo", "bandcamp", "soundcloud", "mixcloud", "lastfm", "itunes", "amazon", "googleplay", "spotify", "discogs", "rym", "musicbrainz"];
        var release = this.props.release.toJSON();

        var downloadLinks = sources.map(function(source){
            if (release[source]) {
                return ;
            };
        });
        return downloadLinks;

    },

    buildLink: function(release){
        return (
            <Link className="mini-link" to={"/release/" + release.cat}>

            </Link>);
    },
    getTags: function(tags){

        return tags.map(function(tag){
            return (<Tag tag={tag}>{tag}</Tag>)
        });

    },
    getCountries: function(countries){
        return countries.map(function(country){
            var country = country.replace('.','');
            return ();
        });
    },
    getRelatedLinks: function(data){
        var relatedRelease = this.props.data.getMayAlsoLike({
            tags: data.tags,
            artist: data.artist,
            ignore: data.cat
        });

        var links = relatedRelease.map(function(model){
            var data = model.toJSON();
            return this.buildLink(data);
        }.bind(this));

        return links;

    },
    getBySameArtist: function(data){
        var bySameArtist = this.props.data.getByArtist(data.artist, data.cat);

        var links = bySameArtist.map(function(model){
            var data = model.toJSON();
            return this.buildLink(data);
        }.bind(this));

        return links;
    },
    startPlaying: function(){
        // push this release to the playlist queue
        bus.emit('queue', this.props.release);
    },
    render: function(){

        var data = this.props.release.toJSON();
        var playable = data.tracks && data.tracks.length;
        var coverPath = url.parse(data.cover);
        var formattedDate = data.momented.format('MMMM Do, YYYY');

        var relatedLinks = this.getRelatedLinks(data);
        var bySameArtist = this.getBySameArtist(data);

        var tags = this.getTags(data.tags);
        var countries = this.getCountries(data.country);
        var downloadLinks = this.getDownloadLinks();

        var sameArtistText = data.artist === 'Various Artists' ? 'More Compilations:' : 'Other releases by '+data.artist+':';

        return (

{data.album} {data.artist}

Released on {formattedDate}

                        Tags:

                        {tags}

Nationality:

                        {countries}

                        Download links:

                        {downloadLinks}

{sameArtistText}

                        {bySameArtist}

You may also like:

                        {relatedLinks}

        )
    }
})

/*

{
    "cat": "enrmp001",
    "artist": "ps",
    "album": "from my own little corner",
    "release_date": "2002-01-14",
    "info_en": "3 track dark ambient ep from our staff member ps<\/a>. Photo artwork by Joana Queir\u00f3z.",
    "info_pt": "EP de dark ambient de 3 temas feito pelo nosso membro de staff ps<\/a>. Fotografia de Joana Queir\u00f3z.",
    "discogs": "http:\/\/www.discogs.com\/ps-From-My-Own-Little-Corner\/release\/606784",
    "jamendo": null,
    "sonicsquirrel": null,
    "scene_org": "http:\/\/scene.org\/file.php?file=%2Fmusic%2Fgroups%2Fenough_records%2Fenrmp001_ps_-_from_my_own_little_corner.zip&fileinfo",
    "bandcamp": "http:\/\/enoughrec.bandcamp.com\/album\/from-my-own-little-corner",
    "soundcloud": null,
    "itunes": null,
    "spotify": null,
    "mixcloud": null,
    "fma": null,
    "rym": "http:\/\/rateyourmusic.com\/release\/ep\/ps_f1\/from_my_own_little_corner\/",
    "musicbrainz": null,
    "dogmazic": null,
    "lastfm": "http:\/\/www.last.fm\/music\/ps\/from+my+own+little+corner",
    "tags": ["laidback", "dark", "ambient", ".pt"],
    "archiveorg": "http:\/\/www.archive.org\/details\/enrmp001_ps_-_from_my_own_little_corner",
    "cover": "http:\/\/enoughrecords.scene.org\/covers\/enrmp001.jpg",
    "cc": "http:\/\/creativecommons.org\/licenses\/by-nc-sa\/3.0\/",
    "cc_img": "ccbyncsa.png",
    "tracks": ["http:\/\/http.de.scene.org\/pub\/music\/groups\/enough_records\/enrmp001_ps_-_from_my_own_little_corner\/01_ps_-_lost_in_familiar_alleys.mp3", "http:\/\/http.de.scene.org\/pub\/music\/groups\/enough_records\/enrmp001_ps_-_from_my_own_little_corner\/02_ps_-_while_she_is_not_here.mp3", "http:\/\/http.de.scene.org\/pub\/music\/groups\/enough_records\/enrmp001_ps_-_from_my_own_little_corner\/03_xhale_vs_ps_-_two_seconds_of_time.mp3"]
}

*/

module.exports = ReleaseDetail;
<|endoftext|>


<commit_before>describe('MainCtrl: ', function() {
  var $rootScope,
      $scope,
      $controller,
      utils,
      board,
      firebaseService,
      auth,
      modalService,
      voteService;

  beforeEach(angular.mock.module('fireideaz'));

  beforeEach(inject(function($injector){
    $rootScope = $injector.get('$rootScope');
    $scope = $rootScope.$new();
    $controller = $injector.get('$controller');
    utils = $injector.get('Utils');
    modalService = $injector.get('ModalService');
    firebaseService = $injector.get('FirebaseService');
    auth = $injector.get('Auth');
    voteService = $injector.get('VoteService');

    $scope.userId = 'userId';
    $scope.board = { max_votes: 6 };

    $controller('MainCtrl', {
      '$scope': $scope,
      'utils': utils,
      'modalService': modalService,
      'firebaseService': firebaseService,
      'auth': auth,
      'voteService': voteService
    });
  }));

  describe('Board', function () {

    it('should return true when sort board order by votes', function() {
      $scope.sortField = 'votes';

      expect($scope.getSortOrder()).to.be.true;
    });

    it('should return false when sort board order is not by votes', function() {
      $scope.sortField = 'something else';

      expect($scope.getSortOrder()).to.be.false;
    });

    it('should change the board context', function() {
      var updateSpy = sinon.spy();

      $scope.boardRef = {
        update: updateSpy
      }

      $scope.changeBoardContext();

      expect(updateSpy.called).to.be.true;
    });

    it('should change the board name', function() {
      var updateSpy = sinon.spy();

      $scope.boardRef = {
        update: updateSpy
      }

      $scope.changeBoardName();

      expect(updateSpy.called).to.be.true;
    });

    it('should create a new board', function () {
      sinon.stub(utils, 'createUserId', function () { return 'userId'; });
      var createUserSpy = sinon.spy(auth, 'createUserAndLog');
      var closeAllSpy = sinon.spy(modalService, 'closeAll');

      $scope.createNewBoard();

      expect(createUserSpy.calledWith($scope.userId)).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });

    it('should create a new board with submitOnEnter fn', function () {
      sinon.stub(utils, 'createUserId', function () { return 'userId'; });
      var createUserSpy = sinon.spy(auth, 'createUserAndLog');
      var closeAllSpy = sinon.spy(modalService, 'closeAll');

      $scope.newBoard.name = "any_name_but_not_null";
      var event = {};
      event.keyCode = 13;
      $scope.submitOnEnter(event, 'createNewBoard');

      expect(createUserSpy.calledWith($scope.userId)).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });

  });

  describe('Messages', function () {
    it('should return array containing 1 element for each vote on a message', function() {
      sinon.stub(voteService, 'returnNumberOfVotesOnMessage', function() { return 3 });

      var array = $scope.getNumberOfVotesOnMessage('userId', 'abc');

      expect(array.length).to.equal(3);
    });

    it('should return empty array', function() {
      sinon.stub(voteService, 'returnNumberOfVotesOnMessage', function() { return 0 });

      var array = $scope.getNumberOfVotesOnMessage('userId', 'abc');

      expect(array.length).to.equal(0);
    });

    it('should delete a message', function() {
      var removeSpy = sinon.spy();
      var closeAllSpy = sinon.spy(modalService, 'closeAll');

      var message = {
        text: 'text of message',
        user_id: '139021'
      }

      $scope.messages = {
        $remove: removeSpy
      }

      $scope.deleteMessage(message);

      expect(closeAllSpy.called).to.be.true;
      expect(removeSpy.calledWith(message)).to.be.true;
    });

    it('should add a new message', function() {
      sinon.stub(firebaseService, 'getServerTimestamp', function() { return '00:00:00' });

      var addMessagePromise = { then: sinon.spy() };
      var addStub = sinon.stub().returns(addMessagePromise);

      var message = {
        text: 'text of message',
        user_id: '139021'
      }

      $scope.messages = {
        $add: addStub
      }

      $scope.addNewMessage({id: 1});

      expect(addStub.called).to.be.true;
    });

    it('should vote on a message', function() {
      sinon.stub(firebaseService, 'getServerTimestamp', function() { return '00:00:00' });
      sinon.stub(voteService, 'isAbleToVote', function() { return true });
      sinon.spy(voteService, 'increaseMessageVotes');
      var updateSpy = sinon.spy();

      $scope.messagesRef = {
        child: function() {
          return { update: updateSpy}
        }
      }

      $scope.userId = 'userId';

      $scope.vote('abc', 5);

      expect(updateSpy.calledWith({votes: 6, date: '00:00:00'})).to.be.true;
      expect(voteService.increaseMessageVotes.calledWith('userId', 'abc')).to.be.true;
    });

    it('should unvote a message', function() {
      sinon.stub(firebaseService, 'getServerTimestamp', function() { return '00:00:00' });
      sinon.stub(voteService, 'canUnvoteMessage', function() { return true });
      sinon.spy(voteService, 'decreaseMessageVotes');
      var updateSpy = sinon.spy();

      $scope.messagesRef = {
        child: function() {
          return { update: updateSpy}
        }
      }

      $scope.userId = 'userId';

      $scope.unvote('abc', 5);

      expect(updateSpy.calledWith({votes: 4, date: '00:00:00'})).to.be.true;
      expect(voteService.decreaseMessageVotes.calledWith('userId', 'abc')).to.be.true;
    });
  });

  describe('Columns', function() {
    var setSpy,
        boardColumns,
        expectedColumns,
        closeAllSpy;

    beforeEach(function() {
      closeAllSpy = sinon.spy(modalService, 'closeAll');

      setSpy = sinon.spy();

      boardColumns = {
        set: setSpy
      }

      sinon.stub(utils, 'toObject', function () {
        return { column: 'column' };
      });

      $scope.board = {
        columns: [
          {
            value: 'columnName',
            id: 1
          },
          {
            value: 'otherColumnName',
            id: 2
          }
        ]
      }

      sinon.stub(firebaseService, 'getBoardColumns', function () {
        return boardColumns;
      });
    });

    it('should add a new column to the board', function() {
      var expectedColumns = [
        {
          value: 'columnName',
          id: 1
        },
        {
          value: 'otherColumnName',
          id: 2
        },
        {
          value: 'anotherColumnName',
          id: 3
        }
      ]

      sinon.stub(utils, 'getNextId', function () { return 3; });

      $scope.addNewColumn('anotherColumnName');

      expect($scope.board.columns).to.deep.equal(expectedColumns);
      expect(setSpy.called).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });

    it('should add a new column to the board with submitOnEnter fn', function() {
      var expectedColumns = [
        {
          value: 'columnName',
          id: 1
        },
        {
          value: 'otherColumnName',
          id: 2
        },
        {
          value: 'anotherColumnName',
          id: 3
        }
      ]

      sinon.stub(utils, 'getNextId', function () { return 3; });

      var event = {};
      event.keyCode = 13;
      $scope.submitOnEnter(event, 'addNewColumn', 'anotherColumnName');

      expect($scope.board.columns).to.deep.equal(expectedColumns);
      expect(setSpy.called).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });

    it('should change column name', function() {
      $scope.changeColumnName(1, 'new name!');

      expect($scope.board.columns[0].value).to.equal('new name!');
      expect(setSpy.called).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });

    it('should delete last column of the board', function() {
      var expectedBoard = {
        columns: [
          {
            value: 'columnName',
            id: 1
          },
          {
            value: 'otherColumnName',
            id: 2
          }
        ]
      };

      $scope.deleteColumn({id: 3});

      expect($scope.board.columns).to.deep.equal(expectedBoard.columns);
      expect(setSpy.called).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });

    it('should delete specific column of the board', function() {
      var expectedColumns = [
        {
          value: 'otherColumnName',
          id: 2
        }
      ];

      $scope.deleteColumn({id: 1});

      expect($scope.board.columns).to.deep.equal(expectedColumns);
      expect(setSpy.called).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });
  });
  describe('vote limits', function() {
    it(' is able to increment the maximum number of votes allowed per user', function() {
      var updateSpy = sinon.spy();

      $scope.boardRef = {
        update: updateSpy
      }

      oldMaxVotes = $scope.maxVotes;
      $scope.incrementMaxVotes();
      expect(updateSpy.calledWith({max_votes: (oldMaxVotes + 1)})).to.be.true;
    })

    it(' is able to decrement the maximum number of votes allowed per user', function() {
      var updateSpy = sinon.spy();

      $scope.boardRef = {
        update: updateSpy
      }

      oldMaxVotes = $scope.maxVotes;
      $scope.decrementMaxVotes();
      expect(updateSpy.calledWith({max_votes: (oldMaxVotes - 1)})).to.be.true;
    })
  })
describe('Import', function() {
    var inputFile = {lastModified: 1491246451076,
      lastModifiedDate: Date.parse ('Mon Apr 03 2017 21:07:31 GMT+0200 (W. Europe Daylight Time)'),
      name: "import.csv",
      size: 515,
      type: "application/vnd.ms-excel"};

    beforeEach(function() {

      $scope.board = {
        columns: [
          {
            value: 'columnName',
            id: 1
          },
          {
            value: 'otherColumnName',
            id: 2
          }
        ]
      }

      sinon.stub(firebaseService, 'getBoardColumns', function () {
        return boardColumns;
      });
    });

    before (function (){
      sinon.spy(Papa, 'parse');
    });

    after (function () {
      Papa.parse.restore();
    });

    it('should call parse meethod', function() {

      $scope.submitImportFile(inputFile);
      expect(Papa.parse.called).to.be.true;
      expect(Papa.parse.calledWith(inputFile)).to.be.true;
    });

    it ('should show error for empty file', function(){
      var emptyFile = inputFile;
      emptyFile.size = 0;
      $scope.submitImportFile(inputFile);
      expect ($scope.import.error).to.be.equal('The file you are trying to import seems to be  empty');
    })

    it('should initialize clear mapping and data', function() {
      var expectedMapping = [];
      $scope.submitImportFile(inputFile);
      expect ($scope.import.mapping).to.deep.equal(expectedMapping);
    });

    it('should parse import data', function() {
      var expectedData = [
        ["Column 1","Column 2","Column 3"],
        ["a","b","c"],
        ["1","2","3"]];
      $scope.submitImportFile('"Column 1","Column 2","Column 3"\n"a","b","c"\n"1","2","3"');
      expect ($scope.import.data).to.deep.equal(expectedData);
     });

    it('should import mapped data', function() {
      var messageDate = Date.parse ('Mon Apr 03 2017 21:07:31 GMT+0200 (W. Europe Daylight Time)');
      var expectedMessages = [
        {text:'C3R1', user_id: 'userId', type: {id: 1}, date: messageDate, votes: 0},
        {text:'C3R2', user_id: 'userId', type: {id: 1}, date: messageDate, votes: 0}];

     sinon.stub(firebaseService, 'getServerTimestamp', function() { return messageDate });
     $scope.userUid = 'userId';
      var addStub = sinon.spy();

      $scope.messages = {
        $add: addStub
      }

      $scope.submitImportFile('"Column 1","Column 2","Column 3"\n"C1R1","C2R1","C3R1"\n"C1R2","C2R2","C3R2"');
      //First init with data, then setup mapping
      $scope.import.mapping = [
        {mapFrom:2, mapTo: 1, name: 'columnName'},
        {mapFrom:'-1', mapTo: 2, name: 'otherColumnName'}];

      $scope.importMessages();
      expect (addStub.calledWith(expectedMessages[0])).to.be.true;
      expect (addStub.calledWith(expectedMessages[1])).to.be.true;
    });
  });
});
 <commit_msg>[#138]Added test for malformed file import<commit_after>describe('MainCtrl: ', function() {
  var $rootScope,
      $scope,
      $controller,
      utils,
      board,
      firebaseService,
      auth,
      modalService,
      voteService;

  beforeEach(angular.mock.module('fireideaz'));

  beforeEach(inject(function($injector){
    $rootScope = $injector.get('$rootScope');
    $scope = $rootScope.$new();
    $controller = $injector.get('$controller');
    utils = $injector.get('Utils');
    modalService = $injector.get('ModalService');
    firebaseService = $injector.get('FirebaseService');
    auth = $injector.get('Auth');
    voteService = $injector.get('VoteService');

    $scope.userId = 'userId';
    $scope.board = { max_votes: 6 };

    $controller('MainCtrl', {
      '$scope': $scope,
      'utils': utils,
      'modalService': modalService,
      'firebaseService': firebaseService,
      'auth': auth,
      'voteService': voteService
    });
  }));

  describe('Board', function () {

    it('should return true when sort board order by votes', function() {
      $scope.sortField = 'votes';

      expect($scope.getSortOrder()).to.be.true;
    });

    it('should return false when sort board order is not by votes', function() {
      $scope.sortField = 'something else';

      expect($scope.getSortOrder()).to.be.false;
    });

    it('should change the board context', function() {
      var updateSpy = sinon.spy();

      $scope.boardRef = {
        update: updateSpy
      }

      $scope.changeBoardContext();

      expect(updateSpy.called).to.be.true;
    });

    it('should change the board name', function() {
      var updateSpy = sinon.spy();

      $scope.boardRef = {
        update: updateSpy
      }

      $scope.changeBoardName();

      expect(updateSpy.called).to.be.true;
    });

    it('should create a new board', function () {
      sinon.stub(utils, 'createUserId', function () { return 'userId'; });
      var createUserSpy = sinon.spy(auth, 'createUserAndLog');
      var closeAllSpy = sinon.spy(modalService, 'closeAll');

      $scope.createNewBoard();

      expect(createUserSpy.calledWith($scope.userId)).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });

    it('should create a new board with submitOnEnter fn', function () {
      sinon.stub(utils, 'createUserId', function () { return 'userId'; });
      var createUserSpy = sinon.spy(auth, 'createUserAndLog');
      var closeAllSpy = sinon.spy(modalService, 'closeAll');

      $scope.newBoard.name = "any_name_but_not_null";
      var event = {};
      event.keyCode = 13;
      $scope.submitOnEnter(event, 'createNewBoard');

      expect(createUserSpy.calledWith($scope.userId)).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });

  });

  describe('Messages', function () {
    it('should return array containing 1 element for each vote on a message', function() {
      sinon.stub(voteService, 'returnNumberOfVotesOnMessage', function() { return 3 });

      var array = $scope.getNumberOfVotesOnMessage('userId', 'abc');

      expect(array.length).to.equal(3);
    });

    it('should return empty array', function() {
      sinon.stub(voteService, 'returnNumberOfVotesOnMessage', function() { return 0 });

      var array = $scope.getNumberOfVotesOnMessage('userId', 'abc');

      expect(array.length).to.equal(0);
    });

    it('should delete a message', function() {
      var removeSpy = sinon.spy();
      var closeAllSpy = sinon.spy(modalService, 'closeAll');

      var message = {
        text: 'text of message',
        user_id: '139021'
      }

      $scope.messages = {
        $remove: removeSpy
      }

      $scope.deleteMessage(message);

      expect(closeAllSpy.called).to.be.true;
      expect(removeSpy.calledWith(message)).to.be.true;
    });

    it('should add a new message', function() {
      sinon.stub(firebaseService, 'getServerTimestamp', function() { return '00:00:00' });

      var addMessagePromise = { then: sinon.spy() };
      var addStub = sinon.stub().returns(addMessagePromise);

      var message = {
        text: 'text of message',
        user_id: '139021'
      }

      $scope.messages = {
        $add: addStub
      }

      $scope.addNewMessage({id: 1});

      expect(addStub.called).to.be.true;
    });

    it('should vote on a message', function() {
      sinon.stub(firebaseService, 'getServerTimestamp', function() { return '00:00:00' });
      sinon.stub(voteService, 'isAbleToVote', function() { return true });
      sinon.spy(voteService, 'increaseMessageVotes');
      var updateSpy = sinon.spy();

      $scope.messagesRef = {
        child: function() {
          return { update: updateSpy}
        }
      }

      $scope.userId = 'userId';

      $scope.vote('abc', 5);

      expect(updateSpy.calledWith({votes: 6, date: '00:00:00'})).to.be.true;
      expect(voteService.increaseMessageVotes.calledWith('userId', 'abc')).to.be.true;
    });

    it('should unvote a message', function() {
      sinon.stub(firebaseService, 'getServerTimestamp', function() { return '00:00:00' });
      sinon.stub(voteService, 'canUnvoteMessage', function() { return true });
      sinon.spy(voteService, 'decreaseMessageVotes');
      var updateSpy = sinon.spy();

      $scope.messagesRef = {
        child: function() {
          return { update: updateSpy}
        }
      }

      $scope.userId = 'userId';

      $scope.unvote('abc', 5);

      expect(updateSpy.calledWith({votes: 4, date: '00:00:00'})).to.be.true;
      expect(voteService.decreaseMessageVotes.calledWith('userId', 'abc')).to.be.true;
    });
  });

  describe('Columns', function() {
    var setSpy,
        boardColumns,
        expectedColumns,
        closeAllSpy;

    beforeEach(function() {
      closeAllSpy = sinon.spy(modalService, 'closeAll');

      setSpy = sinon.spy();

      boardColumns = {
        set: setSpy
      }

      sinon.stub(utils, 'toObject', function () {
        return { column: 'column' };
      });

      $scope.board = {
        columns: [
          {
            value: 'columnName',
            id: 1
          },
          {
            value: 'otherColumnName',
            id: 2
          }
        ]
      }

      sinon.stub(firebaseService, 'getBoardColumns', function () {
        return boardColumns;
      });
    });

    it('should add a new column to the board', function() {
      var expectedColumns = [
        {
          value: 'columnName',
          id: 1
        },
        {
          value: 'otherColumnName',
          id: 2
        },
        {
          value: 'anotherColumnName',
          id: 3
        }
      ]

      sinon.stub(utils, 'getNextId', function () { return 3; });

      $scope.addNewColumn('anotherColumnName');

      expect($scope.board.columns).to.deep.equal(expectedColumns);
      expect(setSpy.called).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });

    it('should add a new column to the board with submitOnEnter fn', function() {
      var expectedColumns = [
        {
          value: 'columnName',
          id: 1
        },
        {
          value: 'otherColumnName',
          id: 2
        },
        {
          value: 'anotherColumnName',
          id: 3
        }
      ]

      sinon.stub(utils, 'getNextId', function () { return 3; });

      var event = {};
      event.keyCode = 13;
      $scope.submitOnEnter(event, 'addNewColumn', 'anotherColumnName');

      expect($scope.board.columns).to.deep.equal(expectedColumns);
      expect(setSpy.called).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });

    it('should change column name', function() {
      $scope.changeColumnName(1, 'new name!');

      expect($scope.board.columns[0].value).to.equal('new name!');
      expect(setSpy.called).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });

    it('should delete last column of the board', function() {
      var expectedBoard = {
        columns: [
          {
            value: 'columnName',
            id: 1
          },
          {
            value: 'otherColumnName',
            id: 2
          }
        ]
      };

      $scope.deleteColumn({id: 3});

      expect($scope.board.columns).to.deep.equal(expectedBoard.columns);
      expect(setSpy.called).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });

    it('should delete specific column of the board', function() {
      var expectedColumns = [
        {
          value: 'otherColumnName',
          id: 2
        }
      ];

      $scope.deleteColumn({id: 1});

      expect($scope.board.columns).to.deep.equal(expectedColumns);
      expect(setSpy.called).to.be.true;
      expect(closeAllSpy.called).to.be.true;
    });
  });
  describe('vote limits', function() {
    it(' is able to increment the maximum number of votes allowed per user', function() {
      var updateSpy = sinon.spy();

      $scope.boardRef = {
        update: updateSpy
      }

      oldMaxVotes = $scope.maxVotes;
      $scope.incrementMaxVotes();
      expect(updateSpy.calledWith({max_votes: (oldMaxVotes + 1)})).to.be.true;
    })

    it(' is able to decrement the maximum number of votes allowed per user', function() {
      var updateSpy = sinon.spy();

      $scope.boardRef = {
        update: updateSpy
      }

      oldMaxVotes = $scope.maxVotes;
      $scope.decrementMaxVotes();
      expect(updateSpy.calledWith({max_votes: (oldMaxVotes - 1)})).to.be.true;
    })
  })
describe('Import', function() {
    var inputFile = {lastModified: 1491246451076,
      lastModifiedDate: Date.parse ('Mon Apr 03 2017 21:07:31 GMT+0200 (W. Europe Daylight Time)'),
      name: "import.csv",
      size: 515,
      type: "application/vnd.ms-excel"};

    beforeEach(function() {

      $scope.board = {
        columns: [
          {
            value: 'columnName',
            id: 1
          },
          {
            value: 'otherColumnName',
            id: 2
          }
        ]
      }

      sinon.stub(firebaseService, 'getBoardColumns', function () {
        return boardColumns;
      });
    });

    before (function (){
      sinon.spy(Papa, 'parse');
    });

    after (function () {
      Papa.parse.restore();
    });

    it('should call parse meethod', function() {

      $scope.submitImportFile(inputFile);
      expect(Papa.parse.called).to.be.true;
      expect(Papa.parse.calledWith(inputFile)).to.be.true;
    });

    it ('should show error for empty file', function(){
      var emptyFile = inputFile;
      emptyFile.size = 0;
      $scope.submitImportFile(inputFile);
      expect ($scope.import.error).to.be.equal('The file you are trying to import seems to be  empty');
    })

    it ('should show error for malformed file', function(){
      var emptyFile = inputFile;
      emptyFile.size = 0;
      $scope.submitImportFile('nn');
      expect ($scope.import.error).to.be.not.empty;
    })

    it('should initialize clear mapping and data', function() {
      var expectedMapping = [];
      $scope.submitImportFile(inputFile);
      expect ($scope.import.mapping).to.deep.equal(expectedMapping);
    });

    it('should parse import data', function() {
      var expectedData = [
        ["Column 1","Column 2","Column 3"],
        ["a","b","c"],
        ["1","2","3"]];
      $scope.submitImportFile('"Column 1","Column 2","Column 3"\n"a","b","c"\n"1","2","3"');
      expect ($scope.import.data).to.deep.equal(expectedData);
     });

    it('should import mapped data', function() {
      var messageDate = Date.parse ('Mon Apr 03 2017 21:07:31 GMT+0200 (W. Europe Daylight Time)');
      var expectedMessages = [
        {text:'C3R1', user_id: 'userId', type: {id: 1}, date: messageDate, votes: 0},
        {text:'C3R2', user_id: 'userId', type: {id: 1}, date: messageDate, votes: 0}];

     sinon.stub(firebaseService, 'getServerTimestamp', function() { return messageDate });
     $scope.userUid = 'userId';
      var addStub = sinon.spy();

      $scope.messages = {
        $add: addStub
      }

      $scope.submitImportFile('"Column 1","Column 2","Column 3"\n"C1R1","C2R1","C3R1"\n"C1R2","C2R2","C3R2"');
      //First init with data, then setup mapping
      $scope.import.mapping = [
        {mapFrom:2, mapTo: 1, name: 'columnName'},
        {mapFrom:'-1', mapTo: 2, name: 'otherColumnName'}];

      $scope.importMessages();
      expect (addStub.calledWith(expectedMessages[0])).to.be.true;
      expect (addStub.calledWith(expectedMessages[1])).to.be.true;
    });
  });
});
 <|endoftext|>


<commit_before>events = {};

var diacriticsMap = [{
  'base': 'a',
  'letters': /[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g
}, {
  'base': 'aa',
  'letters': /[\uA733]/g
}, {
  'base': 'ae',
  'letters': /[\u00E6\u01FD\u01E3]/g
}, {
  'base': 'ao',
  'letters': /[\uA735]/g
}, {
  'base': 'au',
  'letters': /[\uA737]/g
}, {
  'base': 'av',
  'letters': /[\uA739\uA73B]/g
}, {
  'base': 'ay',
  'letters': /[\uA73D]/g
}, {
  'base': 'b',
  'letters': /[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g
}, {
  'base': 'c',
  'letters': /[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g
}, {
  'base': 'd',
  'letters': /[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g
}, {
  'base': 'dz',
  'letters': /[\u01F3\u01C6]/g
}, {
  'base': 'e',
  'letters': /[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g
}, {
  'base': 'f',
  'letters': /[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g
}, {
  'base': 'g',
  'letters': /[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g
}, {
  'base': 'h',
  'letters': /[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g
}, {
  'base': 'hv',
  'letters': /[\u0195]/g
}, {
  'base': 'i',
  'letters': /[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g
}, {
  'base': 'j',
  'letters': /[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g
}, {
  'base': 'k',
  'letters': /[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g
}, {
  'base': 'l',
  'letters': /[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g
}, {
  'base': 'lj',
  'letters': /[\u01C9]/g
}, {
  'base': 'm',
  'letters': /[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g
}, {
  'base': 'n',
  'letters': /[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g
}, {
  'base': 'nj',
  'letters': /[\u01CC]/g
}, {
  'base': 'o',
  'letters': /[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g
}, {
  'base': 'oi',
  'letters': /[\u01A3]/g
}, {
  'base': 'ou',
  'letters': /[\u0223]/g
}, {
  'base': 'oo',
  'letters': /[\uA74F]/g
}, {
  'base': 'p',
  'letters': /[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g
}, {
  'base': 'q',
  'letters': /[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g
}, {
  'base': 'r',
  'letters': /[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g
}, {
  'base': 's',
  'letters': /[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g
}, {
  'base': 't',
  'letters': /[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g
}, {
  'base': 'tz',
  'letters': /[\uA729]/g
}, {
  'base': 'u',
  'letters': /[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g
}, {
  'base': 'v',
  'letters': /[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g
}, {
  'base': 'vy',
  'letters': /[\uA761]/g
}, {
  'base': 'w',
  'letters': /[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g
}, {
  'base': 'x',
  'letters': /[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g
}, {
  'base': 'y',
  'letters': /[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g
}, {
  'base': 'z',
  'letters': /[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g
}];

var removeDiacritics = function(value) {
  _.each(diacriticsMap, function(diacriticMap) {
    value = value.replace(diacriticMap.letters, diacriticMap.base);
  });

  return value;
}

var generateSlug = function(value, separator) {
  // Make text lower case and...
  return removeDiacritics(value.toLowerCase()) // Remove diacritics characters.
    .replace(/[^\w\s-]+/g, '') // Remove unsupported characters.
    .replace(/^\s+|\s+$/g, '') // Trim.
    .replace(/\s+/g, separator); // Replace white characters with separator.
};

events.beforesave = function() {
  var doc = this;
  var Class = doc.constructor;

  // Find a class on which the behavior had been set.
  var behaviorData = Astro.utils.behaviors.findBehavior(Class, 'slug');

  // Check if a field from which we want to create a slug has been
  // modified.
  var modified = doc.getModified();
  if (!_.has(modified, behaviorData.fieldName)) {
    return;
  }

  // Get a value of the field to make a slug from.
  var value = doc.get(behaviorData.fieldName);

  // If a value is null then we can not create a slug.
  if (_.isNull(value)) {
    return;
  }

  // Take the current value of the slug field.
  var currentSlug = doc.get(behaviorData.slugFieldName);

  // If the current slug is not empty and we can not update, then we have to
  // stop here.
  if (!_.isNull(currentSlug) && !behaviorData.canUpdate) {
    return;
  }

  // Generate a slug and use a defined separator.
  var slug = generateSlug(value, behaviorData.separator);

  // If the "unique" option was set, then check whether there are any
  // duplicates. If there is any document with the same slug, then we have to
  // add number at the end of the slug.
  if (behaviorData.unique) {
    var selector = {};
    selector[behaviorData.slugFieldName] = slug;
    var Collection = Class.getCollection();
    var count = Collection.find(selector).count();

    if (count > 0) {
      // Prepare the selector with a regular expression querying all the
      // documents that contains our slug and ends with number.
      var prefix = slug + behaviorData.separator;
      var re = new RegExp('^' + prefix + '\\d+$');
      selector[behaviorData.slugFieldName] = re;

      // Limit the amount of fields being fetched to only the slug field.
      var fields = {};
      fields[behaviorData.slugFieldName] = 1;

      // Set the first number that will be added at the end of the slug to 2.
      var index = 2;

      // Loop through all the documents with the same slug.
      Collection.find(selector, fields).forEach(function(d) {
        var dSlug = d.get(behaviorData.slugFieldName);
        var dIndex = parseInt(dSlug.replace(prefix, ''), 10);
        if (dIndex >= index) {
          index = dIndex + 1;
        }
      });

      slug = prefix + index;
    }
  }

  // Set the slug.
  doc.set(behaviorData.slugFieldName, slug);
};
<commit_msg>Change how current slug check is done<commit_after>events = {};

var diacriticsMap = [{
  'base': 'a',
  'letters': /[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g
}, {
  'base': 'aa',
  'letters': /[\uA733]/g
}, {
  'base': 'ae',
  'letters': /[\u00E6\u01FD\u01E3]/g
}, {
  'base': 'ao',
  'letters': /[\uA735]/g
}, {
  'base': 'au',
  'letters': /[\uA737]/g
}, {
  'base': 'av',
  'letters': /[\uA739\uA73B]/g
}, {
  'base': 'ay',
  'letters': /[\uA73D]/g
}, {
  'base': 'b',
  'letters': /[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g
}, {
  'base': 'c',
  'letters': /[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g
}, {
  'base': 'd',
  'letters': /[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g
}, {
  'base': 'dz',
  'letters': /[\u01F3\u01C6]/g
}, {
  'base': 'e',
  'letters': /[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g
}, {
  'base': 'f',
  'letters': /[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g
}, {
  'base': 'g',
  'letters': /[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g
}, {
  'base': 'h',
  'letters': /[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g
}, {
  'base': 'hv',
  'letters': /[\u0195]/g
}, {
  'base': 'i',
  'letters': /[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g
}, {
  'base': 'j',
  'letters': /[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g
}, {
  'base': 'k',
  'letters': /[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g
}, {
  'base': 'l',
  'letters': /[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g
}, {
  'base': 'lj',
  'letters': /[\u01C9]/g
}, {
  'base': 'm',
  'letters': /[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g
}, {
  'base': 'n',
  'letters': /[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g
}, {
  'base': 'nj',
  'letters': /[\u01CC]/g
}, {
  'base': 'o',
  'letters': /[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g
}, {
  'base': 'oi',
  'letters': /[\u01A3]/g
}, {
  'base': 'ou',
  'letters': /[\u0223]/g
}, {
  'base': 'oo',
  'letters': /[\uA74F]/g
}, {
  'base': 'p',
  'letters': /[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g
}, {
  'base': 'q',
  'letters': /[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g
}, {
  'base': 'r',
  'letters': /[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g
}, {
  'base': 's',
  'letters': /[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g
}, {
  'base': 't',
  'letters': /[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g
}, {
  'base': 'tz',
  'letters': /[\uA729]/g
}, {
  'base': 'u',
  'letters': /[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g
}, {
  'base': 'v',
  'letters': /[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g
}, {
  'base': 'vy',
  'letters': /[\uA761]/g
}, {
  'base': 'w',
  'letters': /[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g
}, {
  'base': 'x',
  'letters': /[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g
}, {
  'base': 'y',
  'letters': /[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g
}, {
  'base': 'z',
  'letters': /[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g
}];

var removeDiacritics = function(value) {
  _.each(diacriticsMap, function(diacriticMap) {
    value = value.replace(diacriticMap.letters, diacriticMap.base);
  });

  return value;
}

var generateSlug = function(value, separator) {
  // Make text lower case and...
  return removeDiacritics(value.toLowerCase()) // Remove diacritics characters.
    .replace(/[^\w\s-]+/g, '') // Remove unsupported characters.
    .replace(/^\s+|\s+$/g, '') // Trim.
    .replace(/\s+/g, separator); // Replace white characters with separator.
};

events.beforeSave = function() {
  var doc = this;
  var Class = doc.constructor;

  // Find a class on which the behavior had been set.
  var behaviorData = Astro.utils.behaviors.findBehavior(Class, 'slug');

  // Check if a field from which we want to create a slug has been
  // modified.
  var modified = doc.getModified();
  if (!_.has(modified, behaviorData.fieldName)) {
    return;
  }

  // Get a value of the field to make a slug from.
  var value = doc.get(behaviorData.fieldName);

  // If a value is null then we can not create a slug.
  if (_.isNull(value)) {
    return;
  }

  // Take the current value of the slug field.
  var currentSlug = doc.get(behaviorData.slugFieldName);

  // If the current slug is not empty and we can not update, then we have to
  // stop here.
  if (currentSlug && !behaviorData.canUpdate) {
    return;
  }

  // Generate a slug and use a defined separator.
  var slug = generateSlug(value, behaviorData.separator);

  // If the "unique" option was set, then check whether there are any
  // duplicates. If there is any document with the same slug, then we have to
  // add number at the end of the slug.
  if (behaviorData.unique) {
    var selector = {};
    selector[behaviorData.slugFieldName] = slug;
    var Collection = Class.getCollection();
    var count = Collection.find(selector).count();

    if (count > 0) {
      // Prepare the selector with a regular expression querying all the
      // documents that contains our slug and ends with number.
      var prefix = slug + behaviorData.separator;
      var re = new RegExp('^' + prefix + '\\d+$');
      selector[behaviorData.slugFieldName] = re;

      // Limit the amount of fields being fetched to only the slug field.
      var fields = {};
      fields[behaviorData.slugFieldName] = 1;

      // Set the first number that will be added at the end of the slug to 2.
      var index = 2;

      // Loop through all the documents with the same slug.
      Collection.find(selector, fields).forEach(function(d) {
        var dSlug = d.get(behaviorData.slugFieldName);
        var dIndex = parseInt(dSlug.replace(prefix, ''), 10);
        if (dIndex >= index) {
          index = dIndex + 1;
        }
      });

      slug = prefix + index;
    }
  }

  // Set the slug.
  doc.set(behaviorData.slugFieldName, slug);
};
<|endoftext|>


<commit_before>var sanitize = require('mongo-sanitize');

module.exports = function(app) {
  const PROJECT_PATH = '/projects';
  var Project = app.models.project;

  var getUserName = function(user) {
    return user ? user.username : null;
  };

  var controller = {};
  controller.listProjects = function(req, res) {
    var username = getUserName(req.user);
    Project.find({}).sort({
      code: 1
    })
      .exec()
      .then((projects) => {
        res.render('projects/projects', {
          username: username,
          messages: req.flash('info'),
          projects: projects
        });
      })
      .onReject((error) => res.status(500).render('error', {
        username: username,
        error: error.errmsg
      }));
  };

  controller.newProject = function(req, res) {
    var username = getUserName(req.user);
    res.render('projects/new', {
      username: username
    });
  };

  controller.saveProject = function(req, res) {
    var data = {
      name: sanitize(req.body.name)
    };
    var username = getUserName(req.user);
    Project.create(data)
      .then(() => {
        res.locals.messages = req.flash('info', 'Projeto criado com sucesso!');
        res.redirect(PROJECT_PATH);
      })
      .onReject((error) => res.status(500).render('error', {
        username: username,
        error: error.errmsg
      }));
  };

  controller.showProject = function(req, res) {
    var _id = sanitize(req.params.id);
    var username = getUserName(req.user);
    Project.findById(_id)
      .exec()
      .then((project) => res.render('projects/show', {
        username: username,
        project: project
      }))
      .onReject((error) => {
        var error = error.message ? error.message : error.errmsg;
        console.error(error);
        res.status(500).render('error', {
          username: username,
          error: error
        })
      });
  };

  controller.updateProject = function(req, res) {
    var _id = sanitize(req.params.id);
    var username = getUserName(req.user);
    var data = {
      name: sanitize(req.body.name)
    };
    Project.findByIdAndUpdate({
      _id: _id
    }, data)
      .exec()
      .then(() => {
        res.locals.messages = req.flash('info', 'Projeto atualizado com sucesso!');
        res.redirect(PROJECT_PATH);
      })
      .onReject((error) => res.status(500).render('error', {
        username: username,
        error: error.errmsg
      }));
  };

  controller.deleteProject = function(req, res) {
    var _id = sanitize(req.params.id);
    var username = getUserName(req.user);
    Project.findOneAndRemove({
      _id: _id
    })
      .exec()
      .then(() => {
        res.locals.messages = req.flash('info', 'Projeto removido com sucesso!');
        res.redirect(PROJECT_PATH)
      })
      .onReject((error) => res.status(500).render('error', {
        username: username,
        error: error.errmsg
      }));
  };

  controller.editProject = function(req, res) {
    var username = getUserName(req.user);
    var _id = sanitize(req.params.id);
    Project.findById({
      _id: _id
    })
      .exec()
      .then((project) => res.render('projects/edit', {
        username: username,
        project: project
      }))
      .onReject((error) => res.status(500).render('error', {
        username: username,
        error: error.errmsg
      }));
  };

  return controller;
};
<commit_msg>Removido a ordenação por código<commit_after>var sanitize = require('mongo-sanitize');

module.exports = function(app) {
  const PROJECT_PATH = '/projects';
  var Project = app.models.project;

  var getUserName = function(user) {
    return user ? user.username : null;
  };

  var controller = {};
  controller.listProjects = function(req, res) {
    var username = getUserName(req.user);
    Project.find({})
      .exec()
      .then((projects) => {
        res.render('projects/projects', {
          username: username,
          messages: req.flash('info'),
          projects: projects
        });
      })
      .onReject((error) => res.status(500).render('error', {
        username: username,
        error: error.errmsg
      }));
  };

  controller.newProject = function(req, res) {
    var username = getUserName(req.user);
    res.render('projects/new', {
      username: username
    });
  };

  controller.saveProject = function(req, res) {
    var data = {
      name: sanitize(req.body.name)
    };
    var username = getUserName(req.user);
    Project.create(data)
      .then(() => {
        res.locals.messages = req.flash('info', 'Projeto criado com sucesso!');
        res.redirect(PROJECT_PATH);
      })
      .onReject((error) => res.status(500).render('error', {
        username: username,
        error: error.errmsg
      }));
  };

  controller.showProject = function(req, res) {
    var _id = sanitize(req.params.id);
    var username = getUserName(req.user);
    Project.findById(_id)
      .exec()
      .then((project) => res.render('projects/show', {
        username: username,
        project: project
      }))
      .onReject((error) => {
        var error = error.message ? error.message : error.errmsg;
        console.error(error);
        res.status(500).render('error', {
          username: username,
          error: error
        })
      });
  };

  controller.updateProject = function(req, res) {
    var _id = sanitize(req.params.id);
    var username = getUserName(req.user);
    var data = {
      name: sanitize(req.body.name)
    };
    Project.findByIdAndUpdate({
      _id: _id
    }, data)
      .exec()
      .then(() => {
        res.locals.messages = req.flash('info', 'Projeto atualizado com sucesso!');
        res.redirect(PROJECT_PATH);
      })
      .onReject((error) => res.status(500).render('error', {
        username: username,
        error: error.errmsg
      }));
  };

  controller.deleteProject = function(req, res) {
    var _id = sanitize(req.params.id);
    var username = getUserName(req.user);
    Project.findOneAndRemove({
      _id: _id
    })
      .exec()
      .then(() => {
        res.locals.messages = req.flash('info', 'Projeto removido com sucesso!');
        res.redirect(PROJECT_PATH)
      })
      .onReject((error) => res.status(500).render('error', {
        username: username,
        error: error.errmsg
      }));
  };

  controller.editProject = function(req, res) {
    var username = getUserName(req.user);
    var _id = sanitize(req.params.id);
    Project.findById({
      _id: _id
    })
      .exec()
      .then((project) => res.render('projects/edit', {
        username: username,
        project: project
      }))
      .onReject((error) => res.status(500).render('error', {
        username: username,
        error: error.errmsg
      }));
  };

  return controller;
};
<|endoftext|>


<commit_before>/*******************************************************************************
* KindEditor - WYSIWYG HTML Editor for Internet
* Copyright (C) 2006-${THISYEAR} kindsoft.net
*
* @author Roddy <luolonghao@gmail.com>
* @website http://www.kindsoft.net/
* @licence http://www.kindsoft.net/license.php
* @version ${VERSION}
*******************************************************************************/

(function (window, undefined) {

	if (window.KindEditor) {
		return;
	}
<commit_msg>Update header.js<commit_after>/*******************************************************************************
* KindEditor - WYSIWYG HTML Editor for Internet
* Copyright (C) 2006-${THISYEAR} kindeditor.net
*
* @author Roddy <luolonghao@gmail.com>
* @website http://kindeditor.net/
* @version ${VERSION}
*******************************************************************************/

(function (window, undefined) {

	if (window.KindEditor) {
		return;
	}
<|endoftext|>


<commit_before>import React from "react";
import run from "../maki-interpreter/interpreter";
import System from "../runtime/System";
import runtime from "../runtime";
import Variable from "./Variable";
import Value from "./Value";
import Command from "./Command";
import { useDropzone } from "react-dropzone";

function readFileAsArrayBuffer(file) {
  return new Promise((resolve, reject) => {
    const fileReader = new FileReader();
    fileReader.onload = () => {
      resolve(fileReader.result);
    };
    fileReader.onerror = reject;
    fileReader.readAsArrayBuffer(file);
  });
}

function Wrapper() {
  const [maki, setMaki] = React.useState(null);
  const onDrop = React.useCallback(async acceptedFiles => {
    if (acceptedFiles.length !== 1) {
      alert("Only drop one file at a time");
      return;
    }
    const file = acceptedFiles[0];
    const arrayBuffer = await readFileAsArrayBuffer(file);
    setMaki(arrayBuffer);
    // Do something with the files
  }, []);
  const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
  return (

      {maki == null ? (
        <>

            Drop a .maki file here to debug

          <input {...getInputProps()} type="file" />
        </>
      ) : (
        <Debugger maki={maki} />
      )}

  );
}

// Given a generator and a handler function, returns a function `next` which,
// when called, will `.next` values off the generator and pass them to `handler`
// until `handler` returns `false`.
function useNextGeneratorValue(gen, handler) {
  return React.useMemo(() => {
    if (gen == null) {
      // TODO: Return null and disable the next button
      return () => {};
    }
    return () => {
      let ret = gen.next();
      while (!ret.done) {
        if (handler(ret.value) === false) {
          return;
        }
        ret = gen.next();
      }
    };
  }, [gen, handler]);
}

const initialState = {
  variables: [],
  stack: [],
  commands: [],
  commandOffset: 0,
  messages: [],
  paused: true,
};

function reducer(state, action) {
  switch (action.type) {
    case "STEPPED":
      const { variables, commands, commandOffset, stack } = action;
      return {
        ...state,
        variables,
        commands,
        commandOffset,
        stack,
      };
    case "GOT_MESSAGE":
      return {
        ...state,
        messages: [...state.messages, action.message],
      };
    case "PAUSE":
      return { ...state, paused: true };
    case "UNPAUSE":
      return { ...state, paused: false };

    default:
      throw new Error("Unknown action type");
  }
}

function Debugger({ maki }) {
  const [gen, setGen] = React.useState(null);
  const [breakPoints, setBreakpoints] = React.useState(new Set([]));
  const [state, dispatch] = React.useReducer(reducer, initialState);

  const { variables, stack, commands, commandOffset, messages, paused } = state;

  const system = React.useMemo(() => {
    const sys = new System();
    sys.messageBox = function(message, messageTitle, flag, notanymoreId) {
      dispatch({
        type: "GOT_MESSAGE",
        message: { message, messageTitle, flag, notanymoreId },
      });
    };
    return sys;
  }, [dispatch]);

  React.useEffect(() => {
    run({ runtime, data: maki, system, debugHandler: setGen });
  }, [maki, system]);

  const nextValue = React.useCallback(
    value => {
      const { i, stack, variables, commands } = value;
      dispatch({
        type: "STEPPED",
        variables,
        commands,
        commandOffset: i,
        stack,
      });
      if (paused) {
        return false;
      }
      if (breakPoints.has(i)) {
        dispatch({ type: "PAUSE" });
        return;
      }

      return true;
    },
    [dispatch, paused, breakPoints]
  );

  const next = useNextGeneratorValue(gen, nextValue);

  // When we unpause, we should resume
  React.useEffect(() => {
    if (!paused) next();
  }, [next, paused]);

  // When we get a new generator, immediatly take the first step.
  React.useEffect(() => {
    next();
  }, [gen, next]);

  React.useEffect(() => {
    function handler(e) {
      switch (e.key) {
        case ";":
          next();
          break;
        default:
        // console.log(e.key);
      }
    }
    document.addEventListener("keydown", handler);
    return () => document.removeEventListener("keydown", handler);
  });

  return (

          Messages{" "}

            {
              messages.filter(({ messageTitle }) => messageTitle === "Success")
                .length
            }

          /{messages.length}

            {messages.map(
              ({ message, messageTitle, flag, notanymoreId }, i) => (

                  {i}
                  {message}

                    {messageTitle}

                  {flag}
                  {notanymoreId}

              )
            )}

        <button onClick={next}>Next</button>
        {paused ? (
          <button onClick={() => dispatch({ type: "UNPAUSE" })}>Play</button>
        ) : (
          <button onClick={() => dispatch({ type: "PAUSE" })}>Pause</button>
        )}

Commands

            {commands != null &&
              commands.map((command, i) => (

                    {breakPoints.has(i) ? (
                      <button
                        onClick={() => {
                          const newBreakPoints = new Set(breakPoints);
                          newBreakPoints.delete(i);
                          setBreakpoints(newBreakPoints);
                        }}
                      >
                        Clear
                      </button>
                    ) : (
                      <button
                        onClick={() => {
                          const newBreakPoints = new Set(breakPoints);
                          newBreakPoints.add(i);
                          setBreakpoints(newBreakPoints);
                        }}
                      >
                        Pause
                      </button>
                    )}
                    {i}

                    <Command command={command} variables={variables} />

              ))}

Stack

            {stack != null &&
              stack.map((value, i) => (

                  {i}

                    <Value value={value} />

              ))}

Variables

            {variables != null &&
              variables.map((variable, i) => (

                  {i}

                    <Variable variable={variable} />

              ))}

  );
}

export default Wrapper;
<commit_msg>Fix debugger alignment<commit_after>import React from "react";
import run from "../maki-interpreter/interpreter";
import System from "../runtime/System";
import runtime from "../runtime";
import Variable from "./Variable";
import Value from "./Value";
import Command from "./Command";
import { useDropzone } from "react-dropzone";

function readFileAsArrayBuffer(file) {
  return new Promise((resolve, reject) => {
    const fileReader = new FileReader();
    fileReader.onload = () => {
      resolve(fileReader.result);
    };
    fileReader.onerror = reject;
    fileReader.readAsArrayBuffer(file);
  });
}

function Wrapper() {
  const [maki, setMaki] = React.useState(null);
  const onDrop = React.useCallback(async acceptedFiles => {
    if (acceptedFiles.length !== 1) {
      alert("Only drop one file at a time");
      return;
    }
    const file = acceptedFiles[0];
    const arrayBuffer = await readFileAsArrayBuffer(file);
    setMaki(arrayBuffer);
    // Do something with the files
  }, []);
  const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
  return (

      {maki == null ? (
        <>

            Drop a .maki file here to debug

          <input {...getInputProps()} type="file" />
        </>
      ) : (
        <Debugger maki={maki} />
      )}

  );
}

// Given a generator and a handler function, returns a function `next` which,
// when called, will `.next` values off the generator and pass them to `handler`
// until `handler` returns `false`.
function useNextGeneratorValue(gen, handler) {
  return React.useMemo(() => {
    if (gen == null) {
      // TODO: Return null and disable the next button
      return () => {};
    }
    return () => {
      let ret = gen.next();
      while (!ret.done) {
        if (handler(ret.value) === false) {
          return;
        }
        ret = gen.next();
      }
    };
  }, [gen, handler]);
}

const initialState = {
  variables: [],
  stack: [],
  commands: [],
  commandOffset: 0,
  messages: [],
  paused: true,
};

function reducer(state, action) {
  switch (action.type) {
    case "STEPPED":
      const { variables, commands, commandOffset, stack } = action;
      return {
        ...state,
        variables,
        commands,
        commandOffset,
        stack,
      };
    case "GOT_MESSAGE":
      return {
        ...state,
        messages: [...state.messages, action.message],
      };
    case "PAUSE":
      return { ...state, paused: true };
    case "UNPAUSE":
      return { ...state, paused: false };

    default:
      throw new Error("Unknown action type");
  }
}

function Debugger({ maki }) {
  const [gen, setGen] = React.useState(null);
  const [breakPoints, setBreakpoints] = React.useState(new Set([]));
  const [state, dispatch] = React.useReducer(reducer, initialState);

  const { variables, stack, commands, commandOffset, messages, paused } = state;

  const system = React.useMemo(() => {
    const sys = new System();
    sys.messageBox = function(message, messageTitle, flag, notanymoreId) {
      dispatch({
        type: "GOT_MESSAGE",
        message: { message, messageTitle, flag, notanymoreId },
      });
    };
    return sys;
  }, [dispatch]);

  React.useEffect(() => {
    run({ runtime, data: maki, system, debugHandler: setGen });
  }, [maki, system]);

  const nextValue = React.useCallback(
    value => {
      const { i, stack, variables, commands } = value;
      dispatch({
        type: "STEPPED",
        variables,
        commands,
        commandOffset: i,
        stack,
      });
      if (paused) {
        return false;
      }
      if (breakPoints.has(i)) {
        dispatch({ type: "PAUSE" });
        return;
      }

      return true;
    },
    [dispatch, paused, breakPoints]
  );

  const next = useNextGeneratorValue(gen, nextValue);

  // When we unpause, we should resume
  React.useEffect(() => {
    if (!paused) next();
  }, [next, paused]);

  // When we get a new generator, immediatly take the first step.
  React.useEffect(() => {
    next();
  }, [gen, next]);

  React.useEffect(() => {
    function handler(e) {
      switch (e.key) {
        case ";":
          next();
          break;
        default:
        // console.log(e.key);
      }
    }
    document.addEventListener("keydown", handler);
    return () => document.removeEventListener("keydown", handler);
  });

  return (

          Messages{" "}

            {
              messages.filter(({ messageTitle }) => messageTitle === "Success")
                .length
            }

          /{messages.length}

            {messages.map(
              ({ message, messageTitle, flag, notanymoreId }, i) => (

                  {i}
                  {message}

                    {messageTitle}

                  {flag}
                  {notanymoreId}

              )
            )}

        <button onClick={next}>Next</button>
        {paused ? (
          <button onClick={() => dispatch({ type: "UNPAUSE" })}>Play</button>
        ) : (
          <button onClick={() => dispatch({ type: "PAUSE" })}>Pause</button>
        )}

Commands

            {commands != null &&
              commands.map((command, i) => (

                    {breakPoints.has(i) ? (
                      <button
                        onClick={() => {
                          const newBreakPoints = new Set(breakPoints);
                          newBreakPoints.delete(i);
                          setBreakpoints(newBreakPoints);
                        }}
                      >
                        Clear
                      </button>
                    ) : (
                      <button
                        onClick={() => {
                          const newBreakPoints = new Set(breakPoints);
                          newBreakPoints.add(i);
                          setBreakpoints(newBreakPoints);
                        }}
                      >
                        Pause
                      </button>
                    )}
                    {i}

                    <Command command={command} variables={variables} />

              ))}

Stack

            {stack != null &&
              stack.map((value, i) => (

                  {i}

                    <Value value={value} />

              ))}

Variables

            {variables != null &&
              variables.map((variable, i) => (

                  {i}

                    <Variable variable={variable} />

              ))}

  );
}

export default Wrapper;
<|endoftext|>