<commit_before>import _ from "lodash";
import R from "ramda";
import React from "react";
import Immutable from "immutable";
import { delay, localStorage } from "js-util";
import rest from "rest-middleware/browser";
import bdd from "./bdd";
import apiConsole from "./api-console";

const LOG_LIST = Symbol("log-list");
const COMPONENT = Symbol("component");

/**
 * The API used internally by the UIHarness components.
 */
class Api {
  constructor() {
    this.current = Immutable.Map();
    this[LOG_LIST] = Immutable.List();
    this.loadInvokeCount = 0;
  }

  /**
   * Initializes the UIHarness environment.
   * @param {Function} callback: Invoked when ready to initialize the DOM.
   */
  init(callback) {
    // Put state into global namespace.
    bdd.register();
    global.UIHarness = global.uih = apiConsole;

    // Insert the <Shell> into the root.
    //    NB: Signal DOM ready after a delay to ensure that the [describe/it]
    //        have fully parsed before initial render. Avoids a redraw.
    delay(() => {
        // Ensure the last loaded suite is set as the current state.
        const suite = this.lastSelectedSuite();
        if (suite) {
          this.loadSuite(this.lastSelectedSuite(), { storeAsLastSuite:false });
        }
        callback()
    });

    // Finish up.
    return this;
  }

  /**
   * Resets the internal API.
   * @param {boolean} hard: Flag indicating if all state from local-storage
   *                        should be cleared away, or just current selection state.
   */
  reset({ hard = true } = {}) {
    if (hard) {
      this.clearLocalStorage();
    } else {
      this.clearLocalStorage("lastInvokedSpec:");
    }
    this.lastSelectedSuite(null);
    this.setCurrent(null);
    this.component(null);
    return this;
  }

  /**
   * Removes all ui-harness values stored in local-storage.
   */
  clearLocalStorage(startsWith = null) {
    localStorage.keys().forEach(key => {
        let match = "ui-harness:";
        if (startsWith) { match += startsWith; }
        if (_.startsWith(key, match)) {
          localStorage.prop(key, null); // Remove.
        }
      });
  }

  /**
   * Gets or sets the current component instance.
   * Pass {null} to clear.
   */
  component(value) {
    // WRITE.
    if (value !== undefined) {
      if (value === null && this[COMPONENT]) {
        // Unload component.
        delete this[COMPONENT];
        delete apiConsole.component;
        this.setCurrent({
          componentType: null,
          componentProps: null,
          componentChildren: null,
          component: null
        });

      } else {

        // Store component instance.
        this[COMPONENT] = value;
        apiConsole.component = value;
        if (this.current.get("component") !== value) {
          // NB: Preform instance comparison before updating the
          //     current state to prevent render loop.
          this.setCurrent({ component: value });
        }
      }
    }

    // READ.
    return this[COMPONENT];
  }

  /**
   * Loads the current suite into the Harness.
   *
   * @param suite: The {Suite} to load.
   * @param options
   *          - storeAsLastSuite: Flag indicating if the suite should be stored
   *                              as the last invoked suite in localStorage.
   *                              Default: true.
   */
  loadSuite(suite, { storeAsLastSuite = true } = {}) {
    // Setup initial conditions.
    if (!suite) { return this; }
    if (this.current.get("suite") === suite) { return this; }

    // Only load the suite if it does not have children
    // ie. is not a container/folder suite.
    if (suite.childSuites.length === 0) {
      // Clear the current state.
      this.setCurrent(null);

      // Prepare the new current state.
      let current = suite.meta.thisContext.toValues();
      current.suite = suite;
      current.indexMode = this.indexMode();
      current.isBeforeInvoked = false;
      this.setCurrent(current);
      if (storeAsLastSuite) { this.lastSelectedSuite(suite); }

      // Invoke before handlers.
      this.invokeBeforeHandlers(suite);

      // If the last invoked spec on the suite contained a load.
      let lastInvokedSpec = this.lastInvokedSpec(suite);
      if (lastInvokedSpec && lastInvokedSpec.spec && lastInvokedSpec.isLoader) {
        this.invokeSpec(lastInvokedSpec.spec);
      }
    }

    // Finish up.
    return this;
  }

  /**
   * Invokes the [before] handlers for
   * the given suite if required.
   * @return {boolean}  - true if the handlers were invoked
   *                    - false if they have already been invoked.
   */
  invokeBeforeHandlers(suite) {
    if (this.current.get("isBeforeInvoked")) { return false; }
    const self = suite.meta.thisContext;
    suite.beforeHandlers.invoke(self);
    this.current = this.current.set("isBeforeInvoked", true);
    return true
  }

  /**
   * Invokes the given spec.
   * @param spec: The [Spec] to invoke.
   * @param callback: Invoked upon completion.
   *                   Immediately if the spec is not asynchronous.
   */
  invokeSpec(spec, callback) {
    // Setup initial conditions.
    const suite = spec.parentSuite;
    const self = suite.meta.thisContext;
    this.invokeBeforeHandlers(suite)
    let loadInvokeCountBefore = this.loadInvokeCount;

    // Invoke the methods.
    spec.invoke(self, callback);

    // Store a reference to last-invoked spec.
    this.lastInvokedSpec(suite, {
      spec: spec,
      isLoader: (this.loadInvokeCount > loadInvokeCountBefore)
    });

    // Increment the current invoke count for the spec.
    let specInvokeCount = this.current.get("specInvokeCount") || {};
    let total = specInvokeCount[spec.id] || 0;
    specInvokeCount[spec.id] = total + 1;
    this.setCurrent({ specInvokeCount: specInvokeCount });

    // Finish up.
    return this;
  }

  /**
   * Invokes a spec on the server.
   * @param spec: The [Spec] to invoke.
   * @param callback: Invoked upon completion.
   */
  invokeServerSpec(spec, callback) {
    const server = rest();
    server.onReady(() => {
        server.methods.invokeSpec.put(spec.id)
        .then((result) => {
          // TODO:
          console.log("result", result);
        });
    });
  }

  /**
   * Gets or sets the last selected [Suite].
   */
  lastSelectedSuite(suite) {
    if (suite) { suite = suite.id; }
    const result = this.localStorage("lastSelectedSuite", suite);
    return bdd.suites[result];
  }

  /**
   * Gets or sets the last spec for the given suite
   * that was invoked that had a `.load()` call within it.
   */
  lastInvokedSpec(suite, { spec, isLoader = false} = {}) {
    const KEY = `lastInvokedSpec:${ suite.id }`;
    let value;
    if (spec !== undefined) {
      // WRITE.
      value = { spec: spec.id, isLoader: isLoader }
      spec = spec.id;
    }

    // READ.
    let result = this.localStorage(KEY, value);
    if (result) {
      result.spec = _.find(suite.specs, spec => spec.id === result.spec);
    }
    return result
  }

  /**
   * Gets or sets the display mode of the left-hand index.
   * @param {string} mode: tree|suite
   */
  indexMode(mode) {
    let result = this.localStorage("indexMode", mode, { default: "tree" });
    if (mode !== undefined) {
      // WRITE (store in current state).
      this.setCurrent({ indexMode:mode });
    }

    // READ.
    result = result || "tree";
    if (result !== "tree" && this.current.get("suite") === undefined) {
      result = "tree"
    }
    return result;
  }

  /**
   * Updates the current state with the given values.
   *     NOTE: These values are cumulatively added to the state.
   *           Use "reset" to clear the state.
   *
   * @param args:  An object containing they [key:values] to set
   *               or null to clear values.
   */
  setCurrent(args) {
    // Update the state object.
    if (args) {
      _.keys(args).forEach(key => { this.current = this.current.set(key, args[key]) });
    } else {
      this.current = this.current.clear();
    }

    // Apply to the <Shell>.
    if (this.shell) { this.shell.setState({ current: this.current }); }
    return this;
  };

  /**
   * Logs a value to the output.
   * @param {array} values: The value or values to append.
   */
  log(...values) {
    values = _.flatten(values);
    const item = { time: new Date(), values };
    this[LOG_LIST] = this[LOG_LIST].push(item);
    this.setCurrent({ log: this[LOG_LIST], showLog: true });
    return this;
  }

  /**
   * Clears the output log.
   */
  clearLog() {
    // console.log("clear log");
    this[LOG_LIST] = this[LOG_LIST].clear();
    this.setCurrent({ log: this[LOG_LIST], showLog: false });

  }

  /**
   * Provides common access to localStorage.
   * @param key:         The unique identifier of the value (this is prefixed with the namespace).
   * @param value:       (optional). The value to set (pass null to remove).
   * @param options:
   *           default:  (optional). The default value to return if the session does not contain the value (ie. undefined).
   *
   * @return the read value.
   */
  localStorage(key, value, options) {
    return localStorage.prop(`ui-harness:${ key }`, value, options);
  }
}

// Singleton instance.
export default new Api();
<commit_msg>Removed lodash (_) from api-internal.<commit_after>import R from "ramda";
import React from "react";
import Immutable from "immutable";
import { delay, localStorage } from "js-util";
import rest from "rest-middleware/browser";
import bdd from "./bdd";
import apiConsole from "./api-console";

const LOG_LIST = Symbol("log-list");
const COMPONENT = Symbol("component");

/**
 * The API used internally by the UIHarness components.
 */
class Api {
  constructor() {
    this.current = Immutable.Map();
    this[LOG_LIST] = Immutable.List();
    this.loadInvokeCount = 0;
  }

  /**
   * Initializes the UIHarness environment.
   * @param {Function} callback: Invoked when ready to initialize the DOM.
   */
  init(callback) {
    // Put state into global namespace.
    bdd.register();
    global.UIHarness = global.uih = apiConsole;

    // Insert the <Shell> into the root.
    //    NB: Signal DOM ready after a delay to ensure that the [describe/it]
    //        have fully parsed before initial render. Avoids a redraw.
    delay(() => {
        // Ensure the last loaded suite is set as the current state.
        const suite = this.lastSelectedSuite();
        if (suite) {
          this.loadSuite(this.lastSelectedSuite(), { storeAsLastSuite:false });
        }
        callback()
    });

    // Finish up.
    return this;
  }

  /**
   * Resets the internal API.
   * @param {boolean} hard: Flag indicating if all state from local-storage
   *                        should be cleared away, or just current selection state.
   */
  reset({ hard = true } = {}) {
    if (hard) {
      this.clearLocalStorage();
    } else {
      this.clearLocalStorage("lastInvokedSpec:");
    }
    this.lastSelectedSuite(null);
    this.setCurrent(null);
    this.component(null);
    return this;
  }

  /**
   * Removes all ui-harness values stored in local-storage.
   */
  clearLocalStorage(startsWith = null) {
    localStorage.keys().forEach(key => {
        let match = "ui-harness:";
        if (startsWith) { match += startsWith; }
        if (key.startsWith(match)) {
          localStorage.prop(key, null); // Remove.
        }
      });
  }

