<commit_before>import Ember from 'ember';
import {parseDateString, formatDate} from 'ghost/utils/date-formatting';
import SettingsMenuMixin from 'ghost/mixins/settings-menu-controller';
import SlugGenerator from 'ghost/models/slug-generator';
import boundOneWay from 'ghost/utils/bound-one-way';
import isNumber from 'ghost/utils/isNumber';

export default Ember.Controller.extend(SettingsMenuMixin, {
    debounceId: null,
    lastPromise: null,
    selectedAuthor: null,
    uploaderReference: null,

    application: Ember.inject.controller(),
    config: Ember.inject.service(),
    ghostPaths: Ember.inject.service('ghost-paths'),
    notifications: Ember.inject.service(),

    initializeSelectedAuthor: Ember.observer('model', function () {
        var self = this;

        return this.get('model.author').then(function (author) {
            self.set('selectedAuthor', author);
            return author;
        });
    }),

    authors: Ember.computed(function () {
        // Loaded asynchronously, so must use promise proxies.
        var deferred = {};

        deferred.promise = this.store.find('user', {limit: 'all'}).then(function (users) {
            return users.rejectBy('id', 'me').sortBy('name');
        }).then(function (users) {
            return users.filter(function (user) {
                return user.get('active');
            });
        });

        return Ember.ArrayProxy
            .extend(Ember.PromiseProxyMixin)
            .create(deferred);
    }),

    /*jshint unused:false */
    publishedAtValue: Ember.computed('model.published_at', {
        get: function () {
            var pubDate = this.get('model.published_at');

            if (pubDate) {
                return formatDate(pubDate);
            }

            return formatDate(moment());
        },
        set: function (key, value) {
            // We're using a fake setter to reset
            // the cache for this property
            return formatDate(moment());
        }
    }),
    /*jshint unused:true */

    slugValue: boundOneWay('model.slug'),

    // Lazy load the slug generator
    slugGenerator: Ember.computed(function () {
        return SlugGenerator.create({
            ghostPaths: this.get('ghostPaths'),
            slugType: 'post'
        });
    }),

    // Requests slug from title
    generateAndSetSlug: function (destination) {
        var self = this,
            title = this.get('model.titleScratch'),
            afterSave = this.get('lastPromise'),
            promise;

        // Only set an "untitled" slug once per post
        if (title === '(Untitled)' && this.get('model.slug')) {
            return;
        }

        promise = Ember.RSVP.resolve(afterSave).then(function () {
            return self.get('slugGenerator').generateSlug(title).then(function (slug) {
                self.set(destination, slug);
            }).catch(function () {
                // Nothing to do (would be nice to log this somewhere though),
                // but a rejected promise needs to be handled here so that a resolved
                // promise is returned.
            });
        });

        this.set('lastPromise', promise);
    },

    metaTitleScratch: boundOneWay('model.meta_title'),
    metaDescriptionScratch: boundOneWay('model.meta_description'),

    seoTitle: Ember.computed('model.titleScratch', 'metaTitleScratch', function () {
        var metaTitle = this.get('metaTitleScratch') || '';

        metaTitle = metaTitle.length > 0 ? metaTitle : this.get('model.titleScratch');

        if (metaTitle.length > 70) {
            metaTitle = metaTitle.substring(0, 70).trim();
            metaTitle = Ember.Handlebars.Utils.escapeExpression(metaTitle);
            metaTitle = Ember.String.htmlSafe(metaTitle + '…');
        }

        return metaTitle;
    }),

    seoDescription: Ember.computed('model.scratch', 'metaDescriptionScratch', function () {
        var metaDescription = this.get('metaDescriptionScratch') || '',
            el,
            html = '',
            placeholder;

        if (metaDescription.length > 0) {
            placeholder = metaDescription;
        } else {
            el = $('.rendered-markdown');

            // Get rendered markdown
            if (el !== undefined && el.length > 0) {
                html = el.clone();
                html.find('.js-drop-zone').remove();
                html = html[0].innerHTML;
            }

            // Strip HTML
            placeholder = $('<div />', {html: html}).text();
            // Replace new lines and trim
            // jscs: disable
            placeholder = placeholder.replace(/\n+/g, ' ').trim();
            // jscs: enable
        }

        if (placeholder.length > 156) {
            // Limit to 156 characters
            placeholder = placeholder.substring(0, 156).trim();
            placeholder = Ember.Handlebars.Utils.escapeExpression(placeholder);
            placeholder = Ember.String.htmlSafe(placeholder + '…');
        }

        return placeholder;
    }),

    seoURL: Ember.computed('model.slug', function () {
        var blogUrl = this.get('config').blogUrl,
            seoSlug = this.get('model.slug') ? this.get('model.slug') : '',
            seoURL = blogUrl + '/' + seoSlug;

        // only append a slash to the URL if the slug exists
        if (seoSlug) {
            seoURL += '/';
        }

        if (seoURL.length > 70) {
            seoURL = seoURL.substring(0, 70).trim();
            seoURL = Ember.String.htmlSafe(seoURL + '…');
        }

        return seoURL;
    }),

    // observe titleScratch, keeping the post's slug in sync
    // with it until saved for the first time.
    addTitleObserver: Ember.observer('model', function () {
        if (this.get('model.isNew') || this.get('model.title') === '(Untitled)') {
            this.addObserver('model.titleScratch', this, 'titleObserver');
        }
    }),

    titleObserver: function () {
        var debounceId,
            title = this.get('model.title');

        // generate a slug if a post is new and doesn't have a title yet or
        // if the title is still '(Untitled)' and the slug is unaltered.
        if ((this.get('model.isNew') && !title) || title === '(Untitled)') {
            debounceId = Ember.run.debounce(this, 'generateAndSetSlug', 'model.slug', 700);
        }

        this.set('debounceId', debounceId);
    },

    // live-query of all tags for tag input autocomplete
    availableTags: Ember.computed(function () {
        return this.get('store').filter('tag', {limit: 'all'}, function () {
            return true;
        });
    }),

    showErrors: function (errors) {
        errors = Ember.isArray(errors) ? errors : [errors];
        this.get('notifications').showErrors(errors);
    },

    actions: {
        togglePage: function () {
            var self = this;

            this.toggleProperty('model.page');
            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save().catch(function (errors) {
                self.showErrors(errors);
                self.get('model').rollback();
            });
        },

        toggleFeatured: function () {
            var self = this;

            this.toggleProperty('model.featured');

            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save(this.get('saveOptions')).catch(function (errors) {
                self.showErrors(errors);
                self.get('model').rollback();
            });
        },

        /**
         * triggered by user manually changing slug
         */
        updateSlug: function (newSlug) {
            var slug = this.get('model.slug'),
                self = this;

            newSlug = newSlug || slug;

            newSlug = newSlug && newSlug.trim();

            // Ignore unchanged slugs or candidate slugs that are empty
            if (!newSlug || slug === newSlug) {
                // reset the input to its previous state
                this.set('slugValue', slug);

                return;
            }

            this.get('slugGenerator').generateSlug(newSlug).then(function (serverSlug) {
                // If after getting the sanitized and unique slug back from the API
                // we end up with a slug that matches the existing slug, abort the change
                if (serverSlug === slug) {
                    return;
                }

                // Because the server transforms the candidate slug by stripping
                // certain characters and appending a number onto the end of slugs
                // to enforce uniqueness, there are cases where we can get back a
                // candidate slug that is a duplicate of the original except for
                // the trailing incrementor (e.g., this-is-a-slug and this-is-a-slug-2)

                // get the last token out of the slug candidate and see if it's a number
                var slugTokens = serverSlug.split('-'),
                    check = Number(slugTokens.pop());

                // if the candidate slug is the same as the existing slug except
                // for the incrementor then the existing slug should be used
                if (isNumber(check) && check > 0) {
                    if (slug === slugTokens.join('-') && serverSlug !== newSlug) {
                        self.set('slugValue', slug);

                        return;
                    }
                }

                self.set('model.slug', serverSlug);

                if (self.hasObserverFor('model.titleScratch')) {
                    self.removeObserver('model.titleScratch', self, 'titleObserver');
                }

                // If this is a new post.  Don't save the model.  Defer the save
                // to the user pressing the save button
                if (self.get('model.isNew')) {
                    return;
                }

                return self.get('model').save();
            }).catch(function (errors) {
                self.showErrors(errors);
                self.get('model').rollback();
            });
        },

        /**
         * Parse user's set published date.
         * Action sent by post settings menu view.
         * (#1351)
         */
        setPublishedAt: function (userInput) {
            var errMessage = '',
                newPublishedAt = parseDateString(userInput),
                publishedAt = this.get('model.published_at'),
                self = this;

            if (!userInput) {
                // Clear out the published_at field for a draft
                if (this.get('model.isDraft')) {
                    this.set('model.published_at', null);
                }

                return;
            }

            // Validate new Published date
            if (!newPublishedAt.isValid()) {
                errMessage = 'Published Date must be a valid date with format: ' +
                    'DD MMM YY @ HH:mm (e.g. 6 Dec 14 @ 15:00)';
            }
            if (newPublishedAt.diff(new Date(), 'h') > 0) {
                errMessage = 'Published Date cannot currently be in the future.';
            }

            // If errors, notify and exit.
            if (errMessage) {
                this.get('model.errors').add('post-setting-date', errMessage);

                return;
            }

            // Do nothing if the user didn't actually change the date
            if (publishedAt && publishedAt.isSame(newPublishedAt)) {
                return;
            }

            // Validation complete
            this.set('model.published_at', newPublishedAt);

            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save().catch(function (errors) {
                self.showErrors(errors);
                self.get('model').rollback();
            });
        },

        setMetaTitle: function (metaTitle) {
            var property = 'meta_title',
                model = this.get('model'),
                currentTitle = model.get(property) || '';

            // Only update if the title has changed
            if (currentTitle === metaTitle) {
                return;
            }

            model.set(property, metaTitle);

            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (model.get('isNew')) {
                return;
            }

            model.save();
        },

        setMetaDescription: function (metaDescription) {
            var property = 'meta_description',
                model = this.get('model'),
                currentDescription = model.get(property) || '';

            // Only update if the description has changed
            if (currentDescription === metaDescription) {
                return;
            }

            model.set(property, metaDescription);

            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (model.get('isNew')) {
                return;
            }

            model.save();
        },

        setCoverImage: function (image) {
            var self = this;

            this.set('model.image', image);

            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save().catch(function (errors) {
                self.showErrors(errors);
                self.get('model').rollback();
            });
        },

        clearCoverImage: function () {
            var self = this;

            this.set('model.image', '');

            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save().catch(function (errors) {
                self.showErrors(errors);
                self.get('model').rollback();
            });
        },

        resetUploader: function () {
            var uploader = this.get('uploaderReference');

            if (uploader && uploader[0]) {
                uploader[0].uploaderUi.reset();
            }
        },

        resetPubDate: function () {
            this.set('publishedAtValue', '');
        },

        closeNavMenu: function () {
            this.get('application').send('closeNavMenu');
        },

        changeAuthor: function (newAuthor) {
            var author = this.get('model.author'),
                model = this.get('model'),
                self = this;

            // return if nothing changed
            if (newAuthor.get('id') === author.get('id')) {
                return;
            }

            model.set('author', newAuthor);

            // if this is a new post (never been saved before), don't try to save it
            if (this.get('model.isNew')) {
                return;
            }

            model.save().catch(function (errors) {
                self.showErrors(errors);
                self.set('selectedAuthor', author);
                model.rollback();
            });
        },

        addTag: function (tagName, index) {
            var self = this,
                currentTags = this.get('model.tags'),
                currentTagNames = currentTags.map(function (tag) { return tag.get('name').toLowerCase(); }),
                availableTagNames = null,
                tagToAdd = null;

            tagName = tagName.trim();

            // abort if tag is already selected
            if (currentTagNames.contains(tagName.toLowerCase())) {
                return;
            }

            this.get('availableTags').then(function (availableTags) {
                availableTagNames = availableTags.map(function (tag) { return tag.get('name').toLowerCase(); });

                // find existing tag or create new
                if (availableTagNames.contains(tagName.toLowerCase())) {
                    tagToAdd = availableTags.find(function (tag) {
                        return tag.get('name').toLowerCase() === tagName.toLowerCase();
                    });
                } else {
                    tagToAdd = self.get('store').createRecord('tag', {
                        name: tagName
                    });

                    // we need to set a UUID so that selectize has a unique value
                    // it will be ignored when sent to the server
                    tagToAdd.set('uuid', Ember.guidFor(tagToAdd));
                }

                // push tag onto post relationship
                if (tagToAdd) { self.get('model.tags').insertAt(index, tagToAdd); }
            });
        },

        removeTag: function (tag) {
            this.get('model.tags').removeObject(tag);

            if (tag.get('isNew')) {
                tag.destroyRecord();
            }
        }
    }
});
<commit_msg>Add config.blogUrl key to PSM's seoURL property Closes #5793<commit_after>import Ember from 'ember';
import {parseDateString, formatDate} from 'ghost/utils/date-formatting';
import SettingsMenuMixin from 'ghost/mixins/settings-menu-controller';
import SlugGenerator from 'ghost/models/slug-generator';
import boundOneWay from 'ghost/utils/bound-one-way';
import isNumber from 'ghost/utils/isNumber';

