<commit_before>import Component from '@ember/component';
import SettingsMenuMixin from 'ghost-admin/mixins/settings-menu-component';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import formatMarkdown from 'ghost-admin/utils/format-markdown';
import moment from 'moment';
import {alias, or} from '@ember/object/computed';
import {computed} from '@ember/object';
import {run} from '@ember/runloop';
import {inject as service} from '@ember/service';
import {task, timeout} from 'ember-concurrency';

const PSM_ANIMATION_LENGTH = 400;

export default Component.extend(SettingsMenuMixin, {
    store: service(),
    config: service(),
    ghostPaths: service(),
    notifications: service(),
    slugGenerator: service(),
    session: service(),
    settings: service(),
    ui: service(),

    post: null,

    _showSettingsMenu: false,
    _showThrobbers: false,

    canonicalUrlScratch: alias('post.canonicalUrlScratch'),
    customExcerptScratch: alias('post.customExcerptScratch'),
    codeinjectionFootScratch: alias('post.codeinjectionFootScratch'),
    codeinjectionHeadScratch: alias('post.codeinjectionHeadScratch'),
    metaDescriptionScratch: alias('post.metaDescriptionScratch'),
    metaTitleScratch: alias('post.metaTitleScratch'),
    ogDescriptionScratch: alias('post.ogDescriptionScratch'),
    ogTitleScratch: alias('post.ogTitleScratch'),
    twitterDescriptionScratch: alias('post.twitterDescriptionScratch'),
    twitterTitleScratch: alias('post.twitterTitleScratch'),
    slugValue: boundOneWay('post.slug'),

    facebookDescription: or('ogDescriptionScratch', 'customExcerptScratch', 'seoDescription'),
    facebookImage: or('post.ogImage', 'post.featureImage'),
    facebookTitle: or('ogTitleScratch', 'seoTitle'),
    twitterDescription: or('twitterDescriptionScratch', 'customExcerptScratch', 'seoDescription'),
    twitterImage: or('post.twitterImage', 'post.featureImage'),
    twitterTitle: or('twitterTitleScratch', 'seoTitle'),

    seoTitle: computed('metaTitleScratch', 'post.titleScratch', function () {
        return this.metaTitleScratch || this.post.titleScratch || '(Untitled)';
    }),

    seoDescription: computed('post.scratch', 'metaDescriptionScratch', function () {
        let metaDescription = this.metaDescriptionScratch || '';
        let mobiledoc = this.get('post.scratch');
        let [markdownCard] = mobiledoc.cards;
        let markdown = markdownCard && markdownCard[1] && markdownCard[1].markdown;
        let placeholder;

        if (metaDescription) {
            placeholder = metaDescription;
        } else {
            let div = document.createElement('div');
            div.innerHTML = formatMarkdown(markdown, false);

            // Strip HTML
            placeholder = div.textContent;
            // Replace new lines and trim
            placeholder = placeholder.replace(/\n+/g, ' ').trim();
        }

        return placeholder;
    }),

    seoURL: computed('post.{slug,canonicalUrl}', 'config.blogUrl', function () {
        let blogUrl = this.get('config.blogUrl');
        let seoSlug = this.post.slug || '';
        let canonicalUrl = this.post.canonicalUrl || '';

        if (canonicalUrl) {
            if (canonicalUrl.match(/^\//)) {
                return `${blogUrl}${canonicalUrl}`;
            } else {
                return canonicalUrl;
            }
        } else {
            let seoURL = `${blogUrl}/${seoSlug}`;

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

            return seoURL;
        }
    }),

    didReceiveAttrs() {
        this._super(...arguments);

        // HACK: ugly method of working around the CSS animations so that we
        // can add throbbers only when the animation has finished
        // TODO: use liquid-fire to handle PSM slide-in and replace tabs manager
        // with something more ember-like
        if (this.showSettingsMenu && !this._showSettingsMenu) {
            this.showThrobbers.perform();
        }

        // fired when menu is closed
        if (!this.showSettingsMenu && this._showSettingsMenu) {
            let post = this.post;
            let errors = post.get('errors');

            // reset the publish date if it has an error
            if (errors.has('publishedAtBlogDate') || errors.has('publishedAtBlogTime')) {
                post.set('publishedAtBlogTZ', post.get('publishedAtUTC'));
                post.validate({attribute: 'publishedAtBlog'});
            }

            // remove throbbers
            this.set('_showThrobbers', false);
        }

        this._showSettingsMenu = this.showSettingsMenu;
    },

    actions: {
        showSubview(subview) {
            this._super(...arguments);

            this.set('subview', subview);

            // Chrome appears to have an animation bug that cancels the slide
            // transition unless there's a delay between the animation starting
            // and the throbbers being removed
            run.later(this, function () {
                this.set('_showThrobbers', false);
            }, 50);
        },

        closeSubview() {
            this._super(...arguments);

            this.set('subview', null);
            this.showThrobbers.perform();
        },

        discardEnter() {
            return false;
        },

        toggleFeatured() {
            this.toggleProperty('post.featured');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        /**
         * triggered by user manually changing slug
         */
        updateSlug(newSlug) {
            return this.updateSlug
                .perform(newSlug)
                .catch((error) => {
                    this.showError(error);
                    this.post.rollbackAttributes();
                });
        },

        setPublishedAtBlogDate(date) {
            let post = this.post;
            let dateString = moment(date).format('YYYY-MM-DD');

            post.get('errors').remove('publishedAtBlogDate');

            if (post.get('isNew') || date === post.get('publishedAtBlogDate')) {
                post.validate({property: 'publishedAtBlog'});
            } else {
                post.set('publishedAtBlogDate', dateString);
                return this.savePost.perform();
            }
        },

        setPublishedAtBlogTime(time) {
            let post = this.post;

            post.get('errors').remove('publishedAtBlogDate');

            if (post.get('isNew') || time === post.get('publishedAtBlogTime')) {
                post.validate({property: 'publishedAtBlog'});
            } else {
                post.set('publishedAtBlogTime', time);
                return this.savePost.perform();
            }
        },

        setCustomExcerpt(excerpt) {
            let post = this.post;
            let currentExcerpt = post.get('customExcerpt');

            if (excerpt === currentExcerpt) {
                return;
            }

            post.set('customExcerpt', excerpt);

            return post.validate({property: 'customExcerpt'}).then(() => this.savePost.perform());
        },

        setHeaderInjection(code) {
            let post = this.post;
            let currentCode = post.get('codeinjectionHead');

            if (code === currentCode) {
                return;
            }

            post.set('codeinjectionHead', code);

            return post.validate({property: 'codeinjectionHead'}).then(() => this.savePost.perform());
        },

        setFooterInjection(code) {
            let post = this.post;
            let currentCode = post.get('codeinjectionFoot');

            if (code === currentCode) {
                return;
            }

            post.set('codeinjectionFoot', code);

            return post.validate({property: 'codeinjectionFoot'}).then(() => this.savePost.perform());
        },

        setMetaTitle(metaTitle) {
            // Grab the post and current stored meta title
            let post = this.post;
            let currentTitle = post.get('metaTitle');

            // If the title entered matches the stored meta title, do nothing
            if (currentTitle === metaTitle) {
                return;
            }

            // If the title entered is different, set it as the new meta title
            post.set('metaTitle', metaTitle);

            // Make sure the meta title is valid and if so, save it into the post
            return post.validate({property: 'metaTitle'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setMetaDescription(metaDescription) {
            // Grab the post and current stored meta description
            let post = this.post;
            let currentDescription = post.get('metaDescription');

            // If the title entered matches the stored meta title, do nothing
            if (currentDescription === metaDescription) {
                return;
            }

            // If the title entered is different, set it as the new meta title
            post.set('metaDescription', metaDescription);

            // Make sure the meta title is valid and if so, save it into the post
            return post.validate({property: 'metaDescription'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setCanonicalUrl(value) {
            // Grab the post and current stored meta description
            let post = this.post;
            let currentCanonicalUrl = post.canonicalUrl;

            // If the value entered matches the stored value, do nothing
            if (currentCanonicalUrl === value) {
                return;
            }

            // If the value supplied is different, set it as the new value
            post.set('canonicalUrl', value);

            // Make sure the value is valid and if so, save it into the post
            return post.validate({property: 'canonicalUrl'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setOgTitle(ogTitle) {
            // Grab the post and current stored facebook title
            let post = this.post;
            let currentTitle = post.get('ogTitle');

            // If the title entered matches the stored facebook title, do nothing
            if (currentTitle === ogTitle) {
                return;
            }

            // If the title entered is different, set it as the new facebook title
            post.set('ogTitle', ogTitle);

            // Make sure the facebook title is valid and if so, save it into the post
            return post.validate({property: 'ogTitle'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setOgDescription(ogDescription) {
            // Grab the post and current stored facebook description
            let post = this.post;
            let currentDescription = post.get('ogDescription');

            // If the title entered matches the stored facebook description, do nothing
            if (currentDescription === ogDescription) {
                return;
            }

            // If the description entered is different, set it as the new facebook description
            post.set('ogDescription', ogDescription);

            // Make sure the facebook description is valid and if so, save it into the post
            return post.validate({property: 'ogDescription'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setTwitterTitle(twitterTitle) {
            // Grab the post and current stored twitter title
            let post = this.post;
            let currentTitle = post.get('twitterTitle');

            // If the title entered matches the stored twitter title, do nothing
            if (currentTitle === twitterTitle) {
                return;
            }

            // If the title entered is different, set it as the new twitter title
            post.set('twitterTitle', twitterTitle);

            // Make sure the twitter title is valid and if so, save it into the post
            return post.validate({property: 'twitterTitle'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setTwitterDescription(twitterDescription) {
            // Grab the post and current stored twitter description
            let post = this.post;
            let currentDescription = post.get('twitterDescription');

            // If the description entered matches the stored twitter description, do nothing
            if (currentDescription === twitterDescription) {
                return;
            }

            // If the description entered is different, set it as the new twitter description
            post.set('twitterDescription', twitterDescription);

            // Make sure the twitter description is valid and if so, save it into the post
            return post.validate({property: 'twitterDescription'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setCoverImage(image) {
            this.set('post.featureImage', image);

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        clearCoverImage() {
            this.set('post.featureImage', '');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        setOgImage(image) {
            this.set('post.ogImage', image);

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        clearOgImage() {
            this.set('post.ogImage', '');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        setTwitterImage(image) {
            this.set('post.twitterImage', image);

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        clearTwitterImage() {
            this.set('post.twitterImage', '');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        changeAuthors(newAuthors) {
            let post = this.post;

            // return if nothing changed
            if (newAuthors.mapBy('id').join() === post.get('authors').mapBy('id').join()) {
                return;
            }

            post.set('authors', newAuthors);
            post.validate({property: 'authors'});

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                post.rollbackAttributes();
            });
        },

        deletePost() {
            if (this.deletePost) {
                this.deletePost();
            }
        }
    },

    showThrobbers: task(function* () {
        yield timeout(PSM_ANIMATION_LENGTH);
        this.set('_showThrobbers', true);
    }).restartable(),

    showError(error) {
        // TODO: remove null check once ValidationEngine has been removed
        if (error) {
            this.notifications.showAPIError(error);
        }
    }
});
<commit_msg>Fixed error when opening meta/facebook/twitter settings with a new post<commit_after>import Component from '@ember/component';
import SettingsMenuMixin from 'ghost-admin/mixins/settings-menu-component';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import formatMarkdown from 'ghost-admin/utils/format-markdown';
import moment from 'moment';
import {alias, or} from '@ember/object/computed';
import {computed} from '@ember/object';
import {run} from '@ember/runloop';
import {inject as service} from '@ember/service';
import {task, timeout} from 'ember-concurrency';

const PSM_ANIMATION_LENGTH = 400;

export default Component.extend(SettingsMenuMixin, {
    store: service(),
    config: service(),
    ghostPaths: service(),
    notifications: service(),
    slugGenerator: service(),
    session: service(),
    settings: service(),
    ui: service(),

    post: null,

    _showSettingsMenu: false,
    _showThrobbers: false,

    canonicalUrlScratch: alias('post.canonicalUrlScratch'),
    customExcerptScratch: alias('post.customExcerptScratch'),
    codeinjectionFootScratch: alias('post.codeinjectionFootScratch'),
    codeinjectionHeadScratch: alias('post.codeinjectionHeadScratch'),
    metaDescriptionScratch: alias('post.metaDescriptionScratch'),
    metaTitleScratch: alias('post.metaTitleScratch'),
    ogDescriptionScratch: alias('post.ogDescriptionScratch'),
    ogTitleScratch: alias('post.ogTitleScratch'),
    twitterDescriptionScratch: alias('post.twitterDescriptionScratch'),
    twitterTitleScratch: alias('post.twitterTitleScratch'),
    slugValue: boundOneWay('post.slug'),

    facebookDescription: or('ogDescriptionScratch', 'customExcerptScratch', 'seoDescription'),
    facebookImage: or('post.ogImage', 'post.featureImage'),
    facebookTitle: or('ogTitleScratch', 'seoTitle'),
    twitterDescription: or('twitterDescriptionScratch', 'customExcerptScratch', 'seoDescription'),
    twitterImage: or('post.twitterImage', 'post.featureImage'),
    twitterTitle: or('twitterTitleScratch', 'seoTitle'),

    seoTitle: computed('metaTitleScratch', 'post.titleScratch', function () {
        return this.metaTitleScratch || this.post.titleScratch || '(Untitled)';
    }),

    seoDescription: computed('post.scratch', 'metaDescriptionScratch', function () {
        let metaDescription = this.metaDescriptionScratch || '';
        let mobiledoc = this.get('post.scratch');
        let [markdownCard] = (mobiledoc && mobiledoc.cards) || [];
        let markdown = markdownCard && markdownCard[1] && markdownCard[1].markdown;
        let placeholder;

        if (metaDescription) {
            placeholder = metaDescription;
        } else {
            let div = document.createElement('div');
            div.innerHTML = formatMarkdown(markdown, false);

            // Strip HTML
            placeholder = div.textContent;
            // Replace new lines and trim
            placeholder = placeholder.replace(/\n+/g, ' ').trim();
        }

        return placeholder;
    }),

    seoURL: computed('post.{slug,canonicalUrl}', 'config.blogUrl', function () {
        let blogUrl = this.get('config.blogUrl');
        let seoSlug = this.post.slug || '';
        let canonicalUrl = this.post.canonicalUrl || '';

        if (canonicalUrl) {
            if (canonicalUrl.match(/^\//)) {
                return `${blogUrl}${canonicalUrl}`;
            } else {
                return canonicalUrl;
            }
        } else {
            let seoURL = `${blogUrl}/${seoSlug}`;

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

            return seoURL;
        }
    }),

    didReceiveAttrs() {
        this._super(...arguments);

        // HACK: ugly method of working around the CSS animations so that we
        // can add throbbers only when the animation has finished
        // TODO: use liquid-fire to handle PSM slide-in and replace tabs manager
        // with something more ember-like
        if (this.showSettingsMenu && !this._showSettingsMenu) {
            this.showThrobbers.perform();
        }

        // fired when menu is closed
        if (!this.showSettingsMenu && this._showSettingsMenu) {
            let post = this.post;
            let errors = post.get('errors');

            // reset the publish date if it has an error
            if (errors.has('publishedAtBlogDate') || errors.has('publishedAtBlogTime')) {
                post.set('publishedAtBlogTZ', post.get('publishedAtUTC'));
                post.validate({attribute: 'publishedAtBlog'});
            }

            // remove throbbers
            this.set('_showThrobbers', false);
        }

        this._showSettingsMenu = this.showSettingsMenu;
    },

    actions: {
        showSubview(subview) {
            this._super(...arguments);

            this.set('subview', subview);

            // Chrome appears to have an animation bug that cancels the slide
            // transition unless there's a delay between the animation starting
            // and the throbbers being removed
            run.later(this, function () {
                this.set('_showThrobbers', false);
            }, 50);
        },

        closeSubview() {
            this._super(...arguments);

            this.set('subview', null);
            this.showThrobbers.perform();
        },

        discardEnter() {
            return false;
        },

        toggleFeatured() {
            this.toggleProperty('post.featured');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        /**
         * triggered by user manually changing slug
         */
        updateSlug(newSlug) {
            return this.updateSlug
                .perform(newSlug)
                .catch((error) => {
                    this.showError(error);
                    this.post.rollbackAttributes();
                });
        },

        setPublishedAtBlogDate(date) {
            let post = this.post;
            let dateString = moment(date).format('YYYY-MM-DD');

            post.get('errors').remove('publishedAtBlogDate');

            if (post.get('isNew') || date === post.get('publishedAtBlogDate')) {
                post.validate({property: 'publishedAtBlog'});
            } else {
                post.set('publishedAtBlogDate', dateString);
                return this.savePost.perform();
            }
        },

        setPublishedAtBlogTime(time) {
            let post = this.post;

            post.get('errors').remove('publishedAtBlogDate');

            if (post.get('isNew') || time === post.get('publishedAtBlogTime')) {
                post.validate({property: 'publishedAtBlog'});
            } else {
                post.set('publishedAtBlogTime', time);
                return this.savePost.perform();
            }
        },

        setCustomExcerpt(excerpt) {
            let post = this.post;
            let currentExcerpt = post.get('customExcerpt');

            if (excerpt === currentExcerpt) {
                return;
            }

            post.set('customExcerpt', excerpt);

            return post.validate({property: 'customExcerpt'}).then(() => this.savePost.perform());
        },

        setHeaderInjection(code) {
            let post = this.post;
            let currentCode = post.get('codeinjectionHead');

            if (code === currentCode) {
                return;
            }

            post.set('codeinjectionHead', code);

            return post.validate({property: 'codeinjectionHead'}).then(() => this.savePost.perform());
        },

        setFooterInjection(code) {
            let post = this.post;
            let currentCode = post.get('codeinjectionFoot');

            if (code === currentCode) {
                return;
            }

            post.set('codeinjectionFoot', code);

            return post.validate({property: 'codeinjectionFoot'}).then(() => this.savePost.perform());
        },

        setMetaTitle(metaTitle) {
            // Grab the post and current stored meta title
            let post = this.post;
            let currentTitle = post.get('metaTitle');

            // If the title entered matches the stored meta title, do nothing
            if (currentTitle === metaTitle) {
                return;
            }

            // If the title entered is different, set it as the new meta title
            post.set('metaTitle', metaTitle);

            // Make sure the meta title is valid and if so, save it into the post
            return post.validate({property: 'metaTitle'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setMetaDescription(metaDescription) {
            // Grab the post and current stored meta description
            let post = this.post;
            let currentDescription = post.get('metaDescription');

            // If the title entered matches the stored meta title, do nothing
            if (currentDescription === metaDescription) {
                return;
            }

            // If the title entered is different, set it as the new meta title
            post.set('metaDescription', metaDescription);

            // Make sure the meta title is valid and if so, save it into the post
            return post.validate({property: 'metaDescription'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setCanonicalUrl(value) {
            // Grab the post and current stored meta description
            let post = this.post;
            let currentCanonicalUrl = post.canonicalUrl;

            // If the value entered matches the stored value, do nothing
            if (currentCanonicalUrl === value) {
                return;
            }

            // If the value supplied is different, set it as the new value
            post.set('canonicalUrl', value);

            // Make sure the value is valid and if so, save it into the post
            return post.validate({property: 'canonicalUrl'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setOgTitle(ogTitle) {
            // Grab the post and current stored facebook title
            let post = this.post;
            let currentTitle = post.get('ogTitle');

            // If the title entered matches the stored facebook title, do nothing
            if (currentTitle === ogTitle) {
                return;
            }

            // If the title entered is different, set it as the new facebook title
            post.set('ogTitle', ogTitle);

            // Make sure the facebook title is valid and if so, save it into the post
            return post.validate({property: 'ogTitle'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setOgDescription(ogDescription) {
            // Grab the post and current stored facebook description
            let post = this.post;
            let currentDescription = post.get('ogDescription');

            // If the title entered matches the stored facebook description, do nothing
            if (currentDescription === ogDescription) {
                return;
            }

            // If the description entered is different, set it as the new facebook description
            post.set('ogDescription', ogDescription);

            // Make sure the facebook description is valid and if so, save it into the post
            return post.validate({property: 'ogDescription'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setTwitterTitle(twitterTitle) {
            // Grab the post and current stored twitter title
            let post = this.post;
            let currentTitle = post.get('twitterTitle');

            // If the title entered matches the stored twitter title, do nothing
            if (currentTitle === twitterTitle) {
                return;
            }

            // If the title entered is different, set it as the new twitter title
            post.set('twitterTitle', twitterTitle);

            // Make sure the twitter title is valid and if so, save it into the post
            return post.validate({property: 'twitterTitle'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setTwitterDescription(twitterDescription) {
            // Grab the post and current stored twitter description
            let post = this.post;
            let currentDescription = post.get('twitterDescription');

            // If the description entered matches the stored twitter description, do nothing
            if (currentDescription === twitterDescription) {
                return;
            }

            // If the description entered is different, set it as the new twitter description
            post.set('twitterDescription', twitterDescription);

            // Make sure the twitter description is valid and if so, save it into the post
            return post.validate({property: 'twitterDescription'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setCoverImage(image) {
            this.set('post.featureImage', image);

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        clearCoverImage() {
            this.set('post.featureImage', '');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        setOgImage(image) {
            this.set('post.ogImage', image);

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        clearOgImage() {
            this.set('post.ogImage', '');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        setTwitterImage(image) {
            this.set('post.twitterImage', image);

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        clearTwitterImage() {
            this.set('post.twitterImage', '');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        changeAuthors(newAuthors) {
            let post = this.post;

            // return if nothing changed
            if (newAuthors.mapBy('id').join() === post.get('authors').mapBy('id').join()) {
                return;
            }

            post.set('authors', newAuthors);
            post.validate({property: 'authors'});

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                post.rollbackAttributes();
            });
        },

        deletePost() {
            if (this.deletePost) {
                this.deletePost();
            }
        }
    },

    showThrobbers: task(function* () {
        yield timeout(PSM_ANIMATION_LENGTH);
        this.set('_showThrobbers', true);
    }).restartable(),

    showError(error) {
        // TODO: remove null check once ValidationEngine has been removed
        if (error) {
            this.notifications.showAPIError(error);
        }
    }
});
<|endoftext|>


<commit_before>'use strict';

var cyclon = require("cyclon.p2p");
var rtc = require("cyclon.p2p-rtc-client");
var rtcComms = require("cyclon.p2p-rtc-comms");
var Utils = require("cyclon.p2p-common");
var StorageService = require("./services/StorageService");

function construct(constructor) {
    return function() {
        var args = arguments;
        function F() {
            return constructor.apply(this, args);
        }
        F.prototype = constructor.prototype;
        return new F();
    }
}

/**
 * RTC Module
 */
var rtcModule = angular.module("cyclon-rtc", []);

rtcModule.factory("RTC", ["IceCandidateBatchingSignallingService", "ChannelFactory", construct(rtc.RTC)]);
rtcModule.factory("ChannelFactory", ["PeerConnectionFactory", "IceCandidateBatchingSignallingService", "$log", construct(rtc.ChannelFactory)]);
rtcModule.factory("PeerConnectionFactory", ["RTCObjectFactory", "$log", "IceServers", "ChannelStateTimeout", construct(rtc.PeerConnectionFactory)]);
rtcModule.factory("RTCObjectFactory", ["$log", construct(rtc.AdapterJsRTCObjectFactory)]);
rtcModule.factory("AsyncExecService", Utils.asyncExecService);
rtcModule.factory("IceCandidateBatchingSignallingService", ["AsyncExecService", "SignallingService", "IceCandidateQuietPeriod", construct(rtc.IceCandidateBatchingSignallingService)]);
rtcModule.factory("SignallingService", ["SignallingSocket", "$log", "HttpRequestService", "StorageService", construct(rtc.SocketIOSignallingService)]);
rtcModule.factory("SignallingSocket", ["SignallingServerService", "SocketFactory", "$log", "AsyncExecService", "StorageService", "TimingService", construct(rtc.RedundantSignallingSocket)]);
rtcModule.factory("HttpRequestService", construct(rtc.HttpRequestService));
rtcModule.factory("StorageService", StorageService);
rtcModule.factory("SignallingServerService", ["SignallingServers", construct(rtc.StaticSignallingServerService)]);
rtcModule.factory("SocketFactory", construct(rtc.SocketFactory));
rtcModule.factory("TimingService", construct(rtc.TimingService));

/**
 * RTC Config here
 */
rtcModule.constant("IceServers", [
    // The Google STUN server
    {urls: ['stun:stun.l.google.com:19302']},
    // Turn over TCP on port 80 for networks with totalitarian security regimes
    {urls: ['turn:54.187.115.223:80?transport=tcp'], username: 'cyclonjsuser', credential: 'sP4zBGasNVKI'}
]);
rtcModule.constant("SignallingServers", JSON.parse('/* @echo SIGNALLING_SERVERS */'));
rtcModule.constant("ChannelStateTimeout", 30000);
rtcModule.constant("IceCandidateQuietPeriod", 300);

/**
 * RTC Comms Module
 */
var rtcCommsModule = angular.module("cyclon-rtc-comms", ["cyclon-rtc"]);

rtcCommsModule.factory("Comms", ["RTC", "ShuffleStateFactory", "$log", construct(rtcComms.WebRTCComms)]);
rtcCommsModule.factory("ShuffleStateFactory", ["$log", "AsyncExecService", construct(rtcComms.ShuffleStateFactory)]);
rtcCommsModule.factory("Bootstrap", ["SignallingSocket", "HttpRequestService", construct(rtcComms.SignallingServerBootstrap)]);

/**
 * Demo app module
 */
var LocalSimulationService = require("./services/LocalSimulationService");
var OverlayService = require("./services/OverlayService");
var FrontendVersionService = require("./services/FrontendVersionService");
var LocationProviderService = require("./services/LocationProviderService");
var PlatformDetectionService = require("./services/PlatformDetectionService");
var ClientInfoService = require("./services/ClientInfoService");
var ShuffleStatsService = require("./services/ShuffleStatsService");
var VersionCheckService = require("./services/VersionCheckService");
var RTCService = require("./services/RTCService");
var SessionInformationService = require("./services/SessionInformationService");
var RankingService = require("./services/RankingService");

var DemoPageController = require("./controllers/DemoPageController");
var LocalSimulationController = require("./controllers/LocalSimulationController");
var ConnectivityTestController = require("./controllers/ConnectivityTestController");

var CacheContentsTable = require("./directives/CacheContentsTable");
var NodeInfo = require("./directives/NodeInfo");
var TopNodesTable = require("./directives/TopNodesTable");
var LocalNodePointerPanel = require("./directives/LocalNodePointerPanel");
var RemoteNodePointerPanel = require("./directives/RemoteNodePointerPanel");

var OutgoingSuccessRateFilter = require("./filters/OutgoingSuccessRateFilter");
var IncomingSuccessRateFilter = require("./filters/IncomingSuccessRateFilter");
var IdOrInfoFilter = require("./filters/IdOrInfoFilter");
var RunningTimeFilter = require("./filters/RunningTimeFilter");

var appModule = angular.module("cyclon-demo", ["ui.bootstrap", "cyclon-rtc-comms"]);

appModule.filter("incomingSuccessRate", IncomingSuccessRateFilter);
appModule.filter("outgoingSuccessRate", OutgoingSuccessRateFilter);
appModule.filter("idOrInfo", IdOrInfoFilter);
appModule.filter("runningTime", ["SessionInformationService", RunningTimeFilter]);
appModule.factory("ShuffleStatsService", ["$rootScope", ShuffleStatsService]);
appModule.factory("SessionInformationService", ["StorageService", SessionInformationService]);
appModule.factory("RankingService", ["$rootScope", "$interval", "OverlayService", "SessionInformationService", RankingService]);
appModule.factory("FrontendVersionService", FrontendVersionService);
appModule.factory("OverlayService", ["$log", "$rootScope", "FrontendVersionService",
    "LocationProviderService", "PlatformDetectionService", "ClientInfoService",
    "ShuffleStatsService", "SessionInformationService", "StorageService",
    "Comms", "Bootstrap", "AsyncExecService", OverlayService]);
appModule.factory("LocalSimulationService", ['$rootScope', '$log', '$interval', LocalSimulationService]);
appModule.factory("LocationProviderService", ["$log", "$http", LocationProviderService]);
appModule.factory("PlatformDetectionService", PlatformDetectionService);
appModule.factory("ClientInfoService", ["StorageService", ClientInfoService]);
appModule.factory("VersionCheckService", ["$rootScope", "$interval", "$http", "$log", "FrontendVersionService", VersionCheckService]);
appModule.factory("RTCService", ["RTC", "$log", "$rootScope", RTCService]);

appModule.directive("cacheContentsTable", CacheContentsTable);
appModule.directive("nodeInfo", NodeInfo);
appModule.directive("topNodesTable", TopNodesTable);
appModule.directive("localNodePointerPanel", LocalNodePointerPanel);
appModule.directive("remoteNodePointerPanel", RemoteNodePointerPanel);
appModule.controller("DemoPageController", ['$http', '$interval', '$log', '$scope', "OverlayService", "ClientInfoService", "VersionCheckService", "RankingService", "StorageService", DemoPageController]);
appModule.controller("LocalSimulationController", ['$scope', 'LocalSimulationService', LocalSimulationController]);
appModule.controller("ConnectivityTestController", ["$timeout", "$scope", "RTCService", ConnectivityTestController]);

// Disable debug, its very noisy
appModule.config(["$logProvider", function ($logProvider) {
    $logProvider.debugEnabled(false);
}]);

angular.element(document).ready(function () {
    angular.bootstrap(document, ['cyclon-demo']);
});
<commit_msg>Use module.service instead of construct hack<commit_after>'use strict';

var cyclon = require("cyclon.p2p");
var rtc = require("cyclon.p2p-rtc-client");
var rtcComms = require("cyclon.p2p-rtc-comms");
var Utils = require("cyclon.p2p-common");
var StorageService = require("./services/StorageService");

/**
 * RTC Module
 */
var rtcModule = angular.module("cyclon-rtc", []);

rtcModule.service("RTC", ["IceCandidateBatchingSignallingService", "ChannelFactory", rtc.RTC]);
rtcModule.service("ChannelFactory", ["PeerConnectionFactory", "IceCandidateBatchingSignallingService", "$log", rtc.ChannelFactory]);
rtcModule.service("PeerConnectionFactory", ["RTCObjectFactory", "$log", "IceServers", "ChannelStateTimeout", rtc.PeerConnectionFactory]);
rtcModule.service("RTCObjectFactory", ["$log", rtc.AdapterJsRTCObjectFactory]);
rtcModule.factory("AsyncExecService", Utils.asyncExecService);
rtcModule.service("IceCandidateBatchingSignallingService", ["AsyncExecService", "SignallingService", "IceCandidateQuietPeriod", rtc.IceCandidateBatchingSignallingService]);
rtcModule.service("SignallingService", ["SignallingSocket", "$log", "HttpRequestService", "StorageService", rtc.SocketIOSignallingService]);
rtcModule.service("SignallingSocket", ["SignallingServerService", "SocketFactory", "$log", "AsyncExecService", "StorageService", "TimingService", rtc.RedundantSignallingSocket]);
rtcModule.service("HttpRequestService", rtc.HttpRequestService);
rtcModule.factory("StorageService", StorageService);
rtcModule.service("SignallingServerService", ["SignallingServers", rtc.StaticSignallingServerService]);
rtcModule.service("SocketFactory", rtc.SocketFactory);
rtcModule.service("TimingService", rtc.TimingService);

/**
 * RTC Config here
 */
rtcModule.constant("IceServers", [
    // The Google STUN server
    {urls: ['stun:stun.l.google.com:19302']},
    // Turn over TCP on port 80 for networks with totalitarian security regimes
    {urls: ['turn:54.187.115.223:80?transport=tcp'], username: 'cyclonjsuser', credential: 'sP4zBGasNVKI'}
]);
rtcModule.constant("SignallingServers", JSON.parse('/* @echo SIGNALLING_SERVERS */'));
rtcModule.constant("ChannelStateTimeout", 30000);
rtcModule.constant("IceCandidateQuietPeriod", 300);

/**
 * RTC Comms Module
 */
var rtcCommsModule = angular.module("cyclon-rtc-comms", ["cyclon-rtc"]);

rtcCommsModule.service("Comms", ["RTC", "ShuffleStateFactory", "$log", rtcComms.WebRTCComms]);
rtcCommsModule.service("ShuffleStateFactory", ["$log", "AsyncExecService", rtcComms.ShuffleStateFactory]);
rtcCommsModule.service("Bootstrap", ["SignallingSocket", "HttpRequestService", rtcComms.SignallingServerBootstrap]);

/**
 * Demo app module
 */
var LocalSimulationService = require("./services/LocalSimulationService");
var OverlayService = require("./services/OverlayService");
var FrontendVersionService = require("./services/FrontendVersionService");
var LocationProviderService = require("./services/LocationProviderService");
var PlatformDetectionService = require("./services/PlatformDetectionService");
var ClientInfoService = require("./services/ClientInfoService");
var ShuffleStatsService = require("./services/ShuffleStatsService");
var VersionCheckService = require("./services/VersionCheckService");
var RTCService = require("./services/RTCService");
var SessionInformationService = require("./services/SessionInformationService");
var RankingService = require("./services/RankingService");

var DemoPageController = require("./controllers/DemoPageController");
var LocalSimulationController = require("./controllers/LocalSimulationController");
var ConnectivityTestController = require("./controllers/ConnectivityTestController");

var CacheContentsTable = require("./directives/CacheContentsTable");
var NodeInfo = require("./directives/NodeInfo");
var TopNodesTable = require("./directives/TopNodesTable");
var LocalNodePointerPanel = require("./directives/LocalNodePointerPanel");
var RemoteNodePointerPanel = require("./directives/RemoteNodePointerPanel");

var OutgoingSuccessRateFilter = require("./filters/OutgoingSuccessRateFilter");
var IncomingSuccessRateFilter = require("./filters/IncomingSuccessRateFilter");
var IdOrInfoFilter = require("./filters/IdOrInfoFilter");
var RunningTimeFilter = require("./filters/RunningTimeFilter");

var appModule = angular.module("cyclon-demo", ["ui.bootstrap", "cyclon-rtc-comms"]);

appModule.filter("incomingSuccessRate", IncomingSuccessRateFilter);
appModule.filter("outgoingSuccessRate", OutgoingSuccessRateFilter);
appModule.filter("idOrInfo", IdOrInfoFilter);
appModule.filter("runningTime", ["SessionInformationService", RunningTimeFilter]);
appModule.factory("ShuffleStatsService", ["$rootScope", ShuffleStatsService]);
appModule.factory("SessionInformationService", ["StorageService", SessionInformationService]);
appModule.factory("RankingService", ["$rootScope", "$interval", "OverlayService", "SessionInformationService", RankingService]);
appModule.factory("FrontendVersionService", FrontendVersionService);
appModule.factory("OverlayService", ["$log", "$rootScope", "FrontendVersionService",
    "LocationProviderService", "PlatformDetectionService", "ClientInfoService",
    "ShuffleStatsService", "SessionInformationService", "StorageService",
    "Comms", "Bootstrap", "AsyncExecService", OverlayService]);
appModule.factory("LocalSimulationService", ['$rootScope', '$log', '$interval', LocalSimulationService]);
appModule.factory("LocationProviderService", ["$log", "$http", LocationProviderService]);
appModule.factory("PlatformDetectionService", PlatformDetectionService);
appModule.factory("ClientInfoService", ["StorageService", ClientInfoService]);
appModule.factory("VersionCheckService", ["$rootScope", "$interval", "$http", "$log", "FrontendVersionService", VersionCheckService]);
appModule.factory("RTCService", ["RTC", "$log", "$rootScope", RTCService]);

appModule.directive("cacheContentsTable", CacheContentsTable);
appModule.directive("nodeInfo", NodeInfo);
appModule.directive("topNodesTable", TopNodesTable);
appModule.directive("localNodePointerPanel", LocalNodePointerPanel);
appModule.directive("remoteNodePointerPanel", RemoteNodePointerPanel);
appModule.controller("DemoPageController", ['$http', '$interval', '$log', '$scope', "OverlayService", "ClientInfoService", "VersionCheckService", "RankingService", "StorageService", DemoPageController]);
appModule.controller("LocalSimulationController", ['$scope', 'LocalSimulationService', LocalSimulationController]);
appModule.controller("ConnectivityTestController", ["$timeout", "$scope", "RTCService", ConnectivityTestController]);

// Disable debug, its very noisy
appModule.config(["$logProvider", function ($logProvider) {
    $logProvider.debugEnabled(false);
}]);

angular.element(document).ready(function () {
    angular.bootstrap(document, ['cyclon-demo']);
});
<|endoftext|>


<commit_before>import Component from '@ember/component';
import SettingsMenuMixin from 'ghost-admin/mixins/settings-menu-component';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import formatMarkdown from 'ghost-admin/utils/format-markdown';
import moment from 'moment';
import {alias, or} from '@ember/object/computed';
import {computed} from '@ember/object';
import {inject as service} from '@ember/service';

export default Component.extend(SettingsMenuMixin, {
    feature: service(),
    store: service(),
    config: service(),
    ajax: service(),
    ghostPaths: service(),
    notifications: service(),
    slugGenerator: service(),
    session: service(),
    settings: service(),
    ui: service(),

    post: null,

    _showSettingsMenu: false,

    canonicalUrlScratch: alias('post.canonicalUrlScratch'),
    customExcerptScratch: alias('post.customExcerptScratch'),
    codeinjectionFootScratch: alias('post.codeinjectionFootScratch'),
    codeinjectionHeadScratch: alias('post.codeinjectionHeadScratch'),
    metaDescriptionScratch: alias('post.metaDescriptionScratch'),
    metaTitleScratch: alias('post.metaTitleScratch'),
    ogDescriptionScratch: alias('post.ogDescriptionScratch'),
    ogTitleScratch: alias('post.ogTitleScratch'),
    twitterDescriptionScratch: alias('post.twitterDescriptionScratch'),
    twitterTitleScratch: alias('post.twitterTitleScratch'),
    slugValue: boundOneWay('post.slug'),

    seoDescription: or('metaDescriptionScratch', 'customExcerptScratch', 'post.excerpt'),
    facebookDescription: or('ogDescriptionScratch', 'customExcerptScratch', 'seoDescription', 'post.excerpt', 'settings.description', ''),
    facebookImage: or('post.ogImage', 'post.featureImage', 'settings.ogImage', 'settings.coverImage'),
    facebookTitle: or('ogTitleScratch', 'seoTitle'),
    twitterDescription: or('twitterDescriptionScratch', 'customExcerptScratch', 'seoDescription', 'post.excerpt', 'settings.description', ''),
    twitterImage: or('post.twitterImage', 'post.featureImage', 'settings.twitterImage', 'settings.coverImage'),
    twitterTitle: or('twitterTitleScratch', 'seoTitle'),

    showVisibilityInput: or('session.user.isOwner', 'session.user.isAdmin', 'session.user.isEditor'),
    showEmailNewsletter: or('session.user.isOwner', 'session.user.isAdmin', 'session.user.isEditor'),

    seoTitle: computed('metaTitleScratch', 'post.titleScratch', function () {
        return this.metaTitleScratch || this.post.titleScratch || '(Untitled)';
    }),

    seoURL: computed('post.{slug,canonicalUrl}', 'config.blogUrl', function () {
        let blogUrl = this.get('config.blogUrl');
        let seoSlug = this.post.slug || '';
        let canonicalUrl = this.post.canonicalUrl || '';

        if (canonicalUrl) {
            if (canonicalUrl.match(/^\//)) {
                return `${blogUrl}${canonicalUrl}`;
            } else {
                return canonicalUrl;
            }
        } else {
            let seoURL = `${blogUrl}/${seoSlug}`;

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

            return seoURL;
        }
    }),

    didReceiveAttrs() {
        this._super(...arguments);

        // fired when menu is closed
        if (!this.showSettingsMenu && this._showSettingsMenu) {
            let post = this.post;
            let errors = post.get('errors');

            // reset the publish date if it has an error
            if (errors.has('publishedAtBlogDate') || errors.has('publishedAtBlogTime')) {
                post.set('publishedAtBlogTZ', post.get('publishedAtUTC'));
                post.validate({attribute: 'publishedAtBlog'});
            }
        }

        this._showSettingsMenu = this.showSettingsMenu;
    },

    actions: {
        showSubview(subview) {
            this._super(...arguments);
            this.set('subview', subview);
        },

        closeSubview() {
            this._super(...arguments);
            this.set('subview', null);
        },

        discardEnter() {
            return false;
        },

        toggleFeatured() {
            this.toggleProperty('post.featured');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        /**
         * triggered by user manually changing slug
         */
        updateSlug(newSlug) {
            return this.updateSlug
                .perform(newSlug)
                .catch((error) => {
                    this.showError(error);
                    this.post.rollbackAttributes();
                });
        },

        setPublishedAtBlogDate(date) {
            let post = this.post;
            let dateString = moment(date).format('YYYY-MM-DD');

            post.get('errors').remove('publishedAtBlogDate');

            if (post.get('isNew') || date === post.get('publishedAtBlogDate')) {
                post.validate({property: 'publishedAtBlog'});
            } else {
                post.set('publishedAtBlogDate', dateString);
                return this.savePost.perform();
            }
        },

        setPublishedAtBlogTime(time) {
            let post = this.post;

            post.get('errors').remove('publishedAtBlogDate');

            if (post.get('isNew') || time === post.get('publishedAtBlogTime')) {
                post.validate({property: 'publishedAtBlog'});
            } else {
                post.set('publishedAtBlogTime', time);
                return this.savePost.perform();
            }
        },

        setCustomExcerpt(excerpt) {
            let post = this.post;
            let currentExcerpt = post.get('customExcerpt');

            if (excerpt === currentExcerpt) {
                return;
            }

            post.set('customExcerpt', excerpt);

            return post.validate({property: 'customExcerpt'}).then(() => this.savePost.perform());
        },

        setHeaderInjection(code) {
            let post = this.post;
            let currentCode = post.get('codeinjectionHead');

            if (code === currentCode) {
                return;
            }

            post.set('codeinjectionHead', code);

            return post.validate({property: 'codeinjectionHead'}).then(() => this.savePost.perform());
        },

        setFooterInjection(code) {
            let post = this.post;
            let currentCode = post.get('codeinjectionFoot');

            if (code === currentCode) {
                return;
            }

            post.set('codeinjectionFoot', code);

            return post.validate({property: 'codeinjectionFoot'}).then(() => this.savePost.perform());
        },

        setMetaTitle(metaTitle) {
            // Grab the post and current stored meta title
            let post = this.post;
            let currentTitle = post.get('metaTitle');

            // If the title entered matches the stored meta title, do nothing
            if (currentTitle === metaTitle) {
                return;
            }

            // If the title entered is different, set it as the new meta title
            post.set('metaTitle', metaTitle);

            // Make sure the meta title is valid and if so, save it into the post
            return post.validate({property: 'metaTitle'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setMetaDescription(metaDescription) {
            // Grab the post and current stored meta description
            let post = this.post;
            let currentDescription = post.get('metaDescription');

            // If the title entered matches the stored meta title, do nothing
            if (currentDescription === metaDescription) {
                return;
            }

            // If the title entered is different, set it as the new meta title
            post.set('metaDescription', metaDescription);

            // Make sure the meta title is valid and if so, save it into the post
            return post.validate({property: 'metaDescription'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setCanonicalUrl(value) {
            // Grab the post and current stored meta description
            let post = this.post;
            let currentCanonicalUrl = post.canonicalUrl;

            // If the value entered matches the stored value, do nothing
            if (currentCanonicalUrl === value) {
                return;
            }

            // If the value supplied is different, set it as the new value
            post.set('canonicalUrl', value);

            // Make sure the value is valid and if so, save it into the post
            return post.validate({property: 'canonicalUrl'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setOgTitle(ogTitle) {
            // Grab the post and current stored facebook title
            let post = this.post;
            let currentTitle = post.get('ogTitle');

            // If the title entered matches the stored facebook title, do nothing
            if (currentTitle === ogTitle) {
                return;
            }

            // If the title entered is different, set it as the new facebook title
            post.set('ogTitle', ogTitle);

            // Make sure the facebook title is valid and if so, save it into the post
            return post.validate({property: 'ogTitle'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setOgDescription(ogDescription) {
            // Grab the post and current stored facebook description
            let post = this.post;
            let currentDescription = post.get('ogDescription');

            // If the title entered matches the stored facebook description, do nothing
            if (currentDescription === ogDescription) {
                return;
            }

            // If the description entered is different, set it as the new facebook description
            post.set('ogDescription', ogDescription);

            // Make sure the facebook description is valid and if so, save it into the post
            return post.validate({property: 'ogDescription'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setTwitterTitle(twitterTitle) {
            // Grab the post and current stored twitter title
            let post = this.post;
            let currentTitle = post.get('twitterTitle');

            // If the title entered matches the stored twitter title, do nothing
            if (currentTitle === twitterTitle) {
                return;
            }

            // If the title entered is different, set it as the new twitter title
            post.set('twitterTitle', twitterTitle);

            // Make sure the twitter title is valid and if so, save it into the post
            return post.validate({property: 'twitterTitle'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setTwitterDescription(twitterDescription) {
            // Grab the post and current stored twitter description
            let post = this.post;
            let currentDescription = post.get('twitterDescription');

            // If the description entered matches the stored twitter description, do nothing
            if (currentDescription === twitterDescription) {
                return;
            }

            // If the description entered is different, set it as the new twitter description
            post.set('twitterDescription', twitterDescription);

            // Make sure the twitter description is valid and if so, save it into the post
            return post.validate({property: 'twitterDescription'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setCoverImage(image) {
            this.set('post.featureImage', image);

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        clearCoverImage() {
            this.set('post.featureImage', '');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        setOgImage(image) {
            this.set('post.ogImage', image);

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        clearOgImage() {
            this.set('post.ogImage', '');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        setTwitterImage(image) {
            this.set('post.twitterImage', image);

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        clearTwitterImage() {
            this.set('post.twitterImage', '');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        changeAuthors(newAuthors) {
            let post = this.post;

            // return if nothing changed
            if (newAuthors.mapBy('id').join() === post.get('authors').mapBy('id').join()) {
                return;
            }

            post.set('authors', newAuthors);
            post.validate({property: 'authors'});

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                post.rollbackAttributes();
            });
        },

        deletePost() {
            if (this.deletePost) {
                this.deletePost();
            }
        }
    },

    showError(error) {
        // TODO: remove null check once ValidationEngine has been removed
        if (error) {
            this.notifications.showAPIError(error);
        }
    }
});
<commit_msg>Fixed linting error<commit_after>import Component from '@ember/component';
import SettingsMenuMixin from 'ghost-admin/mixins/settings-menu-component';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import moment from 'moment';
import {alias, or} from '@ember/object/computed';
import {computed} from '@ember/object';
import {inject as service} from '@ember/service';

export default Component.extend(SettingsMenuMixin, {
    feature: service(),
    store: service(),
    config: service(),
    ajax: service(),
    ghostPaths: service(),
    notifications: service(),
    slugGenerator: service(),
    session: service(),
    settings: service(),
    ui: service(),

    post: null,

    _showSettingsMenu: false,

    canonicalUrlScratch: alias('post.canonicalUrlScratch'),
    customExcerptScratch: alias('post.customExcerptScratch'),
    codeinjectionFootScratch: alias('post.codeinjectionFootScratch'),
    codeinjectionHeadScratch: alias('post.codeinjectionHeadScratch'),
    metaDescriptionScratch: alias('post.metaDescriptionScratch'),
    metaTitleScratch: alias('post.metaTitleScratch'),
    ogDescriptionScratch: alias('post.ogDescriptionScratch'),
    ogTitleScratch: alias('post.ogTitleScratch'),
    twitterDescriptionScratch: alias('post.twitterDescriptionScratch'),
    twitterTitleScratch: alias('post.twitterTitleScratch'),
    slugValue: boundOneWay('post.slug'),

    seoDescription: or('metaDescriptionScratch', 'customExcerptScratch', 'post.excerpt'),
    facebookDescription: or('ogDescriptionScratch', 'customExcerptScratch', 'seoDescription', 'post.excerpt', 'settings.description', ''),
    facebookImage: or('post.ogImage', 'post.featureImage', 'settings.ogImage', 'settings.coverImage'),
    facebookTitle: or('ogTitleScratch', 'seoTitle'),
    twitterDescription: or('twitterDescriptionScratch', 'customExcerptScratch', 'seoDescription', 'post.excerpt', 'settings.description', ''),
    twitterImage: or('post.twitterImage', 'post.featureImage', 'settings.twitterImage', 'settings.coverImage'),
    twitterTitle: or('twitterTitleScratch', 'seoTitle'),

    showVisibilityInput: or('session.user.isOwner', 'session.user.isAdmin', 'session.user.isEditor'),
    showEmailNewsletter: or('session.user.isOwner', 'session.user.isAdmin', 'session.user.isEditor'),

    seoTitle: computed('metaTitleScratch', 'post.titleScratch', function () {
        return this.metaTitleScratch || this.post.titleScratch || '(Untitled)';
    }),

    seoURL: computed('post.{slug,canonicalUrl}', 'config.blogUrl', function () {
        let blogUrl = this.get('config.blogUrl');
        let seoSlug = this.post.slug || '';
        let canonicalUrl = this.post.canonicalUrl || '';

        if (canonicalUrl) {
            if (canonicalUrl.match(/^\//)) {
                return `${blogUrl}${canonicalUrl}`;
            } else {
                return canonicalUrl;
            }
        } else {
            let seoURL = `${blogUrl}/${seoSlug}`;

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

            return seoURL;
        }
    }),

    didReceiveAttrs() {
        this._super(...arguments);

        // fired when menu is closed
        if (!this.showSettingsMenu && this._showSettingsMenu) {
            let post = this.post;
            let errors = post.get('errors');

            // reset the publish date if it has an error
            if (errors.has('publishedAtBlogDate') || errors.has('publishedAtBlogTime')) {
                post.set('publishedAtBlogTZ', post.get('publishedAtUTC'));
                post.validate({attribute: 'publishedAtBlog'});
            }
        }

        this._showSettingsMenu = this.showSettingsMenu;
    },

    actions: {
        showSubview(subview) {
            this._super(...arguments);
            this.set('subview', subview);
        },

        closeSubview() {
            this._super(...arguments);
            this.set('subview', null);
        },

        discardEnter() {
            return false;
        },

        toggleFeatured() {
            this.toggleProperty('post.featured');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        /**
         * triggered by user manually changing slug
         */
        updateSlug(newSlug) {
            return this.updateSlug
                .perform(newSlug)
                .catch((error) => {
                    this.showError(error);
                    this.post.rollbackAttributes();
                });
        },

        setPublishedAtBlogDate(date) {
            let post = this.post;
            let dateString = moment(date).format('YYYY-MM-DD');

            post.get('errors').remove('publishedAtBlogDate');

            if (post.get('isNew') || date === post.get('publishedAtBlogDate')) {
                post.validate({property: 'publishedAtBlog'});
            } else {
                post.set('publishedAtBlogDate', dateString);
                return this.savePost.perform();
            }
        },

        setPublishedAtBlogTime(time) {
            let post = this.post;

            post.get('errors').remove('publishedAtBlogDate');

            if (post.get('isNew') || time === post.get('publishedAtBlogTime')) {
                post.validate({property: 'publishedAtBlog'});
            } else {
                post.set('publishedAtBlogTime', time);
                return this.savePost.perform();
            }
        },

        setCustomExcerpt(excerpt) {
            let post = this.post;
            let currentExcerpt = post.get('customExcerpt');

            if (excerpt === currentExcerpt) {
                return;
            }

            post.set('customExcerpt', excerpt);

            return post.validate({property: 'customExcerpt'}).then(() => this.savePost.perform());
        },

        setHeaderInjection(code) {
            let post = this.post;
            let currentCode = post.get('codeinjectionHead');

            if (code === currentCode) {
                return;
            }

            post.set('codeinjectionHead', code);

            return post.validate({property: 'codeinjectionHead'}).then(() => this.savePost.perform());
        },

        setFooterInjection(code) {
            let post = this.post;
            let currentCode = post.get('codeinjectionFoot');

            if (code === currentCode) {
                return;
            }

            post.set('codeinjectionFoot', code);

            return post.validate({property: 'codeinjectionFoot'}).then(() => this.savePost.perform());
        },

        setMetaTitle(metaTitle) {
            // Grab the post and current stored meta title
            let post = this.post;
            let currentTitle = post.get('metaTitle');

            // If the title entered matches the stored meta title, do nothing
            if (currentTitle === metaTitle) {
                return;
            }

            // If the title entered is different, set it as the new meta title
            post.set('metaTitle', metaTitle);

            // Make sure the meta title is valid and if so, save it into the post
            return post.validate({property: 'metaTitle'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setMetaDescription(metaDescription) {
            // Grab the post and current stored meta description
            let post = this.post;
            let currentDescription = post.get('metaDescription');

            // If the title entered matches the stored meta title, do nothing
            if (currentDescription === metaDescription) {
                return;
            }

            // If the title entered is different, set it as the new meta title
            post.set('metaDescription', metaDescription);

            // Make sure the meta title is valid and if so, save it into the post
            return post.validate({property: 'metaDescription'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setCanonicalUrl(value) {
            // Grab the post and current stored meta description
            let post = this.post;
            let currentCanonicalUrl = post.canonicalUrl;

            // If the value entered matches the stored value, do nothing
            if (currentCanonicalUrl === value) {
                return;
            }

            // If the value supplied is different, set it as the new value
            post.set('canonicalUrl', value);

            // Make sure the value is valid and if so, save it into the post
            return post.validate({property: 'canonicalUrl'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setOgTitle(ogTitle) {
            // Grab the post and current stored facebook title
            let post = this.post;
            let currentTitle = post.get('ogTitle');

            // If the title entered matches the stored facebook title, do nothing
            if (currentTitle === ogTitle) {
                return;
            }

            // If the title entered is different, set it as the new facebook title
            post.set('ogTitle', ogTitle);

            // Make sure the facebook title is valid and if so, save it into the post
            return post.validate({property: 'ogTitle'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setOgDescription(ogDescription) {
            // Grab the post and current stored facebook description
            let post = this.post;
            let currentDescription = post.get('ogDescription');

            // If the title entered matches the stored facebook description, do nothing
            if (currentDescription === ogDescription) {
                return;
            }

            // If the description entered is different, set it as the new facebook description
            post.set('ogDescription', ogDescription);

            // Make sure the facebook description is valid and if so, save it into the post
            return post.validate({property: 'ogDescription'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setTwitterTitle(twitterTitle) {
            // Grab the post and current stored twitter title
            let post = this.post;
            let currentTitle = post.get('twitterTitle');

            // If the title entered matches the stored twitter title, do nothing
            if (currentTitle === twitterTitle) {
                return;
            }

            // If the title entered is different, set it as the new twitter title
            post.set('twitterTitle', twitterTitle);

            // Make sure the twitter title is valid and if so, save it into the post
            return post.validate({property: 'twitterTitle'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setTwitterDescription(twitterDescription) {
            // Grab the post and current stored twitter description
            let post = this.post;
            let currentDescription = post.get('twitterDescription');

            // If the description entered matches the stored twitter description, do nothing
            if (currentDescription === twitterDescription) {
                return;
            }

            // If the description entered is different, set it as the new twitter description
            post.set('twitterDescription', twitterDescription);

            // Make sure the twitter description is valid and if so, save it into the post
            return post.validate({property: 'twitterDescription'}).then(() => {
                if (post.get('isNew')) {
                    return;
                }

                return this.savePost.perform();
            });
        },

        setCoverImage(image) {
            this.set('post.featureImage', image);

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        clearCoverImage() {
            this.set('post.featureImage', '');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        setOgImage(image) {
            this.set('post.ogImage', image);

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        clearOgImage() {
            this.set('post.ogImage', '');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        setTwitterImage(image) {
            this.set('post.twitterImage', image);

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        clearTwitterImage() {
            this.set('post.twitterImage', '');

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                this.post.rollbackAttributes();
            });
        },

        changeAuthors(newAuthors) {
            let post = this.post;

            // return if nothing changed
            if (newAuthors.mapBy('id').join() === post.get('authors').mapBy('id').join()) {
                return;
            }

            post.set('authors', newAuthors);
            post.validate({property: 'authors'});

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

            this.savePost.perform().catch((error) => {
                this.showError(error);
                post.rollbackAttributes();
            });
        },

        deletePost() {
            if (this.deletePost) {
                this.deletePost();
            }
        }
    },

    showError(error) {
        // TODO: remove null check once ValidationEngine has been removed
        if (error) {
            this.notifications.showAPIError(error);
        }
    }
});
<|endoftext|>


<commit_before>/*
 * This file is a webworker (https://developer.mozilla.org/en/docs/Web/Guide/Performance/Using_web_workers).
 * It acts as an interface between the content script (event_page.js) and NLP modules.
 * The content script initialized the worker with var worker =  new Worker(file);
 * It sends messages to the worker using worker.postMessage() and listens to messages by worker.onmessage() listener.
 * The web worker processes the following messages:
 * initialize: Initialize the NLP modules. Respone is 'initdone' or 'initerror'
 * lineadd: Adds a line to current page (creates a new page if there is none).
 * getkeywords: Gets keywords from the current page. response is 'keywordlist'(list of keywords).
 * resetextractor: Resets the extractor module.
 *
*/

this.importScripts("../bower_components/requirejs/require.js");

require.config({
  baseUrl: "../libnlp/",
  paths: {
    text: "../bower_components/requirejs-text/text"
  }
});

var libnlp;
var queuedMessages = [];
var loaded = false;

var createLine = function (lineId, lineText) {
  return {
    id: lineId,
    text: lineText
  };
};

var pages = [];

var createPage = function (pageId) {
  return{
    lines: [],
    id: pageId,
    graph: null
  };
};

var eventPageMessage = function (type, data) {
  return JSON.stringify({ type: type, data: data }, null, 4);
};

this.onmessage = function (event) {
  var message = JSON.parse(event.data);

  switch (message.type) {
    case "initialize":
      require(['libnlp'], function (result) {
        libnlp = result;
	      if (libnlp)
          loaded = true;

	      if (loaded) {
          while (queuedMessages.length > 0)
            this.onmessage(queuedMessages.unshift());

          postMessage(eventPageMessage("initdone", "initialized nlp modules"));
          console.log('Loaded libnlp module');
	      } else {
	        postMessage(eventPageMessage("initerror", "error loading nlp modules"));
          console.log('error loading libnlp module');
	      }
      });
      break;
    case "lineadd":
      if (loaded === false) {
        queuedMessages[queuedMessages.length] = event;
        break;
      }

      if (typeof pages[message.data.pageId] === 'undefined') {
        pages[message.data.pageId] = createPage(message.data.pageId);
        pages[message.data.pageId].graph =
              libnlp.keyphrase_extractor.getGraph();
      }

      var re = new RegExp(String.fromCharCode(160), "g");
      var text = message.data.text.replace(re,' ');

      pages[message.data.pageId].lines[pages[message.data.pageId].lines.length] =
      createLine(message.data.lineId, text);

      text = text.replace(/\[\w+\]/g, '');

      libnlp.keyphrase_extractor.setGraph(pages[message.data.pageId].graph);
      libnlp.keyphrase_extractor.addText(text);

      break;
    case "getkeywords":
      if (loaded === false) {
        queuedMessages[queuedMessages.length] = event;
        break;
      }

      libnlp.keyphrase_extractor.setGraph(pages[message.data.pageId].graph);
      var result = libnlp.keyphrase_extractor.score();
      var keywords = [];
      var ranges = [];

      /**
       * Prefix characters that are special to RegExp() with a backslash
       *     to render them inert.
       * @param {string} str The string to escape RexExp() special characters in.
       * @return {string} The string with escaped special characters.
       */
      function escapeRegExp(str) {
        // the first parameter is a list of characters that are special to RegExp()
        // $& is the string matched in the first parameter
        // \\ is a single backslash to escape the character
        return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
      }

      for (i = 0; i < result.keyphrases.length; i++) {
        var keyphrase = result.keyphrases[i];
        var index = keywords.length;

        keywords[index] = {
          text: keyphrase,
          groupId: index
        };

        for (j = 0; j < pages[message.data.pageId].lines.length; j++) {
          var lineId = pages[message.data.pageId].lines[j].id;
          var prevLineId;
          var lineText = pages[message.data.pageId].lines[j].text;
          var totalText = lineText;
          var lineStart = 0;

          if (j > 0) {
            prevLineId = pages[message.data.pageId].lines[j - 1].id;
            totalText = pages[message.data.pageId].lines[j - 1].text + lineText;
            lineStart = pages[message.data.pageId].lines[j - 1].text.length;
          }

          var regex = new RegExp(escapeRegExp(keyphrase), "gi");
          while ((search = regex.exec(totalText))) {
            var startLine = lineId;
            var endLine = lineId;
            var startChar = search.index - lineStart;
            var endChar = 0;

            if (search.index < lineStart) {
              if (search.index + keyphrase.length - 1 < lineStart)
                continue;
              else {
                startChar = search.index;
                startLine = prevLineId;
              }
            }

            endChar = (search.index - lineStart) + keyphrase.length - 1;
            ranges[ranges.length] = {
              groupId: keywords[index].groupId,
              start: {
                lineNo: startLine,
                charNo: startChar
              },
              end: {
                lineNo: endLine,
                charNo: endChar
              }
            };
          }
        }
      }

      for (i = 0; i < result.keywords.length; i++) {
        var keyword = result.keywords[i];
        var index = keywords.length;

        keywords[index] = {
          text: keyword,
          groupId: index
        };

        for (j = 0; j < pages[message.data.pageId].lines.length; j++) {
          var lineId = pages[message.data.pageId].lines[j].id;
          var lineText = pages[message.data.pageId].lines[j].text;

          var regex = new RegExp(escapeRegExp(keyword), "gi");
          while ((search = regex.exec(lineText))) {
            ranges[ranges.length] = {
              groupId: keywords[index].groupId,
              start: {
                lineNo: lineId,
                charNo: search.index
              },
              end: {
                lineNo: lineId,
                charNo: search.index + (keyword.length - 1)
              }
            };
          }
        }
      }

      postMessage(eventPageMessage("keywordlist", { keywords: keywords,
        ranges: ranges }));

      break;
    case "resetextractor":
      if (loaded === false) {
        queuedMessages[queuedMessages.length] = event;
        break;
      }

      libnlp.keyphrase_extractor.reset();
      break;
    default:
      console.warn("nlp_worker: " + message.type + " is not recognized");
      break;
  }
};
<commit_msg>nlp_worker: Cache keywords on per page basis.<commit_after>/*
 * This file is a webworker (https://developer.mozilla.org/en/docs/Web/Guide/Performance/Using_web_workers).
 * It acts as an interface between the content script (event_page.js) and NLP modules.
 * The content script initialized the worker with var worker =  new Worker(file);
 * It sends messages to the worker using worker.postMessage() and listens to messages by worker.onmessage() listener.
 * The web worker processes the following messages:
 * initialize: Initialize the NLP modules. Respone is 'initdone' or 'initerror'
 * lineadd: Adds a line to current page (creates a new page if there is none).
 * getkeywords: Gets keywords from the current page. response is 'keywordlist'(list of keywords).
 * resetextractor: Resets the extractor module.
 *
*/

this.importScripts("../bower_components/requirejs/require.js");

require.config({
  baseUrl: "../libnlp/",
  paths: {
    text: "../bower_components/requirejs-text/text"
  }
});

var libnlp;
var queuedMessages = [];
var loaded = false;

var createLine = function (lineId, lineText) {
  return {
    id: lineId,
    text: lineText
  };
};

var pages = [];

var createPage = function (pageId) {
  return{
    lines: [],
    id: pageId,
    graph: null,
    keywords: null,
    ranges: null,
  };
};

var eventPageMessage = function (type, data) {
  return JSON.stringify({ type: type, data: data }, null, 4);
};

this.onmessage = function (event) {
  var message = JSON.parse(event.data);

  switch (message.type) {
    case "initialize":
      require(['libnlp'], function (result) {
        libnlp = result;
	      if (libnlp)
          loaded = true;

	      if (loaded) {
          while (queuedMessages.length > 0)
            this.onmessage(queuedMessages.unshift());

          postMessage(eventPageMessage("initdone", "initialized nlp modules"));
          console.log('Loaded libnlp module');
	      } else {
	        postMessage(eventPageMessage("initerror", "error loading nlp modules"));
          console.log('error loading libnlp module');
	      }
      });
      break;
    case "lineadd":
      if (loaded === false) {
        queuedMessages[queuedMessages.length] = event;
        break;
      }

      if (typeof pages[message.data.pageId] === 'undefined') {
        pages[message.data.pageId] = createPage(message.data.pageId);
        pages[message.data.pageId].graph =
              libnlp.keyphrase_extractor.getGraph();
      }

      var re = new RegExp(String.fromCharCode(160), "g");
      var text = message.data.text.replace(re,' ');

      pages[message.data.pageId].lines[pages[message.data.pageId].lines.length] =
      createLine(message.data.lineId, text);

      text = text.replace(/\[\w+\]/g, '');

      libnlp.keyphrase_extractor.setGraph(pages[message.data.pageId].graph);
      libnlp.keyphrase_extractor.addText(text);

      break;
    case "getkeywords":
      if (loaded === false) {
        queuedMessages[queuedMessages.length] = event;
        break;
      }

      if (pages[message.data.pageId].keywords !== null) {
        postMessage(eventPageMessage("keywordlist", {
          keywords: pages[message.data.pageId].keywords,
          ranges: pages[message.data.pageId].ranges
        }));

        break;
      }

      libnlp.keyphrase_extractor.setGraph(pages[message.data.pageId].graph);
      var result = libnlp.keyphrase_extractor.score();
      var keywords = [];
      var ranges = [];

      /**
       * Prefix characters that are special to RegExp() with a backslash
       *     to render them inert.
       * @param {string} str The string to escape RexExp() special characters in.
       * @return {string} The string with escaped special characters.
       */
      function escapeRegExp(str) {
        // the first parameter is a list of characters that are special to RegExp()
        // $& is the string matched in the first parameter
        // \\ is a single backslash to escape the character
        return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
      }

      for (i = 0; i < result.keyphrases.length; i++) {
        var keyphrase = result.keyphrases[i];
        var index = keywords.length;

        keywords[index] = {
          text: keyphrase,
          groupId: index
        };

        for (j = 0; j < pages[message.data.pageId].lines.length; j++) {
          var lineId = pages[message.data.pageId].lines[j].id;
          var prevLineId;
          var lineText = pages[message.data.pageId].lines[j].text;
          var totalText = lineText;
          var lineStart = 0;

          if (j > 0) {
            prevLineId = pages[message.data.pageId].lines[j - 1].id;
            totalText = pages[message.data.pageId].lines[j - 1].text + lineText;
            lineStart = pages[message.data.pageId].lines[j - 1].text.length;
          }

          var regex = new RegExp(escapeRegExp(keyphrase), "gi");
          while ((search = regex.exec(totalText))) {
            var startLine = lineId;
            var endLine = lineId;
            var startChar = search.index - lineStart;
            var endChar = 0;

            if (search.index < lineStart) {
              if (search.index + keyphrase.length - 1 < lineStart)
                continue;
              else {
                startChar = search.index;
                startLine = prevLineId;
              }
            }

            endChar = (search.index - lineStart) + keyphrase.length - 1;
            ranges[ranges.length] = {
              groupId: keywords[index].groupId,
              start: {
                lineNo: startLine,
                charNo: startChar
              },
              end: {
                lineNo: endLine,
                charNo: endChar
              }
            };
          }
        }
      }

      for (i = 0; i < result.keywords.length; i++) {
        var keyword = result.keywords[i];
        var index = keywords.length;

        keywords[index] = {
          text: keyword,
          groupId: index
        };

        for (j = 0; j < pages[message.data.pageId].lines.length; j++) {
          var lineId = pages[message.data.pageId].lines[j].id;
          var lineText = pages[message.data.pageId].lines[j].text;

          var regex = new RegExp(escapeRegExp(keyword), "gi");
          while ((search = regex.exec(lineText))) {
            ranges[ranges.length] = {
              groupId: keywords[index].groupId,
              start: {
                lineNo: lineId,
                charNo: search.index
              },
              end: {
                lineNo: lineId,
                charNo: search.index + (keyword.length - 1)
              }
            };
          }
        }
      }

      pages[message.data.pageId].keywords = keywords;
      pages[message.data.pageId].ranges = ranges;

      postMessage(eventPageMessage("keywordlist", {
        keywords: keywords,
        ranges: ranges
      }));

      break;
    case "resetextractor":
      if (loaded === false) {
        queuedMessages[queuedMessages.length] = event;
        break;
      }

      libnlp.keyphrase_extractor.reset();
      break;
    default:
      console.warn("nlp_worker: " + message.type + " is not recognized");
      break;
  }
};
<|endoftext|>