  /**
   * Gets or sets the current component instance.
   * Pass {null} to clear.
   */
  component(value) {
    // WRITE.
    if (value !== undefined) {
      if (value === null && this[COMPONENT]) {
        // Unload component.
        delete this[COMPONENT];
        delete apiConsole.component;
        this.setCurrent({
          componentType: null,
          componentProps: null,
          componentChildren: null,
          component: null
        });

      } else {

        // Store component instance.
        this[COMPONENT] = value;
        apiConsole.component = value;
        if (this.current.get("component") !== value) {
          // NB: Preform instance comparison before updating the
          //     current state to prevent render loop.
          this.setCurrent({ component: value });
        }
      }
    }

    // READ.
    return this[COMPONENT];
  }

  /**
   * Loads the current suite into the Harness.
   *
   * @param suite: The {Suite} to load.
   * @param options
   *          - storeAsLastSuite: Flag indicating if the suite should be stored
   *                              as the last invoked suite in localStorage.
   *                              Default: true.
   */
  loadSuite(suite, { storeAsLastSuite = true } = {}) {
    // Setup initial conditions.
    if (!suite) { return this; }
    if (this.current.get("suite") === suite) { return this; }

    // Only load the suite if it does not have children
    // ie. is not a container/folder suite.
    if (suite.childSuites.length === 0) {
      // Clear the current state.
      this.setCurrent(null);

      // Prepare the new current state.
      let current = suite.meta.thisContext.toValues();
      current.suite = suite;
      current.indexMode = this.indexMode();
      current.isBeforeInvoked = false;
      this.setCurrent(current);
      if (storeAsLastSuite) { this.lastSelectedSuite(suite); }

      // Invoke before handlers.
      this.invokeBeforeHandlers(suite);

      // If the last invoked spec on the suite contained a load.
      let lastInvokedSpec = this.lastInvokedSpec(suite);
      if (lastInvokedSpec && lastInvokedSpec.spec && lastInvokedSpec.isLoader) {
        this.invokeSpec(lastInvokedSpec.spec);
      }
    }

    // Finish up.
    return this;
  }

  /**
   * Invokes the [before] handlers for
   * the given suite if required.
   * @return {boolean}  - true if the handlers were invoked
   *                    - false if they have already been invoked.
   */
  invokeBeforeHandlers(suite) {
    if (this.current.get("isBeforeInvoked")) { return false; }
    const self = suite.meta.thisContext;
    suite.beforeHandlers.invoke(self);
    this.current = this.current.set("isBeforeInvoked", true);
    return true
  }

  /**
   * Invokes the given spec.
   * @param spec: The [Spec] to invoke.
   * @param callback: Invoked upon completion.
   *                   Immediately if the spec is not asynchronous.
   */
  invokeSpec(spec, callback) {
    // Setup initial conditions.
    const suite = spec.parentSuite;
    const self = suite.meta.thisContext;
    this.invokeBeforeHandlers(suite)
    let loadInvokeCountBefore = this.loadInvokeCount;

    // Invoke the methods.
    spec.invoke(self, callback);

    // Store a reference to last-invoked spec.
    this.lastInvokedSpec(suite, {
      spec: spec,
      isLoader: (this.loadInvokeCount > loadInvokeCountBefore)
    });

    // Increment the current invoke count for the spec.
    let specInvokeCount = this.current.get("specInvokeCount") || {};
    let total = specInvokeCount[spec.id] || 0;
    specInvokeCount[spec.id] = total + 1;
    this.setCurrent({ specInvokeCount: specInvokeCount });

    // Finish up.
    return this;
  }

  /**
   * Invokes a spec on the server.
   * @param spec: The [Spec] to invoke.
   * @param callback: Invoked upon completion.
   */
  invokeServerSpec(spec, callback) {
    const server = rest();
    server.onReady(() => {
        server.methods.invokeSpec.put(spec.id)
        .then((result) => {
          // TODO:
          console.log("result", result);
        });
    });
  }

  /**
   * Gets or sets the last selected [Suite].
   */
  lastSelectedSuite(suite) {
    if (suite) { suite = suite.id; }
    const result = this.localStorage("lastSelectedSuite", suite);
    return bdd.suites[result];
  }

  /**
   * Gets or sets the last spec for the given suite
   * that was invoked that had a `.load()` call within it.
   */
  lastInvokedSpec(suite, { spec, isLoader = false} = {}) {
    const KEY = `lastInvokedSpec:${ suite.id }`;
    let value;
    if (spec !== undefined) {
      // WRITE.
      value = { spec: spec.id, isLoader: isLoader }
      spec = spec.id;
    }

    // READ.
    let result = this.localStorage(KEY, value);
    if (result) {
      result.spec = R.find(spec => spec.id === result.spec, suite.specs);
    }
    return result
  }

  /**
   * Gets or sets the display mode of the left-hand index.
   * @param {string} mode: tree|suite
   */
  indexMode(mode) {
    let result = this.localStorage("indexMode", mode, { default: "tree" });
    if (mode !== undefined) {
      // WRITE (store in current state).
      this.setCurrent({ indexMode:mode });
    }

    // READ.
    result = result || "tree";
    if (result !== "tree" && this.current.get("suite") === undefined) {
      result = "tree"
    }
    return result;
  }

  /**
   * Updates the current state with the given values.
   *     NOTE: These values are cumulatively added to the state.
   *           Use "reset" to clear the state.
   *
   * @param args:  An object containing they [key:values] to set
   *               or null to clear values.
   */
  setCurrent(args) {
    // Update the state object.
    if (args) {
      Object.keys(args).forEach(key => { this.current = this.current.set(key, args[key]) });
    } else {
      this.current = this.current.clear();
    }

    // Apply to the <Shell>.
    if (this.shell) { this.shell.setState({ current: this.current }); }
    return this;
  };

  /**
   * Logs a value to the output.
   * @param {array} values: The value or values to append.
   */
  log(...values) {
    values = R.flatten(values);
    const item = { time: new Date(), values };
    this[LOG_LIST] = this[LOG_LIST].push(item);
    this.setCurrent({ log: this[LOG_LIST], showLog: true });
    return this;
  }

  /**
   * Clears the output log.
   */
  clearLog() {
    // console.log("clear log");
    this[LOG_LIST] = this[LOG_LIST].clear();
    this.setCurrent({ log: this[LOG_LIST], showLog: false });

  }

  /**
   * Provides common access to localStorage.
   * @param key:         The unique identifier of the value (this is prefixed with the namespace).
   * @param value:       (optional). The value to set (pass null to remove).
   * @param options:
   *           default:  (optional). The default value to return if the session does not contain the value (ie. undefined).
   *
   * @return the read value.
   */
  localStorage(key, value, options) {
    return localStorage.prop(`ui-harness:${ key }`, value, options);
  }
}

// Singleton instance.
export default new Api();
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2018 The FOAM Authors. 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.
 */