export default Ember.Controller.extend(SettingsMenuMixin, {
    debounceId: null,
    lastPromise: null,
    selectedAuthor: null,
    uploaderReference: null,

    application: Ember.inject.controller(),
    config: Ember.inject.service(),
    ghostPaths: Ember.inject.service('ghost-paths'),
    notifications: Ember.inject.service(),

    initializeSelectedAuthor: Ember.observer('model', function () {
        var self = this;

        return this.get('model.author').then(function (author) {
            self.set('selectedAuthor', author);
            return author;
        });
    }),

    authors: Ember.computed(function () {
        // Loaded asynchronously, so must use promise proxies.
        var deferred = {};

        deferred.promise = this.store.find('user', {limit: 'all'}).then(function (users) {
            return users.rejectBy('id', 'me').sortBy('name');
        }).then(function (users) {
            return users.filter(function (user) {
                return user.get('active');
            });
        });

        return Ember.ArrayProxy
            .extend(Ember.PromiseProxyMixin)
            .create(deferred);
    }),

    /*jshint unused:false */
    publishedAtValue: Ember.computed('model.published_at', {
        get: function () {
            var pubDate = this.get('model.published_at');

            if (pubDate) {
                return formatDate(pubDate);
            }

            return formatDate(moment());
        },
        set: function (key, value) {
            // We're using a fake setter to reset
            // the cache for this property
            return formatDate(moment());
        }
    }),
    /*jshint unused:true */

    slugValue: boundOneWay('model.slug'),

    // Lazy load the slug generator
    slugGenerator: Ember.computed(function () {
        return SlugGenerator.create({
            ghostPaths: this.get('ghostPaths'),
            slugType: 'post'
        });
    }),

    // Requests slug from title
    generateAndSetSlug: function (destination) {
        var self = this,
            title = this.get('model.titleScratch'),
            afterSave = this.get('lastPromise'),
            promise;

        // Only set an "untitled" slug once per post
        if (title === '(Untitled)' && this.get('model.slug')) {
            return;
        }

        promise = Ember.RSVP.resolve(afterSave).then(function () {
            return self.get('slugGenerator').generateSlug(title).then(function (slug) {
                self.set(destination, slug);
            }).catch(function () {
                // Nothing to do (would be nice to log this somewhere though),
                // but a rejected promise needs to be handled here so that a resolved
                // promise is returned.
            });
        });

        this.set('lastPromise', promise);
    },

    metaTitleScratch: boundOneWay('model.meta_title'),
    metaDescriptionScratch: boundOneWay('model.meta_description'),

    seoTitle: Ember.computed('model.titleScratch', 'metaTitleScratch', function () {
        var metaTitle = this.get('metaTitleScratch') || '';

        metaTitle = metaTitle.length > 0 ? metaTitle : this.get('model.titleScratch');

        if (metaTitle.length > 70) {
            metaTitle = metaTitle.substring(0, 70).trim();
            metaTitle = Ember.Handlebars.Utils.escapeExpression(metaTitle);
            metaTitle = Ember.String.htmlSafe(metaTitle + '…');
        }

        return metaTitle;
    }),

    seoDescription: Ember.computed('model.scratch', 'metaDescriptionScratch', function () {
        var metaDescription = this.get('metaDescriptionScratch') || '',
            el,
            html = '',
            placeholder;

        if (metaDescription.length > 0) {
            placeholder = metaDescription;
        } else {
            el = $('.rendered-markdown');

            // Get rendered markdown
            if (el !== undefined && el.length > 0) {
                html = el.clone();
                html.find('.js-drop-zone').remove();
                html = html[0].innerHTML;
            }

            // Strip HTML
            placeholder = $('<div />', {html: html}).text();
            // Replace new lines and trim
            // jscs: disable
            placeholder = placeholder.replace(/\n+/g, ' ').trim();
            // jscs: enable
        }

        if (placeholder.length > 156) {
            // Limit to 156 characters
            placeholder = placeholder.substring(0, 156).trim();
            placeholder = Ember.Handlebars.Utils.escapeExpression(placeholder);
            placeholder = Ember.String.htmlSafe(placeholder + '…');
        }

        return placeholder;
    }),

    seoURL: Ember.computed('model.slug', 'config.blogUrl', function () {
        var blogUrl = this.get('config.blogUrl'),
            seoSlug = this.get('model.slug') ? this.get('model.slug') : '',
            seoURL = blogUrl + '/' + seoSlug;

        // only append a slash to the URL if the slug exists
        if (seoSlug) {
            seoURL += '/';
        }

        if (seoURL.length > 70) {
            seoURL = seoURL.substring(0, 70).trim();
            seoURL = Ember.String.htmlSafe(seoURL + '…');
        }

        return seoURL;
    }),

    // observe titleScratch, keeping the post's slug in sync
    // with it until saved for the first time.
    addTitleObserver: Ember.observer('model', function () {
        if (this.get('model.isNew') || this.get('model.title') === '(Untitled)') {
            this.addObserver('model.titleScratch', this, 'titleObserver');
        }
    }),

    titleObserver: function () {
        var debounceId,
            title = this.get('model.title');

        // generate a slug if a post is new and doesn't have a title yet or
        // if the title is still '(Untitled)' and the slug is unaltered.
        if ((this.get('model.isNew') && !title) || title === '(Untitled)') {
            debounceId = Ember.run.debounce(this, 'generateAndSetSlug', 'model.slug', 700);
        }

        this.set('debounceId', debounceId);
    },

    // live-query of all tags for tag input autocomplete
    availableTags: Ember.computed(function () {
        return this.get('store').filter('tag', {limit: 'all'}, function () {
            return true;
        });
    }),

    showErrors: function (errors) {
        errors = Ember.isArray(errors) ? errors : [errors];
        this.get('notifications').showErrors(errors);
    },

    actions: {
        togglePage: function () {
            var self = this;

            this.toggleProperty('model.page');
            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save().catch(function (errors) {
                self.showErrors(errors);
                self.get('model').rollback();
            });
        },

        toggleFeatured: function () {
            var self = this;

            this.toggleProperty('model.featured');

            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save(this.get('saveOptions')).catch(function (errors) {
                self.showErrors(errors);
                self.get('model').rollback();
            });
        },

        /**
         * triggered by user manually changing slug
         */
        updateSlug: function (newSlug) {
            var slug = this.get('model.slug'),
                self = this;

            newSlug = newSlug || slug;

            newSlug = newSlug && newSlug.trim();

            // Ignore unchanged slugs or candidate slugs that are empty
            if (!newSlug || slug === newSlug) {
                // reset the input to its previous state
                this.set('slugValue', slug);

                return;
            }

            this.get('slugGenerator').generateSlug(newSlug).then(function (serverSlug) {
                // If after getting the sanitized and unique slug back from the API
                // we end up with a slug that matches the existing slug, abort the change
                if (serverSlug === slug) {
                    return;
                }

                // Because the server transforms the candidate slug by stripping
                // certain characters and appending a number onto the end of slugs
                // to enforce uniqueness, there are cases where we can get back a
                // candidate slug that is a duplicate of the original except for
                // the trailing incrementor (e.g., this-is-a-slug and this-is-a-slug-2)

                // get the last token out of the slug candidate and see if it's a number
                var slugTokens = serverSlug.split('-'),
                    check = Number(slugTokens.pop());

                // if the candidate slug is the same as the existing slug except
                // for the incrementor then the existing slug should be used
                if (isNumber(check) && check > 0) {
                    if (slug === slugTokens.join('-') && serverSlug !== newSlug) {
                        self.set('slugValue', slug);

                        return;
                    }
                }

                self.set('model.slug', serverSlug);

                if (self.hasObserverFor('model.titleScratch')) {
                    self.removeObserver('model.titleScratch', self, 'titleObserver');
                }

                // If this is a new post.  Don't save the model.  Defer the save
                // to the user pressing the save button
                if (self.get('model.isNew')) {
                    return;
                }

                return self.get('model').save();
            }).catch(function (errors) {
                self.showErrors(errors);
                self.get('model').rollback();
            });
        },

        /**
         * Parse user's set published date.
         * Action sent by post settings menu view.
         * (#1351)
         */
        setPublishedAt: function (userInput) {
            var errMessage = '',
                newPublishedAt = parseDateString(userInput),
                publishedAt = this.get('model.published_at'),
                self = this;

            if (!userInput) {
                // Clear out the published_at field for a draft
                if (this.get('model.isDraft')) {
                    this.set('model.published_at', null);
                }

                return;
            }

            // Validate new Published date
            if (!newPublishedAt.isValid()) {
                errMessage = 'Published Date must be a valid date with format: ' +
                    'DD MMM YY @ HH:mm (e.g. 6 Dec 14 @ 15:00)';
            }
            if (newPublishedAt.diff(new Date(), 'h') > 0) {
                errMessage = 'Published Date cannot currently be in the future.';
            }

            // If errors, notify and exit.
            if (errMessage) {
                this.get('model.errors').add('post-setting-date', errMessage);

                return;
            }

            // Do nothing if the user didn't actually change the date
            if (publishedAt && publishedAt.isSame(newPublishedAt)) {
                return;
            }

            // Validation complete
            this.set('model.published_at', newPublishedAt);

            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save().catch(function (errors) {
                self.showErrors(errors);
                self.get('model').rollback();
            });
        },

        setMetaTitle: function (metaTitle) {
            var property = 'meta_title',
                model = this.get('model'),
                currentTitle = model.get(property) || '';

            // Only update if the title has changed
            if (currentTitle === metaTitle) {
                return;
            }

            model.set(property, metaTitle);

            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (model.get('isNew')) {
                return;
            }

            model.save();
        },

        setMetaDescription: function (metaDescription) {
            var property = 'meta_description',
                model = this.get('model'),
                currentDescription = model.get(property) || '';

            // Only update if the description has changed
            if (currentDescription === metaDescription) {
                return;
            }

            model.set(property, metaDescription);

            // If this is a new post.  Don't save the model.  Defer the save
            // to the user pressing the save button
            if (model.get('isNew')) {
                return;
            }

            model.save();
        },

        setCoverImage: function (image) {
            var self = this;

            this.set('model.image', image);

            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save().catch(function (errors) {
                self.showErrors(errors);
                self.get('model').rollback();
            });
        },

        clearCoverImage: function () {
            var self = this;

            this.set('model.image', '');

            if (this.get('model.isNew')) {
                return;
            }

            this.get('model').save().catch(function (errors) {
                self.showErrors(errors);
                self.get('model').rollback();
            });
        },

        resetUploader: function () {
            var uploader = this.get('uploaderReference');

            if (uploader && uploader[0]) {
                uploader[0].uploaderUi.reset();
            }
        },

        resetPubDate: function () {
            this.set('publishedAtValue', '');
        },

        closeNavMenu: function () {
            this.get('application').send('closeNavMenu');
        },

        changeAuthor: function (newAuthor) {
            var author = this.get('model.author'),
                model = this.get('model'),
                self = this;

            // return if nothing changed
            if (newAuthor.get('id') === author.get('id')) {
                return;
            }

            model.set('author', newAuthor);

            // if this is a new post (never been saved before), don't try to save it
            if (this.get('model.isNew')) {
                return;
            }

            model.save().catch(function (errors) {
                self.showErrors(errors);
                self.set('selectedAuthor', author);
                model.rollback();
            });
        },

        addTag: function (tagName, index) {
            var self = this,
                currentTags = this.get('model.tags'),
                currentTagNames = currentTags.map(function (tag) { return tag.get('name').toLowerCase(); }),
                availableTagNames = null,
                tagToAdd = null;

            tagName = tagName.trim();

            // abort if tag is already selected
            if (currentTagNames.contains(tagName.toLowerCase())) {
                return;
            }

            this.get('availableTags').then(function (availableTags) {
                availableTagNames = availableTags.map(function (tag) { return tag.get('name').toLowerCase(); });

                // find existing tag or create new
                if (availableTagNames.contains(tagName.toLowerCase())) {
                    tagToAdd = availableTags.find(function (tag) {
                        return tag.get('name').toLowerCase() === tagName.toLowerCase();
                    });
                } else {
                    tagToAdd = self.get('store').createRecord('tag', {
                        name: tagName
                    });

                    // we need to set a UUID so that selectize has a unique value
                    // it will be ignored when sent to the server
                    tagToAdd.set('uuid', Ember.guidFor(tagToAdd));
                }

                // push tag onto post relationship
                if (tagToAdd) { self.get('model.tags').insertAt(index, tagToAdd); }
            });
        },

        removeTag: function (tag) {
            this.get('model.tags').removeObject(tag);

            if (tag.get('isNew')) {
                tag.destroyRecord();
            }
        }
    }
});
<|endoftext|>


