<commit_before>import Controller from '@ember/controller';
import envConfig from 'ghost-admin/config/environment';
import {action} from '@ember/object';
import {currencies, getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency-decorators';
import {tracked} from '@glimmer/tracking';

const CURRENCIES = currencies.map((currency) => {
    return {
        value: currency.isoCode.toLowerCase(),
        label: `${currency.isoCode} - ${currency.name}`,
        isoCode: currency.isoCode
    };
});

export default class MembersAccessController extends Controller {
    @service config;
    @service feature;
    @service membersUtils;
    @service settings;
    @service store;
    @service session;

    @tracked showLeavePortalModal = false;
    @tracked showLeaveRouteModal = false;
    @tracked showPortalSettings = false;
    @tracked showStripeConnect = false;
    @tracked showProductModal = false;

    @tracked product = null;
    @tracked products = null;
    @tracked productModel = null;
    @tracked paidSignupRedirect;
    @tracked freeSignupRedirect;
    @tracked stripeMonthlyAmount = 5;
    @tracked stripeYearlyAmount = 50;
    @tracked currency = 'usd';
    @tracked stripePlanError = '';

    @tracked portalPreviewUrl = '';

    portalPreviewGuid = Date.now().valueOf();

    queryParams = ['showPortalSettings'];

    get allCurrencies() {
        return getCurrencyOptions();
    }

    get siteUrl() {
        return this.config.get('blogUrl');
    }

    get selectedCurrency() {
        return CURRENCIES.findBy('value', this.currency);
    }

    get isConnectDisallowed() {
        const siteUrl = this.config.get('blogUrl');
        return envConfig.environment !== 'development' && !/^https:/.test(siteUrl);
    }

    get hasChangedPrices() {
        if (this.product) {
            const monthlyPrice = this.product.get('monthlyPrice');
            const yearlyPrice = this.product.get('yearlyPrice');

            if (monthlyPrice?.amount && parseFloat(this.stripeMonthlyAmount) !== (monthlyPrice.amount / 100)) {
                return true;
            }
            if (yearlyPrice?.amount && parseFloat(this.stripeYearlyAmount) !== (yearlyPrice.amount / 100)) {
                return true;
            }
        }

        return false;
    }

    @action
    setup() {
        this.fetchProducts.perform();
        this.updatePortalPreview();
    }

    leaveRoute(transition) {
        if (this.settings.get('hasDirtyAttributes') || this.hasChangedPrices) {
            transition.abort();
            this.leaveSettingsTransition = transition;
            this.showLeaveRouteModal = true;
        }
    }

    @action
    async confirmLeave() {
        this.settings.rollbackAttributes();
        this.resetPrices();
        this.leaveSettingsTransition.retry();
    }

    @action
    cancelLeave() {
        this.showLeaveRouteModal = false;
        this.leaveSettingsTransition = null;
    }

    @action
    async membersSubscriptionAccessChanged() {
        const oldValue = this.settings.changedAttributes().membersSignupAccess?.[0];

        if (oldValue === 'none') {
            // when saved value is 'none' the server won't inject the portal script
            // to work around that and show the expected portal preview we save and
            // force a refresh
            await this.switchFromNoneTask.perform();
        } else {
            this.updatePortalPreview();
        }
    }

    @action
    setStripePlansCurrency(event) {
        const newCurrency = event.value;
        this.currency = newCurrency;
    }

    @action
    setPaidSignupRedirect(url) {
        this.paidSignupRedirect = url;
    }

    @action
    setFreeSignupRedirect(url) {
        this.freeSignupRedirect = url;
    }

    @action
    validatePaidSignupRedirect() {
        return this._validateSignupRedirect(this.paidSignupRedirect, 'membersPaidSignupRedirect');
    }

    @action
    validateFreeSignupRedirect() {
        return this._validateSignupRedirect(this.freeSignupRedirect, 'membersFreeSignupRedirect');
    }

    @action
    validateStripePlans({updatePortalPreview = true} = {}) {
        this.stripePlanError = undefined;

        try {
            const yearlyAmount = this.stripeYearlyAmount;
            const monthlyAmount = this.stripeMonthlyAmount;
            const symbol = getSymbol(this.currency);
            if (!yearlyAmount || yearlyAmount < 1 || !monthlyAmount || monthlyAmount < 1) {
                throw new TypeError(`Subscription amount must be at least ${symbol}1.00`);
            }

            if (updatePortalPreview) {
                this.updatePortalPreview();
            }
        } catch (err) {
            this.stripePlanError = err.message;
        }
    }

    @action
    openStripeConnect() {
        this.stripeEnabledOnOpen = this.membersUtils.isStripeEnabled;
        this.showStripeConnect = true;
    }

    @action
    async closeStripeConnect() {
        if (this.stripeEnabledOnOpen !== this.membersUtils.isStripeEnabled) {
            await this.saveSettingsTask.perform({forceRefresh: true});
        }
        this.showStripeConnect = false;
    }

    @action
    async openEditProduct(product) {
        this.productModel = product;
        this.showProductModal = true;
    }

    @action
    async openNewProduct() {
        this.productModel = this.store.createRecord('product');
        this.showProductModal = true;
    }

    @action
    closeProductModal() {
        this.showProductModal = false;
    }

    @action
    openPortalSettings() {
        this.saveSettingsTask.perform();
        this.showPortalSettings = true;
    }

    @action
    closePortalSettings() {
        const changedAttributes = this.settings.changedAttributes();
        if (changedAttributes && Object.keys(changedAttributes).length > 0) {
            this.showLeavePortalModal = true;
        } else {
            this.showPortalSettings = false;
            this.updatePortalPreview();
        }
    }

    @action
    async confirmClosePortalSettings() {
        this.settings.rollbackAttributes();
        this.showPortalSettings = false;
        this.showLeavePortalModal = false;
        this.updatePortalPreview();
    }

    @action
    cancelClosePortalSettings() {
        this.showLeavePortalModal = false;
    }

    @action
    updatePortalPreview({forceRefresh} = {forceRefresh: false}) {
        // TODO: can these be worked out from settings in membersUtils?
        const monthlyPrice = this.stripeMonthlyAmount * 100;
        const yearlyPrice = this.stripeYearlyAmount * 100;
        let portalPlans = this.settings.get('portalPlans') || [];

        let isMonthlyChecked = portalPlans.includes('monthly');
        let isYearlyChecked = portalPlans.includes('yearly');

        const newUrl = new URL(this.membersUtils.getPortalPreviewUrl({
            button: false,
            monthlyPrice,
            yearlyPrice,
            currency: this.currency,
            isMonthlyChecked,
            isYearlyChecked,
            portalPlans: null
        }));

        if (forceRefresh) {
            this.portalPreviewGuid = Date.now().valueOf();
        }
        newUrl.searchParams.set('v', this.portalPreviewGuid);

        this.portalPreviewUrl = newUrl;
    }

    @action
    portalPreviewInserted(iframe) {
        this.portalPreviewIframe = iframe;

        if (!this.portalMessageListener) {
            this.portalMessageListener = (event) => {
                // don't resize membership portal preview when events fire in customize portal modal
                if (this.showPortalSettings) {
                    return;
                }

                const resizeEvents = ['portal-ready', 'portal-preview-updated'];
                if (resizeEvents.includes(event.data.type) && event.data.payload?.height) {
                    this.portalPreviewIframe.parentNode.style.height = `${event.data.payload.height}px`;
                }
            };

            window.addEventListener('message', this.portalMessageListener, true);
        }
    }

    @action
    portalPreviewDestroyed() {
        this.portalPreviewIframe = null;

        if (this.portalMessageListener) {
            window.removeEventListener('message', this.portalMessageListener);
        }
    }

    @action
    confirmProductSave() {
        this.updatePortalPreview({forceRefresh: true});
        return this.fetchProducts.perform();
    }

    @task
    *switchFromNoneTask() {
        return yield this.saveSettingsTask.perform({forceRefresh: true});
    }

    setupPortalProduct(product) {
        if (product) {
            const monthlyPrice = product.get('monthlyPrice');
            const yearlyPrice = product.get('yearlyPrice');
            if (monthlyPrice && monthlyPrice.amount) {
                this.stripeMonthlyAmount = (monthlyPrice.amount / 100);
                this.currency = monthlyPrice.currency;
            }
            if (yearlyPrice && yearlyPrice.amount) {
                this.stripeYearlyAmount = (yearlyPrice.amount / 100);
            }
            this.updatePortalPreview();
        }
    }

    @task({drop: true})
    *fetchProducts() {
        this.products = yield this.store.query('product', {include: 'monthly_price,yearly_price,benefits'});
        this.product = this.products.firstObject;
        this.setupPortalProduct(this.product);
    }

    @task({drop: true})
    *saveSettingsTask(options) {
        if (!this.feature.get('multipleProducts')) {
            yield this.validateStripePlans({updatePortalPreview: false});

            if (this.stripePlanError) {
                return;
            }

            if (this.settings.get('errors').length !== 0) {
                return;
            }

            yield this.saveProduct();
            const result = yield this.settings.save();

            this.updatePortalPreview(options);

            return result;
        } else {
            if (this.settings.get('errors').length !== 0) {
                return;
            }
            // When no filer is selected in `Specific tier(s)` option
            if (!this.settings.get('defaultContentVisibility')) {
                return;
            }
            const result = yield this.settings.save();
            this.updatePortalPreview(options);
            return result;
        }
    }

    async saveProduct() {
        const isStripeConnected = this.settings.get('stripeConnectAccountId');
        if (this.product && isStripeConnected) {
            const monthlyAmount = this.stripeMonthlyAmount * 100;
            const yearlyAmount = this.stripeYearlyAmount * 100;

            this.product.set('monthlyPrice', {
                nickname: 'Monthly',
                amount: monthlyAmount,
                active: true,
                currency: this.currency,
                interval: 'month',
                type: 'recurring'
            });
            this.product.set('yearlyPrice', {
                nickname: 'Yearly',
                amount: yearlyAmount,
                active: true,
                currency: this.currency,
                interval: 'year',
                type: 'recurring'
            });

            const savedProduct = await this.product.save();
            return savedProduct;
        }
    }

    resetPrices() {
        const monthlyPrice = this.product.get('monthlyPrice');
        const yearlyPrice = this.product.get('yearlyPrice');

        this.stripeMonthlyAmount = monthlyPrice ? (monthlyPrice.amount / 100) : 5;
        this.stripeYearlyAmount = yearlyPrice ? (yearlyPrice.amount / 100) : 50;
    }

    reset() {
        this.showLeaveRouteModal = false;
        this.showLeavePortalModal = false;
        this.showPortalSettings = false;
    }

    _validateSignupRedirect(url, type) {
        const siteUrl = this.config.get('blogUrl');
        let errMessage = `Please enter a valid URL`;
        this.settings.get('errors').remove(type);
        this.settings.get('hasValidated').removeObject(type);

        if (url === null) {
            this.settings.get('errors').add(type, errMessage);
            this.settings.get('hasValidated').pushObject(type);
            return false;
        }

        if (url === undefined) {
            // Not initialised
            return;
        }

        if (url.href.startsWith(siteUrl)) {
            const path = url.href.replace(siteUrl, '');
            this.settings.set(type, path);
        } else {
            this.settings.set(type, url.href);
        }
    }
}
<commit_msg>Fixed preview iframe communication error messages<commit_after>import Controller from '@ember/controller';
import envConfig from 'ghost-admin/config/environment';
import {action} from '@ember/object';
import {currencies, getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency-decorators';
import {tracked} from '@glimmer/tracking';

const CURRENCIES = currencies.map((currency) => {
    return {
        value: currency.isoCode.toLowerCase(),
        label: `${currency.isoCode} - ${currency.name}`,
        isoCode: currency.isoCode
    };
});

export default class MembersAccessController extends Controller {
    @service config;
    @service feature;
    @service membersUtils;
    @service settings;
    @service store;
    @service session;

    @tracked showLeavePortalModal = false;
    @tracked showLeaveRouteModal = false;
    @tracked showPortalSettings = false;
    @tracked showStripeConnect = false;
    @tracked showProductModal = false;

    @tracked product = null;
    @tracked products = null;
    @tracked productModel = null;
    @tracked paidSignupRedirect;
    @tracked freeSignupRedirect;
    @tracked stripeMonthlyAmount = 5;
    @tracked stripeYearlyAmount = 50;
    @tracked currency = 'usd';
    @tracked stripePlanError = '';

    @tracked portalPreviewUrl = '';

    portalPreviewGuid = Date.now().valueOf();

    queryParams = ['showPortalSettings'];

    get allCurrencies() {
        return getCurrencyOptions();
    }

    get siteUrl() {
        return this.config.get('blogUrl');
    }

    get selectedCurrency() {
        return CURRENCIES.findBy('value', this.currency);
    }

    get isConnectDisallowed() {
        const siteUrl = this.config.get('blogUrl');
        return envConfig.environment !== 'development' && !/^https:/.test(siteUrl);
    }

    get hasChangedPrices() {
        if (this.product) {
            const monthlyPrice = this.product.get('monthlyPrice');
            const yearlyPrice = this.product.get('yearlyPrice');

            if (monthlyPrice?.amount && parseFloat(this.stripeMonthlyAmount) !== (monthlyPrice.amount / 100)) {
                return true;
            }
            if (yearlyPrice?.amount && parseFloat(this.stripeYearlyAmount) !== (yearlyPrice.amount / 100)) {
                return true;
            }
        }

        return false;
    }

    @action
    setup() {
        this.fetchProducts.perform();
        this.updatePortalPreview();
    }

    leaveRoute(transition) {
        if (this.settings.get('hasDirtyAttributes') || this.hasChangedPrices) {
            transition.abort();
            this.leaveSettingsTransition = transition;
            this.showLeaveRouteModal = true;
        }
    }

    @action
    async confirmLeave() {
        this.settings.rollbackAttributes();
        this.resetPrices();
        this.leaveSettingsTransition.retry();
    }

    @action
    cancelLeave() {
        this.showLeaveRouteModal = false;
        this.leaveSettingsTransition = null;
    }

    @action
    async membersSubscriptionAccessChanged() {
        const oldValue = this.settings.changedAttributes().membersSignupAccess?.[0];

        if (oldValue === 'none') {
            // when saved value is 'none' the server won't inject the portal script
            // to work around that and show the expected portal preview we save and
            // force a refresh
            await this.switchFromNoneTask.perform();
        } else {
            this.updatePortalPreview();
        }
    }

    @action
    setStripePlansCurrency(event) {
        const newCurrency = event.value;
        this.currency = newCurrency;
    }

    @action
    setPaidSignupRedirect(url) {
        this.paidSignupRedirect = url;
    }

    @action
    setFreeSignupRedirect(url) {
        this.freeSignupRedirect = url;
    }

    @action
    validatePaidSignupRedirect() {
        return this._validateSignupRedirect(this.paidSignupRedirect, 'membersPaidSignupRedirect');
    }

    @action
    validateFreeSignupRedirect() {
        return this._validateSignupRedirect(this.freeSignupRedirect, 'membersFreeSignupRedirect');
    }

    @action
    validateStripePlans({updatePortalPreview = true} = {}) {
        this.stripePlanError = undefined;

        try {
            const yearlyAmount = this.stripeYearlyAmount;
            const monthlyAmount = this.stripeMonthlyAmount;
            const symbol = getSymbol(this.currency);
            if (!yearlyAmount || yearlyAmount < 1 || !monthlyAmount || monthlyAmount < 1) {
                throw new TypeError(`Subscription amount must be at least ${symbol}1.00`);
            }

            if (updatePortalPreview) {
                this.updatePortalPreview();
            }
        } catch (err) {
            this.stripePlanError = err.message;
        }
    }

    @action
    openStripeConnect() {
        this.stripeEnabledOnOpen = this.membersUtils.isStripeEnabled;
        this.showStripeConnect = true;
    }

    @action
    async closeStripeConnect() {
        if (this.stripeEnabledOnOpen !== this.membersUtils.isStripeEnabled) {
            await this.saveSettingsTask.perform({forceRefresh: true});
        }
        this.showStripeConnect = false;
    }

    @action
    async openEditProduct(product) {
        this.productModel = product;
        this.showProductModal = true;
    }

    @action
    async openNewProduct() {
        this.productModel = this.store.createRecord('product');
        this.showProductModal = true;
    }

    @action
    closeProductModal() {
        this.showProductModal = false;
    }

    @action
    openPortalSettings() {
        this.saveSettingsTask.perform();
        this.showPortalSettings = true;
    }

    @action
    closePortalSettings() {
        const changedAttributes = this.settings.changedAttributes();
        if (changedAttributes && Object.keys(changedAttributes).length > 0) {
            this.showLeavePortalModal = true;
        } else {
            this.showPortalSettings = false;
            this.updatePortalPreview();
        }
    }

    @action
    async confirmClosePortalSettings() {
        this.settings.rollbackAttributes();
        this.showPortalSettings = false;
        this.showLeavePortalModal = false;
        this.updatePortalPreview();
    }

    @action
    cancelClosePortalSettings() {
        this.showLeavePortalModal = false;
    }

    @action
    updatePortalPreview({forceRefresh} = {forceRefresh: false}) {
        // TODO: can these be worked out from settings in membersUtils?
        const monthlyPrice = this.stripeMonthlyAmount * 100;
        const yearlyPrice = this.stripeYearlyAmount * 100;
        let portalPlans = this.settings.get('portalPlans') || [];

        let isMonthlyChecked = portalPlans.includes('monthly');
        let isYearlyChecked = portalPlans.includes('yearly');

        const newUrl = new URL(this.membersUtils.getPortalPreviewUrl({
            button: false,
            monthlyPrice,
            yearlyPrice,
            currency: this.currency,
            isMonthlyChecked,
            isYearlyChecked,
            portalPlans: null
        }));

        if (forceRefresh) {
            this.portalPreviewGuid = Date.now().valueOf();
        }
        newUrl.searchParams.set('v', this.portalPreviewGuid);

        this.portalPreviewUrl = newUrl;
    }

    @action
    portalPreviewInserted(iframe) {
        this.portalPreviewIframe = iframe;

        if (!this.portalMessageListener) {
            this.portalMessageListener = (event) => {
                // don't resize membership portal preview when events fire in customize portal modal
                if (this.showPortalSettings) {
                    return;
                }

                const resizeEvents = ['portal-ready', 'portal-preview-updated'];
                if (resizeEvents.includes(event.data.type) && event.data.payload?.height && this.portalPreviewIframe.parentNode) {
                    this.portalPreviewIframe.parentNode.style.height = `${event.data.payload.height}px`;
                }
            };

            window.addEventListener('message', this.portalMessageListener, true);
        }
    }

    @action
    portalPreviewDestroyed() {
        this.portalPreviewIframe = null;

        if (this.portalMessageListener) {
            window.removeEventListener('message', this.portalMessageListener);
        }
    }

    @action
    confirmProductSave() {
        this.updatePortalPreview({forceRefresh: true});
        return this.fetchProducts.perform();
    }

    @task
    *switchFromNoneTask() {
        return yield this.saveSettingsTask.perform({forceRefresh: true});
    }

    setupPortalProduct(product) {
        if (product) {
            const monthlyPrice = product.get('monthlyPrice');
            const yearlyPrice = product.get('yearlyPrice');
            if (monthlyPrice && monthlyPrice.amount) {
                this.stripeMonthlyAmount = (monthlyPrice.amount / 100);
                this.currency = monthlyPrice.currency;
            }
            if (yearlyPrice && yearlyPrice.amount) {
                this.stripeYearlyAmount = (yearlyPrice.amount / 100);
            }
            this.updatePortalPreview();
        }
    }

    @task({drop: true})
    *fetchProducts() {
        this.products = yield this.store.query('product', {include: 'monthly_price,yearly_price,benefits'});
        this.product = this.products.firstObject;
        this.setupPortalProduct(this.product);
    }

    @task({drop: true})
    *saveSettingsTask(options) {
        if (!this.feature.get('multipleProducts')) {
            yield this.validateStripePlans({updatePortalPreview: false});

            if (this.stripePlanError) {
                return;
            }

            if (this.settings.get('errors').length !== 0) {
                return;
            }

            yield this.saveProduct();
            const result = yield this.settings.save();

            this.updatePortalPreview(options);

            return result;
        } else {
            if (this.settings.get('errors').length !== 0) {
                return;
            }
            // When no filer is selected in `Specific tier(s)` option
            if (!this.settings.get('defaultContentVisibility')) {
                return;
            }
            const result = yield this.settings.save();
            this.updatePortalPreview(options);
            return result;
        }
    }

    async saveProduct() {
        const isStripeConnected = this.settings.get('stripeConnectAccountId');
        if (this.product && isStripeConnected) {
            const monthlyAmount = this.stripeMonthlyAmount * 100;
            const yearlyAmount = this.stripeYearlyAmount * 100;

            this.product.set('monthlyPrice', {
                nickname: 'Monthly',
                amount: monthlyAmount,
                active: true,
                currency: this.currency,
                interval: 'month',
                type: 'recurring'
            });
            this.product.set('yearlyPrice', {
                nickname: 'Yearly',
                amount: yearlyAmount,
                active: true,
                currency: this.currency,
                interval: 'year',
                type: 'recurring'
            });

            const savedProduct = await this.product.save();
            return savedProduct;
        }
    }

    resetPrices() {
        const monthlyPrice = this.product.get('monthlyPrice');
        const yearlyPrice = this.product.get('yearlyPrice');

        this.stripeMonthlyAmount = monthlyPrice ? (monthlyPrice.amount / 100) : 5;
        this.stripeYearlyAmount = yearlyPrice ? (yearlyPrice.amount / 100) : 50;
    }

    reset() {
        this.showLeaveRouteModal = false;
        this.showLeavePortalModal = false;
        this.showPortalSettings = false;
    }

    _validateSignupRedirect(url, type) {
        const siteUrl = this.config.get('blogUrl');
        let errMessage = `Please enter a valid URL`;
        this.settings.get('errors').remove(type);
        this.settings.get('hasValidated').removeObject(type);

        if (url === null) {
            this.settings.get('errors').add(type, errMessage);
            this.settings.get('hasValidated').pushObject(type);
            return false;
        }

        if (url === undefined) {
            // Not initialised
            return;
        }

        if (url.href.startsWith(siteUrl)) {
            const path = url.href.replace(siteUrl, '');
            this.settings.set(type, path);
        } else {
            this.settings.set(type, url.href);
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// @ts-nocheck
// TODO(crbug.com/1011811): Enable TypeScript compiler checks

import * as Root from '../../root/root.js';
import * as ThemeSupport from '../../theme_support/theme_support.js';

/**
 * @param {!Node} node
 * @param {string} cssFile
 * @param {!{enableLegacyPatching:boolean}} options
 * @suppressGlobalPropertiesCheck
 */
export function appendStyle(node, cssFile, options = {
  enableLegacyPatching: false
}) {
  const content = Root.Runtime.cachedResources.get(cssFile) || '';
  if (!content) {
    console.error(cssFile + ' not preloaded. Check module.json');
  }
  let styleElement = createElement('style');
  styleElement.textContent = content;
  node.appendChild(styleElement);

  /**
   * We are incrementally removing patching support in favour of CSS variables for supporting dark mode.
   * See https://docs.google.com/document/d/1QrSSRsJRzaQBY3zz73ZL84bTcFUV60yMtE5cuu6ED14 for details.
   */
  if (options.enableLegacyPatching) {
    const themeStyleSheet = ThemeSupport.ThemeSupport.instance().themeStyleSheet(cssFile, content);
    if (themeStyleSheet) {
      styleElement = createElement('style');
      styleElement.textContent = themeStyleSheet + '\n' + Root.Runtime.Runtime.resolveSourceURL(cssFile + '.theme');
      node.appendChild(styleElement);
    }
  }
}
<commit_msg>TypeScriptify append-style.js<commit_after>// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as Root from '../../root/root.js';
import * as ThemeSupport from '../../theme_support/theme_support.js';

/**
 * @param {!Node} node
 * @param {string} cssFile
 * @param {!{enableLegacyPatching:boolean}} options
 * @suppressGlobalPropertiesCheck
 */
export function appendStyle(node, cssFile, options = {
  enableLegacyPatching: false
}) {
  const content = Root.Runtime.cachedResources.get(cssFile) || '';
  if (!content) {
    console.error(cssFile + ' not preloaded. Check module.json');
  }
  let styleElement = document.createElement('style');
  styleElement.textContent = content;
  node.appendChild(styleElement);

  /**
   * We are incrementally removing patching support in favour of CSS variables for supporting dark mode.
   * See https://docs.google.com/document/d/1QrSSRsJRzaQBY3zz73ZL84bTcFUV60yMtE5cuu6ED14 for details.
   */
  if (options.enableLegacyPatching) {
    const themeStyleSheet = ThemeSupport.ThemeSupport.instance().themeStyleSheet(cssFile, content);
    if (themeStyleSheet) {
      styleElement = document.createElement('style');
      styleElement.textContent = themeStyleSheet + '\n' + Root.Runtime.Runtime.resolveSourceURL(cssFile + '.theme');
      node.appendChild(styleElement);
    }
  }
}
<|endoftext|>


<commit_before>var controllers = require('../../core/controllers'),
    moment = require('moment'),
    _ = require('lodash');

controllers.controller('chatController', ['$scope', 'apiService', 'dataService', 'tokenService', chatController]);

function chatController ($scope, apiService, dataService, tokenService) {

    $scope.messages = {};
    $scope.scrollOptions = {
        static: true
    };

    apiService.subscribe('club.cottonway.chat.on_new_room', onNewRoom);

    apiService.subscribe('club.cottonway.chat.on_new_message', onNewMessage);

    apiService.subscribe('club.cottonway.chat.on_new_peer', onNewPeer);

    apiService.call('club.cottonway.chat.rooms')
        .then(function (response) {

            var peerIds = [],
                rooms = [];

            _.each(response.rooms, function (room) {

                rooms.push(room);
                $scope.messages[room.id] = [];
                peerIds = _.union(peerIds, room.peerIds);

                apiService.call('club.cottonway.chat.messages', [
                    room.id,
                    null
                ])
                    .then(function(list) {

                        $scope.messages[room.id] = _.chain(list.messages)
                            .map(function (item) {
                                return _.extend({
                                    timeFormatted: moment(new Date(item.time)).format('HH:mm')
                                }, item);
                            })
                            .sortBy(function (item) {
                                return new Date(item.time).getTime();
                            })
                            .value();
                    });
            });

            apiService.call('club.cottonway.chat.peers', [
                peerIds
            ])
                .then(function (data) {

                    $scope.peers = _.reduce(data.peers, function (memo, peer) {

                        memo[peer.id] = peer;
                        return memo;
                    }, {});
                });

            $scope.rooms = _.sortBy(rooms, function (item) {
                return +item.isPrivate;
            });
            $scope.currentRoom = _.first($scope.rooms);
        });

    $scope.startRoom = function (peerId) {

        var room = _.find($scope.rooms, function (item) {
            return item.isPrivate && item.peerIds[0] === peerId;
        });

        if (room) {

            $scope.setCurrentRoom(room);
        } else {

            apiService.call('club.cottonway.chat.start_room', [
                [peerId]
            ])
                .then(function (response) {

                    $scope.rooms.push(response.room);
                    $scope.messages[response.room.id] = [];
                    $scope.setCurrentRoom(response.room);
                });
        }
    };

    $scope.sendMessage = function (text) {

        if (text) {
            if (dataService('needSign').var) {
                tokenService.signMessage(dataService('certId').var, text)
                    .then(function (signature) {
                        send(text, signature);
                    });
            } else {
                send(text, null);
            }
        }

        function send (text, signature) {

            var options = {};

            if (signature) {
                options.signature = signature;
            }

            apiService.call('club.cottonway.chat.send_message', [
                $scope.currentRoom.id,
                text
            ], options)
                .then(function (response) {

                    $scope.scrollOptions.static = false;
                    $scope.message = '';
                    $scope.messages[$scope.currentRoom.id].push(response.message);
                });
        }
    };

    $scope.setCurrentRoom = function (room) {

        room.newCount = 0;
        $scope.scrollOptions.static = true;
        $scope.currentRoom = room;
    };

    $scope.roomTitle = function (room) {

        return room.isPrivate && !_.isUndefined($scope.peers) ?
            $scope.peers[room.peerIds[0]].name :
            room.title;
    };

    $scope.getPeer = function (message) {

        if (message.sender === dataService('api').user.id) {
            return dataService('api').user;
        } else {
            return $scope.peers[message.sender];
        }
    };

    function onNewRoom(args) {

        $scope.rooms.push(args[0]);
        $scope.messages[args[0].id] = [];
    }

    function onNewMessage(args) {

        var roomId = args[0].roomId,
            room = _.find($scope.rooms, {id: roomId});

        if (room) {
            room.newCount = room.newCount || 0;
            room.newCount += +($scope.currentRoom.id !== roomId);
        }

        $scope.scrollOptions.static = false;

        !_.isUndefined($scope.messages[roomId]) && $scope.messages[roomId].push(args[0]);
    }

    function onNewPeer(args) {

        var roomId = args[0].roomId,
            peer = args[0].peer,
            room = _.find($scope.rooms, {id: roomId});

        $scope.peers[peer.id] = peer;

        room && room.peerIds.push(peer.id);
    }
}
<commit_msg>time fix<commit_after>var controllers = require('../../core/controllers'),
    moment = require('moment'),
    _ = require('lodash');

controllers.controller('chatController', ['$scope', 'apiService', 'dataService', 'tokenService', chatController]);

function chatController ($scope, apiService, dataService, tokenService) {

    $scope.messages = {};
    $scope.scrollOptions = {
        static: true
    };

    apiService.subscribe('club.cottonway.chat.on_new_room', onNewRoom);

    apiService.subscribe('club.cottonway.chat.on_new_message', onNewMessage);

    apiService.subscribe('club.cottonway.chat.on_new_peer', onNewPeer);

    apiService.call('club.cottonway.chat.rooms')
        .then(function (response) {

            var peerIds = [],
                rooms = [];

            _.each(response.rooms, function (room) {

                rooms.push(room);
                $scope.messages[room.id] = [];
                peerIds = _.union(peerIds, room.peerIds);

                apiService.call('club.cottonway.chat.messages', [
                    room.id,
                    null
                ])
                    .then(function(list) {

                        $scope.messages[room.id] = _.chain(list.messages)
                            .map(function (item) {
                                formatTime(item);
                                return item;
                            })
                            .sortBy(function (item) {
                                return new Date(item.time).getTime();
                            })
                            .value();
                    });
            });

            apiService.call('club.cottonway.chat.peers', [
                peerIds
            ])
                .then(function (data) {

                    $scope.peers = _.reduce(data.peers, function (memo, peer) {

                        memo[peer.id] = peer;
                        return memo;
                    }, {});
                });

            $scope.rooms = _.sortBy(rooms, function (item) {
                return +item.isPrivate;
            });
            $scope.currentRoom = _.first($scope.rooms);
        });

    $scope.startRoom = function (peerId) {

        var room = _.find($scope.rooms, function (item) {
            return item.isPrivate && item.peerIds[0] === peerId;
        });

        if (room) {

            $scope.setCurrentRoom(room);
        } else {

            apiService.call('club.cottonway.chat.start_room', [
                [peerId]
            ])
                .then(function (response) {

                    $scope.rooms.push(response.room);
                    $scope.messages[response.room.id] = [];
                    $scope.setCurrentRoom(response.room);
                });
        }
    };

    $scope.sendMessage = function (text) {

        if (text) {
            if (dataService('needSign').var) {
                tokenService.signMessage(dataService('certId').var, text)
                    .then(function (signature) {
                        send(text, signature);
                    });
            } else {
                send(text, null);
            }
        }

        function send (text, signature) {

            var options = {};

            if (signature) {
                options.signature = signature;
            }

            apiService.call('club.cottonway.chat.send_message', [
                $scope.currentRoom.id,
                text
            ], options)
                .then(function (response) {

                    $scope.scrollOptions.static = false;
                    $scope.message = '';
                    formatTime(response.message);
                    $scope.messages[$scope.currentRoom.id].push(response.message);
                });
        }
    };

    $scope.setCurrentRoom = function (room) {

        room.newCount = 0;
        $scope.scrollOptions.static = true;
        $scope.currentRoom = room;
    };

    $scope.roomTitle = function (room) {

        return room.isPrivate && !_.isUndefined($scope.peers) ?
            $scope.peers[room.peerIds[0]].name :
            room.title;
    };

    $scope.getPeer = function (message) {

        if (message.sender === dataService('api').user.id) {
            return dataService('api').user;
        } else {
            return $scope.peers[message.sender];
        }
    };

    function onNewRoom(args) {

        $scope.rooms.push(args[0]);
        $scope.messages[args[0].id] = [];
    }

    function onNewMessage(args) {

        var roomId = args[0].roomId,
            room = _.find($scope.rooms, {id: roomId});

        if (room) {
            room.newCount = room.newCount || 0;
            room.newCount += +($scope.currentRoom.id !== roomId);
        }

        $scope.scrollOptions.static = false;

        formatTime(args[0]);
        !_.isUndefined($scope.messages[roomId]) && $scope.messages[roomId].push(args[0]);
    }

    function onNewPeer(args) {

        var roomId = args[0].roomId,
            peer = args[0].peer,
            room = _.find($scope.rooms, {id: roomId});

        $scope.peers[peer.id] = peer;

        room && room.peerIds.push(peer.id);
    }

    function formatTime (message) {
        message.timeFormatted = moment(new Date(message.time)).format('HH:mm');
    }
}
<|endoftext|>


<commit_before>import api from "./api";
import dispatcher from "./dispatcher";
import hashParams from "./hashParams";
import session from "../util/session";

let id = 0;

function sortItems(a, b) {
    return (a.sort < b.sort ? 1 : a.sort > b.sort ? -1 : 0);
}

export default {
    showMessage(state, action, message, type, autoDismiss) {
        const notification = {
            content: message,
            type: type || "info",
            id: id++
        };
        if (autoDismiss) {
            setTimeout(() => {
                dispatcher.trigger("dismiss:notification", notification.id);
            }, 2000);
        }
        const notifications = state.model.notifications;
        notifications.push(notification);
        action({notifications});
    },
    loadItems(state, action) {
        if (typeof state.model.activeProject === "undefined") {
            return dispatcher.trigger("app:error", new Error("active project is not set"));
        }
        const cacheKey = `data.items.${state.model.activeProject.id}`;
        if (session.getCache(cacheKey)) {
            action(session.getCache(cacheKey));
        }
        // todo: ensure model.activeProject is set
        api.tasks.all(state.model)
            .then(res => {
                if (res.status !== 200) {
                    throw new Error("res.status is not OK:200 but " + res.status);
                }
                return res;
            })
            .then(res => res.text())
            .then(body => JSON.parse(body))
            .then(json => {
                const items = json.map(i => {
                    i.id = i._id;
                    return i;
                })
                .sort(sortItems);
                return items;
            })
            .then(items => action({items}))
            .then(items => session.setCache(cacheKey, items))
            .then(() => dispatcher.trigger("loaded:items"))
            .catch(e => dispatcher.trigger("app:error", e));
    },
    loadProjects(state, action) {
        api.projects.all(state.model)
            .then(res => {
                if (res.status !== 200) {
                    throw new Error("res.status is not OK:200 but " + res.status);
                }
                return res;
            })
            .then(res => res.text())
            .then(body => JSON.parse(body))
            .then(json => {
                const projects = json.map(i => {
                    i.id = i._id;
                    return i;
                })
                return projects;
            })
            .then(projects => action({projects}))
            .then(() => dispatcher.trigger("loaded:projects"))
            .catch(e => dispatcher.trigger("app:error", e));
    },
    selectItem(state, action, activeItemId, editing=false) {
        if (!activeItemId) {
            return action({activeItem, editing});
        }
        const activeItem = state.model.items.filter(i => i.id === activeItemId)[0];
        if (activeItem) {
            hashParams.set({item: activeItem.id});
            action({activeItem, editing});
        } else {
            dispatcher.trigger("app:error");
        }
    },
    checkItem(state, action, itemId, checked) {
        const items = state.model.items.map(i => {
            if (i.id === itemId) {
                i.checked = checked
                api.tasks.update({id: i.id, checked}).catch(e => dispatcher.trigger("app:error", e));
            }
            return i;
        });
        action({items});
    },
    saveItem(state, action, item, sort) {
        const newItems = state.model.items.filter(i => i.id !== item.id);

        newItems.unshift(item);
        const items = newItems
            .sort(sortItems)
            .map((item, index) => {
                if (!sort) {
                    return item;
                }
                item.sort = newItems.length - index - 1;
                return item;
            });
        action({items});
        if (!sort) {
            dispatcher.trigger("select:item", item.id);
            api.tasks.updateAndThrow(item).catch(e => dispatcher.trigger("app:error", e));
        } else {
            api.tasks.updateAll(items).catch(e => dispatcher.trigger("app:error", e));
        }
    },
    editItem(state, action) {
        action({editing: true, preview: false});
    },
    previewItem(state, action) {
        action({preview: true});
    },
    cancelPreviewItem(state, action) {
        action({preview: false});
    },
    cancelItem(state, action) {
        action({editing: false});
    },
    createProject(state, action, project) {
        const {projects} = state.model;
        api.projects.create(project)
            .then(res => {
                if (res.status !== 201) {
                    throw new Error("res.status is not OK:201 but " + res.status);
                }
                return res;
            })
            .then(res => res.text())
            .then(body => JSON.parse(body))
            .then(json => {
                json.id = json._id;
                projects.unshift(json);
                action({projects});
                dispatcher.trigger("select:project", json.id);
            })
            .catch(e => dispatcher.trigger("dispatcher:error", e));
    },
    newItem(state, action) {
        const itemId = id++;
        const {model} = state;
        const {items} = model;
        const sort = (items.length ? Math.max(...items.map(i => i.sort)) : 0) + 1; // todo ?= items.length
        const project = model.activeProject.id;
        const item = {title: "New Item", description: "", sort, project};
        api.tasks.create(item, model)
            .then(res => {
                if (res.status !== 201) {
                    throw new Error("res.status is not OK:201 but " + res.status);
                }
                return res;
            })
            .then(res => res.text())
            .then(body => JSON.parse(body))
            .then(json => {
                json.id = json._id;
                dispatcher.trigger("save:item-detail", json, true);
                dispatcher.trigger("select:item", json.id, true);
            })
            .catch(e => dispatcher.trigger("dispatcher:error", e));
    },
    deleteItem(state, action, id) {
        const items = state.model.items.filter(i => i.id !== id);
        action({items});
        action({activeItem: null});
        api.tasks.remove(id)
            .then(res => {
                if (res.status !== 204) {
                    throw new Error("res.status is not OK:204 but " + res.status);
                }
                return res;
            })
            .catch(e => dispatcher.trigger("app:error", e));
    },
    deleteProject(state, action, id) {
        const projects = state.model.projects.filter(p => p.id !== id);
        action({projects});
        action({activeProject: null});
        action({activeItem: null});
        api.projects.remove(id)
            .then(res => {
                if (res.status !== 204) {
                    throw new Error("res.status is not OK:204 but " + res.status);
                }
                return res;
            })
            .catch(e => dispatcher.trigger("app:error", e));
    },
    clearChecked(state, action) {
        state.model.items
            .filter(i => i.checked)
            .forEach(i => {
                api.tasks.remove(i.id)
                    .then(res => {
                        if (res.status !== 204) {
                            throw new Error("res.status is not OK:204 but " + res.status);
                        }
                        return res;
                    })
                    .catch(e => dispatcher.trigger("app:error", e));
            });
        const items = state.model.items.filter(i => !(i.checked));
        if (state.model.activeItem && state.model.activeItem.checked) {
            action({activeItem: null});
        }
        action({items});
    },
    dismissNotification(state, action, id) {
        const notifications = state.model.notifications.filter(n => n.id !== id);
        action({notifications});
    },
    selectProject(state, action, activeProjectId) {
        const activeProject = state.model.projects.filter(p => p.id === activeProjectId)[0];

        if (activeProject) {
            action({
                items: [],
                activeItem: null,
                activeProject,
                editing: false
            });
            hashParams.set({project: activeProject.id, item: null});
            dispatcher.trigger("load:items");
        } else {
            dispatcher.trigger("app:error");
        }
    },
    changeFilter(state, action, filterName, filterState) {
        const {filter} = state.model;
        filter[filterName] = filterState;
        action({filter, activeItem: null});
        dispatcher.trigger("load:items");
    },
    logout(state, action) {
        api.user.logout().catch(e => true);
        session.clear();
        dispatcher.trigger("goto:page", "login");
        dispatcher.trigger("show:message", "logged out.", "info", true);
    },
    gotoPage(state, action, pageName) {
        hashParams.set({page: pageName});
        action({pageName});
    },
    gotoUrl(state, action, url) {
        window.location = url;
    },
    startApp(state, action) {
        const {project, item} = hashParams.get();
        if (project) {
            dispatcher.once("loaded:projects", () => {
                if (state.model.projects.filter(it => {
                    return it.id === project;
                }).length) {
                    dispatcher.trigger("select:project", project);
                } else {
                    dispatcher.trigger("show:message", "Selected Project does not exist.");
                }
            });
        }

        if (item) {
            dispatcher.once("loaded:items", () => {
                if (state.model.items.filter(it => {
                    return it.id === item;
                }).length) {
                    dispatcher.trigger("select:item", item, false);
                } else {
                    dispatcher.trigger("show:message", "Selected Item does not exist.");
                }

            });
        }
        dispatcher.trigger("load:projects");
    },
    appError(state, action, e) {
        if (e.public) {
            return dispatcher.trigger("show:message", e.message);
        }
        if (DBG) {
            console.log((e.silent ? "Silent" : "") + e);
        }
        if (!(e.silent)) {
            dispatcher.trigger("show:message", "Ooops, something went wrong...", "warning");
        }
    }
};
<commit_msg>open reveal presentation in new tab<commit_after>import api from "./api";
import dispatcher from "./dispatcher";
import hashParams from "./hashParams";
import session from "../util/session";

let id = 0;

function sortItems(a, b) {
    return (a.sort < b.sort ? 1 : a.sort > b.sort ? -1 : 0);
}

export default {
    showMessage(state, action, message, type, autoDismiss) {
        const notification = {
            content: message,
            type: type || "info",
            id: id++
        };
        if (autoDismiss) {
            setTimeout(() => {
                dispatcher.trigger("dismiss:notification", notification.id);
            }, 2000);
        }
        const notifications = state.model.notifications;
        notifications.push(notification);
        action({notifications});
    },
    loadItems(state, action) {
        if (typeof state.model.activeProject === "undefined") {
            return dispatcher.trigger("app:error", new Error("active project is not set"));
        }
        const cacheKey = `data.items.${state.model.activeProject.id}`;
        if (session.getCache(cacheKey)) {
            action(session.getCache(cacheKey));
        }
        // todo: ensure model.activeProject is set
        api.tasks.all(state.model)
            .then(res => {
                if (res.status !== 200) {
                    throw new Error("res.status is not OK:200 but " + res.status);
                }
                return res;
            })
            .then(res => res.text())
            .then(body => JSON.parse(body))
            .then(json => {
                const items = json.map(i => {
                    i.id = i._id;
                    return i;
                })
                .sort(sortItems);
                return items;
            })
            .then(items => action({items}))
            .then(items => session.setCache(cacheKey, items))
            .then(() => dispatcher.trigger("loaded:items"))
            .catch(e => dispatcher.trigger("app:error", e));
    },
    loadProjects(state, action) {
        api.projects.all(state.model)
            .then(res => {
                if (res.status !== 200) {
                    throw new Error("res.status is not OK:200 but " + res.status);
                }
                return res;
            })
            .then(res => res.text())
            .then(body => JSON.parse(body))
            .then(json => {
                const projects = json.map(i => {
                    i.id = i._id;
                    return i;
                })
                return projects;
            })
            .then(projects => action({projects}))
            .then(() => dispatcher.trigger("loaded:projects"))
            .catch(e => dispatcher.trigger("app:error", e));
    },
    selectItem(state, action, activeItemId, editing=false) {
        if (!activeItemId) {
            return action({activeItem, editing});
        }
        const activeItem = state.model.items.filter(i => i.id === activeItemId)[0];
        if (activeItem) {
            hashParams.set({item: activeItem.id});
            action({activeItem, editing});
        } else {
            dispatcher.trigger("app:error");
        }
    },
    checkItem(state, action, itemId, checked) {
        const items = state.model.items.map(i => {
            if (i.id === itemId) {
                i.checked = checked
                api.tasks.update({id: i.id, checked}).catch(e => dispatcher.trigger("app:error", e));
            }
            return i;
        });
        action({items});
    },
    saveItem(state, action, item, sort) {
        const newItems = state.model.items.filter(i => i.id !== item.id);

        newItems.unshift(item);
        const items = newItems
            .sort(sortItems)
            .map((item, index) => {
                if (!sort) {
                    return item;
                }
                item.sort = newItems.length - index - 1;
                return item;
            });
        action({items});
        if (!sort) {
            dispatcher.trigger("select:item", item.id);
            api.tasks.updateAndThrow(item).catch(e => dispatcher.trigger("app:error", e));
        } else {
            api.tasks.updateAll(items).catch(e => dispatcher.trigger("app:error", e));
        }
    },
    editItem(state, action) {
        action({editing: true, preview: false});
    },
    previewItem(state, action) {
        action({preview: true});
    },
    cancelPreviewItem(state, action) {
        action({preview: false});
    },
    cancelItem(state, action) {
        action({editing: false});
    },
    createProject(state, action, project) {
        const {projects} = state.model;
        api.projects.create(project)
            .then(res => {
                if (res.status !== 201) {
                    throw new Error("res.status is not OK:201 but " + res.status);
                }
                return res;
            })
            .then(res => res.text())
            .then(body => JSON.parse(body))
            .then(json => {
                json.id = json._id;
                projects.unshift(json);
                action({projects});
                dispatcher.trigger("select:project", json.id);
            })
            .catch(e => dispatcher.trigger("dispatcher:error", e));
    },
    newItem(state, action) {
        const itemId = id++;
        const {model} = state;
        const {items} = model;
        const sort = (items.length ? Math.max(...items.map(i => i.sort)) : 0) + 1; // todo ?= items.length
        const project = model.activeProject.id;
        const item = {title: "New Item", description: "", sort, project};
        api.tasks.create(item, model)
            .then(res => {
                if (res.status !== 201) {
                    throw new Error("res.status is not OK:201 but " + res.status);
                }
                return res;
            })
            .then(res => res.text())
            .then(body => JSON.parse(body))
            .then(json => {
                json.id = json._id;
                dispatcher.trigger("save:item-detail", json, true);
                dispatcher.trigger("select:item", json.id, true);
            })
            .catch(e => dispatcher.trigger("dispatcher:error", e));
    },
    deleteItem(state, action, id) {
        const items = state.model.items.filter(i => i.id !== id);
        action({items});
        action({activeItem: null});
        api.tasks.remove(id)
            .then(res => {
                if (res.status !== 204) {
                    throw new Error("res.status is not OK:204 but " + res.status);
                }
                return res;
            })
            .catch(e => dispatcher.trigger("app:error", e));
    },
    deleteProject(state, action, id) {
        const projects = state.model.projects.filter(p => p.id !== id);
        action({projects});
        action({activeProject: null});
        action({activeItem: null});
        api.projects.remove(id)
            .then(res => {
                if (res.status !== 204) {
                    throw new Error("res.status is not OK:204 but " + res.status);
                }
                return res;
            })
            .catch(e => dispatcher.trigger("app:error", e));
    },
    clearChecked(state, action) {
        state.model.items
            .filter(i => i.checked)
            .forEach(i => {
                api.tasks.remove(i.id)
                    .then(res => {
                        if (res.status !== 204) {
                            throw new Error("res.status is not OK:204 but " + res.status);
                        }
                        return res;
                    })
                    .catch(e => dispatcher.trigger("app:error", e));
            });
        const items = state.model.items.filter(i => !(i.checked));
        if (state.model.activeItem && state.model.activeItem.checked) {
            action({activeItem: null});
        }
        action({items});
    },
    dismissNotification(state, action, id) {
        const notifications = state.model.notifications.filter(n => n.id !== id);
        action({notifications});
    },
    selectProject(state, action, activeProjectId) {
        const activeProject = state.model.projects.filter(p => p.id === activeProjectId)[0];

        if (activeProject) {
            action({
                items: [],
                activeItem: null,
                activeProject,
                editing: false
            });
            hashParams.set({project: activeProject.id, item: null});
            dispatcher.trigger("load:items");
        } else {
            dispatcher.trigger("app:error");
        }
    },
    changeFilter(state, action, filterName, filterState) {
        const {filter} = state.model;
        filter[filterName] = filterState;
        action({filter, activeItem: null});
        dispatcher.trigger("load:items");
    },
    logout(state, action) {
        api.user.logout().catch(e => true);
        session.clear();
        dispatcher.trigger("goto:page", "login");
        dispatcher.trigger("show:message", "logged out.", "info", true);
    },
    gotoPage(state, action, pageName) {
        hashParams.set({page: pageName});
        action({pageName});
    },
    gotoUrl(state, action, url) {
        var win = window.open(url, "_blank").focus();
    },
    startApp(state, action) {
        const {project, item} = hashParams.get();
        if (project) {
            dispatcher.once("loaded:projects", () => {
                if (state.model.projects.filter(it => {
                    return it.id === project;
                }).length) {
                    dispatcher.trigger("select:project", project);
                } else {
                    dispatcher.trigger("show:message", "Selected Project does not exist.");
                }
            });
        }

        if (item) {
            dispatcher.once("loaded:items", () => {
                if (state.model.items.filter(it => {
                    return it.id === item;
                }).length) {
                    dispatcher.trigger("select:item", item, false);
                } else {
                    dispatcher.trigger("show:message", "Selected Item does not exist.");
                }

            });
        }
        dispatcher.trigger("load:projects");
    },
    appError(state, action, e) {
        if (e.public) {
            return dispatcher.trigger("show:message", e.message);
        }
        if (DBG) {
            console.log((e.silent ? "Silent" : "") + e);
        }
        if (!(e.silent)) {
            dispatcher.trigger("show:message", "Ooops, something went wrong...", "warning");
        }
    }
};
<|endoftext|>


<commit_before>/**
 * Webpack configuration
 */

var path = require("path");
var SourceMapDevToolPlugin = require("webpack/lib/SourceMapDevToolPlugin");

module.exports = {
  cache: true,
  context: path.join(__dirname, "app"),
  entry: "./js/app/app",
  output: {
    path: path.join(__dirname, "app/js-dist"),
    filename: "bundle.js"
  },
  optimize: {
    minimize: true
  },
  module: {
    loaders: [
      { test: /\.hbs$/, loader: "handlebars-loader" }
    ]
  },
  resolve: {
    alias: {
      "underscore": "lodash/dist/lodash.underscore"
    }
  },
  plugins: [
    // Manually do source maps to use alternate host.
    new SourceMapDevToolPlugin(
      "bundle.js.map",
      "\n//# sourceMappingURL=http://127.0.0.1:3001/app/js-dist/[url]")
  ]
};
<commit_msg>Switch config plugin syntax.<commit_after>/**
 * Webpack configuration
 */

var path = require("path");
var webpack = require("webpack");

module.exports = {
  cache: true,
  context: path.join(__dirname, "app"),
  entry: "./js/app/app",
  output: {
    path: path.join(__dirname, "app/js-dist"),
    filename: "bundle.js"
  },
  optimize: {
    minimize: true
  },
  module: {
    loaders: [
      { test: /\.hbs$/, loader: "handlebars-loader" }
    ]
  },
  resolve: {
    alias: {
      "underscore": "lodash/dist/lodash.underscore"
    }
  },
  plugins: [
    // Manually do source maps to use alternate host.
    new webpack.SourceMapDevToolPlugin(
      "bundle.js.map",
      "\n//# sourceMappingURL=http://127.0.0.1:3001/app/js-dist/[url]")
  ]
};
<|endoftext|>


<commit_before>/**
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

// Authenticate to Algolia Database.
// TODO: Make sure you configure the `algolia.appId` and `algolia.apiKey` Google Cloud environment variables.
const algoliasearch = require('algoliasearch');
const client = algoliasearch(functions.config().algolia.appId, functions.config().algolia.apiKey);

// Name fo the algolia index for Blog posts content.
const ALGOLIA_POSTS_INDEX_NAME = 'blogposts';

// Updates the search index when new blog entries are created or updated.
exports.indexentry = functions.database.ref('/blog-posts/{blogid}').onWrite(event => {
  const index = client.initIndex(ALGOLIA_POSTS_INDEX_NAME);
  const firebaseObject = event.data.val();
  firebaseObject.objectID = event.data.key;

  return index.saveObject(firebaseObject).then(
      () => event.data.adminRef.child('last_index_timestamp').set(Date.parse(event.timestamp).getTime()));
});

// Starts a search query whenever a query is requested (by adding one to the `/search/queries`
// element. Search results are then written under `/search/results`.
exports.searchentry = functions.database.ref('/search/queries/{queryid}').onWrite(event => {
  const index = client.initIndex(ALGOLIA_POSTS_INDEX_NAME);

  const query = event.data.val().query;
  const key = event.data.key;

  return index.search(query).then(content => {
    const updates = {
      '/search/last_query_timestamp': Date.parse(event.timestamp).getTime()
    };
    updates[`/search/results/${key}`] = content;
    return admin.database().ref().update(updates);
  });
});
<commit_msg>Fix indentation<commit_after>/**
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

// Authenticate to Algolia Database.
// TODO: Make sure you configure the `algolia.appId` and `algolia.apiKey` Google Cloud environment variables.
const algoliasearch = require('algoliasearch');
const client = algoliasearch(functions.config().algolia.appId, functions.config().algolia.apiKey);

// Name fo the algolia index for Blog posts content.
const ALGOLIA_POSTS_INDEX_NAME = 'blogposts';

// Updates the search index when new blog entries are created or updated.
exports.indexentry = functions.database.ref('/blog-posts/{blogid}').onWrite(event => {
  const index = client.initIndex(ALGOLIA_POSTS_INDEX_NAME);
  const firebaseObject = event.data.val();
  firebaseObject.objectID = event.data.key;

  return index.saveObject(firebaseObject).then(
      () => event.data.adminRef.child('last_index_timestamp').set(
          Date.parse(event.timestamp).getTime()));
});

// Starts a search query whenever a query is requested (by adding one to the `/search/queries`
// element. Search results are then written under `/search/results`.
exports.searchentry = functions.database.ref('/search/queries/{queryid}').onWrite(event => {
  const index = client.initIndex(ALGOLIA_POSTS_INDEX_NAME);

  const query = event.data.val().query;
  const key = event.data.key;

  return index.search(query).then(content => {
    const updates = {
      '/search/last_query_timestamp': Date.parse(event.timestamp).getTime()
    };
    updates[`/search/results/${key}`] = content;
    return admin.database().ref().update(updates);
  });
});
<|endoftext|>


<commit_before>function getElement(className) {
  return document.getElementsByClassName(className)[0];
}

var splash_screen = getElement('splash-screen');
var day_field = getElement('day');
var location_field = getElement('location');
var background_img = getElement('background');

var message_field = getElement('message');
var choice_btns = [
  getElement('choice1'),
  getElement('choice2'),
  getElement('choice3')
];

var energy_bar = getElement('energy-bar');
var mood_bar = getElement('mood-bar');

var done = getElement('done');
var out_of_energy = getElement('out-of-energy');
var out_of_mood = getElement('out-of-mood');
var restart = getElement('restart');

var game_data;

var state = {};

function finishGame(energyOrMood) {
  if (energyOrMood === 'energy') {
    out_of_energy.style.display = 'block';
    out_of_mood.style.display = 'none';
  } else {
    out_of_energy.style.display = 'none';
    out_of_mood.style.display = 'block';
  }
  done.style.display = 'block';
}

function adjustEnergy(delta) {
  state.energy += delta;
  if (state.energy <= 0) {
    finishGame('energy');
  } else if (state.energy > 100) {
    state.energy = 100;
  }
  energy_bar.style.width = Math.min(100, state.energy) + '%';
}

function adjustMood(delta) {
  state.mood += delta;
  if (state.mood <= 0) {
    finishGame('mood');
  } else if (state.mood > 100) {
    state.mood = 100;
  }
  mood_bar.style.width = Math.min(100, state.mood) + '%';
}

function loadStep() {
  var step = game_data[current_step];

  day_field.innerHTML = step.day;
  location_field.innerHTML = step.location;
  background_img.src = 'images/' + step.background;

  message_field.innerHTML = step.message;

  state.choices = [null, null, null];
  if (step.choices.length === 3) {
    state.choices = step.choices;
  } else if (step.choices.length === 2) {
    state.choices[1] = step.choices[0];
    state.choices[2] = step.choices[1];
  } else if (step.choices.length === 1) {
    state.choices[2] = step.choices[0];
  }

  state.choices.forEach(function (choice, i) {
    if (choice) {
      choice_btns[i].style.display = 'block';
      choice_btns[i].innerHTML = choice.text;
    } else {
      choice_btns[i].style.display = 'none';
    }
  });
}

function startGame() {
  state.energy = state.mood = 100;
  adjustEnergy(0);
  adjustMood(0);
  current_step = 0;
  loadStep();
}

// Load Game Data
var req = new XMLHttpRequest();
req.open('GET', './data.json', true);
req.onreadystatechange = function () {
  if (req.readyState != 4 || req.status !== 200) return;
  game_data = JSON.parse(req.responseText);
  startGame();
}
req.send();

// Event Handlers
function choiceHandler(idx) {
  adjustEnergy(state.choices[idx].energy);
  adjustMood(state.choices[idx].mood);
  current_step++;
  if (state.choices[idx].skip) {
    current_step += state.choices[idx].skip;
  }
  loadStep();
}

choice_btns[0].addEventListener('click', function () {
  choiceHandler(0);
});
choice_btns[1].addEventListener('click', function () {
  choiceHandler(1);
});
choice_btns[2].addEventListener('click', function () {
  choiceHandler(2);
});

splash_screen.addEventListener('click', function () {
  splash_screen.style.opacity = 0;
  setTimeout(function () {
    splash_screen.style.display = 'none';
  }, 2000);
});

restart.addEventListener('click', function () {
  done.style.display = 'none';
  startGame();
});

<commit_msg>Clicking on the day will continue a game that has ended.<commit_after>function getElement(className) {
  return document.getElementsByClassName(className)[0];
}

var splash_screen = getElement('splash-screen');
var day_field = getElement('day');
var location_field = getElement('location');
var background_img = getElement('background');

var message_field = getElement('message');
var choice_btns = [
  getElement('choice1'),
  getElement('choice2'),
  getElement('choice3')
];

var energy_bar = getElement('energy-bar');
var mood_bar = getElement('mood-bar');

var done = getElement('done');
var out_of_energy = getElement('out-of-energy');
var out_of_mood = getElement('out-of-mood');
var restart = getElement('restart');

var game_data;

var state = {};

function finishGame(energyOrMood) {
  if (state.done) {
    return;
  }
  state.done = true;
  if (energyOrMood === 'energy') {
    out_of_energy.style.display = 'block';
    out_of_mood.style.display = 'none';
  } else {
    out_of_energy.style.display = 'none';
    out_of_mood.style.display = 'block';
  }
  done.style.display = 'block';
}

function adjustEnergy(delta) {
  state.energy += delta;
  if (state.energy <= 0) {
    finishGame('energy');
  } else if (state.energy > 100) {
    state.energy = 100;
  }
  energy_bar.style.width = Math.min(100, state.energy) + '%';
}

function adjustMood(delta) {
  state.mood += delta;
  if (state.mood <= 0) {
    finishGame('mood');
  } else if (state.mood > 100) {
    state.mood = 100;
  }
  mood_bar.style.width = Math.min(100, state.mood) + '%';
}

function loadStep() {
  var step = game_data[current_step];

  day_field.innerHTML = step.day;
  location_field.innerHTML = step.location;
  background_img.src = 'images/' + step.background;

  message_field.innerHTML = step.message;

  state.choices = [null, null, null];
  if (step.choices.length === 3) {
    state.choices = step.choices;
  } else if (step.choices.length === 2) {
    state.choices[1] = step.choices[0];
    state.choices[2] = step.choices[1];
  } else if (step.choices.length === 1) {
    state.choices[2] = step.choices[0];
  }

  state.choices.forEach(function (choice, i) {
    if (choice) {
      choice_btns[i].style.display = 'block';
      choice_btns[i].innerHTML = choice.text;
    } else {
      choice_btns[i].style.display = 'none';
    }
  });
}

function startGame() {
  state.energy = state.mood = 100;
  state.done = false;
  adjustEnergy(0);
  adjustMood(0);
  current_step = 0;
  loadStep();
}

// Load Game Data
var req = new XMLHttpRequest();
req.open('GET', './data.json', true);
req.onreadystatechange = function () {
  if (req.readyState != 4 || req.status !== 200) return;
  game_data = JSON.parse(req.responseText);
  startGame();
}
req.send();

// Event Handlers
function choiceHandler(idx) {
  adjustEnergy(state.choices[idx].energy);
  adjustMood(state.choices[idx].mood);
  current_step++;
  if (state.choices[idx].skip) {
    current_step += state.choices[idx].skip;
  }
  loadStep();
}

choice_btns[0].addEventListener('click', function () {
  choiceHandler(0);
});
choice_btns[1].addEventListener('click', function () {
  choiceHandler(1);
});
choice_btns[2].addEventListener('click', function () {
  choiceHandler(2);
});

splash_screen.addEventListener('click', function () {
  splash_screen.style.opacity = 0;
  setTimeout(function () {
    splash_screen.style.display = 'none';
  }, 2000);
});

restart.addEventListener('click', function () {
  done.style.display = 'none';
  startGame();
});

// Hidden functionality - clicking on the day field dismisses the 'done' dialog
day_field.addEventListener('click', function () {
  if (state.done) {
    done.style.display = 'none';
  }
});

<|endoftext|>


<commit_before>
$(document).ready(function() {

	function loadJSON(data, device) {
		for (var task in data.tasks) {
			if(task != 'length') add(data.tasks[task], task, device);
		}
	}

	$.getJSON('json/server.json?n=1', function(data) {
		loadJSON(data, 'server');
		server = data;
	});
	$.getJSON('json/comp1.json?n=1', function(data) {
		loadJSON(data, 'comp1');
		comp1 = data;
	});
	$.getJSON('json/comp2.json?n=1', function(data) {
		loadJSON(data, 'comp2');
		comp2 = data;
	});

});

function add(task, id, device) {

	// If task is deleted, show greyed out task
	if(task.hasOwnProperty('deleted')) {
		$('#' + device + ' ul').append('
Deleted
');

	// Else show task as normal
	} else {
		$('#' + device + ' ul').append('
' + task.content + '' + task.notes + '
');
	}
}

function clone(input) {
	return JSON.parse(JSON.stringify(input));
}

function get(comp, el) {

	// var comp = {};

	// //Gets Tasks from computer
	// $('#' + computer + ' ul').find('li').map(function() {

	// 	// Get timestamps (content | notes | priority)
	// 	var time = $(this).attr('data-time').split('|');

	// 	// Convert strings to integers
	// 	for(var x in time) {
	// 		time[x] = parseInt(time[x]);
	// 	}

	// 	if($(this).attr('data-deleted') == 'true') {

	// 		// Task has been deleted!
	// 		comp[$(this).attr('class')] = {
	// 			deleted: time[0]
	// 		}

	// 	} else {

	// 		// Get attributes
	// 		comp[$(this).attr('class')] = {
	// 			content:  {
	// 				value: $(this).find('.content').text(),
	// 				time: time[0]
	// 			},
	// 			notes: {
	// 				value: $(this).find('.notes').text(),
	// 				time: time[1]
	// 			},
	// 			priority: {
	// 				value: $(this).attr('data-priority'),
	// 				time: time[2]
	// 			}
	// 		}
	// 	}
	// });

	// Loop through each task
	for(var task in comp.tasks) {

		// Task was deleted on computer but not on the server
		if(comp.tasks[task].hasOwnProperty('deleted') && !server.tasks[task].hasOwnProperty('deleted')) {

			// We use this to check whether the task was modified AFTER it was deleted
			var deleteTask = true;

			// Loop through each attribute on server
			for(var key in server.tasks[task]) {

				// Check if server task was modified after task was deleted
				if(server.tasks[task].time[key] > comp.tasks[task].deleted) {

					// Since it has been modified after it was deleted, we don't delete the task
					deleteTask = false;

				}
			}

			// If there have been no modifications to the task after it has been deleted
			if(deleteTask) {

				// Clone computer's task to server
				server.tasks[task] = clone(comp.tasks[task]);

			}

		// Task is deleted on the server and the computer
		} else if(comp.tasks[task].hasOwnProperty('deleted') && server.tasks[task].hasOwnProperty('deleted')){

			// Use the latest time stamp
			if(comp.tasks[task].deleted > server.tasks[task].deleted) {

				// If the task was deleted on a computer after it was deleted on the server, then update the time stamp
				server.tasks[task].deleted = comp.tasks[task].deleted;

			}

		} else {

			// Loop through each attribute on computer
			for(var key in comp.tasks[task]) {

				// Check if task was deleted on server
				 if (server.tasks[task].hasOwnProperty('deleted')) {

					// Check if task was modified after it was deleted
					if(comp.tasks[task].time[key] > server.tasks[task].deleted) {

						// Update the server with the entire task (including attributes and timestamps)
						server.tasks[task] = comp.tasks[task];

					}

				// Task has not been deleted
				} else {

					// If the attribute was updated after the server
					if(comp.tasks[task].time[key] > server.tasks[task].time[key]) {

						// Update the servers version
						server.tasks[task][key] = comp.tasks[task][key];

						// Update the timestamp
						server.tasks[task].time[key] = comp.tasks[task].time[key];

					}
				}
			}
		}
	}

	// Print to UI
	$('#server ul').html('');
	$('#' + el + ' ul').html('');
	for(var task in server.tasks) {
		if(task != 'length') add(server.tasks[task], task, 'server');
		if(task != 'length') add(server.tasks[task], task, el)
	}

}<commit_msg>Bug Fixes<commit_after>
$(document).ready(function() {

	function loadJSON(data, device) {
		for (var task in data.tasks) {
			if(task != 'length') add(data.tasks[task], task, device);
		}
	}

	$.getJSON('json/server.json?n=1', function(data) {
		loadJSON(data, 'server');
		server = data;
	});
	$.getJSON('json/comp1.json?n=1', function(data) {
		loadJSON(data, 'comp1');
		comp1 = data;
	});
	$.getJSON('json/comp2.json?n=1', function(data) {
		loadJSON(data, 'comp2');
		comp2 = data;
	});

});

function add(task, id, device) {

	// If task is deleted, show greyed out task
	if(task.hasOwnProperty('deleted')) {
		$('#' + device + ' ul').append('
Deleted
');

	// Else show task as normal
	} else {
		$('#' + device + ' ul').append('
' + task.content + '' + task.notes + '
');
	}
}

function clone(input) {
	return JSON.parse(JSON.stringify(input));
}

function get(comp, el) {

	// var comp = {};

	// //Gets Tasks from computer
	// $('#' + computer + ' ul').find('li').map(function() {

	// 	// Get timestamps (content | notes | priority)
	// 	var time = $(this).attr('data-time').split('|');

	// 	// Convert strings to integers
	// 	for(var x in time) {
	// 		time[x] = parseInt(time[x]);
	// 	}

	// 	if($(this).attr('data-deleted') == 'true') {

	// 		// Task has been deleted!
	// 		comp[$(this).attr('class')] = {
	// 			deleted: time[0]
	// 		}

	// 	} else {

	// 		// Get attributes
	// 		comp[$(this).attr('class')] = {
	// 			content:  {
	// 				value: $(this).find('.content').text(),
	// 				time: time[0]
	// 			},
	// 			notes: {
	// 				value: $(this).find('.notes').text(),
	// 				time: time[1]
	// 			},
	// 			priority: {
	// 				value: $(this).attr('data-priority'),
	// 				time: time[2]
	// 			}
	// 		}
	// 	}
	// });

	// Loop through each task
	for(var task in comp.tasks) {

		// Task was deleted on computer but not on the server
		if(comp.tasks[task].hasOwnProperty('deleted') && !server.tasks[task].hasOwnProperty('deleted')) {

			// We use this to check whether the task was modified AFTER it was deleted
			var deleteTask = true;

			// Loop through each attribute on server
			for(var key in server.tasks[task]) {

				// Check if server task was modified after task was deleted
				if(server.tasks[task].time[key] > comp.tasks[task].deleted) {

					// Since it has been modified after it was deleted, we don't delete the task
					deleteTask = false;

				}
			}

			// If there have been no modifications to the task after it has been deleted
			if(deleteTask) {

				// Clone computer's task to server
				server.tasks[task] = clone(comp.tasks[task]);

			}

		// Task is deleted on the server and the computer
		} else if(comp.tasks[task].hasOwnProperty('deleted') && server.tasks[task].hasOwnProperty('deleted')){

			// Use the latest time stamp
			if(comp.tasks[task].deleted > server.tasks[task].deleted) {

				// If the task was deleted on a computer after it was deleted on the server, then update the time stamp
				server.tasks[task].deleted = comp.tasks[task].deleted;

			}

		} else {

			// Loop through each attribute on computer
			for(var key in comp.tasks[task]) {

				//Don't loop through timestamps
				if (key != 'time') {

					// Check if task was deleted on server
					 if (server.tasks[task].hasOwnProperty('deleted')) {

						// Check if task was modified after it was deleted
						if(comp.tasks[task].time[key] > server.tasks[task].deleted) {

							// Update the server with the entire task (including attributes and timestamps)
							server.tasks[task] = comp.tasks[task];

						}

					// Task has not been deleted
					} else {

						// If the attribute was updated after the server
						if(comp.tasks[task].time[key] > server.tasks[task].time[key]) {

							//Due to the shitty nature of Nitro, a task can be updated without being updated
							//We need to check for this.
							if (comp.tasks[task][key] != server.tasks[task][key]) {

								// Update the servers version
								server.tasks[task][key] = comp.tasks[task][key];

								// Update the timestamp
								server.tasks[task].time[key] = comp.tasks[task].time[key];
							}
						}
					}
				}
			}
		}
	}

	// Print to UI
	$('#server ul').html('');
	$('#' + el + ' ul').html('');
	for(var task in server.tasks) {
		if(task != 'length') add(server.tasks[task], task, 'server');
		if(task != 'length') add(server.tasks[task], task, el)
	}

}<|endoftext|>


<commit_before>var catPics = ["img/01.jpg", "img/02.jpg", "img/03.jpg", "img/04.jpg", "img/05.jpg", "img/06.jpg", "img/07.jpg", "img/08.jpg", "img/09.jpg", "img/10.jpg", "img/11.jpg", "img/12.jpg", "img/13.jpg", "img/14.jpg"];

function displayImage1() {
  var pic1 = document.getElementById("pic1");
  var num1 = Math.floor(Math.random() * (14 - 1) + 1);
  pic1.src = catPics[num1];
}

function displayImage2() {
  var pic2 = document.getElementById("pic2");
  var num2 = Math.floor(Math.random() * (14 - 1) + 1);
  pic2.src = catPics[num2];
}

displayImage1();
displayImage2();

var reroll = document.getElementById("submit");
reroll.addEventListener("click", function() {
  displayImage1();
  displayImage2();
  if (displayImage1 == displayImage2) {
    displayImage1();
    displayImage2();
  }
});

var count = 0;
var vote = document.getElementById("pic1");
vote.addEventListener("click", function() {
  count += 1;
  console.log(count);
});

<commit_msg>added Photo constructor function<commit_after>var catPics = ["img/01.jpg", "img/02.jpg", "img/03.jpg", "img/04.jpg", "img/05.jpg", "img/06.jpg", "img/07.jpg", "img/08.jpg", "img/09.jpg", "img/10.jpg", "img/11.jpg", "img/12.jpg", "img/13.jpg", "img/14.jpg"];

function displayImage1() {
  var pic1 = document.getElementById("pic1");
  var num1 = Math.floor(Math.random() * (14 - 1) + 1);
  pic1.src = catPics[num1];
}

function displayImage2() {
  var pic2 = document.getElementById("pic2");
  var num2 = Math.floor(Math.random() * (14 - 1) + 1);
  pic2.src = catPics[num2];
}

displayImage1();
displayImage2();

var reroll = document.getElementById("submit");
reroll.addEventListener("click", function() {
  displayImage1();
  displayImage2();
  if (displayImage1 == displayImage2) {
    displayImage1();
    displayImage2();
  }
});

var count = 0;
var vote = document.getElementById("pic1");
vote.addEventListener("click", function() {
  count += 1;
});

var Photo = function(path) {
  this.path = "img/" + path + ".jpg";
  this.votes = 0;
  this.receiveVote = function() {
    this.votes += 1;
    console.log(this.votes);
  };
};

var cat1 = new Photo("01");
var cat2 = new Photo("02");
var cat3 = new Photo("03");
var cat4 = new Photo("04");
var cat5 = new Photo("05");
var cat6 = new Photo("06");
var cat7 = new Photo("07");
var cat8 = new Photo("08");
var cat9 = new Photo("09");
var cat10 = new Photo("10");
var cat11 = new Photo("11");
var cat12 = new Photo("12");
var cat13 = new Photo("13");
var cat14 = new Photo("14");

var cats = [];
cats.push(cat1, cat2, cat3, cat4, cat5, cat6, cat7, cat8, cat9, cat10, cat11, cat12, cat13, cat14);

console.log(cat1.path);

<|endoftext|>


<commit_before>import { deepContains } from './utilities.js';
import { merge } from './updates.js';

// We consider the keyboard to be active if the window has received a keydown
// event since the last mousedown event.
let keyboardActive = false;

const focusVisibleChangedListenerKey = Symbol('focusVisibleChangedListener');

/**
 * Mixin which tracks a component's focus state so that it can render a focus
 * indication (e.g., a glowing outline) if and only if the keyboard is active.
 * The keyboard is considered to be active if a keyboard event has occurred
 * since the last mousedown event.
 *
 * This is loosely modeled after the proposed
 * [focus-visible](https://github.com/WICG/focus-visible) feature for CSS.
 *
 * @module FocusVisibleMixin
 */
export default function FocusVisibleMixin(Base) {

  // The class prototype added by the mixin.
  return class FocusVisible extends Base {

    constructor() {
      // @ts-ignore
      super();

      // We listen to focusin/focusout instead of focus/blur because components
      // like Menu want to handle focus visiblity for the items they contain,
      // and those contained items can get the focus. Using focusin/focusout
      // lets us know whether this element *or any element it contains* has the
      // focus.
      //
      // Focus events are problematic in that they can occur during rendering:
      // if an element with the focus is updated so that its tabindex is
      // removed, it will lose focus. Since these focus handlers need to set
      // state, this could lead to setting state during rendering, which is bad.
      // To avoid this problem, we use promise timing to defer the setting of
      // state.
      this.addEventListener('focusout', event => {
        Promise.resolve().then(() => {
          // What has the focus now?
          const newFocusedElement = event.relatedTarget || document.activeElement;
          const isFocusedElement = this === newFocusedElement;
          /** @type {any} */
          const cast = this;
          const containsFocus = deepContains(cast, newFocusedElement);
          const lostFocus = !isFocusedElement && !containsFocus;
          if (lostFocus) {
            this.setState({
              focusVisible: false
            });
            // No longer need to listen for changes in focus visibility.
            document.removeEventListener('focus-visible-changed',
              this[focusVisibleChangedListenerKey]);
            this[focusVisibleChangedListenerKey] = null;
          }
        });
      });
      this.addEventListener('focusin', () => {
        Promise.resolve().then(() => {
          if (this.state.focusVisible !== keyboardActive) {
            // Show the element as focused if the keyboard has been used.
            this.setState({
              focusVisible: keyboardActive
            });
          }
          if (!this[focusVisibleChangedListenerKey]) {
            // Listen to subsequent changes in focus visibility.
            this[focusVisibleChangedListenerKey] = () => refreshFocus(this);
            document.addEventListener('focus-visible-changed',
              this[focusVisibleChangedListenerKey]);
          }
        });
      });
    }

    get defaultState() {
      return Object.assign({}, super.defaultState, {
        focusVisible: false
      });
    }

    /**
     * Temporarily suppress visibility of the keyboard focus until the next
     * keydown event.
     *
     * This can be useful in components like [Menu](Menu) that actively manage
     * where the focus is in response to mouse hover activity. If the user uses
     * the keyboard to invoke a menu, then changes to using the mouse, it can be
     * distracting to see the focus indicator moving as well. In such
     * situations, the component can invoke this method (e.g., in response to
     * `mousemove`) to temporarily suppress focus visibility.
     */
    suppressFocusVisibility() {
      keyboardActive = false;
      refreshFocus(this);
    }

    get updates() {
      const base = super.updates || {};
      // Suppress the component's normal `outline` style unless we know the
      // focus should be visible. If a base class (e.g., mixin further up the
      // prototype chain) has a different opinion about the outline, defer to
      // it.
      const outline = base.style && base.style.outline ||
        !this.state.focusVisible && 'none' ||
        null;
      return merge(base, {
        style: {
          outline
        }
      });
    }
  }
}

function refreshFocus(element) {
  element.setState({
    focusVisible: keyboardActive
  });
}

function updateKeyboardActive(newKeyboardActive) {
  if (keyboardActive !== newKeyboardActive) {
    keyboardActive = newKeyboardActive;
    const event = new CustomEvent('focus-visible-changed', {
      detail: {
        focusVisible: !keyboardActive
      }
    });
    document.dispatchEvent(event);
  }
}

// Listen for top-level keydown and mousedown events.
// Use capture phase so we detect events even if they're handled.
window.addEventListener('keydown', () => {
  updateKeyboardActive(true);
}, { capture: true });

window.addEventListener('mousedown', () => {
  updateKeyboardActive(false);
}, { capture: true });
<commit_msg>Fix logical interpretation of event flag.<commit_after>import { deepContains } from './utilities.js';
import { merge } from './updates.js';

// We consider the keyboard to be active if the window has received a keydown
// event since the last mousedown event.
let keyboardActive = false;

const focusVisibleChangedListenerKey = Symbol('focusVisibleChangedListener');

/**
 * Mixin which tracks a component's focus state so that it can render a focus
 * indication (e.g., a glowing outline) if and only if the keyboard is active.
 * The keyboard is considered to be active if a keyboard event has occurred
 * since the last mousedown event.
 *
 * This is loosely modeled after the proposed
 * [focus-visible](https://github.com/WICG/focus-visible) feature for CSS.
 *
 * @module FocusVisibleMixin
 */
export default function FocusVisibleMixin(Base) {

  // The class prototype added by the mixin.
  return class FocusVisible extends Base {

    constructor() {
      // @ts-ignore
      super();

      // We listen to focusin/focusout instead of focus/blur because components
      // like Menu want to handle focus visiblity for the items they contain,
      // and those contained items can get the focus. Using focusin/focusout
      // lets us know whether this element *or any element it contains* has the
      // focus.
      //
      // Focus events are problematic in that they can occur during rendering:
      // if an element with the focus is updated so that its tabindex is
      // removed, it will lose focus. Since these focus handlers need to set
      // state, this could lead to setting state during rendering, which is bad.
      // To avoid this problem, we use promise timing to defer the setting of
      // state.
      this.addEventListener('focusout', event => {
        Promise.resolve().then(() => {
          // What has the focus now?
          const newFocusedElement = event.relatedTarget || document.activeElement;
          const isFocusedElement = this === newFocusedElement;
          /** @type {any} */
          const cast = this;
          const containsFocus = deepContains(cast, newFocusedElement);
          const lostFocus = !isFocusedElement && !containsFocus;
          if (lostFocus) {
            this.setState({
              focusVisible: false
            });
            // No longer need to listen for changes in focus visibility.
            document.removeEventListener('focus-visible-changed',
              this[focusVisibleChangedListenerKey]);
            this[focusVisibleChangedListenerKey] = null;
          }
        });
      });
      this.addEventListener('focusin', () => {
        Promise.resolve().then(() => {
          if (this.state.focusVisible !== keyboardActive) {
            // Show the element as focused if the keyboard has been used.
            this.setState({
              focusVisible: keyboardActive
            });
          }
          if (!this[focusVisibleChangedListenerKey]) {
            // Listen to subsequent changes in focus visibility.
            this[focusVisibleChangedListenerKey] = () => refreshFocus(this);
            document.addEventListener('focus-visible-changed',
              this[focusVisibleChangedListenerKey]);
          }
        });
      });
    }

    get defaultState() {
      return Object.assign({}, super.defaultState, {
        focusVisible: false
      });
    }

    /**
     * Temporarily suppress visibility of the keyboard focus until the next
     * keydown event.
     *
     * This can be useful in components like [Menu](Menu) that actively manage
     * where the focus is in response to mouse hover activity. If the user uses
     * the keyboard to invoke a menu, then changes to using the mouse, it can be
     * distracting to see the focus indicator moving as well. In such
     * situations, the component can invoke this method (e.g., in response to
     * `mousemove`) to temporarily suppress focus visibility.
     */
    suppressFocusVisibility() {
      keyboardActive = false;
      refreshFocus(this);
    }

    get updates() {
      const base = super.updates || {};
      // Suppress the component's normal `outline` style unless we know the
      // focus should be visible. If a base class (e.g., mixin further up the
      // prototype chain) has a different opinion about the outline, defer to
      // it.
      const outline = base.style && base.style.outline ||
        !this.state.focusVisible && 'none' ||
        null;
      return merge(base, {
        style: {
          outline
        }
      });
    }
  }
}

function refreshFocus(element) {
  element.setState({
    focusVisible: keyboardActive
  });
}

function updateKeyboardActive(newKeyboardActive) {
  if (keyboardActive !== newKeyboardActive) {
    keyboardActive = newKeyboardActive;
    const event = new CustomEvent('focus-visible-changed', {
      detail: {
        focusVisible: keyboardActive
      }
    });
    document.dispatchEvent(event);
  }
}

// Listen for top-level keydown and mousedown events.
// Use capture phase so we detect events even if they're handled.
window.addEventListener('keydown', () => {
  updateKeyboardActive(true);
}, { capture: true });

window.addEventListener('mousedown', () => {
  updateKeyboardActive(false);
}, { capture: true });
<|endoftext|>


<commit_before>alert("Hola Mundo");
<commit_msg>Modificacion Alert()<commit_after>alert("Editado");
<|endoftext|>