/*
  Accessible through browser at location path static/foam2/src/foam/nanos/controller/index.html
  Available on browser console as ctrl. (exports axiom)
*/
foam.CLASS({
  package: 'foam.nanos.controller',
  name: 'ApplicationController',
  extends: 'foam.u2.Element',

  documentation: 'FOAM Application Controller.',

  implements: [
    'foam.box.Context',
    'foam.nanos.controller.AppStyles'
  ],

  requires: [
    'foam.nanos.client.ClientBuilder',
    'foam.nanos.auth.Group',
    'foam.nanos.auth.ResendVerificationEmail',
    'foam.nanos.auth.SignInView',
    'foam.nanos.auth.User',
    'foam.nanos.auth.resetPassword.ResetView',
    'foam.nanos.u2.navigation.TopNavigation',
    'foam.nanos.u2.navigation.FooterView',
    'foam.u2.stack.Stack',
    'foam.u2.stack.StackView'
  ],

  imports: [
    'installCSS',
    'sessionSuccess',
    'window'
  ],

  exports: [
    'as ctrl',
    'appConfig',
    'group',
    'loginSuccess',
    'logo',
    'requestLogin',
    'signUpEnabled',
    'stack',
    'currentMenu',
    'lastMenuLaunched',
    'menuListener',
    'lastMenuLaunchedListener',
    'user',
    'webApp',
    'wrapCSS as installCSS'
  ],

  constants: {
    MACROS: [ 'primaryColor', 'secondaryColor', 'tableColor', 'tableHoverColor', 'accentColor', 'secondaryHoverColor', 'groupCSS' ]
  },

  css: `
    body {
      font-family: 'Roboto', sans-serif;
      font-size: 14px;
      letter-spacing: 0.2px;
      color: #373a3c;
      background: #edf0f5;
      margin: 0;
    }
    .foam-u2-UnstyledActionView-signIn {
      margin-left: 25px !important;
    }
    .stack-wrapper {
      margin-bottom: -10px;
      min-height: calc(80% - 60px);
    }
    .stack-wrapper:after {
      content: "";
      display: block;
    }
    .foam-u2-UnstyledActionView:focus{
      outline: none;
    }
  `,

  properties: [
    {
      name: 'clientPromise',
      factory: function() {
        var self = this;
        return self.ClientBuilder.create().promise.then(function(cls) {
          self.client = cls.create(null, self);
          return self.client;
        });
      },
    },
    {
      name: 'client',
    },
    {
      name: 'appConfig',
      expression: function(client) {
        return client && client.appConfig || null;
      }
    },
    {
      name: 'stack',
      factory: function() { return this.Stack.create(); }
    },
    {
      class: 'foam.core.FObjectProperty',
      of: 'foam.nanos.auth.User',
      name: 'user',
      factory: function() { return this.User.create(); }
    },
    {
      class: 'foam.core.FObjectProperty',
      of: 'foam.nanos.auth.Group',
      name: 'group',
      factory: function() { return this.Group.create(); }
    },
    {
      class: 'Boolean',
      name: 'signUpEnabled',
      adapt: function(_, v) {
        return foam.String.isInstance(v) ? v !== 'false' : v;
      }
    },
    {
      class: 'Boolean',
      name: 'loginSuccess'
    },
    { class: 'URL', name: 'logo' },
    'currentMenu',
    'lastMenuLaunched',
    'webApp',
    'primaryColor',
    'secondaryColor',
    'secondaryHoverColor',
    'tableColor',
    'tableHoverColor',
    'accentColor',
    'groupCSS',
    'topNavigation_',
    'footerView_'
  ],

  methods: [
    function init() {
      this.SUPER();
      var self = this;
      self.clientPromise.then(function(client) {
        self.setPrivate_('__subContext__', client.__subContext__);
        foam.__context__.register(foam.u2.UnstyledActionView, 'foam.u2.ActionView');
        self.getCurrentUser();

        window.onpopstate = function(event) {
          if ( location.hash != null) {
            var hid = location.hash.substr(1);

            hid && self.client.menuDAO.find(hid).then(function(menu) {
              menu && menu.launch(this, null);
            });
          }
        };
      });
    },

    function initE() {
      var self = this;
      self.clientPromise.then(function() {
        self
          .addClass(self.myClass())
          .start('div', null, self.topNavigation_$).end()
          .start('div').addClass('stack-wrapper')
            .tag({class: 'foam.u2.stack.StackView', data: self.stack, showActions: false})
          .end()
          .start('div', null, self.footerView_$).end();

          // Sets up application view
          self.topNavigation_.add(self.TopNavigation.create());
          self.footerView_.add(self.FooterView.create());
      });
    },

    function setPortalView(group) {
      // Replaces contents of top navigation and footer view with group views
      this.topNavigation_ && this.topNavigation_.replaceChild(
        foam.lookup(group.topNavigation).create(null, this),
        this.topNavigation_.children[0]
      );

      this.footerView_ && this.footerView_.replaceChild(
        foam.lookup(group.footerView).create(null, this),
        this.footerView_.children[0]
      );
    },

    function getCurrentUser() {
      var self = this;

      // get current user, else show login
      this.client.auth.getCurrentUser(null).then(function (result) {
        self.loginSuccess = !! result;
        if ( result ) {
          self.user.copyFrom(result);
          if ( ! self.user.emailVerified ) {
            self.stack.push({ class: 'foam.nanos.auth.ResendVerificationEmail' });
            return;
          }
          self.onUserUpdate();
        }
      })
      .catch(function (err) {
        self.requestLogin().then(function() {
          self.getCurrentUser();
        });
      });
    },

    function expandShortFormMacro(css, m) {
      /* A short-form macros is of the form %PRIMARY_COLOR%. */
      var M = m.toUpperCase();

      return css.replace(
        new RegExp("%" + M + "%", 'g'),
        '/*%' + M + '%*/ ' + this[m]);
    },

    function expandLongFormMacro(css, m) {
      // A long-form macros is of the form "/*%PRIMARY_COLOR%*/ blue".
      var M = m.toUpperCase();

      return css.replace(
        new RegExp('/\\*%' + M + '%\\*/[^;]*', 'g'),
        '/*%' + M + '%*/ ' + this[m]);
    },

    // CSS preprocessor, works on classes instantiated in subContext
    function wrapCSS(text, id) {
      if ( text ) {
        if ( ! this.accentColor ) {
          var self = this;

          this.accentColor$.sub(function(s) {
            self.wrapCSS(text, id);
            s.detach();
          });
        }

        let eid = foam.u2.Element.NEXT_ID();

        for ( var i = 0 ; i < this.MACROS.length ; i++ ) {
          let m     = this.MACROS[i];
          var text2 = this.expandShortFormMacro(this.expandLongFormMacro(text, m), m);

            // If the macro was found, then listen for changes to the property
            // and update the CSS if it changes.
            if ( text != text2 ) {
              text = text2;
              this.slot(m).sub(function() {
                var el = this.getElementById(eid);
                el.innerText = this.expandLongFormMacro(el.innerText, m);
              }.bind(this));
            }
        }

        this.installCSS(text, id, eid);
      }
    },

    function requestLogin() {
      var self = this;

      // don't go to log in screen if going to reset password screen
      if ( location.hash != null && location.hash === '#reset')
        return new Promise(function (resolve, reject) {
          self.stack.push({ class: 'foam.nanos.auth.resetPassword.ResetView' });
          self.loginSuccess$.sub(resolve);
        });

      return new Promise(function(resolve, reject) {
        self.stack.push({ class: 'foam.nanos.auth.SignInView' });
        self.loginSuccess$.sub(resolve);
      });
    }
  ],

  listeners: [
    async function onUserUpdate() {
      var group = await this.client.groupDAO.find(this.user.group);

      this.group.copyFrom(group);
      this.setPortalView(group);

      for ( var i = 0; i < this.MACROS.length; i++ ) {
        var m = this.MACROS[i];
        if ( group[m] ) this[m] = group[m];
      }

      var hash = this.window.location.hash;
      if ( hash ) hash = hash.substring(1);

      if ( hash ) {
        window.onpopstate();
      } else if ( group ) {
        this.window.location.hash = group.defaultMenu;
      }
    },

    // This listener should be called when a Menu item has been launched
    // by some Menu View. Is exported.
    function menuListener(m) {
      this.currentMenu = m;
    },

    // This listener should be called when a Menu has been launched but does
    // not navigate to a new screen. Typically for SubMenus
    function lastMenuLaunchedListener(m) {
      this.lastMenuLaunched = m;
    }
  ]
});
<commit_msg>Add disabled colour macro<commit_after>/**
 * @license
 * Copyright 2018 The FOAM Authors. 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.
 */

/*
  Accessible through browser at location path static/foam2/src/foam/nanos/controller/index.html
  Available on browser console as ctrl. (exports axiom)
*/
foam.CLASS({
  package: 'foam.nanos.controller',
  name: 'ApplicationController',
  extends: 'foam.u2.Element',

  documentation: 'FOAM Application Controller.',

  implements: [
    'foam.box.Context',
    'foam.nanos.controller.AppStyles'
  ],

  requires: [
    'foam.nanos.client.ClientBuilder',
    'foam.nanos.auth.Group',
    'foam.nanos.auth.ResendVerificationEmail',
    'foam.nanos.auth.SignInView',
    'foam.nanos.auth.User',
    'foam.nanos.auth.resetPassword.ResetView',
    'foam.nanos.u2.navigation.TopNavigation',
    'foam.nanos.u2.navigation.FooterView',
    'foam.u2.stack.Stack',
    'foam.u2.stack.StackView'
  ],

  imports: [
    'installCSS',
    'sessionSuccess',
    'window'
  ],

  exports: [
    'as ctrl',
    'appConfig',
    'group',
    'loginSuccess',
    'logo',
    'requestLogin',
    'signUpEnabled',
    'stack',
    'currentMenu',
    'lastMenuLaunched',
    'menuListener',
    'lastMenuLaunchedListener',
    'user',
    'webApp',
    'wrapCSS as installCSS'
  ],

  constants: {
    MACROS: [ 'primaryColor', 'secondaryColor', 'tableColor', 'tableHoverColor', 'accentColor', 'secondaryHoverColor', 'secondaryDisabledColor', 'groupCSS' ]
  },

  css: `
    body {
      font-family: 'Roboto', sans-serif;
      font-size: 14px;
      letter-spacing: 0.2px;
      color: #373a3c;
      background: #edf0f5;
      margin: 0;
    }
    .foam-u2-UnstyledActionView-signIn {
      margin-left: 25px !important;
    }
    .stack-wrapper {
      margin-bottom: -10px;
      min-height: calc(80% - 60px);
    }
    .stack-wrapper:after {
      content: "";
      display: block;
    }
    .foam-u2-UnstyledActionView:focus{
      outline: none;
    }
  `,

  properties: [
    {
      name: 'clientPromise',
      factory: function() {
        var self = this;
        return self.ClientBuilder.create().promise.then(function(cls) {
          self.client = cls.create(null, self);
          return self.client;
        });
      },
    },
    {
      name: 'client',
    },
    {
      name: 'appConfig',
      expression: function(client) {
        return client && client.appConfig || null;
      }
    },
    {
      name: 'stack',
      factory: function() { return this.Stack.create(); }
    },
    {
      class: 'foam.core.FObjectProperty',
      of: 'foam.nanos.auth.User',
      name: 'user',
      factory: function() { return this.User.create(); }
    },
    {
      class: 'foam.core.FObjectProperty',
      of: 'foam.nanos.auth.Group',
      name: 'group',
      factory: function() { return this.Group.create(); }
    },
    {
      class: 'Boolean',
      name: 'signUpEnabled',
      adapt: function(_, v) {
        return foam.String.isInstance(v) ? v !== 'false' : v;
      }
    },
    {
      class: 'Boolean',
      name: 'loginSuccess'
    },
    { class: 'URL', name: 'logo' },
    'currentMenu',
    'lastMenuLaunched',
    'webApp',
    'primaryColor',
    'secondaryColor',
    'secondaryHoverColor',
    'secondaryDisabledColor',
    'tableColor',
    'tableHoverColor',
    'accentColor',
    'groupCSS',
    'topNavigation_',
    'footerView_'
  ],

  methods: [
    function init() {
      this.SUPER();
      var self = this;
      self.clientPromise.then(function(client) {
        self.setPrivate_('__subContext__', client.__subContext__);
        foam.__context__.register(foam.u2.UnstyledActionView, 'foam.u2.ActionView');
        self.getCurrentUser();

        window.onpopstate = function(event) {
          if ( location.hash != null) {
            var hid = location.hash.substr(1);

            hid && self.client.menuDAO.find(hid).then(function(menu) {
              menu && menu.launch(this, null);
            });
          }
        };
      });
    },

    function initE() {
      var self = this;
      self.clientPromise.then(function() {
        self
          .addClass(self.myClass())
          .start('div', null, self.topNavigation_$).end()
          .start('div').addClass('stack-wrapper')
            .tag({class: 'foam.u2.stack.StackView', data: self.stack, showActions: false})
          .end()
          .start('div', null, self.footerView_$).end();

          // Sets up application view
          self.topNavigation_.add(self.TopNavigation.create());
          self.footerView_.add(self.FooterView.create());
      });
    },

    function setPortalView(group) {
      // Replaces contents of top navigation and footer view with group views
      this.topNavigation_ && this.topNavigation_.replaceChild(
        foam.lookup(group.topNavigation).create(null, this),
        this.topNavigation_.children[0]
      );

      this.footerView_ && this.footerView_.replaceChild(
        foam.lookup(group.footerView).create(null, this),
        this.footerView_.children[0]
      );
    },

    function getCurrentUser() {
      var self = this;

      // get current user, else show login
      this.client.auth.getCurrentUser(null).then(function (result) {
        self.loginSuccess = !! result;
        if ( result ) {
          self.user.copyFrom(result);
          if ( ! self.user.emailVerified ) {
            self.stack.push({ class: 'foam.nanos.auth.ResendVerificationEmail' });
            return;
          }
          self.onUserUpdate();
        }
      })
      .catch(function (err) {
        self.requestLogin().then(function() {
          self.getCurrentUser();
        });
      });
    },

    function expandShortFormMacro(css, m) {
      /* A short-form macros is of the form %PRIMARY_COLOR%. */
      var M = m.toUpperCase();

      return css.replace(
        new RegExp("%" + M + "%", 'g'),
        '/*%' + M + '%*/ ' + this[m]);
    },

    function expandLongFormMacro(css, m) {
      // A long-form macros is of the form "/*%PRIMARY_COLOR%*/ blue".
      var M = m.toUpperCase();

      return css.replace(
        new RegExp('/\\*%' + M + '%\\*/[^;]*', 'g'),
        '/*%' + M + '%*/ ' + this[m]);
    },

    // CSS preprocessor, works on classes instantiated in subContext
    function wrapCSS(text, id) {
      if ( text ) {
        if ( ! this.accentColor ) {
          var self = this;

          this.accentColor$.sub(function(s) {
            self.wrapCSS(text, id);
            s.detach();
          });
        }

        let eid = foam.u2.Element.NEXT_ID();

        for ( var i = 0 ; i < this.MACROS.length ; i++ ) {
          let m     = this.MACROS[i];
          var text2 = this.expandShortFormMacro(this.expandLongFormMacro(text, m), m);

            // If the macro was found, then listen for changes to the property
            // and update the CSS if it changes.
            if ( text != text2 ) {
              text = text2;
              this.slot(m).sub(function() {
                var el = this.getElementById(eid);
                el.innerText = this.expandLongFormMacro(el.innerText, m);
              }.bind(this));
            }
        }

        this.installCSS(text, id, eid);
      }
    },

    function requestLogin() {
      var self = this;

      // don't go to log in screen if going to reset password screen
      if ( location.hash != null && location.hash === '#reset')
        return new Promise(function (resolve, reject) {
          self.stack.push({ class: 'foam.nanos.auth.resetPassword.ResetView' });
          self.loginSuccess$.sub(resolve);
        });

      return new Promise(function(resolve, reject) {
        self.stack.push({ class: 'foam.nanos.auth.SignInView' });
        self.loginSuccess$.sub(resolve);
      });
    }
  ],

  listeners: [
    async function onUserUpdate() {
      var group = await this.client.groupDAO.find(this.user.group);

      this.group.copyFrom(group);
      this.setPortalView(group);

      for ( var i = 0; i < this.MACROS.length; i++ ) {
        var m = this.MACROS[i];
        if ( group[m] ) this[m] = group[m];
      }

      var hash = this.window.location.hash;
      if ( hash ) hash = hash.substring(1);

      if ( hash ) {
        window.onpopstate();
      } else if ( group ) {
        this.window.location.hash = group.defaultMenu;
      }
    },

    // This listener should be called when a Menu item has been launched
    // by some Menu View. Is exported.
    function menuListener(m) {
      this.currentMenu = m;
    },

    // This listener should be called when a Menu has been launched but does
    // not navigate to a new screen. Typically for SubMenus
    function lastMenuLaunchedListener(m) {
      this.lastMenuLaunched = m;
    }
  ]
});
<|endoftext|>