<commit_before>function appendableObserveSearchfield(fieldId, targetId, url) {
  alert("apodskopask")
  $('#'+fieldId).each(function() {
    var $this = $(this);
    $this.attr('data-value-was', $this.val());
    var check = function() {
      var val = $this.val();
      if ($this.attr('data-value-was') != val){
        $this.attr('data-value-was', val);
        $.ajax({
          url: url,
          type: 'get',
          data: {q: $this.val()},
          success: function(data){
            $target = $('#'+targetId)
              .find('*:not(:has(input:checked), :checked)')
              .remove()
              .end()
              .append(data)

            $target
              .find(':checked')
              .each(function(idx, el) {
                $target
                  .find( 'label:has([value="' + el.value + '"]:not(:checked))' )
                  .remove()
              });
          },
          beforeSend: function(){ $this.addClass('ajax-loading'); },
          complete: function(){ $this.removeClass('ajax-loading'); }
        });
      }
    };
    var reset = function() {
      if (timer) {
        clearInterval(timer);
        timer = setInterval(check, 300);
      }
    };
    var timer = setInterval(check, 300);
    $this.bind('keyup click mousemove', reset);
  });
}<commit_msg>Removed unnecessary alert<commit_after>function appendableObserveSearchfield(fieldId, targetId, url) {
  $('#'+fieldId).each(function() {
    var $this = $(this);
    $this.attr('data-value-was', $this.val());
    var check = function() {
      var val = $this.val();
      if ($this.attr('data-value-was') != val){
        $this.attr('data-value-was', val);
        $.ajax({
          url: url,
          type: 'get',
          data: {q: $this.val()},
          success: function(data){
            $target = $('#'+targetId)
              .find('*:not(:has(input:checked), :checked)')
              .remove()
              .end()
              .append(data)

            $target
              .find(':checked')
              .each(function(idx, el) {
                $target
                  .find( 'label:has([value="' + el.value + '"]:not(:checked))' )
                  .remove()
              });
          },
          beforeSend: function(){ $this.addClass('ajax-loading'); },
          complete: function(){ $this.removeClass('ajax-loading'); }
        });
      }
    };
    var reset = function() {
      if (timer) {
        clearInterval(timer);
        timer = setInterval(check, 300);
      }
    };
    var timer = setInterval(check, 300);
    $this.bind('keyup click mousemove', reset);
  });
}<|endoftext|>