<commit_before>define(['gizmo/superdesk'], function(Gizmo) {
	/*!
	 * Extended collection which is autoupdateing itself
	 */
	return Gizmo.Collection.extend
	({
		_timeInterval: 10000,
		_idInterval: 0,
		_stats: { limit: 15, offset: 0, lastCId: 0, fistOrder: Infinity, total: 0 },
		/*!
		 * for auto refresh
		 */
		keep: false,
		init: function(){
			var self = this;
			self.model.on('publish reorder', function(evt, post){
				if((self._stats.lastCId + 1) === parseInt(post.get('CId')))
					self._stats.lastCId++;
			});
			self.on('readauto', function(evt, data, attr){
				//console.log('attr.lastCId: ',attr);
				// set offset to limit
				self._stats.offset = self._stats.limit;
				// set total from the attributes
				self._stats.total = parseInt(attr.total);
				attr.lastCId = parseInt(attr.lastCId);
				if(attr.lastCId > self._stats.lastCId) {
					self._stats.lastCId = attr.lastCId;
				}
				self.getFirstOrder(data);
			}).on('readauto updatesauto addingsauto removeingsauto update',function(evt, data)
			{
				self.getLastCid(data);
				self.getFirstOrder(data);
			}).on('addingsauto', function(evt, data){
				/*!
				 * If addings ( from getting the auto updates )
				 *   receive we need to increase the total count of the posts and the offset
				 *   with the numbers of posts added
				 */
				self._stats.total += data.length;
				self._stats.offset += data.length;

			}).on('removeingsauto', function(evt, data){
				/*!
				 * If removeings from the collection ( from getting the autou pdates )
				 *   receive we need to decrease the total and the offset for the next page
				 *   with the numbers of posts added
				 */
				self._stats.total -= data.length;
				self._stats.offset -= data.length;

			}).on('addings', function(evt, data){
				/*!
				 * If addings ( from getting the next page )
				 *   receive we need to increase the offset for the next page
				 *   with the numbers of posts added
				 */
				self._stats.offset += data.length;
			});
		},
		destroy: function(){ this.stop(); },
		auto: function(fn)
		{
			var self = this;
			ret = this.stop().start();
			this._idInterval = setInterval(function(){self.start();}, this._timeInterval);
			return ret;
		},
		start: function()
		{
			var self = this, requestOptions = {data: {'cId.since': this._stats.lastCId, 'order.start': this._stats.fistOrder }, headers:  { 'X-Filter': self._xfilter, 'X-Format-DateTime': 'M/dd/yyyy HH:mm:ss'}};
			if(self._stats.lastCId === 0) delete requestOptions.data;
			if(!this.keep && self.view && !self.view.checkElement())
			{
				self.stop();
				return;
			}
			this.triggerHandler('beforeUpdate');
			return this.autosync(requestOptions);
		},
		stop: function()
		{
			var self = this;
			clearInterval(self._idInterval);
			return this;
		},
		/*!
		 * Get the minim Order value from the post list received.
		 */
		getFirstOrder: function(data)
		{
			for(var i=0, Order, count=data.length; i<count; i++) {
				Order = parseFloat(data[i].get('Order'))
				if( !isNaN(Order) && (this._stats.fistOrder > Order) )
					this._stats.fistOrder = Order;
			}
		},
		/*!
		 * Get the maximum CId value from the post list received.
		 */
		getLastCid: function(data)
		{
			for(var i=0, CId, count=data.length; i<count; i++) {
				var CId = parseInt(data[i].get('CId'))
				if( !isNaN(CId) && (this._stats.lastCId < CId) )
					this._stats.lastCId = CId;
			}
		},
        autosync: function()
        {
        var self = this;
        return (this.href &&
            this.syncAdapter.request.call(this.syncAdapter, this.href).read(arguments[0]).done(function(data)
            {
                var attr = self.parseAttributes(data), list = self._parse(data), changeset = [], removeings = [], updates = [], addings = [], count = self._list.length;
                // important or it will infiloop
                for( var i=0; i < list.length; i++ )
                {
                    var model = false;
                    for( var j=0; j<self._list.length; j++ ) {
						if( list[i].hash() == self._list[j].hash() )
                        {
							model = list[i];
                            break;
                        }
					}
//					console.log('length', list[i].length);
//					console.log(self._list);
                    if( !model ) {
                        if( !list[i].isDeleted() ) {
							self._list.push(list[i]);
							changeset.push(list[i]);
                            if( self.hasEvent('addingsauto') ) {
                                addings.push(list[i]);
                            }
						} else {
                            if( self.hasEvent('updatesauto') ) {
							    updates.push(list[i]);
                            }
						}
                    }
                    else {
                        if( self.hasEvent('updatesauto') ) {
                            updates.push(model);
                        }
                        if(self.isCollectionDeleted(model)) {
                            self._list.splice(j,1);
                            if( self.hasEvent('removeingsauto') ) {
                                removeings.push(model);
                            }

                        }
                        if( model.isDeleted()) {
                            model._remove();
                        } else if( model.isChanged() ){
							changeset.push(model);
						}
                        else {
                            model.on('delete', function(){ self.remove(this.hash()); })
                                    .on('garbage', function(){ this.desynced = true; });
                        }
                    }
                }
                self.desynced = false;
				/**
				 * If the initial data is empty then trigger READ event
				 * else UPDATE with the changeset if there are some
				 */
				if( ( count === 0) ){
					//console.log('read: ',$.extend({},attr));
					self.triggerHandler('readauto',[self._list,attr]);
                } else {
                    /**
                     * Trigger handler with changeset extraparameter as a vector of vectors,
                     * caz jquery will send extraparameters as arguments when calling handler
                     */
                    if( updates.length && self.hasEvent('updatesauto') ) {
                        self.triggerHandler('updatesauto', [updates,attr]);
                    }
                    if( addings.length && self.hasEvent('addingsauto') ) {
                        self.triggerHandler('addingsauto', [addings,attr]);
                    }
                    if( removeings.length && self.hasEvent('removeingsauto') ) {
                        self.triggerHandler('removeingsauto', [removeings,attr]);
                    }
					self.triggerHandler('updateauto', [changeset,attr]);
				}
            }));
        }
	},{ register: 'AutoCollection' });
});<commit_msg>NR-694: Collaborators tab is not updated with posts created by other collaborators<commit_after>define(['gizmo/superdesk'], function(Gizmo) {
	/*!
	 * Extended collection which is autoupdateing itself
	 */
	return Gizmo.Collection.extend
	({
		_timeInterval: 10000,
		_idInterval: 0,
		_stats: { limit: 15, offset: 0, lastCId: 0, fistOrder: Infinity, total: 0 },
		/*!
		 * for auto refresh
		 */
		keep: false,
		init: function(){
			var self = this;
			self.model.on('publish reorder', function(evt, post){
				if((self._stats.lastCId + 1) === parseInt(post.get('CId')))
					self._stats.lastCId++;
			});
			self.on('readauto', function(evt, data, attr){
				//console.log('attr.lastCId: ',attr);
				// set offset to limit
				self._stats.offset = self._stats.limit;
				// set total from the attributes
				self._stats.total = parseInt(attr.total);
				attr.lastCId = parseInt(attr.lastCId);
				if(attr.lastCId > self._stats.lastCId) {
					self._stats.lastCId = attr.lastCId;
				}
				self.getFirstOrder(data);
			}).on('readauto updatesauto addingsauto removeingsauto update',function(evt, data)
			{
				self.getLastCid(data);
				self.getFirstOrder(data);
			}).on('addingsauto', function(evt, data){
				/*!
				 * If addings ( from getting the auto updates )
				 *   receive we need to increase the total count of the posts and the offset
				 *   with the numbers of posts added
				 */
				self._stats.total += data.length;
				self._stats.offset += data.length;

			}).on('removeingsauto', function(evt, data){
				/*!
				 * If removeings from the collection ( from getting the autou pdates )
				 *   receive we need to decrease the total and the offset for the next page
				 *   with the numbers of posts added
				 */
				self._stats.total -= data.length;
				self._stats.offset -= data.length;

			}).on('addings', function(evt, data){
				/*!
				 * If addings ( from getting the next page )
				 *   receive we need to increase the offset for the next page
				 *   with the numbers of posts added
				 */
				self._stats.offset += data.length;
			});
		},
		destroy: function(){ this.stop(); },
		auto: function(fn)
		{
			var self = this;
			ret = this.stop().start();
			this._idInterval = setInterval(function(){self.start();}, this._timeInterval);
			return ret;
		},
		start: function()
		{
			var self = this, requestOptions = {data: {'cId.since': this._stats.lastCId, 'order.start': this._stats.fistOrder }, headers:  { 'X-Filter': self._xfilter, 'X-Format-DateTime': 'M/dd/yyyy HH:mm:ss'}};
			if(self._stats.lastCId === 0) delete requestOptions.data;
			if(!this.keep && self.view && !self.view.checkElement())
			{
				self.stop();
				return;
			}
			this.triggerHandler('beforeUpdate');
			return this.autosync(requestOptions);
		},
		stop: function()
		{
			var self = this;
			clearInterval(self._idInterval);
			return this;
		},
		/*!
		 * Get the minim Order value from the post list received.
		 */
		getFirstOrder: function(data)
		{
			for(var i=0, Order, count=data.length; i<count; i++) {
				Order = parseFloat(data[i].get('Order'))
				if( !isNaN(Order) && (this._stats.fistOrder > Order) )
					this._stats.fistOrder = Order;
			}
		},
		/*!
		 * Get the maximum CId value from the post list received.
		 */
		getLastCid: function(data)
		{
			for(var i=0, CId, count=data.length; i<count; i++) {
				var CId = parseInt(data[i].get('CId'))
				if( !isNaN(CId) && (this._stats.lastCId < CId) )
					this._stats.lastCId = CId;
			}
		},
        autosync: function()
        {
        var self = this;
        return (this.href &&
            this.syncAdapter.request.call(this.syncAdapter, this.href).read(arguments[0]).done(function(data)
            {
                var attr = self.parseAttributes(data), list = self._parse(data), changeset = [], removeings = [], updates = [], addings = [], count = self._list.length;
                // important or it will infiloop
                for( var i=0; i < list.length; i++ )
                {
                    var model = false;
                    for( var j=0; j<self._list.length; j++ ) {
						if( list[i].hash() == self._list[j].hash() )
                        {
							model = list[i];
                            break;
                        }
					}
//					console.log('length', list[i].length);
//					console.log('model: ',model);
//					console.log('list: ',self._list);
                    if( !model ) {
                        if(self.isCollectionDeleted(list[i])) {
                            if( self.hasEvent('removeingsauto') ) {
                                if( self.hasEvent('removeingsauto') ) {
                                	removeings.push(list[i]);
                            	}
                            }
                        } else if( !list[i].isDeleted() ) {
								self._list.push(list[i]);
								changeset.push(list[i]);
                            if( self.hasEvent('addingsauto') ) {
                                addings.push(list[i]);
                            }
						} else {
                            if( self.hasEvent('updatesauto') ) {
							    updates.push(list[i]);
                            }
						}
                    }
                    else {
                        if( self.hasEvent('updatesauto') ) {
                            updates.push(model);
                        }
                        if(self.isCollectionDeleted(model)) {
                            self._list.splice(j,1);
                            if( self.hasEvent('removeingsauto') ) {
                                removeings.push(model);
                            }

                        }
                        if( model.isDeleted()) {
                            model._remove();
                        } else if( model.isChanged() ){
							changeset.push(model);
						}
                        else {
                            model.on('delete', function(){ self.remove(this.hash()); })
                                    .on('garbage', function(){ this.desynced = true; });
                        }
                    }
                }
                self.desynced = false;
				/**
				 * If the initial data is empty then trigger READ event
				 * else UPDATE with the changeset if there are some
				 */
				if( ( count === 0) ){
					//console.log('read: ',$.extend({},attr));
					self.triggerHandler('readauto',[self._list,attr]);
                } else {
                    /**
                     * Trigger handler with changeset extraparameter as a vector of vectors,
                     * caz jquery will send extraparameters as arguments when calling handler
                     */
                    if( updates.length && self.hasEvent('updatesauto') ) {
                        self.triggerHandler('updatesauto', [updates,attr]);
                    }
                    if( addings.length && self.hasEvent('addingsauto') ) {
                        self.triggerHandler('addingsauto', [addings,attr]);
                    }
                    if( removeings.length && self.hasEvent('removeingsauto') ) {
                        self.triggerHandler('removeingsauto', [removeings,attr]);
                    }
					self.triggerHandler('updateauto', [changeset,attr]);
				}
            }));
        }
	},{ register: 'AutoCollection' });
});<|endoftext|>