<commit_before>/**
 * @file hoppfile.js
 * @license MIT
 * @copyright 10244872 Canada Inc.
 */

const hopp = require('hopp')

/**
 * Set of plugins.
 */
const plugins = [
  'hopp-sample-plugin',
  'hopp-plugin-accord',
  'hopp-plugin-babel',
  'hopp-plugin-concat',
]

/**
 * Setup plugins.
 */
plugins.forEach(name => {
  exports[name] =
    hopp(`./packages/${name}/lib/**/**.js`)
      .babel({
        babelrc: false,
        presets: [
          ['env', {
            targets: {
              node: '4'
            }
          }]
        ]
      })
      .dest(`./packages/${name}/dist`)
})

/**
 * Handle hopp building.
 */
function buildHopp(presets, dest) {
  return hopp('./packages/hopp/src/**/**.js')
    .rename((file, dir, src) => {
      return dir + src.substr(src.indexOf('hopp/src') + 'hopp/src'.length)
    })
    .babel({
      babelrc: false,
      sourceMaps: true,
      presets
    })
    .dest('./packages/hopp/' + dest)
}

exports['hopp:latest'] =
  buildHopp([
    ['env', {
      targets: {
        node: '7'
      }
    }]
  ], 'dist')

exports['hopp:legacy'] =
  buildHopp([
    ['env', {
      targets: {
        node: '4'
      }
    }]
  ], 'dist-legacy')

exports['hopp'] =
  hopp.steps([
    'hopp:latest',
    'hopp:legacy'
  ])

/**
 * Do all.
 */
exports.default = hopp.all(plugins.concat([ 'hopp' ]))

/**
 * Watch all.
 */
exports.watch = hopp.watch(plugins.concat([ 'hopp' ]))<commit_msg>Add sourcemapping for all plugin builds<commit_after>/**
 * @file hoppfile.js
 * @license MIT
 * @copyright 10244872 Canada Inc.
 */

const hopp = require('hopp')

/**
 * Set of plugins.
 */
const plugins = [
  'hopp-sample-plugin',
  'hopp-plugin-accord',
  'hopp-plugin-babel',
  'hopp-plugin-concat',
]

/**
 * Setup plugins.
 */
plugins.forEach(name => {
  exports[name] =
    hopp(`./packages/${name}/lib/**/**.js`)
      .babel({
        babelrc: false,
        sourceMaps: true,
        presets: [
          ['env', {
            targets: {
              node: '4'
            }
          }]
        ]
      })
      .dest(`./packages/${name}/dist`)
})

/**
 * Handle hopp building.
 */
function buildHopp(presets, dest) {
  return hopp('./packages/hopp/src/**/**.js')
    .rename((file, dir, src) => {
      return dir + src.substr(src.indexOf('hopp/src') + 'hopp/src'.length)
    })
    .babel({
      babelrc: false,
      sourceMaps: true,
      presets
    })
    .dest('./packages/hopp/' + dest)
}

exports['hopp:latest'] =
  buildHopp([
    ['env', {
      targets: {
        node: '7'
      }
    }]
  ], 'dist')

exports['hopp:legacy'] =
  buildHopp([
    ['env', {
      targets: {
        node: '4'
      }
    }]
  ], 'dist-legacy')

exports['hopp'] =
  hopp.steps([
    'hopp:latest',
    'hopp:legacy'
  ])

/**
 * Do all.
 */
exports.default = hopp.all(plugins.concat([ 'hopp' ]))

/**
 * Watch all.
 */
exports.watch = hopp.watch(plugins.concat([ 'hopp' ]))<|endoftext|>


<commit_before>describe('Period Amount directive', function() {
    var compile, scope, element;

    beforeEach(module('motech-common'));

    beforeEach(inject(function($compile, $rootScope){
        compile = $compile;
        scope = $rootScope.$new();

        element = angular.element('<input type="text" ng-model="repeatString" period-amount></input>');
        compile(element)(scope);
        scope.$digest();
    }));

    it('Should fail if ngModel is not specified', function() {
        expect(function() {
            compile(angular.element('<input type="text" period-amount></input>'))(scope);
            scope.$digest();
        }).toThrow();
    });

    it('Should parse period', function() {
        var button = element.parent().find('.period-modal-opener');

        button.triggerHandler('click');
        scope.$digest();
    });

    it('Should set slider max values', function() {

    });

    //it('Should return period string', function() {
    //    scope.periodString = "P1Y2M3W4DT5H6M7S"; // 1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, 7 seconds
    //    var returned = scope.compileValueInputs(1, 2, 3, 4, 5, 6, 7);
    //    expect(returned).toEqual(scope.periodString);
    //});
});<commit_msg>MOTECH-2756: Added tests for period-amount directive in Scheduler<commit_after>describe('Period Amount directive', function() {
    var compile, scope, element;
    var ngModelName = 'periodString';

    beforeEach(module('motech-common'));

    beforeEach(inject(function($compile, $rootScope){
        compile = $compile;
        scope = $rootScope.$new();

        element = angular.element('<period-modal ng-model="' + ngModelName + '" period-amount></period-modal>');
        scope.sliderMax = {
            year: 10,
            month: 24,
            week: 55,
            day: 365,
            hour: 125,
            minute: 360,
            second: 360
        };
        compile(element)(scope);
        scope.$digest();
    }));

    it('Should load period amount', function() {
        expect(element.attr('period-amount')).toBeDefined();
    });

    it('Should pass ng-model', function() {
        expect(element.attr('ng-model')).toEqual(ngModelName);
    });

    it('Should set slider max values', function() {
        var maxValues = {
            year: 10,
            month: 24,
            week: 55,
            day: 365,
            hour: 125,
            minute: 360,
            second: 360
        };
        expect(element.scope().sliderMax).toEqual(maxValues);
    });

});<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */

var express = require('express')
  , api = require('lib/db-api')
  , log = require('debug')('proposal');

var app = module.exports = express();

app.get('/proposal/:id', function (req, res) {
  log('Request /proposal/%s', req.params.id);

  api.proposal.get(req.params.id, function (err, proposal) {
    if (err) return _handleError(err, req, res);

    log('Serving proposal %j', proposal);
    res.json(proposal);
  });
});

app.post('/proposal/create', function (req, res) {
  log('Request /proposal/create %j', req.body.proposal);
  var proposal = req.body.proposal;
  // This must be uncommented on production!
  // proposal.author = req.author;

  api.proposal.create(proposal, function (err, proposalDoc) {
    if (err) return _handleError(err, req, res);

    log('Serving proposal %j', proposalDoc);
    res.json(proposalDoc);
  });
});

app.post('/proposal/:id/comment', function (req, res) {
  log('Request /proposal/%s/comment %j', req.params.id, req.body.comment);
  var comment = req.body.comment;
  // This must be uncommented on production!
  // comment.author = req.author;

  api.proposal.comment(req.params.id, comment, function (err, proposal) {
    if (err) return _handleError(err, req, res);

    log('Serving proposal %j', proposal);
    res.json(proposal);
  });
});

function _handleError (err, req, res) {
  log("Error found: %j", err);
  res.json({ error: err });
}<commit_msg>[proposal] - Submit proposal in html format, request handler<commit_after>/**
 * Module dependencies.
 */

var express = require('express')
  , api = require('lib/db-api')
  , log = require('debug')('proposal');

var app = module.exports = express();

app.get('/proposal/:id', function (req, res) {
  log('Request /proposal/%s', req.params.id);

  api.proposal.get(req.params.id, function (err, proposal) {
    if (err) return _handleError(err, req, res);

    log('Serving proposal %j', proposal);
    res.json(proposal);
  });
});

app.post('/proposal/create', function (req, res) {
  log('Request /proposal/create %j', req.body.proposal);
  var proposal = req.body.proposal;
  // This must be uncommented on production!
  proposal.author = req.user;

  api.proposal.create(proposal, function (err, proposalDoc) {
    if (err) return _handleError(err, req, res);

    res.format({
      html: function() {
        log('Redirect to home.')
        res.redirect('/');
      },
      json: function() {
        log('Serving proposal %j', proposalDoc);
        res.json(proposalDoc);
      }
    })
  });
});

app.post('/proposal/:id/comment', function (req, res) {
  log('Request /proposal/%s/comment %j', req.params.id, req.body.comment);
  var comment = req.body.comment;
  // This must be uncommented on production!
  // comment.author = req.author;

  api.proposal.comment(req.params.id, comment, function (err, proposal) {
    if (err) return _handleError(err, req, res);

    log('Serving proposal %j', proposal);
    res.json(proposal);
  });
});

function _handleError (err, req, res) {
  log("Error found: %j", err);
  res.json({ error: err });
}<|endoftext|>


<commit_before>'use strict';

var test = require('ava');
var Psv = require('../index.js');