<commit_before>(function () {
  'use strict';

  function ready () {
    $.removeNodes('iframe');

    var node = $.$('#continuetoimage > form input');
    if (node) {
      // first pass
      node.click();
      // somehow imgrun.net need to click twice
      node.click();
      return;
    }

    // second pass
    var i = $('img[class^=centred]');
    $.openImage(i.src);
  }

  $.register({
    rule: [
      {
        host: [
          // com
          // starts with image
          /^image(ontime|corn|picsa)\.com$/,
          // ends with image
          /^(zonezeed|zelje|croft|myhot|bok|hostur)image\.com$/,
          // starts with img
          /^img(rill|next|savvy|\.spicyzilla|twyti|xyz|devil|tzar|ban|pu|beer)\.com$/,
          // starts with img-
          /^img-(zone|planet)\.com$/,
          // starts with www
          /^www\.(imagefolks|img(blow|lemon))\.com$/,
          // else
          /^(picstwist|ericsony|wpc8|uplimg|xxx\.pornprimehd|lexiit|thumbnailus|nimplus)\.com$/,
          /^((i|hentai)\.)?imgslip\.com$/,
          /^(i|xxx)\.hentaiyoutube\.com$/,
          /^(go|er)imge\.com$/,
          /^(like\.)?08lkk\.com$/,
          /^(www\.)?\.imgult\.com$/,
          /^nudeximg\.com$/,
          /imgseeds\.com$/,
          /damimage\.com$/,
          /imagedecode\.com$/,
          // net
          /^img(serve|coin|fap|candy|master|-view|run)\.net$/,
          /^(gallerycloud|imagelaser|project-photo|pix-link)\.net$/,
          // org
          /^img(studio|spot)\.org$/,
          /^image(\.adlock|on|team)\.org$/,
          /^(dragimage|teenshot)\.org$/,
          // else
          /^(hotimages|55888)\.eu$/,
          /^imgcloud\.co$/,
          /^pixup\.us$/,
          /^bulkimg\.info$/,
          /^img\.yt$/,
          /^vava\.in$/,
          /^pixxx\.me$/,
          /^porno-pirat\.ru$/,
        ],
        path: /^\/img-.*\.html$/,
      },
      {
        host: [
          /^img(run|twyti)\.net$/,
          /^imgtwyti\.com$/,
        ],
        path: /^\/t\/img-.*\.html$/,
      },
      {
        host: /^imgking\.co$/,
        path: /^\/img-.*\.htmls$/,
      },
    ],
    ready: ready,
  });

  $.register({
    rule: {
      host: /^www.img(adult|taxi).com$/,
      path: /^\/img-.*\.html$/,
    },
    start: function () {
      var c = $.getCookie('user');
      if (c) {
        return;
      }
      $.setCookie('user', 'true');
      window.location.reload();
    },
    ready: ready,
  });

  function helper () {
    // crack the shitty qqc.co visitScript 5440
    $.window.setTimeout = _.nop;

    // this site checks cookie that caculate from session
    // do an AJAX to skip checking
    return $.get(window.location.toString()).then(function (data) {
      return $.toDOM(data);
    });
  }

  $.register({
    rule: {
      host: /^08lkk\.com$/,
      path: /^\/Photo\/img-.+\.html$/,
    },
    start: function () {
      helper().then(function (page) {
        var i = $('img[class^=centred]', page);
        $.openImage(i.src);
      });
    },
  });

  $.register({
    rule: {
      host: /^08lkk\.com$/,
      path: /^\/\d+\/img-.*\.html$/,
    },
    start: function () {
      helper().then(function (page) {
        var bbcode = $.$('#imagecodes input', page);
        bbcode = bbcode.value.match(/.+\[IMG\]([^\[]+)\[\/IMG\].+/);
        bbcode = bbcode[1];
        bbcode = bbcode.replace('small', 'big');

        $.openImage(bbcode);
      });
    },
  });

})();

// ex: ts=2 sts=2 sw=2 et
// sublime: tab_size 2; translate_tabs_to_spaces true; detect_indentation false; use_tab_stops true;
// kate: space-indent on; indent-width 2;
<commit_msg>newimagepost.com<commit_after>(function () {
  'use strict';

  function ready () {
    $.removeNodes('iframe');

    var node = $.$('#continuetoimage > form input');
    if (node) {
      // first pass
      node.click();
      // somehow imgrun.net need to click twice
      node.click();
      return;
    }

    // second pass
    var i = $('img[class^=centred]');
    $.openImage(i.src);
  }

  $.register({
    rule: [
      {
        host: [
          // com
          // starts with image
          /^image(ontime|corn|picsa)\.com$/,
          // ends with image
          /^(zonezeed|zelje|croft|myhot|bok|hostur)image\.com$/,
          // starts with img
          /^img(rill|next|savvy|\.spicyzilla|twyti|xyz|devil|tzar|ban|pu|beer)\.com$/,
          // starts with img-
          /^img-(zone|planet)\.com$/,
          // starts with www
          /^www\.(imagefolks|img(blow|lemon))\.com$/,
          // else
          /^(picstwist|ericsony|wpc8|uplimg|xxx\.pornprimehd|lexiit|thumbnailus|nimplus|newimagepost)\.com$/,
          /^((i|hentai)\.)?imgslip\.com$/,
          /^(i|xxx)\.hentaiyoutube\.com$/,
          /^(go|er)imge\.com$/,
          /^(like\.)?08lkk\.com$/,
          /^(www\.)?\.imgult\.com$/,
          /^nudeximg\.com$/,
          /imgseeds\.com$/,
          /damimage\.com$/,
          /imagedecode\.com$/,
          // net
          /^img(serve|coin|fap|candy|master|-view|run)\.net$/,
          /^(gallerycloud|imagelaser|project-photo|pix-link)\.net$/,
          // org
          /^img(studio|spot)\.org$/,
          /^image(\.adlock|on|team)\.org$/,
          /^(dragimage|teenshot)\.org$/,
          // else
          /^(hotimages|55888)\.eu$/,
          /^imgcloud\.co$/,
          /^pixup\.us$/,
          /^bulkimg\.info$/,
          /^img\.yt$/,
          /^vava\.in$/,
          /^pixxx\.me$/,
          /^porno-pirat\.ru$/,
        ],
        path: /^\/img-.*\.html$/,
      },
      {
        host: [
          /^img(run|twyti)\.net$/,
          /^imgtwyti\.com$/,
        ],
        path: /^\/t\/img-.*\.html$/,
      },
      {
        host: /^imgking\.co$/,
        path: /^\/img-.*\.htmls$/,
      },
    ],
    ready: ready,
  });

  $.register({
    rule: {
      host: /^www.img(adult|taxi).com$/,
      path: /^\/img-.*\.html$/,
    },
    start: function () {
      var c = $.getCookie('user');
      if (c) {
        return;
      }
      $.setCookie('user', 'true');
      window.location.reload();
    },
    ready: ready,
  });

  function helper () {
    // crack the shitty qqc.co visitScript 5440
    $.window.setTimeout = _.nop;

    // this site checks cookie that caculate from session
    // do an AJAX to skip checking
    return $.get(window.location.toString()).then(function (data) {
      return $.toDOM(data);
    });
  }

  $.register({
    rule: {
      host: /^08lkk\.com$/,
      path: /^\/Photo\/img-.+\.html$/,
    },
    start: function () {
      helper().then(function (page) {
        var i = $('img[class^=centred]', page);
        $.openImage(i.src);
      });
    },
  });

  $.register({
    rule: {
      host: /^08lkk\.com$/,
      path: /^\/\d+\/img-.*\.html$/,
    },
    start: function () {
      helper().then(function (page) {
        var bbcode = $.$('#imagecodes input', page);
        bbcode = bbcode.value.match(/.+\[IMG\]([^\[]+)\[\/IMG\].+/);
        bbcode = bbcode[1];
        bbcode = bbcode.replace('small', 'big');

        $.openImage(bbcode);
      });
    },
  });

})();

// ex: ts=2 sts=2 sw=2 et
// sublime: tab_size 2; translate_tabs_to_spaces true; detect_indentation false; use_tab_stops true;
// kate: space-indent on; indent-width 2;
<|endoftext|>


<commit_before>// namespace
var App = App || {};

App.PlayMissionState = (function () {
    "use strict";

    var fn = function (game) {
        Phaser.State.call(this, game);
    };

    fn.prototype = Object.create(Phaser.State.prototype);
    fn.prototype.constructor = fn;

    fn.prototype.init = function () {
        this.hud = new App.HUD(this.game, this.state);

        this.sector = {
            name: "Test Sector",
            width: this.game.world.width * 2,
            height: this.game.world.height * 2
        };

        // create a shorter accessor to the player object
        this.player = this.game.global.player;
    };

    fn.prototype.preload = function () {
        // image assets
        this.load.image('space', 'assets/images/spaceBGDarkPurple.png');
        this.load.image('player', this.player.getHullAsset().file);
        this.load.image('enemy1', 'assets/images/enemyShipG.png');
        this.load.image('greenLaser', 'assets/images/LaserGreen.png');
        this.load.image('redLaser', 'assets/images/LaserRed.png');

        // audio assets
        this.game.load.audio('thrust', 'assets/sounds/thrust.wav');

        // hud assets
        this.hud.loadAssets();
    };

    fn.prototype.create = function () {
        // background
        this.background = this.add.tileSprite(0, 0, this.sector.width, this.sector.height, 'space');
        this.game.world.setBounds(0, 0, this.sector.width, this.sector.height);

        // use p2 for ships
        this.game.physics.startSystem(Phaser.Physics.P2JS);

        // setup player ship spite
        this.player.setShipSprite(this.add.sprite(this.game.world.width / 2, this.game.world.height / 2, 'player'));
        this.player_ship = this.player.getShipSprite(); // easier accessor to player ship sprite
        this.player_ship.anchor.setTo(this.player.getHullAsset().anchor);
        this.player_ship.scale.setTo(this.player.getHullAsset().scale);

        this.game.physics.p2.enable(this.player_ship);

        this.enemy1 = this.add.sprite(this.game.world.width / 3, this.game.world.height / 3, 'enemy1');
        this.enemy1.anchor.setTo(0.5);
        this.enemy1.scale.setTo(-0.5);
        this.enemy1.follow = 0;
        this.enemy1.followx = this.game.world.randomX;
        this.enemy1.followy = this.game.world.randomY;

        this.game.physics.p2.enable(this.enemy1);

        this.cursors = game.input.keyboard.createCursorKeys();

        //  Notice that the sprite doesn't have any momentum at all,
        //  it's all just set by the camera follow type.
        //  0.1 is the amount of linear interpolation to use.
        //  The smaller the value, the smooth the camera (and the longer it takes to catch up)
        game.camera.follow(this.player_ship, Phaser.Camera.FOLLOW_LOCKON, 0.1, 0.1);

        // Audio
        this.thrust_sound = this.game.add.audio('thrust');
        // I an't figure out how to get onDown and onUp working
        // so just using this flag for now. -- HookBot
        this.thrusting = false;
    };

    fn.prototype.update = function () {
        if (this.cursors.up.isDown) {
            this.player_ship.body.thrust(this.player.getHullThrust());
            if (!this.thrusting) {
                // XXX: Is there any way to force a loop?
                this.thrust_sound.play();
                this.thrusting = true;
            }
        }
        else if (this.cursors.up.isUp) {
            if (this.thrusting) {
                this.thrust_sound.stop();
                this.thrusting = false;
            }
        }

        if (this.cursors.down.isDown) {
            this.player_ship.body.reverse(this.player.getHullThrust());
            if (!this.thrusting) {
                // XXX: Is there any way to force a loop?
                this.thrust_sound.play();
                this.thrusting = true;
            }
        }
        else if (this.cursors.down.isUp) {
            if (this.thrusting) {
                this.thrust_sound.stop();
                this.thrusting = false;
            }
        }

        if (this.cursors.left.isDown) {
            this.player_ship.body.rotateLeft(this.player.getHullRotation());
        }
        else if (this.cursors.right.isDown) {
            this.player_ship.body.rotateRight(this.player.getHullRotation());
        }
        else {
            this.player_ship.body.setZeroRotation();
        }

        if (Math.abs(this.player_ship.body.x - this.enemy1.body.x) < 200 && Math.abs(this.player_ship.body.y - this.enemy1.body.y) < 200) {
            this.enemy1.follow = 1;
        }
        else if (Math.abs(this.player_ship.body.x - this.enemy1.body.x) > 450 && Math.abs(this.player_ship.body.y - this.enemy1.body.y) > 450) {
            this.enemy1.follow = 0;
            this.enemy1.followx = this.game.world.randomX;
            this.enemy1.followy = this.game.world.randomX;
        }

        if (this.enemy1.follow == 1) {
            this.accelerateToObject(this.enemy1,this.player_ship,60);  //start accelerateToObject on every bullet
        } else if (this.enemy1.follow == 0) {
            if (Math.abs(this.enemy1.followx - this.enemy1.x) < 75 && Math.abs(this.enemy1.followy - this.enemy1.y) < 75) {
                this.enemy1.followx = (((Math.random() * (.8 - .2) + .2) * this.game.world.width) + this.enemy1.x) % this.game.world.width;
                this.enemy1.followy = (((Math.random() * (.8 - .2) + .2) * this.game.world.height) + this.enemy1.y) % this.game.world.height;
            }
            this.accelerateToObject(this.enemy1,undefined,45);  //start accelerateToObject on every bullet
        }
    }

    fn.prototype.accelerateToObject = function(obj1, obj2, speed) {
        var x;
        var y;
        if (typeof obj2 === 'undefined') {
            x = obj1.followx;
            y = obj1.followy;
        } else {
            x = obj2.x;
            y = obj2.y;
        }
        if (typeof speed === 'undefined') { speed = 60; }
        var angle = Math.atan2(y - obj1.y, x - obj1.x);
        obj1.body.rotation = angle + game.math.degToRad(90);  // correct angle of angry bullets (depends on the sprite used)
        obj1.body.force.x = Math.cos(angle) * speed;    // accelerateToObject
        obj1.body.force.y = Math.sin(angle) * speed;
    }

    return fn;
})();
<commit_msg>changed bad reference in HUD constructor<commit_after>// namespace
var App = App || {};

App.PlayMissionState = (function () {
    "use strict";

    var fn = function (game) {
        Phaser.State.call(this, game);
    };

    fn.prototype = Object.create(Phaser.State.prototype);
    fn.prototype.constructor = fn;

    fn.prototype.init = function () {
        this.hud = new App.HUD(this.game, this);

        this.sector = {
            name: "Test Sector",
            width: this.game.world.width * 2,
            height: this.game.world.height * 2
        };

        // create a shorter accessor to the player object
        this.player = this.game.global.player;
    };

    fn.prototype.preload = function () {
        // image assets
        this.load.image('space', 'assets/images/spaceBGDarkPurple.png');
        this.load.image('player', this.player.getHullAsset().file);
        this.load.image('enemy1', 'assets/images/enemyShipG.png');
        this.load.image('greenLaser', 'assets/images/LaserGreen.png');
        this.load.image('redLaser', 'assets/images/LaserRed.png');

        // audio assets
        this.game.load.audio('thrust', 'assets/sounds/thrust.wav');

        // hud assets
        this.hud.loadAssets();
    };

    fn.prototype.create = function () {
        // background
        this.background = this.add.tileSprite(0, 0, this.sector.width, this.sector.height, 'space');
        this.game.world.setBounds(0, 0, this.sector.width, this.sector.height);

        // use p2 for ships
        this.game.physics.startSystem(Phaser.Physics.P2JS);

        // setup player ship spite
        this.player.setShipSprite(this.add.sprite(this.game.world.width / 2, this.game.world.height / 2, 'player'));
        this.player_ship = this.player.getShipSprite(); // easier accessor to player ship sprite
        this.player_ship.anchor.setTo(this.player.getHullAsset().anchor);
        this.player_ship.scale.setTo(this.player.getHullAsset().scale);

        this.game.physics.p2.enable(this.player_ship);

        this.enemy1 = this.add.sprite(this.game.world.width / 3, this.game.world.height / 3, 'enemy1');
        this.enemy1.anchor.setTo(0.5);
        this.enemy1.scale.setTo(-0.5);
        this.enemy1.follow = 0;
        this.enemy1.followx = this.game.world.randomX;
        this.enemy1.followy = this.game.world.randomY;

        this.game.physics.p2.enable(this.enemy1);

        this.cursors = game.input.keyboard.createCursorKeys();

        //  Notice that the sprite doesn't have any momentum at all,
        //  it's all just set by the camera follow type.
        //  0.1 is the amount of linear interpolation to use.
        //  The smaller the value, the smooth the camera (and the longer it takes to catch up)
        game.camera.follow(this.player_ship, Phaser.Camera.FOLLOW_LOCKON, 0.1, 0.1);

        // Audio
        this.thrust_sound = this.game.add.audio('thrust');
        // I an't figure out how to get onDown and onUp working
        // so just using this flag for now. -- HookBot
        this.thrusting = false;
    };

    fn.prototype.update = function () {
        if (this.cursors.up.isDown) {
            this.player_ship.body.thrust(this.player.getHullThrust());
            if (!this.thrusting) {
                // XXX: Is there any way to force a loop?
                this.thrust_sound.play();
                this.thrusting = true;
            }
        }
        else if (this.cursors.up.isUp) {
            if (this.thrusting) {
                this.thrust_sound.stop();
                this.thrusting = false;
            }
        }

        if (this.cursors.down.isDown) {
            this.player_ship.body.reverse(this.player.getHullThrust());
            if (!this.thrusting) {
                // XXX: Is there any way to force a loop?
                this.thrust_sound.play();
                this.thrusting = true;
            }
        }
        else if (this.cursors.down.isUp) {
            if (this.thrusting) {
                this.thrust_sound.stop();
                this.thrusting = false;
            }
        }

        if (this.cursors.left.isDown) {
            this.player_ship.body.rotateLeft(this.player.getHullRotation());
        }
        else if (this.cursors.right.isDown) {
            this.player_ship.body.rotateRight(this.player.getHullRotation());
        }
        else {
            this.player_ship.body.setZeroRotation();
        }

        if (Math.abs(this.player_ship.body.x - this.enemy1.body.x) < 200 && Math.abs(this.player_ship.body.y - this.enemy1.body.y) < 200) {
            this.enemy1.follow = 1;
        }
        else if (Math.abs(this.player_ship.body.x - this.enemy1.body.x) > 450 && Math.abs(this.player_ship.body.y - this.enemy1.body.y) > 450) {
            this.enemy1.follow = 0;
            this.enemy1.followx = this.game.world.randomX;
            this.enemy1.followy = this.game.world.randomX;
        }

        if (this.enemy1.follow == 1) {
            this.accelerateToObject(this.enemy1,this.player_ship,60);  //start accelerateToObject on every bullet
        } else if (this.enemy1.follow == 0) {
            if (Math.abs(this.enemy1.followx - this.enemy1.x) < 75 && Math.abs(this.enemy1.followy - this.enemy1.y) < 75) {
                this.enemy1.followx = (((Math.random() * (.8 - .2) + .2) * this.game.world.width) + this.enemy1.x) % this.game.world.width;
                this.enemy1.followy = (((Math.random() * (.8 - .2) + .2) * this.game.world.height) + this.enemy1.y) % this.game.world.height;
            }
            this.accelerateToObject(this.enemy1,undefined,45);  //start accelerateToObject on every bullet
        }
    }

    fn.prototype.accelerateToObject = function(obj1, obj2, speed) {
        var x;
        var y;
        if (typeof obj2 === 'undefined') {
            x = obj1.followx;
            y = obj1.followy;
        } else {
            x = obj2.x;
            y = obj2.y;
        }
        if (typeof speed === 'undefined') { speed = 60; }
        var angle = Math.atan2(y - obj1.y, x - obj1.x);
        obj1.body.rotation = angle + game.math.degToRad(90);  // correct angle of angry bullets (depends on the sprite used)
        obj1.body.force.x = Math.cos(angle) * speed;    // accelerateToObject
        obj1.body.force.y = Math.sin(angle) * speed;
    }

    return fn;
})();
<|endoftext|>


<commit_before>importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js');

const CACHE_NAME = 'workbox:assets';

// Cache all
const REGEXP_ALL = /.*\.(?:js|css|png|jpg|jpeg|svg|gif)/;

// [OK] Stale While Revalidate
workbox.routing.registerRoute(
  REGEXP_ALL,
  workbox.strategies.staleWhileRevalidate({
    cacheName: `${CACHE_NAME}:stale-while-revalidate`,
  }),
);
<commit_msg>Update sw.js<commit_after>importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js');

const CACHE_NAME = 'workbox:js';

const APP_JS = new RegExp('/static/custom/.*\.js');
const APP_ANGULAR_JS = new RegExp('/static/custom/app/.*\.js');
const VENDOR_JS = new RegExp('/static/vandor/.*\.js');

// [OK] Stale While Revalidate
workbox.routing.registerRoute(
  APP_JS,
  workbox.strategies.staleWhileRevalidate({
    cacheName: `${CACHE_NAME}:stale-while-revalidate`,
  }),
);

// [OK] Stale While Revalidate
workbox.routing.registerRoute(
  APP_ANGULAR_JS,
  workbox.strategies.staleWhileRevalidate({
    cacheName: `${CACHE_NAME}:stale-while-revalidate`,
  }),
);

// [OK] Stale While Revalidate
workbox.routing.registerRoute(
  VENDOR_JS,
  workbox.strategies.staleWhileRevalidate({
    cacheName: `${CACHE_NAME}:stale-while-revalidate`,
  }),
);
<|endoftext|>


<commit_before>var Backbone = require('backbone');
var LayerDefinitionModel = require('../../../../../../../javascripts/cartodb3/data/layer-definition-model');
var AnalysisFormsCollection = require('../../../../../../../javascripts/cartodb3/editor/layers/layer-content-views/analyses/analysis-forms-collection');
var AnalysesWorkflowListView = require('../../../../../../../javascripts/cartodb3/editor/layers/layer-content-views/analyses/analyses-workflow-list-view');
var AnalysisDefinitionNodesCollection = require('../../../../../../../javascripts/cartodb3/data/analysis-definition-nodes-collection');

describe('editor/layers/layer-content-view/analyses/analyses-workflow-list-view', function () {
  beforeEach(function () {
    this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
      configModel: {},
      userModel: {}
    });

    this.analysisDefinitionNodesCollection.add({
      id: 'a1',
      type: 'source',
      status: 'ready'
    });

    this.analysisDefinitionNodesCollection.add({
      id: 'a2',
      type: 'source',
      status: 'ready'
    });

    this.analysisDefinitionNodesCollection.add({
      id: 'a3',
      type: 'source',
      status: 'ready'
    });

    this.layerDefModel = new LayerDefinitionModel({
      id: 'l-1',
      options: {
        type: 'CartoDB',
        table_name: 'foo',
        source: 'a1',
        letter: 'a'
      }
    }, {
      parse: true,
      collection: new Backbone.Collection(),
      configModel: {}
    });
    spyOn(this.layerDefModel, 'getAnalysisDefinitionNodeModel');

    this.viewModel = new Backbone.Model({
      selectedNodeId: 'a2'
    });

    this.analysisFormsCollection = new AnalysisFormsCollection(null, {
      configModel: {},
      userActions: {},
      layerDefinitionModel: this.layerDefModel,
      analysisSourceOptionsModel: {}
    });
    this.analysisFormsCollection.add([
      {id: 'a3'},
      {id: 'a2'},
      {id: 'a1'}
    ]);
    spyOn(this.analysisFormsCollection, 'deleteNode');

    this.view = new AnalysesWorkflowListView({
      analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
      analysisFormsCollection: this.analysisFormsCollection,
      model: this.viewModel,
      layerId: this.layerDefModel.id
    });
    this.view.render();
  });

  it('should not have any leaks', function () {
    expect(this.view).toHaveNoLeaks();
  });

  it('should display add button and the list of analysis', function () {
    expect(this.view.$('.js-add-analysis').length).toBe(1);
    expect(this.view.$('.js-add-analysis').data('layer-id')).toEqual('l-1');
  });

  it('should render an item per node', function () {
    expect(this.view.$el.html()).toContain('a3');
    expect(this.view.$el.html()).toContain('a2');
    expect(this.view.$el.html()).toContain('a1');
  });

  it('should highlight selected node', function () {
    expect(this.view.$('.is-selected').length).toEqual(1);
    expect(this.view.$('li:eq(2)').hasClass('is-selected')).toBeTruthy();
    expect(this.view.$('li:eq(2)').text()).toContain('a2');
  });

  describe('when another non-selected node is clicked', function () {
    beforeEach(function () {
      this.view.$('li:eq(3)').click();
    });

    it('should change selected workflow item', function () {
      expect(this.viewModel.get('selectedNodeId')).toEqual('a1');
    });

    it('should only have one item selected still', function () {
      expect(this.view.$('.is-selected').length).toEqual(1);
    });
  });
});
<commit_msg>tooltips: analyses workflow list view specs<commit_after>var Backbone = require('backbone');
var _ = require('underscore');
var LayerDefinitionModel = require('../../../../../../../javascripts/cartodb3/data/layer-definition-model');
var AnalysisFormsCollection = require('../../../../../../../javascripts/cartodb3/editor/layers/layer-content-views/analyses/analysis-forms-collection');
var AnalysesWorkflowListView = require('../../../../../../../javascripts/cartodb3/editor/layers/layer-content-views/analyses/analyses-workflow-list-view');
var AnalysisDefinitionNodesCollection = require('../../../../../../../javascripts/cartodb3/data/analysis-definition-nodes-collection');