test('array 正确验证', function(t) {
    const schema = {
        key1: {
            type: [Number],
            max: 5,
            min: 3,
            required: true
        }
    };
    const data = {
        key1: [1,2,3]
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    t.true(validate);
});

test('array 正确验证2', function(t) {
    const schema = {
        array: {
            type: Array,
            max: 5,
            min: 3,
            required: true
        }
    };
    const data = {
        array: [1, '1', true, {}]
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    t.true(validate);
});

test('array 错误验证', function(t) {
    const schema = {
        key1: {
            type: [Number],
            max: 5,
            min: 3,
            required: true
        }
    };
    const data = {
        key1: [1]
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    // psv.printErrors();
    t.false(validate);
});

test('array 错误验证2', function(t) {
    const schema = {
        key1: {
            type: [Number],
            max: 5,
            min: 3,
            required: true
        }
    };
    const data = {
        key1: [1, 2, 3, 4, 5, 6]
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    t.false(validate);
});

test('array 正确验证.嵌套', function(t) {
    const schema2 = {
        key3: {
            type: [],
            required: true
        }
    };
    const schema = {
        key1: {
            type: schema2,
            required: true
        }
    };
    const data = {
        key1: {
            key3: []
        }
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    t.true(validate);
});

test('array 错误验证.嵌套', function(t) {
    const schema2 = {
        key3: {
            type: [Number],
            required: true
        }
    };
    const schema = {
        key1: {
            type: schema2,
            required: true
        }
    };
    const data = {
        key1: {
            key3: 1
        }
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    // psv.printErrors();
    t.false(validate);
});

test.skip('array 对象.嵌套', function(t) {
    const schema2 = {
        str: {
            type: String,
            required: true
        },
        num: {
            type: Number,
            required: true
        }
    };
    const schema = {
        key: {
            type: [schema2],
            required: true
        }
    };
    const data = {
        key: [
            {str: '1', num: 0},
            {str: '1', num: 0}
        ]
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    t.true(validate);
});

test('array 对象.嵌套.错误', function(t) {
    const schema = {
        key: {
            type: [Number],
            required: true
        }
    };
    const data = {
        key: [
            {str: '1', num: 0},
            {str: '1', num: 0}
        ]
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    // psv.printErrors();
    t.false(validate);
});
<commit_msg>remove test skip<commit_after>'use strict';

var test = require('ava');
var Psv = require('../index.js');

test('array 正确验证', function(t) {
    const schema = {
        key1: {
            type: [Number],
            max: 5,
            min: 3,
            required: true
        }
    };
    const data = {
        key1: [1,2,3]
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    t.true(validate);
});

test('array 正确验证2', function(t) {
    const schema = {
        array: {
            type: Array,
            max: 5,
            min: 3,
            required: true
        }
    };
    const data = {
        array: [1, '1', true, {}]
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    t.true(validate);
});

test('array 错误验证', function(t) {
    const schema = {
        key1: {
            type: [Number],
            max: 5,
            min: 3,
            required: true
        }
    };
    const data = {
        key1: [1]
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    // psv.printErrors();
    t.false(validate);
});

test('array 错误验证2', function(t) {
    const schema = {
        key1: {
            type: [Number],
            max: 5,
            min: 3,
            required: true
        }
    };
    const data = {
        key1: [1, 2, 3, 4, 5, 6]
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    t.false(validate);
});

test('array 正确验证.嵌套', function(t) {
    const schema2 = {
        key3: {
            type: [],
            required: true
        }
    };
    const schema = {
        key1: {
            type: schema2,
            required: true
        }
    };
    const data = {
        key1: {
            key3: []
        }
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    t.true(validate);
});

test('array 错误验证.嵌套', function(t) {
    const schema2 = {
        key3: {
            type: [Number],
            required: true
        }
    };
    const schema = {
        key1: {
            type: schema2,
            required: true
        }
    };
    const data = {
        key1: {
            key3: 1
        }
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    // psv.printErrors();
    t.false(validate);
});

test('array 对象.嵌套', function(t) {
    const schema2 = {
        str: {
            type: String,
            required: true
        },
        num: {
            type: Number,
            required: true
        }
    };
    const schema = {
        key: {
            type: [schema2],
            required: true
        }
    };
    const data = {
        key: [
            {str: '1', num: 0},
            {str: '1', num: 0}
        ]
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    t.true(validate);
});

test('array 对象.嵌套.错误', function(t) {
    const schema = {
        key: {
            type: [Number],
            required: true
        }
    };
    const data = {
        key: [
            {str: '1', num: 0},
            {str: '1', num: 0}
        ]
    };
    const psv = new Psv(schema, data);
    const validate = psv.validate();
    // psv.printErrors();
    t.false(validate);
});
<|endoftext|>


<commit_before>/*
 * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Cloudkick licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var path = require('path');

var async = require('async');
var sprintf = require('sprintf').sprintf;
var express = require('express');

var config = require('util/config');
var log = require('util/log');
var fsUtil = require('util/fs');
var misc = require('util/misc');
var Errorf = misc.Errorf;

var httpServer = require('services/http').instance;
var pluginConstants = require('plugins/constants');
var pluginsHttpUtils = require('plugins/http');

function PluginManager() {
  this._availablePlugins = {};
  this._enabledPlugins = {};
}

/**
 * Return all the available plugins (all the plugins which are located in the
 * plugins directory).
 * @param {Function} callback Callback called with (err, availablePlugins)
 */
PluginManager.prototype.getAvailablePlugins = function(callback) {
};

/**
 * Return an object with the enabled plugins and their settings.
 * @param {Function} callback Callback called with (err, enabledPlugins)
 */
PluginManager.prototype.getEnabledPlugins = function(callback) {
  callback(null, config.get()['plugins']['enabled']);
};

/**
 * Read "plugins" part from the settings file and load all the enabled plugins.
 *
 * @param {Function} callback Callback called when the initialization completes.
 */
PluginManager.prototype._initialize = function(callback) {
  var self = this;
  var pluginName, pluginSettings;
  var confPlugins = config.get()['plugins'] || {};
  var enabledPlugins = confPlugins['enabled'];
  var enabledPluginNames = Object.keys(enabledPlugins);

  if (!enabledPlugins) {
    log.info('No plugins have been enabled');
    return;
  }

  async.forEach(enabledPluginNames, function(pluginName, callback) {
    var pluginSettings = enabledPlugins[pluginName];
    self._validatePluginSettings(pluginName, pluginSettings, function onValidated(err) {
      if (err) {
        log.error('Failed to validate plugin "%s" settings: %s', pluginName, err);
        callback();
        return;
      }

      // Load and enable the plugin
      log.info(sprintf('Enabling plugin "%s"', pluginName));
      self.enablePlugin(pluginName, callback);
    });
  }, callback);
};

/**
 * Validate plugin settings. Validation rules are defined by plugin author in
 * the plugin package.json file.
 *
 * @param {String} pluginName Plugin name.
 * @param {Object} pluginSettings Plugin settings.
 */
PluginManager.prototype.validatePluginSettings = function(pluginName,
                                                          pluginSettings,
                                                          callback) {
  // @TODO: Cache available plugins in this._availablePlugins dictionary
  var self = this;
  var pluginPath = path.join(config.get()['plugins']['root'], pluginName);

  async.waterfall([
    function readPluginManifest(callback) {
      self._readPluginManifest(pluginPath, callback);
    },

    function validatePluginSettings(manifest, callback) {
      var rules = manifest['settings'];
      // @TODO: Use validators from manifest/...
      callback();
    }
  ],

  function(err) {
    callback(err);
  });
};

/**
 * Read plugin manifest file.
 *
 * @param {String} pluginPath Path to the plugin directory.
 * @param {Function} callback Callback called with (err, data)
 */
PluginManager.prototype._readPluginManifest = function(pluginPath, callback) {
  var manifestPath = path.join(pluginPath, pluginConstants.PLUGIN_MANIFEST_NAME);
  fsUtil.jsonFile(manifestPath, callback);
};

/*
 * Enable a plugin and does the following;
 * - Register plugin HTTP endpoints
 * - Register and load plugin services
 * - Register plugin job manager
 * @param {String} pluginName Plugin name.
 * @param {Function} callback Callback called with (err)
 */
PluginManager.prototype.enablePlugin = function(pluginName, callback) {
  var self = this;
  var endpointsPaths = [];
  var pluginPath = path.join(config.get()['plugins']['root'], pluginName);

  if (misc.inArray(pluginName, Object.keys(this._enabledPlugins))) {
    callback(new Errorf('Plugin "%s" is already enabled'));
    return;
  }

  async.waterfall([
    // 1. Discover http endpoints
    pluginsHttpUtils.discoverEndpoints,

    function registerHttpEndpoints(endpointRoutes, callback) {
      async.forEach(endpointRoutes, function(endpointRoute, callback) {
        self._registerPluginEndpoints(endpointRoute, callback);
      }, callback);
    },

    function discoverServices(callback) {
    },

    function registerAndStartServices(callback) {
    },

    function discoverJobs(callback) {
    },

    function registerJobs(callback) {
    }
  ],

  function(err) {
    // @TODO: Cleanup on error (unregister endpoints, etc.)
    callback(err);
  });

  this._enabledPlugins[pluginName] = {
    'endpoints': endpointsPaths, // Store full paths of the plugin endpoints
    'services': [], // Store names of the plugin services
    'jobs': [], // Store names of the plugin jobs
  };
};

/**
 * Register plugin HTTP endpoint handler with the Cast HTTP server.
 *
 * @param {String} method HTTP method (get / post / etc)
 * @param {String} path Full path including all required prefixes (e.g.
 * /plugins/<plugin_name>/<path>)
 * @param {Array} endpointRoutes Endpoint routes array.
 * @param {Function} Route handler function.
 */
PluginManager.prototype._registerPluginEndpoints = function(endpointRoutes) {
  var i, len, route, routeArgsLen, method, path, fullPath, middleware, handler;
  var registerMethod, args;

  for (i = 0, len = endpointRoutes.length; i < len; i++) {
    route = endpointRoutes[i];
    routeArgsLen = route.length;
    method = route[0].toLowerCase();
    path = route[1];
    fullPath = sprintf('%s/%s', pluginConstants.HTTP_ENDPOINT_PREFIX, path);

    // Actually register it
    args = [];
    if (routeArgsLen === 3) {
      // No middleware
      middleware = [];
    }
    else {
      middleware = route.splice(2, routeArgsLen - 1);
    }

    handler = route[3];
    //endpointsPaths.push(path);
    registerMethod = httpServer[method];

    if (middleware) {
      args.push(path);
      args = args.concat(middleware);
      args.push(handler)
    }
    else {
      args = [ path, handler ];
    }

    registerMethod.apply(httpServer, args);
  }
};

PluginManager.prototype.disablePlugin = function(pluginName, callback) {
  if (!misc.inArray(pluginName, Object.keys(this._enabledPlugins))) {
    callback(new Errorf('Plugin "%s" is not enabled'));
    return;
  }

  var plugin = this._enabledPlugins[pluginName];

  // Unregister all the plugin endpoints
  // Stop all the services
  // Kill all the pending jobs (?)
};
<commit_msg>More progress on the manager.<commit_after>/*
 * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Cloudkick licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var path = require('path');

var async = require('async');
var sprintf = require('sprintf').sprintf;
var express = require('express');

var config = require('util/config');
var log = require('util/log');
var fsUtil = require('util/fs');
var misc = require('util/misc');
var Errorf = misc.Errorf;

var httpServer = require('services/http').instance;
var pluginConstants = require('plugins/constants');
var pluginsHttpUtils = require('plugins/http');
var pluginServicesUtils = require('plugin/services');

function PluginManager() {
  this._availablePlugins = {};
  this._enabledPlugins = {};
}

/**
 * Return all the available plugins (all the plugins which are located in the
 * plugins directory).
 * @param {Function} callback Callback called with (err, availablePlugins)
 */
PluginManager.prototype.getAvailablePlugins = function(callback) {
};

/**
 * Return an object with the enabled plugins and their settings.
 * @param {Function} callback Callback called with (err, enabledPlugins)
 */
PluginManager.prototype.getEnabledPlugins = function(callback) {
  callback(null, config.get()['plugins']['enabled']);
};

/**
 * Read "plugins" part from the settings file and load all the enabled plugins.
 *
 * @param {Function} callback Callback called when the initialization completes.
 */
PluginManager.prototype._initialize = function(callback) {
  var self = this;
  var pluginName, pluginSettings;
  var confPlugins = config.get()['plugins'] || {};
  var enabledPlugins = confPlugins['enabled'];
  var enabledPluginNames = Object.keys(enabledPlugins);

  if (!enabledPlugins) {
    log.info('No plugins have been enabled');
    return;
  }

  async.forEach(enabledPluginNames, function(pluginName, callback) {
    var pluginSettings = enabledPlugins[pluginName];
    self._validatePluginSettings(pluginName, pluginSettings, function onValidated(err) {
      if (err) {
        log.error('Failed to validate plugin "%s" settings: %s', pluginName, err);
        callback();
        return;
      }

      // Load and enable the plugin
      log.info(sprintf('Enabling plugin "%s"', pluginName));
      self.enablePlugin(pluginName, callback);
    });
  }, callback);
};

/**
 * Validate plugin settings. Validation rules are defined by plugin author in
 * the plugin package.json file.
 *
 * @param {String} pluginName Plugin name.
 * @param {Object} pluginSettings Plugin settings.
 */
PluginManager.prototype.validatePluginSettings = function(pluginName,
                                                          pluginSettings,
                                                          callback) {
  // @TODO: Cache available plugins in this._availablePlugins dictionary
  var self = this;
  var pluginPath = path.join(config.get()['plugins']['root'], pluginName);

  async.waterfall([
    function readPluginManifest(callback) {
      self._readPluginManifest(pluginPath, callback);
    },

    function validatePluginSettings(manifest, callback) {
      var rules = manifest['settings'];
      // @TODO: Use validators from manifest/...
      callback();
    }
  ],

  function(err) {
    callback(err);
  });
};

/**
 * Read plugin manifest file.
 *
 * @param {String} pluginPath Path to the plugin directory.
 * @param {Function} callback Callback called with (err, data)
 */
PluginManager.prototype._readPluginManifest = function(pluginPath, callback) {
  var manifestPath = path.join(pluginPath, pluginConstants.PLUGIN_MANIFEST_NAME);
  fsUtil.jsonFile(manifestPath, callback);
};

/*
 * Enable a plugin and does the following;
 * - Register plugin HTTP endpoints
 * - Register and load plugin services
 * - Register plugin job manager
 * @param {String} pluginName Plugin name.
 * @param {Function} callback Callback called with (err)
 */
PluginManager.prototype.enablePlugin = function(pluginName, callback) {
  var self = this;
  var pluginEndpointPaths = [], pluginServices = [];
  var ops = [];

  var pluginPath = path.join(config.get()['plugins']['root'], pluginName);

  if (misc.inArray(pluginName, Object.keys(this._enabledPlugins))) {
    callback(new Errorf('Plugin "%s" is already enabled'));
    return;
  }

  // Register HTTP endpoints
  function registerHttpEndpoints(endpointRoutes, callback) {
    async.forEach(endpointRoutes, function(endpointRoute, callback) {
      var registeredPaths = self._registerPluginEndpoints(endpointRoute, callback);
      pluginEndpointPaths = pluginEndpointPaths.concat(registeredPaths);
    }, callback);
  }

  // Register and start services
  function registerAndStartServices(services, callback) {
    async.forEach(services, function(service, callback) {
      // pluginServices.push(service.name);
      service.start();
      callback();
    }, callback);
  }

  // Discover all the resources (endpoints, services, jobs) and decide what needs
  // to be done
  async.paralell([
    function discoverHttpEndpoints(callback) {
      pluginsHttpUtils.discoverEndpoints(pluginPath, function onDiscover(err, routes) {
        if (!err && routes) {
          ops.push(async.apply(registerHttpEndpoints, routes));
        }

        callback();
      });
    },

    function discoverServices(callback) {
      pluginServicesUtils.discoverServices(pluginPath, function onDiscover(err, services) {
        if (!err && services) {
          ops.push(async.apply(registerAndStartServices, services));
        }

        callback();
      });
    }

    // @TODO: Discover and register jobs
  ],

  function(err) {
    async.parallel(ops, callback);
  });

  this._enabledPlugins[pluginName] = {
    'endpoints': pluginEndpointPaths, // Store full paths of the plugin endpoints
    'services': pluginServices, // Store names of the plugin services
    'jobs': [] // Store names of the plugin jobs
  };
};

/**
 * Register plugin HTTP endpoint handler with the Cast HTTP server.
 *
 * @param {Array} endpointRoutes Routes array (method, path, middleware, ..., handler)
 * @return {Array} All the paths which have been successfully registered.
 */
PluginManager.prototype._registerPluginEndpoints = function(endpointRoutes) {
  var i, len, route, routeArgsLen, method, path, fullPath, middleware, handler;
  var registerMethod, args;
  var registeredPaths = [];

  for (i = 0, len = endpointRoutes.length; i < len; i++) {
    route = endpointRoutes[i];
    routeArgsLen = route.length;

    if (routeArgsLen < 3) {
      // Invalid route, should we throw?
      continue;
    }

    method = route[0].toLowerCase();
    path = route[1];
    fullPath = sprintf('%s/%s', pluginConstants.HTTP_ENDPOINT_PREFIX, path);

    // Actually register it
    args = [];
    if (routeArgsLen === 3) {
      // No middleware
      middleware = [];
    }
    else {
      middleware = route.splice(2, routeArgsLen - 1);
    }

    handler = route[3];
    registerMethod = httpServer[method];

    if (middleware) {
      args.push(path);
      args = args.concat(middleware);
      args.push(handler);
    }
    else {
      args = [ path, handler ];
    }

    // @TODO: Handle possible error
    registerMethod.apply(httpServer, args);
    registeredPaths.push(fullPath);
  }
};

/**
 * Disable a plugin.
 *
 * @param {String} pluginName Plugin name.
 * @param {Function} callback Callback called with ()
 */
PluginManager.prototype.disablePlugin = function(pluginName, callback) {
  var i, len;
  if (!misc.inArray(pluginName, Object.keys(this._enabledPlugins))) {
    callback(new Errorf('Plugin "%s" is not enabled'));
    return;
  }

  var plugin = this._enabledPlugins[pluginName];

  async.series([
    // Remove all the plugin HTTP endpoints
    function removeHttpEndpoints(callback) {
      var endpoint;
      var endpoints = plugin['endpoints'];

      for (i = 0, len = endpoints.length; i < len; i++) {
        endpoint = endpoints[i];
        httpServer.remove(endpoint);
      }

      callback();
    },

    // Stop all the services
    function stopAndUnregisterServices(callback) {
      var service;
      var services = plugin['services'];
      for (i = 0, len = services.length; i < len; i++) {
        service = services[i];
        service.stop();
      }
    },

    // Remove jobs. Note all the pending jobs will be canceled.
    function stopAndRemoveJobs(callback) {
      // @TODO:
      callback();
    }
  ], callback);
};
<|endoftext|>


<commit_before>/**
 * Reverse proxy based on htpp-proxy
 */
let url = require("url")
let router = require("./router")
let http_proxy = require('http-proxy')
const logger = require('../lib/Logger').getLogger('proxy-custom')

module.exports = {
  proxyPage: proxyPage
}

let proxy = http_proxy.createProxyServer()
configureProxy()

function configureProxy() {

  /*
  proxy.on('error', function (err, req, res) {
    res.writeHead(500, {
      'Content-Type': 'text/plain'
    });

    res.end('Something went wrong. And we are reporting a custom error message.');
  });

  //
  // Listen for the `proxyRes` event on `proxy`.
  //
  proxy.on('proxyReq', function (proxyRes, req, res) {
    logger.debug('proxy:before sending data');
  });

  //
  // Listen for the `proxyRes` event on `proxy`.
  //
  proxy.on('proxyRes', function (proxyRes, req, res) {
    logger.debug('RAW Response from the target', JSON.stringify(proxyRes.headers, true, 2));
  });

  //
  // Listen for the `open` event on `proxy`.
  //
  proxy.on('open', function (proxySocket) {
    // listen for messages coming FROM the target here
    proxySocket.on('data', function () {
      logger.trace('proxied')
    });
  });

  //
  // Listen for the `close` event on `proxy`.
  //
  proxy.on('close', function (res, socket, head) {
    // view disconnected websocket connections
    logger.trace('disconnected');
  });
  */
}

//http-proxy cannot reverse rules line /customfolder/=>http://tecnalia.com because if calling /customfolder/a.txt is requested as http://tecnalia.com/customfolder/a.txt
//it only works well with server to server redirections server1:port=>server2:port
//it expects the same path for both servers
//very important ignorePath: true, if not , the real target is automatically created to target+path
function proxyPage(page, request, response, stop) {
  var externalAddr = page
  if (request && request.headers.referer) {
    let route = router.resolve(url.parse(request.headers.referer).path)
    var urlReferer = url.parse(route.path)
    if (page.startsWith('/')) {
      externalAddr = urlReferer.protocol + '//' + urlReferer.host + page
    } else {
      var directory = route.path.substring(0, route.path.lastIndexOf('/'))
      externalAddr = directory + page
    }
  }
  let target = url.parse(externalAddr)
  request.headers.host = target.host
  request.headers.hostname = target.hostname
  proxy.web(request, response, {
    target: externalAddr,
    ignorePath: true
  })
}
<commit_msg>proxy: subresources with referrer and starting with http;// must be also served<commit_after>/**
 * Reverse proxy based on htpp-proxy
 */
let url = require("url")
let router = require("./router")
let http_proxy = require('http-proxy')
const logger = require('../lib/Logger').getLogger('proxy-custom')

module.exports = {
  proxyPage: proxyPage
}

let proxy = http_proxy.createProxyServer()
configureProxy()

function configureProxy() {

  /*
  proxy.on('error', function (err, req, res) {
    res.writeHead(500, {
      'Content-Type': 'text/plain'
    });

    res.end('Something went wrong. And we are reporting a custom error message.');
  });

  //
  // Listen for the `proxyRes` event on `proxy`.
  //
  proxy.on('proxyReq', function (proxyRes, req, res) {
    logger.debug('proxy:before sending data');
  });

  //
  // Listen for the `proxyRes` event on `proxy`.
  //
  proxy.on('proxyRes', function (proxyRes, req, res) {
    logger.debug('RAW Response from the target', JSON.stringify(proxyRes.headers, true, 2));
  });

  //
  // Listen for the `open` event on `proxy`.
  //
  proxy.on('open', function (proxySocket) {
    // listen for messages coming FROM the target here
    proxySocket.on('data', function () {
      logger.trace('proxied')
    });
  });

  //
  // Listen for the `close` event on `proxy`.
  //
  proxy.on('close', function (res, socket, head) {
    // view disconnected websocket connections
    logger.trace('disconnected');
  });
  */
}

//http-proxy cannot reverse rules line /customfolder/=>http://server.com because if calling /customfolder/a.txt is requested as http://server.com/customfolder/a.txt
//it only works well with server to server redirections server1:port=>server2:port
//it expects the same path for both servers
//very important ignorePath: true, if not , the real target is automatically created to target+path
function proxyPage(page, request, response, stop) {
  var externalAddr = page
  if (request && request.headers.referer) {
    let route = router.resolve(url.parse(request.headers.referer).path)
    var urlReferer = url.parse(route.path)
    if (page.startsWith('/')) {
      externalAddr = urlReferer.protocol + '//' + urlReferer.host + page
    } else if (page.includes('://')) {
      externalAddr = page
    } else {
      var directory = route.path.substring(0, route.path.lastIndexOf('/'))
      externalAddr = directory + page
    }
  }
  let target = url.parse(externalAddr)
  request.headers.host = target.host
  request.headers.hostname = target.hostname
  proxy.web(request, response, {
    target: externalAddr,
    ignorePath: true
  })
}
<|endoftext|>


<commit_before>class screenImageData {
    constructor() {
        this.isDrawingData = true;
        this.imagedata = [];
    }

    addData(scale, data)
    {
        this.imagedata.push({
            scale: scale,
            data: data
        });
    }

    getData(scale)
    {
        for (var i = 0; i < this.imagedata.length; i++)
        {
            if (this.imagedata[i].scale == scale)
                return this.imagedata[i].data;
        }

        return null;
    }

}

class screenDrawing {
        constructor(onScreenCanvas) {
            this.width = 128;
            this.height = 32;

            this.scales = [0.5, 1, 2];
            this.canvas = [null, null, null];

            this.resetCanvas();

            this.noFill = false;
            this.noStroke = false;

        }

        resetCanvas() {
            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];
                this.canvas[i] = new OffscreenCanvas(this.width * scale, this.height * scale);

                var ctx = this.canvas[i].getContext('2d');

                ctx.imageSmoothingEnabled = false

                ctx.fillStyle = "white";
                ctx.fillRect(0, 0, this.canvas[i].width, this.canvas[i].height);

                ctx.fillStyle = "black";
                ctx.strokeStyle = "black";
                ctx.lineWidth = scale;
            }
        }

        getStateData() {
            var imageData = new screenImageData();

            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];

                var ctx = this.canvas[i].getContext('2d');
                var imagedata = ctx.getImageData(0, 0, this.canvas[i].width, this.canvas[i].height);

                imageData.addData(scale, imagedata);
            }

            return imageData;
        }

        _drawPoint(canvas, scale, x, y) {
            var ctx = canvas.getContext('2d');

            ctx.fillRect(
                scale * x, scale * y, scale * 1, scale * 1);
        }

        drawPoint(x, y) {
            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];
                this._drawPoint(this.canvas[i], scale, x, y);
            }
        }

        _drawLine(canvas, scale, x0, y0, x1, y1) {
            var ctx = canvas.getContext('2d');

            ctx.beginPath();
            ctx.moveTo(scale * x0, scale * y0);
            ctx.lineTo(scale * x1, scale * y1);
            ctx.closePath();
            ctx.stroke();
        }

        drawLine(x0, y0, x1, y1) {
            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];
                this._drawLine(this.canvas[i], scale, x0, y0, x1, y1);
            }
        }

        _drawRectangle(canvas, scale, x0, y0, width, height) {
            var ctx = canvas.getContext('2d');

            /*
            if (!this.noStroke) {
                ctx.strokeRect(scale * x0, scale * y0, scale * width, scale * height);
            }*/

            if (!this.noFill) {
                ctx.fillRect(scale * x0, scale * y0, scale * width, scale * height);
            }

        }

        drawRectangle(x0, y0, width, height) {
            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];
                this._drawRectangle(this.canvas[i], scale, x0, y0, width, height);
            }

        }

        _drawCircle(canvas, x0, y0, diameter) {
            var ctx = canvas.getContext('2d');

            ctx.beginPath();
            ctx.arc(scale * x0, scale * y0, scale * diameter / 2, 0, Math.PI * 2);
            ctx.closePath();

            if (!this.noFill) {
                ctx.fill();
            }
        }

        drawCircle(x0, y0, diameter) {
            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];
                this._drawCircle(this.canvas[i], scale, x0, y0, diameter);
            }
        }

        _clearScreen(canvas, scale) {
            var ctx = canvas.getContext('2d');
            ctx.fillStyle = "white";
            ctx.fillRect(0, 0, this.canvas[i].width, this.canvas[i].height);

            ctx.fillStyle = "black";
            ctx.strokeStyle = "black";
            ctx.lineWidth = scale;

        }

        clearScreen() {
            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];
                this._clearScreen(this.canvas[i], scale);
            }

        }

        copyToCanvas(canvas, scale)
        {
            for (var i = 0; i < this.scales.length; i++) {
                var currentScale = this.scales[i];

                if (currentScale == scale) {
                    var ctx = canvas.getContext('2d');
                    ctx.drawImage(this.canvas[i],
                                0,
                                0,
                                this.canvas[i].width,
                                this.canvas[i].height,
                                0,
                                0,
                                canvas.width,
                                canvas.height);
                    }
                }
        }
}<commit_msg>quickPi: fix previous fix<commit_after>class screenImageData {
    constructor() {
        this.isDrawingData = true;
        this.imagedata = [];
    }

    addData(scale, data)
    {
        this.imagedata.push({
            scale: scale,
            data: data
        });
    }

    getData(scale)
    {
        for (var i = 0; i < this.imagedata.length; i++)
        {
            if (this.imagedata[i].scale == scale)
                return this.imagedata[i].data;
        }

        return null;
    }

}

class screenDrawing {
        constructor(onScreenCanvas) {
            this.width = 128;
            this.height = 32;

            this.scales = [0.5, 1, 2];
            this.canvas = [null, null, null];

            this.resetCanvas();

            this.noFill = false;
            this.noStroke = false;

        }

        resetCanvas() {
            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];
                this.canvas[i] = new OffscreenCanvas(this.width * scale, this.height * scale);

                var ctx = this.canvas[i].getContext('2d');

                ctx.imageSmoothingEnabled = false

                ctx.fillStyle = "white";
                ctx.fillRect(0, 0, this.canvas[i].width, this.canvas[i].height);

                ctx.fillStyle = "black";
                ctx.strokeStyle = "black";
                ctx.lineWidth = scale;
            }
        }

        getStateData() {
            var imageData = new screenImageData();

            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];

                var ctx = this.canvas[i].getContext('2d');
                var imagedata = ctx.getImageData(0, 0, this.canvas[i].width, this.canvas[i].height);

                imageData.addData(scale, imagedata);
            }

            return imageData;
        }

        _drawPoint(canvas, scale, x, y) {
            var ctx = canvas.getContext('2d');

            ctx.fillRect(
                scale * x, scale * y, scale * 1, scale * 1);
        }

        drawPoint(x, y) {
            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];
                this._drawPoint(this.canvas[i], scale, x, y);
            }
        }

        _drawLine(canvas, scale, x0, y0, x1, y1) {
            var ctx = canvas.getContext('2d');

            ctx.beginPath();
            ctx.moveTo(scale * x0, scale * y0);
            ctx.lineTo(scale * x1, scale * y1);
            ctx.closePath();
            ctx.stroke();
        }

        drawLine(x0, y0, x1, y1) {
            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];
                this._drawLine(this.canvas[i], scale, x0, y0, x1, y1);
            }
        }

        _drawRectangle(canvas, scale, x0, y0, width, height) {
            var ctx = canvas.getContext('2d');

            /*
            if (!this.noStroke) {
                ctx.strokeRect(scale * x0, scale * y0, scale * width, scale * height);
            }*/

            if (!this.noFill) {
                ctx.fillRect(scale * x0, scale * y0, scale * width, scale * height);
            }

        }

        drawRectangle(x0, y0, width, height) {
            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];
                this._drawRectangle(this.canvas[i], scale, x0, y0, width, height);
            }

        }

        _drawCircle(canvas, x0, y0, diameter) {
            var ctx = canvas.getContext('2d');

            ctx.beginPath();
            ctx.arc(scale * x0, scale * y0, scale * diameter / 2, 0, Math.PI * 2);
            ctx.closePath();

            if (!this.noFill) {
                ctx.fill();
            }
        }

        drawCircle(x0, y0, diameter) {
            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];
                this._drawCircle(this.canvas[i], scale, x0, y0, diameter);
            }
        }

        _clearScreen(canvas, scale) {
            var ctx = canvas.getContext('2d');
            ctx.fillStyle = "white";
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            ctx.fillStyle = "black";
            ctx.strokeStyle = "black";
        }

        clearScreen() {
            for (var i = 0; i < this.scales.length; i++) {
                var scale = this.scales[i];
                this._clearScreen(this.canvas[i], scale);
            }

        }

        copyToCanvas(canvas, scale)
        {
            for (var i = 0; i < this.scales.length; i++) {
                var currentScale = this.scales[i];

                if (currentScale == scale) {
                    var ctx = canvas.getContext('2d');
                    ctx.drawImage(this.canvas[i],
                                0,
                                0,
                                this.canvas[i].width,
                                this.canvas[i].height,
                                0,
                                0,
                                canvas.width,
                                canvas.height);
                    }
                }
        }
}<|endoftext|>