describe('editor/layers/layer-content-view/analyses/analyses-workflow-list-view', function () {
  beforeEach(function () {
    this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
      configModel: {},
      userModel: {}
    });

    this.analysisDefinitionNodesCollection.add({
      id: 'a1',
      type: 'source',
      status: 'ready'
    });

    this.analysisDefinitionNodesCollection.add({
      id: 'a2',
      type: 'source',
      status: 'ready'
    });

    this.analysisDefinitionNodesCollection.add({
      id: 'a3',
      type: 'source',
      status: 'ready'
    });

    this.layerDefModel = new LayerDefinitionModel({
      id: 'l-1',
      options: {
        type: 'CartoDB',
        table_name: 'foo',
        source: 'a1',
        letter: 'a'
      }
    }, {
      parse: true,
      collection: new Backbone.Collection(),
      configModel: {}
    });
    spyOn(this.layerDefModel, 'getAnalysisDefinitionNodeModel');

    this.viewModel = new Backbone.Model({
      selectedNodeId: 'a2'
    });

    this.analysisFormsCollection = new AnalysisFormsCollection(null, {
      configModel: {},
      userActions: {},
      layerDefinitionModel: this.layerDefModel,
      analysisSourceOptionsModel: {}
    });
    this.analysisFormsCollection.add([
      {id: 'a3'},
      {id: 'a2'},
      {id: 'a1'}
    ]);
    spyOn(this.analysisFormsCollection, 'deleteNode');

    this.view = new AnalysesWorkflowListView({
      analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
      analysisFormsCollection: this.analysisFormsCollection,
      model: this.viewModel,
      layerId: this.layerDefModel.id
    });
    this.view.render();
  });

  it('should render correctly', function () {
    expect(_.size(this.view._subviews)).toBe(4); // [3 x Analysis Node, Tooltip]
  });

  it('should display add button and the list of analysis', function () {
    expect(this.view.$('.js-add-analysis').length).toBe(1);
    expect(this.view.$('.js-add-analysis').data('layer-id')).toEqual('l-1');
  });

  it('should render an item per node', function () {
    var content = this.view.$el.html();

    expect(content).toContain('a3');
    expect(content).toContain('a2');
    expect(content).toContain('a1');
  });

  it('should highlight selected node', function () {
    expect(this.view.$('.is-selected').length).toEqual(1);
    expect(this.view.$('li:eq(2)').hasClass('is-selected')).toBeTruthy();
    expect(this.view.$('li:eq(2)').text()).toContain('a2');
  });

  describe('when another non-selected node is clicked', function () {
    beforeEach(function () {
      this.view.$('li:eq(3)').click();
    });

    it('should change selected workflow item', function () {
      expect(this.viewModel.get('selectedNodeId')).toEqual('a1');
    });

    it('should only have one item selected still', function () {
      expect(this.view.$('.is-selected').length).toEqual(1);
    });
  });

  it('should not have any leaks', function () {
    expect(this.view).toHaveNoLeaks();
  });

  afterEach(function () {
    this.view.clean();
  });
});
<|endoftext|>


<commit_before>  /**
   * @license
   * Copyright 2018 The FOAM Authors. All Rights Reserved.
   * http://www.apache.org/licenses/LICENSE-2.0
   */

  foam.CLASS({
    package: 'foam.nanos.notification',
    name: 'NotificationRowView',
    extends: 'foam.u2.View',

    requires: [
      'foam.nanos.auth.User',
      'foam.nanos.notification.NotificationView',
      'foam.u2.PopupView'
    ],

    imports: [
      'invoiceDAO',
      'notificationDAO',
      'stack',
      'user',
      'userDAO'
    ],

    exports: [
      'as data'
    ],

    css: `
      ^ i {
        margin-top: 5px;
      }
      ^ .popUpDropDown {
        padding: 0 !important;
        z-index: 1000;
        background: white;
        opacity: 1;
        box-shadow: 2px 2px 2px 2px rgba(0, 0, 0, 0.19);
      }
      ^ .net-nanopay-ui-ActionView-optionsDropDown {
        width: 26px !important;
        height: 26px !important;
        border: none !important;
        background-color: rgba(164, 179, 184, 0.0);
        float: right;
        margin-right:20px !important;
        margin-top:20px !important;
      }
      ^ .net-nanopay-ui-ActionView-optionsDropDown > img {
        height:23px;
      }
      ^ .dot{
        height: 500px;
        width: 500px;
        border-radius: 50%;
        color:black;
      }
      ^ .net-nanopay-ui-ActionView-optionsDropDown:hover {
        background-color: rgba(164, 179, 184, 0.3);
      }
      ^ .popUpDropDown > div {
        font-size: 14px;
        font-weight: 300;
        letter-spacing: 0.2px;
        color: #093649;
        padding: 10px 16px;
        text-align: left;
      }
      ^ .popUpDropDown > div:hover {
        background-color: #59a5d5;
        color: white;
        cursor: pointer;
      }
      ^ .net-nanopay-ui-ActionView {
        background-color: rgba(201, 76, 76, 0.0);
        width: 135px;
        height: 40px;
        border-radius: 2px;
        border: solid 1px #59a5d5;
        margin-left:20px;
        margin-top:10px;
      }
      ^ .net-nanopay-ui-ActionView:hover {
        background-color: #d2e2ef;
      }
      ^ .net-nanopay-ui-ActionView > span {
        height: 40px;
        font-family: Roboto;
        font-size: 14px;
        font-weight: 300;
        line-height: 2.86;
        letter-spacing: 0.2px;
        text-align: center;
        color: #59a5d5;
      }
      ^ .net-nanopay-ui-ActionView-popup {
        background-color: rgba(201, 76, 76, 0.0);
        width: 135px;
        height: 40px;
        border-radius: 2px;
        border: solid 1px #59a5d5;
        margin-bottom:20px;
        margin-left:20px;
        margin-top:10px;
      }
      ^ .net-nanopay-ui-ActionView-popup > span {
        height: 40px;
        font-family: Roboto;
        font-size: 14px;
        font-weight: 300;
        line-height: 2.86;
        letter-spacing: 0.2px;
        text-align: center;
        color: #59a5d5;
      }
      ^ div {
        padding-bottom:20px;
      }
      ^ .msg {
        font-size: 12px;
        word-wrap: break-word;
        padding-bottom: 0;
        padding-top: 20;
        line-height: 1.4;
        padding-left: 20px;
        width: 414px;
        -webkit-line-clamp: 4;
        -webkit-box-orient: vertical;
        display: -webkit-box;
        text-overflow: ellipsis;
        margin-right: 10;
        overflow: hidden;
        color: #093649;
      }
      ^ .msg.fully-visible {
        display: block;
      }
      ^ .popUpDropdown > .destructive-action {
        color: #D81E05;
      }
    `,

    properties: [
      'of',
      'optionsBtn_',
      'optionPopup_'
    ],

    methods: [
      function initE() {
        this
          .on('mouseover', this.read)
          .addClass(this.myClass());

        this.tag(this.OPTIONS_DROP_DOWN, {
          icon: 'images/ic-options.png',
          showLabel: true
        }, this.optionsBtn_$);
        this.add(this.NotificationView.create({
          of: this.data.cls_,
          data: this.data
        }));
      }
    ],

    actions: [
      {
        name: 'optionsDropDown',
        label: '',
        code: function(X) {
          var self = this;
          self.optionPopup_ = this.PopupView.create({
            width: 205,
            x: -137,
            y: 40
          });

          self.optionPopup_.addClass('popUpDropDown')
            .start('div')
              .addClass('destructive-action')
              .add('Remove')
              .on('click', this.removeNotification)
            .end()
            .callIf(this.data.notificationType !== 'General', function() {
              this.start('div')
                .add('Hide notifications like this')
                .on('click', self.hideNotificationType)
              .end();
            })
            .start('div')
              .add('Mark as Unread')
              .on('click', this.markUnread)
            .end();
          self.optionsBtn_.add(self.optionPopup_);
        }
      },
    ],

    listeners: [
      function removeNotification() {
        this.notificationDAO.remove(this.data);
      },

      function hideNotificationType() {
        this.user = this.user.clone();
        this.user.disabledTopics.push(this.data.notificationType);
        this.userDAO.put(this.user);
        this.stack.push({
          class: 'foam.nanos.notification.NotificationListView'
        });
      },

      function read() {
        if ( ! this.data.read ) {
            this.data.read = true;
            this.notificationDAO.put(this.data);
        }
      },

      function markUnread() {
        if ( this.data.read ) {
          this.data.read = false;
          this.notificationDAO.put(this.data);
        }
        this.stack.push({
          class: 'foam.nanos.notification.NotificationListView'
        });
      }
    ]
  });