<commit_before>import { shallowMount } from '@vue/test-utils'
import ActionButton from '~/components/ActionButton'

describe('ActionButton', () => {
  describe('icon props', () => {
    test('pass string', () => {
      const wrapper = shallowMount(ActionButton, {
        propsData: {
          icon: 'user'
        }
      })
      expect(wrapper.contains('.fa-user')).toBe(true)
    })
    test('pass array', () => {
      const wrapper = shallowMount(ActionButton, {
        propsData: {
          icon: ['user', 'users']
        }
      })
      expect(wrapper.contains('.fa-user')).toBe(true)
      expect(wrapper.contains('.fa-users')).toBe(false)
      wrapper.setProps({
        checked: true
      })
      expect(wrapper.contains('.fa-user')).toBe(false)
      expect(wrapper.contains('.fa-users')).toBe(true)
    })
  })
})
<commit_msg>Change from shallowMount to mount<commit_after>import { mount } from '@vue/test-utils'
import ActionButton from '~/components/ActionButton'

describe('ActionButton', () => {
  describe('icon props', () => {
    test('pass string', () => {
      const wrapper = mount(ActionButton, {
        propsData: {
          icon: 'user'
        }
      })
      expect(wrapper.contains('.fa-user')).toBe(true)
    })
    test('pass array', () => {
      const wrapper = mount(ActionButton, {
        propsData: {
          icon: ['user', 'users']
        }
      })
      expect(wrapper.contains('.fa-user')).toBe(true)
      expect(wrapper.contains('.fa-users')).toBe(false)
      wrapper.setProps({
        checked: true
      })
      expect(wrapper.contains('.fa-user')).toBe(false)
      expect(wrapper.contains('.fa-users')).toBe(true)
    })
  })
})
<|endoftext|>