<commit_msg>Align noti context menu correctly<commit_after>  /**
   * @license
   * Copyright 2018 The FOAM Authors. All Rights Reserved.
   * http://www.apache.org/licenses/LICENSE-2.0
   */

  foam.CLASS({
    package: 'foam.nanos.notification',
    name: 'NotificationRowView',
    extends: 'foam.u2.View',

    requires: [
      'foam.nanos.auth.User',
      'foam.nanos.notification.NotificationView',
      'foam.u2.PopupView'
    ],

    imports: [
      'invoiceDAO',
      'notificationDAO',
      'stack',
      'user',
      'userDAO'
    ],

    exports: [
      'as data'
    ],

    css: `
      ^ i {
        margin-top: 5px;
      }
      ^ .popUpDropDown {
        padding: 0 !important;
        z-index: 1000;
        background: white;
        opacity: 1;
        box-shadow: 2px 2px 2px 2px rgba(0, 0, 0, 0.19);
      }
      ^ .net-nanopay-ui-ActionView-optionsDropDown {
        width: 26px !important;
        height: 26px !important;
        border: none !important;
        background-color: rgba(164, 179, 184, 0.0);
        float: right;
        margin-right:20px !important;
        margin-top:20px !important;
      }
      ^ .net-nanopay-ui-ActionView-optionsDropDown > img {
        height:23px;
      }
      ^ .dot{
        height: 500px;
        width: 500px;
        border-radius: 50%;
        color:black;
      }
      ^ .net-nanopay-ui-ActionView-optionsDropDown:hover {
        background-color: rgba(164, 179, 184, 0.3);
      }
      ^ .popUpDropDown > div {
        font-size: 14px;
        font-weight: 300;
        letter-spacing: 0.2px;
        color: #093649;
        padding: 10px 16px;
        text-align: left;
      }
      ^ .popUpDropDown > div:hover {
        background-color: #59a5d5;
        color: white;
        cursor: pointer;
      }
      ^ .net-nanopay-ui-ActionView {
        background-color: rgba(201, 76, 76, 0.0);
        width: 135px;
        height: 40px;
        border-radius: 2px;
        border: solid 1px #59a5d5;
        margin-left:20px;
        margin-top:10px;
      }
      ^ .net-nanopay-ui-ActionView:hover {
        background-color: #d2e2ef;
      }
      ^ .net-nanopay-ui-ActionView > span {
        height: 40px;
        font-family: Roboto;
        font-size: 14px;
        font-weight: 300;
        line-height: 2.86;
        letter-spacing: 0.2px;
        text-align: center;
        color: #59a5d5;
      }
      ^ .net-nanopay-ui-ActionView-popup {
        background-color: rgba(201, 76, 76, 0.0);
        width: 135px;
        height: 40px;
        border-radius: 2px;
        border: solid 1px #59a5d5;
        margin-bottom:20px;
        margin-left:20px;
        margin-top:10px;
      }
      ^ .net-nanopay-ui-ActionView-popup > span {
        height: 40px;
        font-family: Roboto;
        font-size: 14px;
        font-weight: 300;
        line-height: 2.86;
        letter-spacing: 0.2px;
        text-align: center;
        color: #59a5d5;
      }
      ^ div {
        padding-bottom:20px;
      }
      ^ .msg {
        font-size: 12px;
        word-wrap: break-word;
        padding-bottom: 0;
        padding-top: 20;
        line-height: 1.4;
        padding-left: 20px;
        width: 414px;
        -webkit-line-clamp: 4;
        -webkit-box-orient: vertical;
        display: -webkit-box;
        text-overflow: ellipsis;
        margin-right: 10;
        overflow: hidden;
        color: #093649;
      }
      ^ .msg.fully-visible {
        display: block;
      }
      ^ .popUpDropdown > .destructive-action {
        color: #D81E05;
      }
    `,

    properties: [
      'of',
      'optionsBtn_',
      'optionPopup_'
    ],

    methods: [
      function initE() {
        this
          .on('mouseover', this.read)
          .addClass(this.myClass());

        this.tag(this.OPTIONS_DROP_DOWN, {
          icon: 'images/ic-options.png',
          showLabel: true
        }, this.optionsBtn_$);
        this.add(this.NotificationView.create({
          of: this.data.cls_,
          data: this.data
        }));
      }
    ],

    actions: [
      {
        name: 'optionsDropDown',
        label: '',
        code: function(X) {
          var self = this;
          self.optionPopup_ = this.PopupView.create({
            width: 205,
            x: -160,
            y: 40
          });

          self.optionPopup_.addClass('popUpDropDown')
            .start('div')
              .addClass('destructive-action')
              .add('Remove')
              .on('click', this.removeNotification)
            .end()
            .callIf(this.data.notificationType !== 'General', function() {
              this.start('div')
                .add('Hide notifications like this')
                .on('click', self.hideNotificationType)
              .end();
            })
            .start('div')
              .add('Mark as Unread')
              .on('click', this.markUnread)
            .end();
          self.optionsBtn_.add(self.optionPopup_);
        }
      },
    ],

    listeners: [
      function removeNotification() {
        this.notificationDAO.remove(this.data);
      },

      function hideNotificationType() {
        this.user = this.user.clone();
        this.user.disabledTopics.push(this.data.notificationType);
        this.userDAO.put(this.user);
        this.stack.push({
          class: 'foam.nanos.notification.NotificationListView'
        });
      },

      function read() {
        if ( ! this.data.read ) {
            this.data.read = true;
            this.notificationDAO.put(this.data);
        }
      },

      function markUnread() {
        if ( this.data.read ) {
          this.data.read = false;
          this.notificationDAO.put(this.data);
        }
        this.stack.push({
          class: 'foam.nanos.notification.NotificationListView'
        });
      }
    ]
  });
<|endoftext|>