<commit_before>
(function() {
	var createListItem = function(ary) {
		var html = "";
		for (var i = 0; i < ary.length; i++) {
			html += "
" + ary[i] + "
"
		}
		return html;
	};

	var reloadItemList = function() {
		var url = "/api/mediashow.php";

		$.ajax({
			type: "GET",
			cache: false,
			url: url,
			dataType: "json",
			success: function(data){
				$("#audio-list").html(createListItem(data["audios"]));
				$("#video-list").html(createListItem(data["videos"]));
				callback();
			}
		});

	};

	var downloadVideo = function(query, callback) {
		var url = "/api/youtubedl.php" + query;

		$.ajax({
			type: "GET",
			cache: false,
			url: url,
			dataType: "json",
			success: function(data){
				callback();
			}
		});
	};

	ViewManager.event("#youtube-dl", "init", function() {
		ViewManager.spin("start");
		reloadItemList(function() {
			ViewManager.spin("end", {delay:300});
		});
	});

	ViewManager.event("#youtube-dl", "destroy", function() {
		$("#youtube-dl button[type='reset']").click();
	});

	$(document).on("submit", "body[menu='#youtube-dl'] div#youtube-dl form", function(e) {
		var vid = $("#inputVid").val();
		var title = encodeURI($("#inputTitle").val());
		var type = $('input[name=mediaType]:checked').val()

		if(vid === "") {
			ViewManager.toast("error", "invalid parameter");
			return;
		}

		var query = "?vid="+vid+"&title="+title+"&type="+type;

		ViewManager.spin("start");
		downloadVideo(query, function() {
			reloadItemList(function() {
				ViewManager.spin("end", {delay:300});
			});
		});

		return false;
	});

})();<commit_msg>import.<commit_after>
(function() {
	var createListItem = function(ary) {
		var html = "";
		for (var i = 0; i < ary.length; i++) {
			html += "
" + ary[i] + "
"
		}
		return html;
	};

	var reloadItemList = function(callback) {
		var url = "/api/mediashow.php";

		$.ajax({
			type: "GET",
			cache: false,
			url: url,
			dataType: "json",
			success: function(data){
				$("#audio-list").html(createListItem(data["audios"]));
				$("#video-list").html(createListItem(data["videos"]));
				callback();
			}
		});

	};

	var downloadVideo = function(query, callback) {
		var url = "/api/youtubedl.php" + query;

		$.ajax({
			type: "GET",
			cache: false,
			url: url,
			dataType: "json",
			success: function(data){
				callback();
			}
		});
	};

	ViewManager.event("#youtube-dl", "init", function() {
		ViewManager.spin("start");
		reloadItemList(function() {
			ViewManager.spin("end", {delay:300});
		});
	});

	ViewManager.event("#youtube-dl", "destroy", function() {
		$("#youtube-dl button[type='reset']").click();
	});

	$(document).on("submit", "body[menu='#youtube-dl'] div#youtube-dl form", function(e) {
		var vid = $("#inputVid").val();
		var title = encodeURI($("#inputTitle").val());
		var type = $('input[name=mediaType]:checked').val()

		if(vid === "") {
			ViewManager.toast("error", "invalid parameter");
			return;
		}

		var query = "?vid="+vid+"&title="+title+"&type="+type;

		ViewManager.spin("start");
		downloadVideo(query, function() {
			reloadItemList(function() {
				ViewManager.spin("end", {delay:300});
			});
		});

		return false;
	});

})();<|endoftext|>