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

/**
 * Time To Live (TTL) Select Caching DAO only caches select() operations
 * for a limited amount of time.
*/
foam.CLASS({
  package: 'foam.dao',
  name: 'TTLSelectCachingDAO',
  extends: 'foam.dao.ProxyDAO',

  requires: [
    'foam.dao.DAOSink',
    'foam.dao.PromisedDAO',
    'foam.dao.QuickSink'
  ],

  imports: [ 'merged' ],

  properties: [
    {
      /** The cache to read items quickly. */
      name: 'cache',
      factory: function() { return {}; }
    },
    {
       class: 'Long',
       name: 'purgeTime',
       documentation: 'Time to wait before purging cache.',
       units: 'ms',
       value: 25000
     },
     {
       class: 'Long',
       name: 'maxCacheSize',
       value: 100
     },
     {
       name: 'purgeCache',
       transient: true,
       expression: function(purgeTime) {
         return this.merged(() => { this.cache = {};}, purgeTime);
       }
     }
  ],

  methods: [
    /** Puts are sent to the cache and to the source, ensuring both
      are up to date. */
    function put_(x, o) {
      this.cache = {};
      return this.delegate.put_(x, o);
    },

    function select_(x, sink, skip, limit, order, predicate) {
      var self = this;
      var key  = [sink, skip, limit, order, predicate].toString();

      if ( this.cache[key] ) {
        // console.log('************************ CACHED: ', key);
        return Promise.resolve(this.cache[key]);
      }

      return new Promise(function (resolve, reject) {
        self.delegate.select_(x, sink, skip, limit, order, predicate).then(function(s) {
          self.cache[key] = s;
//          console.log('************************ CACHING: ', key);
          // TODO: check if cache is > maxCacheSize and remove oldest entry if it is
          self.purgeCache();
          resolve(s);
        });
      });
    },

    /** Removes are sent to the cache and to the source, ensuring both
      are up to date. */
    function remove_(x, o) {
      this.cache = {};
      return this.delegate.remove_(x, o);
    },

    /** removeAll is executed on the cache and the source, ensuring both
      are up to date. */
    function removeAll_(x, skip, limit, order, predicate) {
      this.cache = {};
      this.delegate.removeAll_(x, skip, limit, order, predicate);
    },

    function cmd_(x, obj) {
      if ( obj == this.PURGE ) {
        this.cache = {};
      } else {
        this.SUPER(x, obj);
      }
    }
  ]
});
<commit_msg>Fix bug in TTLSelectCachingDAO caching function sinks, which it shouldn't.<commit_after>/**
 * @license
 * Copyright 2018 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

/**
 * Time To Live (TTL) Select Caching DAO only caches select() operations
 * for a limited amount of time.
*/
foam.CLASS({
  package: 'foam.dao',
  name: 'TTLSelectCachingDAO',
  extends: 'foam.dao.ProxyDAO',

  requires: [
    'foam.dao.DAOSink',
    'foam.dao.PromisedDAO',
    'foam.dao.QuickSink'
  ],

  imports: [ 'merged' ],

  properties: [
    {
      /** The cache to read items quickly. */
      name: 'cache',
      factory: function() { return {}; }
    },
    {
       class: 'Long',
       name: 'purgeTime',
       documentation: 'Time to wait before purging cache.',
       units: 'ms',
       value: 25000
     },
     {
       class: 'Long',
       name: 'maxCacheSize',
       value: 100
     },
     {
       name: 'purgeCache',
       transient: true,
       expression: function(purgeTime) {
         return this.merged(() => { this.cache = {};}, purgeTime);
       }
     }
  ],

  methods: [
    /** Puts are sent to the cache and to the source, ensuring both
      are up to date. */
    function put_(x, o) {
      this.cache = {};
      return this.delegate.put_(x, o);
    },

    function select_(x, sink, skip, limit, order, predicate) {
      if ( foam.dao.AnonymousSink.isInstance(sink) )
        return this.SUPER(x, sink, skip, limit, order, predicate);

      var self = this;
      var key  = [sink, skip, limit, order, predicate].toString();

      if ( this.cache[key] ) {
        // console.log('************************ CACHED: ', key);
        return Promise.resolve(this.cache[key]);
      }

      return new Promise(function (resolve, reject) {
        self.delegate.select_(x, sink, skip, limit, order, predicate).then(function(s) {
          self.cache[key] = s;
//          console.log('************************ CACHING: ', key);
          // TODO: check if cache is > maxCacheSize and remove oldest entry if it is
          self.purgeCache();
          resolve(s);
        });
      });
    },

    /** Removes are sent to the cache and to the source, ensuring both
      are up to date. */
    function remove_(x, o) {
      this.cache = {};
      return this.delegate.remove_(x, o);
    },

    /** removeAll is executed on the cache and the source, ensuring both
      are up to date. */
    function removeAll_(x, skip, limit, order, predicate) {
      this.cache = {};
      this.delegate.removeAll_(x, skip, limit, order, predicate);
    },

    function cmd_(x, obj) {
      if ( obj == this.PURGE ) {
        this.cache = {};
      } else {
        this.SUPER(x, obj);
      }
    }
  ]
});
<|endoftext|>


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

foam.CLASS({
  package: 'foam.i18n',
  name: 'TranslationConsole',
  extends: 'foam.u2.Controller',

  static: [
    function OPEN() {
      var w      = global.window.open("", 'Translation Console', "width=800,height=800,scrollbars=no", true);
      var window = foam.core.Window.create({window: w}, ctrl);
      var v      = this.create({}, window);
      v.write(window.document);

      foam.core.I18NString.GETTER__ = function(proto, prop, obj, key) {
        if ( obj.sourceCls_ ) {
          var source = obj.sourceCls_.id + '.' + obj.name + '.' + prop.name;
          var translation = v.translationService.getTranslation(v.locale, source, '');
          v.onTranslation(null, null, foam.locale, source, translation, obj.instance_[key]);
          console.log('**************************** ', source, obj.instance_[key]);
        }
        return obj.instance_[key];
      };

    }
  ],

  css: `
    * {
      font-family: Roboto, sans-serif;
      color: #555;
    }
    body {
      font-family: Roboto;
      background: rgb(238, 238, 238);
      overflow: none;
    }
    button { padding: 6px; }
    .foam-u2-ActionView-medium { height: 34px !important; background: pink; }
    .foam-u2-view-TableView-th-editColumns { display: none; }
    .foam-u2-view-TableView-td[name="contextMenuCell"] { display: none; }
    .foam-u2-view-ScrollTableView { height: auto !important; }

  `,

  classes: [
    {
      name: 'Row',

      imports: [ 'locale', 'localeDAO', 'search', 'translationService' ],

      requires: [ 'foam.i18n.Locale' ],

      tableColumns: [ 'source', 'defaultText', 'text', 'update' ],

      ids: [ 'source' ],

      properties: [
        {
          class: 'String',
          name: 'source',
          tableWidth: 350
        },
        {
          class: 'String',
          name: 'defaultText',
          displayWidth: 200
        },
        {
          class: 'String',
          name: 'text',
          tableCellFormatter: function(val, obj, prop) {
            this.startContext({data: obj}).add(prop).endContext();
          },
          tableWidth: 250
        }
      ],

      methods: [
      ],

      actions: [
        function update() {
          console.log('***** UPDATE', this.source, this.text);
          var l = this.Locale.create({
            locale:  this.locale.substring(0, 2),
            variant: this.locale.substring(3),
            source:  this.source,
            target:  this.text
          });

          this.localeDAO.put(l);

          this.translationService.localeEntries[this.source] = this.text;
        }
      ]
    }
  ],

  imports: [
    'translationService'
  ],

  exports: [ 'locale', 'search' ],

  requires: [
    'foam.dao.MDAO',
    'foam.u2.borders.CardBorder'
  ],

  properties: [
    {
      class: 'String',
      name: 'search',
      view: {
        class: 'foam.u2.TextField',
        type: 'search',
        onKey: true
      }
    },
    {
      name: 'dao',
      factory: function() { return this.MDAO.create({of: this.Row}); }
    },
    {
      name: 'filteredDAO',
      expression: function(search, dao) {
        return dao;
      },
      view: 'foam.u2.view.ScrollTableView'
    },
    {
      class: 'String',
      name: 'locale',
      factory: function() { return foam.locale; }
//          view: 'net.nanopay.ui.topNavigation.LanguageChoiceView'
    }
  ],

  methods: [
    function init() {
      this.SUPER();
      this.translationService.translation.sub(this.onTranslation);
    },

    function initE() {
      this.
        addClass(this.myClass()).
        start(this.CardBorder).
          style({height: '32px'}).
          start('span').
            style({'padding-top': '5px', display: 'inline-block', 'font-size': 'larger'}).            add('Translation Console').
          end().
          start('div').
            style({float: 'right'}).
            add('Search: ', this.SEARCH, '  Locale: ', this.LOCALE).
          end().
        end().
        start(this.CardBorder, {}, this.content$).
          style({'overflow-y':'scroll'}).
          style({'margin-top': '10px', height: '90%' }).
          add(this.FILTERED_DAO).
        end();
    }
  ],

  listeners: [
    function onTranslation(_, __, locale, source, txt, defaultText) {
      this.dao.put(this.Row.create({
        locale:      locale,
        source:      source,
        text:        txt,
        defaultText: defaultText
      }));
    }
  ]
});
<commit_msg>Add search to TranslationConsole<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.i18n',
  name: 'TranslationConsole',
  extends: 'foam.u2.Controller',

  implements: [ 'foam.mlang.Expressions' ],

  static: [
    function OPEN() {
      var w      = global.window.open("", 'Translation Console', "width=800,height=800,scrollbars=no", true);
      var window = foam.core.Window.create({window: w}, ctrl);
      var v      = this.create({}, window);
      v.write(window.document);

      foam.core.I18NString.GETTER__ = function(proto, prop, obj, key) {
        if ( obj.sourceCls_ ) {
          var source = obj.sourceCls_.id + '.' + obj.name + '.' + prop.name;
          var translation = v.translationService.getTranslation(v.locale, source, '');
          v.onTranslation(null, null, foam.locale, source, translation, obj.instance_[key]);
          console.log('**************************** ', source, obj.instance_[key]);
        }
        return obj.instance_[key];
      };

    }
  ],

  css: `
    * {
      font-family: Roboto, sans-serif;
      color: #555;
    }
    body {
      font-family: Roboto;
      background: rgb(238, 238, 238);
      overflow: none;
    }
    button { padding: 6px; }
    .foam-u2-ActionView-medium { height: 34px !important; background: pink; }
    .foam-u2-view-TableView-th-editColumns { display: none; }
    .foam-u2-view-TableView-td[name="contextMenuCell"] { display: none; }
    .foam-u2-view-ScrollTableView { height: auto !important; }

  `,

  classes: [
    {
      name: 'Row',

      imports: [ 'locale', 'localeDAO', 'search', 'translationService' ],

      requires: [ 'foam.i18n.Locale' ],

      tableColumns: [ 'source', 'defaultText', 'text', 'update' ],

      ids: [ 'source' ],

      properties: [
        {
          class: 'String',
          name: 'source',
          tableWidth: 350
        },
        {
          class: 'String',
          name: 'defaultText',
          displayWidth: 200
        },
        {
          class: 'String',
          name: 'text',
          tableCellFormatter: function(val, obj, prop) {
            this.startContext({data: obj}).add(prop).endContext();
          },
          tableWidth: 250
        }
      ],

      methods: [
      ],

      actions: [
        function update() {
          console.log('***** UPDATE', this.source, this.text);
          var l = this.Locale.create({
            locale:  this.locale.substring(0, 2),
            variant: this.locale.substring(3),
            source:  this.source,
            target:  this.text
          });

          this.localeDAO.put(l);

          this.translationService.localeEntries[this.source] = this.text;
        }
      ]
    }
  ],

  imports: [
    'translationService'
  ],

  exports: [ 'locale', 'search' ],

  requires: [
    'foam.dao.MDAO',
    'foam.u2.borders.CardBorder'
  ],

  properties: [
    {
      class: 'String',
      name: 'search',
      view: {
        class: 'foam.u2.TextField',
        type: 'search',
        onKey: true
      }
    },
    {
      name: 'dao',
      factory: function() { return this.MDAO.create({of: this.Row}); }
    },
    {
      name: 'filteredDAO',
      expression: function(search, dao) {
        search = search.trim();
        if ( search == '' ) return dao;

        return dao.where(
          this.OR(
            this.CONTAINS_IC(this.Row.SOURCE,       search),
            this.CONTAINS_IC(this.Row.DEFAULT_TEXT, search),
            this.CONTAINS_IC(this.Row.TEXT,         search)
          ));
      },
      view: 'foam.u2.view.ScrollTableView'
    },
    {
      class: 'String',
      name: 'locale',
      factory: function() { return foam.locale; }
//          view: 'net.nanopay.ui.topNavigation.LanguageChoiceView'
    }
  ],

  methods: [
    function init() {
      this.SUPER();
      this.translationService.translation.sub(this.onTranslation);
    },

    function initE() {
      this.
        addClass(this.myClass()).
        start(this.CardBorder).
          style({height: '32px'}).
          start('span').
            style({'padding-top': '5px', display: 'inline-block', 'font-size': 'larger'}).            add('Translation Console').
          end().
          start('div').
            style({float: 'right'}).
            add('Search: ', this.SEARCH, '  Locale: ', this.LOCALE).
          end().
        end().
        start(this.CardBorder, {}, this.content$).
          style({'overflow-y':'scroll'}).
          style({'margin-top': '10px', height: '90%' }).
          add(this.FILTERED_DAO).
        end();
    }
  ],

  listeners: [
    function onTranslation(_, __, locale, source, txt, defaultText) {
      this.dao.put(this.Row.create({
        locale:      locale,
        source:      source,
        text:        txt,
        defaultText: defaultText
      }));
    }
  ]
});
<|endoftext|>


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

foam.CLASS({
  package: 'foam.u2.view',
  name: 'MultiChoiceView',
  extends: 'foam.u2.View',
  flags: ['web'],

  implements: [ 'foam.mlang.Expressions' ],

  requires: [ 'foam.u2.view.CardSelectView' ],

  documentation: `
    -takes a faceted CardSelectView - based on each choice[2].cls_

    Wraps a tag that represents multiple choices.

    The choices are in [value, label, isSelected, choiceMode ] quartets.

    However the client can simply pass in [value, label] and it will adapt to a [value, label, isSelected, choiceMode ] format

    Or the client can instead pass in a DAO to the MultiChoiceView.dao and the choices list will
    be automatically generated

    Calling the following methods:

    MultiChoiceView.data will be automatically set to a predicated dao based on the choices selected only if
    the minSelected, maxSelected criteria is respected, it will be foam.dao.NullDAO othrewise
  `,

  css: `
    ^helpTextRow {
      font-size: 14pt;
      padding: 8pt 0;
    }
    ^flexer {
      flex-wrap: wrap;
      align-items: stretch;
      text-align: center;
    }
    ^innerFlexer {
      display: inline-flex;
      padding: 4px;
      box-sizing: border-box;
    }
  `,

  constants: {
    NUM_COLUMNS: 3
  },

  messages: [
    { name: 'OPTIONS_MSG', message: 'options' },
    { name: 'CHOOSE_1_OF_FOLLOWING_OPTIONS', message: 'Choose one of the following options' },
    { name: 'CHOOSE_AT_LEAST_1_OPTION', message: 'Choose at least one option' },
    { name: 'CHOOSE_AT_LEAST', message: 'Choose at least' },
    { name: 'CHOOSE_EXACTLY', message: 'Choose exactly' },
    { name: 'CHOOSE', message: 'Choose' }
  ],

  properties: [
    {
      class: 'Function',
      name: 'onSelect'
    },
    {
      name: 'choices',
      documentation: `
        An array of choices which are single choice is denoted as [value, label, isSelected, choiceMode, isFinal], however the user can
        just pass in [value, label] and the adapt function will turn it into the [value, label, isSelected, choiceMode, isFinal] format
        for processing purposes
      `,
      factory: function() {
        return [];
      },
      adapt: function(_, n) {
        if ( ! Array.isArray(n) ) throw new Error('Please submit an array to choices in the MultiChoiceView');

        if ( n.length > 0 ) {
          n = n.sort();
          var valueLabelChoices = n.filter(choice => choice.length === 2);
          var fullChoices = n.filter(choice => choice.length === 5);

          if ( valueLabelChoices.length === n.length ) return n.map(choice => [choice[0], choice[1], false, this.mode, false]);
          if ( fullChoices.length === n.length ) return n;

          throw new Error('Items in choices array do not have consistent lengths');
        }

        return n;
      },
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'dao',
      documentation: `
        If the user just wants to pass in a dao, it will be processed to populate the
        choices array instead of manually inputing the choices
      `
    },
    {
      name: 'selectedChoices',
      value: [],
      postSet: function(o, n) {
        if ( this.onSelect ) this.onSelect(o, n);

        if ( this.selectedChoices.length < this.maxSelected ) {
          this.choices.forEach(choice => {
            var isSelected = foam.core.Slot.isInstance(choice[2])
              ? choice[2].get()
              : choice[2];

            var isFinal = foam.core.Slot.isInstance(choice[4])
              ? choice[4].get()
              : choice[4];

            if ( ! isSelected && ! isFinal ) {
              choice[3] = this.mustSlot(choice[3]);
              choice[3].set(foam.u2.DisplayMode.RW);
            }
          });
        } else {
          this.choices.forEach(choice => {
            var isSelected = foam.core.Slot.isInstance(choice[2])
              ? choice[2].get()
              : choice[2];

            var isFinal = foam.core.Slot.isInstance(choice[4])
              ? choice[4].get()
              : choice[4];

            if ( ! isSelected && ! isFinal ) {
              choice[3] = this.mustSlot(choice[3]);
              choice[3].set(foam.u2.DisplayMode.DISABLED);
            }
          });
        }
      }
    },
    {
      class: 'Boolean',
      name: 'showMinMaxHelper',
      value: true
    },
    {
      class: 'Boolean',
      name: 'isValidNumberOfChoices',
      expression: function(minSelected, maxSelected, selectedChoices){
        return selectedChoices.length >= minSelected && selectedChoices.length <= maxSelected;
      }
    },
    {
      class: 'Int',
      name: 'minSelected',
      expression: function(choices) {
        return choices.length > 0 ? 1 : 0;
      }
    },
    {
      class: 'Int',
      name: 'maxSelected',
      value: 2
    },
    {
      class: 'Boolean',
      name: 'isVertical',
      value: false
    },
    {
      class: 'Boolean',
      name: 'isDaoFetched',
      value: false
    },
    {
      name: 'objToChoice',
      class: 'Function',
      value: function(obj) {
        return [obj.id, obj.toSummary()];
      }
    },
    {
      name: 'helpText_',
      expression: function(minSelected, maxSelected) {
        // TODO: Change this when formatted messages are supported
        return ( maxSelected > 0 )
          ? ( minSelected == maxSelected )
            ? ( minSelected == 1 )
              ? this.CHOOSE_1_OF_FOLLOWING_OPTIONS
              : `${this.CHOOSE_EXACTLY} ${minSelected} ${this.OPTIONS_MSG}`
            : `${this.CHOOSE} ${minSelected} - ${maxSelected} ${this.OPTIONS_MSG}`
          : ( minSelected == 1 )
            ? this.CHOOSE_AT_LEAST_1_OPTION
            : `${this.CHOOSE_AT_LEAST} ${minSelected} ${this.OPTIONS_MSG}`
          ;
      }
    }
  ],

  methods: [
    function initE() {
      var self = this;

      this.onDAOUpdate();

      this
        .start()
          .add(self.slot(function(showMinMaxHelper, helpText_) {
            return self.E().callIf(showMinMaxHelper, function() {
              this
              .start(foam.u2.layout.Rows)
                .start()
                  .addClass(self.myClass('helpTextRow'))
                  .add(self.helpText_)
                .end()
              .end();
            });
          }))
        .end()
        .start(self.isVertical ? foam.u2.layout.Rows : foam.u2.layout.Cols)
          .addClass(self.myClass('flexer'))
          .add( // TODO isDoaFetched and simpSlot0 aren't used should be clean up
            self.isDaoFetched$.map(isDaoFetched => {
              var newChoices = [];

              var toRender = self.choices.map(function(choice) {
                var simpSlot0 = self.mustSlot(choice[0]);
                var simpSlot1 = self.mustSlot(choice[1]);
                var simpSlot2 = self.mustSlot(choice[2]);
                var simpSlot3 = self.mustSlot(choice[3]);
                var simpSlot4 = self.mustSlot(choice[4]);

                newChoices = [
                  ...newChoices,
                  [choice[0], simpSlot1, simpSlot2, simpSlot3, simpSlot4]
                ];
                var cap = choice[2] && choice[2].obj && choice[2].obj.capability;
                var cls =  cap && cap.cls_.id;
                return self.E()
                  .addClass(self.myClass('innerFlexer'))
                  // NOTE: This should not be the way we implement columns.
                  .style({
                    'width': `${100 / self.NUM_COLUMNS}%`
                  })
                  .tag(self.CardSelectView, {
                      data$: simpSlot2,
                      label$: simpSlot1,
                      mode$: simpSlot3,
                      of: cls,
                      obj: cap
                    });
              });

              this.choices = newChoices;

              self.selectedChoices$ = foam.core.ArraySlot.create({
                slots: newChoices.map(choice => {
                  return this.mustSlot(choice[2]);
                })
              }).map(v => {
                var selectedChoices = [];
                v.forEach((w, i) => {
                  if ( w ) {
                    selectedChoices.push(this.choices[i]);
                  }
                });
                return selectedChoices;
              });
              return toRender;
            })
          )
        .end();
    },

    function mustSlot(v) {
      return foam.core.Slot.isInstance(v) ?
        v :
        foam.core.SimpleSlot.create({ value: v });
    }
  ],

  listeners: [
    {
      name: 'onDAOUpdate',
      isFramed: true,
      code: function() {
        if ( ! this.dao || ! foam.dao.DAO.isInstance(this.dao) ) return;

        var of = this.dao.of;
        if ( of._CHOICE_TEXT_ ) {
          this.dao.select(this.PROJECTION(of.ID, of._CHOICE_TEXT_)).then(s => {
            this.choices      = s.projection;
            this.isDaoFetched = true;
          });
          return;
        }
        console.warn('Inefficient ChoiceView. Consider creating transient _choiceText_ property on ' + of.id + ' DAO, prop: ' + this.prop_);

        /* Ex.:
        {
          class: 'String',
          name: '_choiceText_',
          transient: true,
          javaGetter: 'return getName();',
          getter: function() { return this.name; }
        }
        */
        var p = this.mode === foam.u2.DisplayMode.RW ?
          this.dao.select().then(s => s.array) :
          this.dao.find(this.data).then(o => o ? [o] : []);

        p.then(a => {
          var choices = a.map(this.objToChoice);
          var choiceLabels = a.map(o => this.objToChoice(o)[1]);
          Promise.all(choiceLabels).then(resolvedChoiceLabels => {
            for ( let i = 0; i < choices.length; i++ ) {
              choices[i][1] = resolvedChoiceLabels[i];
            }
            this.choices = choices;
            this.isDaoFetched = true;
          });
        });
      }
    }
  ]
});
<commit_msg>align multichoiceview options when 2 options only<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'MultiChoiceView',
  extends: 'foam.u2.View',
  flags: ['web'],

  implements: [ 'foam.mlang.Expressions' ],

  requires: [ 'foam.u2.view.CardSelectView' ],

  documentation: `
    -takes a faceted CardSelectView - based on each choice[2].cls_

    Wraps a tag that represents multiple choices.

    The choices are in [value, label, isSelected, choiceMode ] quartets.

    However the client can simply pass in [value, label] and it will adapt to a [value, label, isSelected, choiceMode ] format

    Or the client can instead pass in a DAO to the MultiChoiceView.dao and the choices list will
    be automatically generated

    Calling the following methods:

    MultiChoiceView.data will be automatically set to a predicated dao based on the choices selected only if
    the minSelected, maxSelected criteria is respected, it will be foam.dao.NullDAO othrewise
  `,

  css: `
    ^helpTextRow {
      font-size: 14pt;
      padding: 8pt 0;
    }
    ^flexer {
      flex-wrap: wrap;
      align-items: stretch;
      text-align: center;
      justify-content:flex-start;
    }
    ^innerFlexer {
      display: inline-flex;
      padding: 4px;
      box-sizing: border-box;
    }
  `,

  constants: {
    NUM_COLUMNS: 3
  },

  messages: [
    { name: 'OPTIONS_MSG', message: 'options' },
    { name: 'CHOOSE_1_OF_FOLLOWING_OPTIONS', message: 'Choose one of the following options' },
    { name: 'CHOOSE_AT_LEAST_1_OPTION', message: 'Choose at least one option' },
    { name: 'CHOOSE_AT_LEAST', message: 'Choose at least' },
    { name: 'CHOOSE_EXACTLY', message: 'Choose exactly' },
    { name: 'CHOOSE', message: 'Choose' }
  ],

  properties: [
    {
      class: 'Function',
      name: 'onSelect'
    },
    {
      name: 'choices',
      documentation: `
        An array of choices which are single choice is denoted as [value, label, isSelected, choiceMode, isFinal], however the user can
        just pass in [value, label] and the adapt function will turn it into the [value, label, isSelected, choiceMode, isFinal] format
        for processing purposes
      `,
      factory: function() {
        return [];
      },
      adapt: function(_, n) {
        if ( ! Array.isArray(n) ) throw new Error('Please submit an array to choices in the MultiChoiceView');

        if ( n.length > 0 ) {
          n = n.sort();
          var valueLabelChoices = n.filter(choice => choice.length === 2);
          var fullChoices = n.filter(choice => choice.length === 5);

          if ( valueLabelChoices.length === n.length ) return n.map(choice => [choice[0], choice[1], false, this.mode, false]);
          if ( fullChoices.length === n.length ) return n;

          throw new Error('Items in choices array do not have consistent lengths');
        }

        return n;
      },
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'dao',
      documentation: `
        If the user just wants to pass in a dao, it will be processed to populate the
        choices array instead of manually inputing the choices
      `
    },
    {
      name: 'selectedChoices',
      value: [],
      postSet: function(o, n) {
        if ( this.onSelect ) this.onSelect(o, n);

        if ( this.selectedChoices.length < this.maxSelected ) {
          this.choices.forEach(choice => {
            var isSelected = foam.core.Slot.isInstance(choice[2])
              ? choice[2].get()
              : choice[2];

            var isFinal = foam.core.Slot.isInstance(choice[4])
              ? choice[4].get()
              : choice[4];

            if ( ! isSelected && ! isFinal ) {
              choice[3] = this.mustSlot(choice[3]);
              choice[3].set(foam.u2.DisplayMode.RW);
            }
          });
        } else {
          this.choices.forEach(choice => {
            var isSelected = foam.core.Slot.isInstance(choice[2])
              ? choice[2].get()
              : choice[2];

            var isFinal = foam.core.Slot.isInstance(choice[4])
              ? choice[4].get()
              : choice[4];

            if ( ! isSelected && ! isFinal ) {
              choice[3] = this.mustSlot(choice[3]);
              choice[3].set(foam.u2.DisplayMode.DISABLED);
            }
          });
        }
      }
    },
    {
      class: 'Boolean',
      name: 'showMinMaxHelper',
      value: true
    },
    {
      class: 'Boolean',
      name: 'isValidNumberOfChoices',
      expression: function(minSelected, maxSelected, selectedChoices){
        return selectedChoices.length >= minSelected && selectedChoices.length <= maxSelected;
      }
    },
    {
      class: 'Int',
      name: 'minSelected',
      expression: function(choices) {
        return choices.length > 0 ? 1 : 0;
      }
    },
    {
      class: 'Int',
      name: 'maxSelected',
      value: 2
    },
    {
      class: 'Boolean',
      name: 'isVertical',
      value: false
    },
    {
      class: 'Boolean',
      name: 'isDaoFetched',
      value: false
    },
    {
      name: 'objToChoice',
      class: 'Function',
      value: function(obj) {
        return [obj.id, obj.toSummary()];
      }
    },
    {
      name: 'helpText_',
      expression: function(minSelected, maxSelected) {
        // TODO: Change this when formatted messages are supported
        return ( maxSelected > 0 )
          ? ( minSelected == maxSelected )
            ? ( minSelected == 1 )
              ? this.CHOOSE_1_OF_FOLLOWING_OPTIONS
              : `${this.CHOOSE_EXACTLY} ${minSelected} ${this.OPTIONS_MSG}`
            : `${this.CHOOSE} ${minSelected} - ${maxSelected} ${this.OPTIONS_MSG}`
          : ( minSelected == 1 )
            ? this.CHOOSE_AT_LEAST_1_OPTION
            : `${this.CHOOSE_AT_LEAST} ${minSelected} ${this.OPTIONS_MSG}`
          ;
      }
    }
  ],

  methods: [
    function initE() {
      var self = this;

      this.onDAOUpdate();

      this
        .start()
          .add(self.slot(function(showMinMaxHelper, helpText_) {
            return self.E().callIf(showMinMaxHelper, function() {
              this
              .start(foam.u2.layout.Rows)
                .start()
                  .addClass(self.myClass('helpTextRow'))
                  .add(self.helpText_)
                .end()
              .end();
            });
          }))
        .end()
        .start(self.isVertical ? foam.u2.layout.Rows : foam.u2.layout.Cols)
          .addClass(self.myClass('flexer'))
          .add( // TODO isDoaFetched and simpSlot0 aren't used should be clean up
            self.isDaoFetched$.map(isDaoFetched => {
              var newChoices = [];

              var toRender = self.choices.map(function(choice) {
                var simpSlot0 = self.mustSlot(choice[0]);
                var simpSlot1 = self.mustSlot(choice[1]);
                var simpSlot2 = self.mustSlot(choice[2]);
                var simpSlot3 = self.mustSlot(choice[3]);
                var simpSlot4 = self.mustSlot(choice[4]);

                newChoices = [
                  ...newChoices,
                  [choice[0], simpSlot1, simpSlot2, simpSlot3, simpSlot4]
                ];
                var cap = choice[2] && choice[2].obj && choice[2].obj.capability;
                var cls =  cap && cap.cls_.id;
                return self.E()
                  .addClass(self.myClass('innerFlexer'))
                  // NOTE: This should not be the way we implement columns.
                  .style({
                    'width': `${100 / self.NUM_COLUMNS}%`
                  })
                  .tag(self.CardSelectView, {
                      data$: simpSlot2,
                      label$: simpSlot1,
                      mode$: simpSlot3,
                      of: cls,
                      obj: cap
                    });
              });

              this.choices = newChoices;

              self.selectedChoices$ = foam.core.ArraySlot.create({
                slots: newChoices.map(choice => {
                  return this.mustSlot(choice[2]);
                })
              }).map(v => {
                var selectedChoices = [];
                v.forEach((w, i) => {
                  if ( w ) {
                    selectedChoices.push(this.choices[i]);
                  }
                });
                return selectedChoices;
              });
              return toRender;
            })
          )
        .end();
    },

    function mustSlot(v) {
      return foam.core.Slot.isInstance(v) ?
        v :
        foam.core.SimpleSlot.create({ value: v });
    }
  ],

  listeners: [
    {
      name: 'onDAOUpdate',
      isFramed: true,
      code: function() {
        if ( ! this.dao || ! foam.dao.DAO.isInstance(this.dao) ) return;

        var of = this.dao.of;
        if ( of._CHOICE_TEXT_ ) {
          this.dao.select(this.PROJECTION(of.ID, of._CHOICE_TEXT_)).then(s => {
            this.choices      = s.projection;
            this.isDaoFetched = true;
          });
          return;
        }
        console.warn('Inefficient ChoiceView. Consider creating transient _choiceText_ property on ' + of.id + ' DAO, prop: ' + this.prop_);

        /* Ex.:
        {
          class: 'String',
          name: '_choiceText_',
          transient: true,
          javaGetter: 'return getName();',
          getter: function() { return this.name; }
        }
        */
        var p = this.mode === foam.u2.DisplayMode.RW ?
          this.dao.select().then(s => s.array) :
          this.dao.find(this.data).then(o => o ? [o] : []);

        p.then(a => {
          var choices = a.map(this.objToChoice);
          var choiceLabels = a.map(o => this.objToChoice(o)[1]);
          Promise.all(choiceLabels).then(resolvedChoiceLabels => {
            for ( let i = 0; i < choices.length; i++ ) {
              choices[i][1] = resolvedChoiceLabels[i];
            }
            this.choices = choices;
            this.isDaoFetched = true;
          });
        });
      }
    }
  ]
});
<|endoftext|>


<commit_before>module.exports = function(Review) {

  var loopback = require('loopback');

  Review.afterRemote(
    'access',
    function(ctx, instance, next) {
      console.log(instance.likes());
      ctx.Model.include = function(subItems, subInclude, callback) { callback(); };
      next();
    }
  );

  //Review.afterRemote(
  //  'like',
  //
  //);

  //Review.prototype.like = function(user) {
  //  var self = this;
  //  self.likes({
  //      where: {
  //        userId: user.id
  //      }
  //    },
  //    function(err, likes) {
  //      console.log(likes);
  //    }
  //  );
  //};

  Review.prototype.like = function(cb) {

    var self = this;
    var ctx = loopback.getCurrentContext();
    var currUser = ctx && ctx.get('currentUser');

    var app = Review.app;

    self.likes.create(
      {
        user: currUser
      }
    );

    console.log(currUser);

    cb(null, "Success");

  };

  Review.prototype.unlike = function(user) {
    var self = this;

  };

  Review.observe('before save', function(ctx, next) {
    var loopback = require('loopback');
    var newctx = loopback.getCurrentContext();
    // Force userId to be that of the logged in user
    if(newctx !== null){
      var currentUser = newctx && newctx.get('currentUser');
      if(currentUser !== undefined){
        ctx.instance.userId = currentUser.id;

        // Check that user has not submitted a review the the past 3 months
        ONE_MONTH = 30 * 24 * 60 * 60 * 1000;  // Month in milliseconds
        Review.findOne(
          {
            where: {
              userId: currentUser.id,
              companyId: ctx.instance.companyId,
              created: {
                gt: Date.now() - (ONE_MONTH * 3)
              }
            }
          },
          function(err, review) {
            if(review) {
              var error = new Error('You\'ve already reviewed this company in the past three months.');
              error.status = 403;
              next(error);
            } else {
              next();
            }
          }
        );
      }
    } else {
      next();
    }
  });

  //Review.like = function(id, cb) {
  //  cb(null, null);
  //};

  Review.remoteMethod(
    'like',
    {
      returns: {arg: "msg", type: "string"},
      isStatic: false
    }
  );

};
<commit_msg>added endpoint "isLikedByUser" to see if current user has liked the review.<commit_after>module.exports = function (Review) {

  var loopback = require('loopback');

  Review.afterRemote(
    'access',
    function (ctx, instance, next) {
      console.log(instance.likes());
      ctx.Model.include = function (subItems, subInclude, callback) {
        callback();
      };
      next();
    }
  );

  /**
   * Remote method to have a user like/unlike a review.
   * @param cb
   */
  Review.prototype.like = function (cb) {

    var self = this;                // reference to this review
    var app = Review.app;           // reference to the loopback app
    var ctx = loopback.getCurrentContext();
    var currUser = ctx && ctx.get('currentUser'); // get the current user

    if (currUser) {                 // see if there is a current user (is user logged in?)

      //check if user review of company exists already
      app.models.ReviewLike.findOne(
        {
          where: {
            userId: currUser.id,
            reviewId: self.id
          }
        },
        function (err, theLike) {
          if (!err) {
            if (theLike) {
              app.models.ReviewLike.destroyById(theLike.id, function (err) {
                if (!err) {
                  // review has been unliked.
                  cb(null, "Unliked", false);
                } else {
                  cb(err);
                }
              });

            } else {
              self.likes.create(
                {
                  user: currUser
                },
                function (err) {
                  if (!err) {
                    // Review has been liked
                    cb(null, "Liked", true);
                  } else {
                    cb(err);
                  }
                }
              );
            }
          } else {
            cb(err);
          }
        });

    } else {
      var error = new Error("User not authenticated");
      error.status = 403;
      cb(error);
    }
  };

  /**
   * Remote method to see if a user has liked this review
   * @param cb
   */
  Review.prototype.isLikedByUser = function (cb) {
    var self = this;          // reference to this review
    var app = Review.app;     // reference to the Loopback app
    var ctx = loopback.getCurrentContext();
    var currUser = ctx && ctx.get('currentUser');
    if (currUser) {
      app.models.ReviewLike.findOne(
        {
          where: {
            userId: currUser.id,
            reviewId: self.id
          }
        },
        function (err, theLike) {
          if (!err) {
            if (theLike) {
              // is liked
              cb(null, true);
            } else {
              // is not liked
              cb(null, false);

            }
          } else {
            cb(err);
          }
        });
    } else {
      // construct "unauthenticated" error
      var error = new Error("User not authenticated");
      error.status = 403;
      cb(error);
    }

  };

  Review.observe('before save', function (ctx, next) {
    var loopback = require('loopback');
    var newctx = loopback.getCurrentContext();
    // Force userId to be that of the logged in user
    if (newctx !== null) {
      var currentUser = newctx && newctx.get('currentUser');
      if (currentUser !== undefined) {
        ctx.instance.userId = currentUser.id;

        // Check that user has not submitted a review the the past 3 months
        ONE_MONTH = 30 * 24 * 60 * 60 * 1000;  // Month in milliseconds
        Review.findOne(
          {
            where: {
              userId: currentUser.id,
              companyId: ctx.instance.companyId,
              created: {
                gt: Date.now() - (ONE_MONTH * 3)
              }
            }
          },
          function (err, review) {
            if (review) {
              var error = new Error('You\'ve already reviewed this company in the past three months.');
              error.status = 403;
              next(error);
            } else {
              next();
            }
          }
        );
      }
    } else {
      next();
    }
  });

  //Review.like = function(id, cb) {
  //  cb(null, null);
  //};

  Review.remoteMethod(
    'like',
    {
      returns: [{arg: "msg", type: "string"}, {arg: "isLiked", type: "boolean"}],
      isStatic: false,
      description: "Like or unlike this review as the current user.s"
    }
  );

  Review.remoteMethod(
    'isLikedByUser',
    {
      returns: [{arg: "isLiked", type: "boolean"}],
      isStatic: false,
      http: {
        verb: "get"
      },
      description: "Check if this review was liked by the current user."
    }
  );

  // Disable unused methods
  Review.disableRemoteMethod('deleteLikeById', true);

};
<|endoftext|>


<commit_before>

DocumentVDOM.prototype.parseRelaxNG = function () {
	//check if it's a schema file
	//parse all global elements and create an ElementVDOM object
	var rootChildren = this.xmldoc.documentElement.childNodes;
	for (var i = 0; i < rootChildren.length; i++) {
		if (rootChildren[i].isRelaxNGElement("start")) {
			this.parseStart(rootChildren[i]);
		}
	}
	return true;
}
var rng_nsResolver;
DocumentVDOM.prototype.parseStart = function(node) {
	var startChildren = node.childNodes;
	rng_nsResolver = new bxe_RelaxNG_nsResolver(this.xmldoc.documentElement);

	for (var i = 0; i < startChildren.length; i++) {
		if (startChildren[i].isRelaxNGElement("element")) {
			var startElement = new ElementVDOM(startChildren[i]);
			this.firstChild = startElement;
			startElement.parentNode = this;
			var nsParts = rng_nsResolver.parseNodeNameOnElement(startChildren[i]);
			startElement.nodeName = nsParts.nodeName;
			startElement.localName = nsParts.localName;
			startElement.namespaceURI = nsParts.namespaceURI;
			startElement.prefix = nsParts.prefix;
			startElement.canBeRoot = true;
			startElement.nextSibling = null;
			startElement.previousSibling = null;

			break;
		}
	}

	startElement.parseChildren();
	dump("RelaxNG is parsed\n");

}

function bxe_RelaxNG_nsResolver(node) {
	var rootAttr = node.attributes;
	this.defaultNamespace = null;
	this.namespaces = new Array();
	for(var i = 0; i < rootAttr.length; i++) {
		if (rootAttr[i].namespaceURI == "http://www.w3.org/2000/xmlns/") {
			this.namespaces[rootAttr[i].localName] = rootAttr[i].value;
		} else if (rootAttr[i].localName == "ns") {
			this.defaultNamespace= rootAttr[i].value;
		}
	}
}
bxe_RelaxNG_nsResolver.prototype.lookupNamespaceURI = function(prefix) {
	if (this.namespaces[prefix]) {
		return this.namespaces[prefix];
	}
	return null;
}

bxe_RelaxNG_nsResolver.prototype.parseNodeNameOnElement = function(node) {
	var nodename = node.getAttribute("name");
	if (nodename) {
		return this.parseNodeName(nodename);
	}
	// no attribute name, search for childnode with name attribute
	var child = node.firstChild;
	var ret = new Object();
	while (child) {
		if (child.nodeType == 1 && child.localName == "name") {
			child.getAttribute("ns");
			ret.namespaceURI = child.getAttribute("ns");
			ret.localName = child.firstChild.data;
			ret.prefix = "";
			return ret;
		}
		child = child.nextSibling;
	}

}

bxe_RelaxNG_nsResolver.prototype.parseNodeName = function(nodename) {
	var spli = nodename.split(":");
	var ret = new Object;
	ret.nodeName = nodename;

	if (spli.length > 1) {
		ret.localName = spli[1];
		ret.namespaceURI = this.lookupNamespaceURI(spli[0]);
		ret.prefix = spli[0];
	} else {
		ret.localName = spli[0];
		ret.namespaceURI = this.defaultNamespace;
		ret.prefix = "";
	}
	return ret;

}

Node.prototype.__defineGetter__ ("hasRelaxNGNamespace", function() {

	if (this.namespaceURI == "http://relaxng.org/ns/structure/1.0") {
		return true;
	} else {
		return false;
	}
}
)
Node.prototype.isRelaxNGElement = function(nodename) {

//	dump ("isRelaxNGElement" + this.nodeType  +  " " + this.nodeName + " " + this.hasRelaxNGNamespace + "\n");
	if (this.nodeType == 1 && this.nodeName == nodename && this.hasRelaxNGNamespace) {
		return true;
	} else {
		return false;
	}
}

NodeVDOM.prototype.parseChildren = function(node) {
	var childNodes;

	if (node) {
		childNodes = node.childNodes;
	} else {
		childNodes = this.node.childNodes;
	}
	var newChoice;
	var newOneOrMore;

	for (var i = 0; i < childNodes.length; i++) {
		if (childNodes[i].isRelaxNGElement("element")) {
			var newElement = new ElementVDOM(childNodes[i]);
			var nsParts = rng_nsResolver.parseNodeNameOnElement(childNodes[i]);
			newElement.nodeName = nsParts.nodeName;
			newElement.localName = nsParts.localName;
			newElement.namespaceURI = nsParts.namespaceURI;
			newElement.prefix = nsParts.prefix;
			this.appendChild(newElement);
			newElement.parseChildren();

		} else if (childNodes[i].isRelaxNGElement("ref")) {
			//FIXME this can be done smarter... cache the defines.
			var grammarChild = this.node.ownerDocument.documentElement.childNodes;
			//dump ("ref: " + childNodes[i].getAttribute("name") +"\n");
			for (var j = 0; j < grammarChild.length; j++) {
				if (grammarChild[j].isRelaxNGElement("define") && grammarChild[j].getAttribute("name") == childNodes[i].getAttribute("name")) {
					//dump ("define" + grammarChild[j].getAttribute("name") +"\n");
					this.parseChildren(grammarChild[j]);
				}
			}
		}

		else if (childNodes[i].isRelaxNGElement("oneOrMore")) {
			newOneOrMore = new OneOrMoreVDOM(childNodes[i]);
			this.appendChild(newOneOrMore)
			newOneOrMore.parseChildren(childNodes[i]);

		} else if (childNodes[i].isRelaxNGElement("text")) {
			this.appendChild(new TextVDOM(childNodes[i]));

		} else if (childNodes[i].isRelaxNGElement("zeroOrMore")) {
			newOneOrMore = new OneOrMoreVDOM(childNodes[i]);
			this.appendChild(newOneOrMore);
			newOneOrMore.appendChild(new EmptyVDOM());
			newOneOrMore.parseChildren(childNodes[i]);

		} else if (childNodes[i].isRelaxNGElement("attribute")) {
			if (this.node.localName == "optional") {
				this.parentNode.addAttributeNode(new AttributeVDOM(childNodes[i]), "optional");
			} else {
				this.addAttributeNode(new AttributeVDOM(childNodes[i]), "optional");
			}
		} else if (childNodes[i].isRelaxNGElement("optional")) {
			newChoice = new ChoiceVDOM(childNodes[i]);
			this.appendChild(newChoice);
			newChoice.appendChild(new EmptyVDOM());
			newChoice.parseChildren();
		}
		else if (childNodes[i].isRelaxNGElement("choice")) {
			newChoice = new ChoiceVDOM(childNodes[i]);
			this.appendChild(newChoice);
			newChoice.parseChildren();
		}
	}
}

ChoiceVDOM.prototype = new NodeVDOM();

ChoiceVDOM.prototype.isValid = function(ctxt) {
	var child = this.firstChild;
	//dump ("Choice.isValid: " + this.nodeName+"\n");
	var hasEmpty = false;
	while (child) {
		//dump ("Choice.child.isValid: " + child.nodeName + "\n");
		if (child.type == "RELAXNG_EMPTY") {
			hasEmpty = true;
		}
		if (child.isValid(ctxt)) {
			ctxt.vdom = this;
			return true;
		}
		child= child.nextSibling;
	}
	if (hasEmpty) {
		var vdom = ctxt.nextVDOM();
		if (vdom) {
			return vdom.isValid(ctxt);
		}
	}
	return false;
}

function ChoiceVDOM(node) {
	this.node = node;
	this.type = "RELAXNG_CHOICE";
	this.nodeName = "RELAXNG_CHOICE";
	this.attributes = new Array();
}
EmptyVDOM.prototype = new NodeVDOM();

function EmptyVDOM(node) {
	this.node = node;
	this.type = "RELAXNG_EMPTY";
	this.nodeName = "RELAXNG_EMPTY";
}

EmptyVDOM.prototype.isValid  = function() {
	return false;
}
TextVDOM.prototype = new NodeVDOM();

function TextVDOM(node ) {
	this.node = node;
	this.type = "RELAXNG_TEXT";
	this.nodeName = "RELAXNG_TEXT";
}

TextVDOM.prototype.isValid = function(ctxt) {
	//dump("TextVDOM.isValid :" + ctxt.node.data + ":\n");
	if (ctxt.node.nodeType == 3) {
		return true;
	} else {
		return false;
	}

}

OneOrMoreVDOM.prototype = new NodeVDOM();

function OneOrMoreVDOM(node) {
	this.type = "RELAXNG_ONEORMORE";
	this.nodeName = "RELAXNG_ONEORMORE";
	this.node = node;
	this.hit = false;
}

OneOrMoreVDOM.prototype.isValid = function(ctxt) {
	var child = this.firstChild;
	//dump ("OneorMore.isValid:\n");
	while (child) {
		//dump ("OneorMore.child.isValid: " + child.nodeName + "\n");
		if (child.isValid(ctxt)) {
			ctxt.vdom = this;
			this.hit = true;
			return true;
		}
		child = child.nextSibling;
	}
	if (this.hit) {
		var vdom = ctxt.nextVDOM();
		if (vdom) {
			return vdom.isValid(ctxt);
		}
	}
	return false;
}

OneOrMoreVDOM.prototype.allowedElements = function() {
	var child = this.firstChild;
	var ac = new Array();

	while (child) {
		var subac = child.allowedElements();
		if (subac.nodeName) {
			ac.push(subac);
		} else {
			for (var i = 0; i < subac.length; i++) {
				ac.push(subac[i]);
			}
		}
		child = child.nextSibling;
	}
	return ac;

}

ChoiceVDOM.prototype.allowedElements = function() {
	var child = this.firstChild;
	var ac = new Array();

	while (child) {
		var subac = child.allowedElements();
		if (subac && subac.nodeName) {
			ac.push(subac);
		} else if (subac) {
			for (var i = 0; i < subac.length; i++) {
				ac.push(subac[i]);
			}
		}
		child = child.nextSibling;
	}

	return ac;

}

ElementVDOM.prototype.allowedElements = function() {
	var nodeName = "" ;
	if (this.prefix) {
		nodeName = this.prefix +":";
	}
	return {"nodeName":nodeName + this.localName, "namespaceURI": this.namespaceURI, "localName": this.localName};
}

ElementVDOM.prototype.__defineSetter__("nodeName", function(name) {
	var html = true;
	if (html) {
		this._xmlnodeName = name;
	}
}
)

ElementVDOM.prototype.__defineGetter__("nodeName", function(name) {
	return this._xmlnodeName;
}
)

DocumentVDOM.prototype.getStructure = function() {

	 return "\n"+ this.firstChild.getStructure();
}

NodeVDOM.prototype.getStructure = function(level) {
	var out = this.nodeName + " " + this.minOccurs + " " + this.maxOccurs + "\n";
	var child = this.firstChild;
	if (!level ) {
		level = 0;
	}
	var indent = "";
	for (var i = 0; i <= level; i++) {
		indent += "  ";
	}
	for ( i in this.attributes) {
		out += indent + "@" + i + " " + this.attributes[i].dataType + "\n";

	}
	if ( typeof child != "undefined") {
		while ( child != null && child != "undefined") {

			//out += indent + child.nodeName + "\n";

			out += indent + child.getStructure(level + 1);
			child = child.nextSibling;
		}

	}
	return out;
}

XMLNode.prototype.__defineGetter__(
"isWhitespaceOnly",
function()
{
	if (this.nodeType == 3) {
		if(/\S+/.test(this._node.nodeValue)) // any non white space visible characters
			return false;

		return true;
	} else {
		return false;
	}
}
);

<commit_msg>simple, non-recursive include support<commit_after>

DocumentVDOM.prototype.parseRelaxNG = function () {
	//check if it's a schema file
	//parse all global elements and create an ElementVDOM object
	var rootChildren = this.xmldoc.documentElement.childNodes;

	//do includes
	this.parseIncludes();

	for (var i = 0; i < rootChildren.length; i++) {
		if (rootChildren[i].isRelaxNGElement("start")) {
			this.parseStart(rootChildren[i]);
		}
	}
	return true;
}

DocumentVDOM.prototype.parseIncludes = function() {
	var rootChildren = this.xmldoc.documentElement.childNodes;

	for (var i = 0; i < rootChildren.length; i++) {
		if (rootChildren[i].isRelaxNGElement("include")) {
			var td = new mozileTransportDriver("http");
			debug (rootChildren[i].getAttribute("href"));
			td.load(rootChildren[i].getAttribute("href"),null, false);
			if (td.document.documentElement.isRelaxNGElement("grammar")) {
				var child = td.document.documentElement.firstChild;
				while (child) {
					var newChild = this.xmldoc.importNode(child,true);
					this.xmldoc.documentElement.appendChild(newChild);
					child = child.nextSibling;
				}
			}
		}
	}

}

var rng_nsResolver;
DocumentVDOM.prototype.parseStart = function(node) {
	var startChildren = node.childNodes;
	rng_nsResolver = new bxe_RelaxNG_nsResolver(this.xmldoc.documentElement);

	for (var i = 0; i < startChildren.length; i++) {
		if (startChildren[i].isRelaxNGElement("element")) {
			var startElement = new ElementVDOM(startChildren[i]);
			this.firstChild = startElement;
			startElement.parentNode = this;
			var nsParts = rng_nsResolver.parseNodeNameOnElement(startChildren[i]);
			startElement.nodeName = nsParts.nodeName;
			startElement.localName = nsParts.localName;
			startElement.namespaceURI = nsParts.namespaceURI;
			startElement.prefix = nsParts.prefix;
			startElement.canBeRoot = true;
			startElement.nextSibling = null;
			startElement.previousSibling = null;

			break;
		}
	}

	startElement.parseChildren();
	dump("RelaxNG is parsed\n");

}

function bxe_RelaxNG_nsResolver(node) {
	var rootAttr = node.attributes;
	this.defaultNamespace = null;
	this.namespaces = new Array();
	for(var i = 0; i < rootAttr.length; i++) {
		if (rootAttr[i].namespaceURI == "http://www.w3.org/2000/xmlns/") {
			this.namespaces[rootAttr[i].localName] = rootAttr[i].value;
		} else if (rootAttr[i].localName == "ns") {
			this.defaultNamespace= rootAttr[i].value;
		}
	}
}
bxe_RelaxNG_nsResolver.prototype.lookupNamespaceURI = function(prefix) {
	if (this.namespaces[prefix]) {
		return this.namespaces[prefix];
	}
	return null;
}

bxe_RelaxNG_nsResolver.prototype.parseNodeNameOnElement = function(node) {
	var nodename = node.getAttribute("name");
	if (nodename) {
		return this.parseNodeName(nodename);
	}
	// no attribute name, search for childnode with name attribute
	var child = node.firstChild;
	var ret = new Object();
	while (child) {
		if (child.nodeType == 1 && child.localName == "name") {
			child.getAttribute("ns");
			ret.namespaceURI = child.getAttribute("ns");
			ret.localName = child.firstChild.data;
			ret.prefix = "";
			return ret;
		}
		child = child.nextSibling;
	}

}

bxe_RelaxNG_nsResolver.prototype.parseNodeName = function(nodename) {
	var spli = nodename.split(":");
	var ret = new Object;
	ret.nodeName = nodename;

	if (spli.length > 1) {
		ret.localName = spli[1];
		ret.namespaceURI = this.lookupNamespaceURI(spli[0]);
		ret.prefix = spli[0];
	} else {
		ret.localName = spli[0];
		ret.namespaceURI = this.defaultNamespace;
		ret.prefix = "";
	}
	return ret;

}

Node.prototype.__defineGetter__ ("hasRelaxNGNamespace", function() {

	if (this.namespaceURI == "http://relaxng.org/ns/structure/1.0") {
		return true;
	} else {
		return false;
	}
}
)
Node.prototype.isRelaxNGElement = function(nodename) {

//	dump ("isRelaxNGElement" + this.nodeType  +  " " + this.nodeName + " " + this.hasRelaxNGNamespace + "\n");
	if (this.nodeType == 1 && this.nodeName == nodename && this.hasRelaxNGNamespace) {
		return true;
	} else {
		return false;
	}
}

NodeVDOM.prototype.parseChildren = function(node) {
	var childNodes;

	if (node) {
		childNodes = node.childNodes;
	} else {
		childNodes = this.node.childNodes;
	}
	var newChoice;
	var newOneOrMore;

	for (var i = 0; i < childNodes.length; i++) {
		if (childNodes[i].isRelaxNGElement("element")) {
			var newElement = new ElementVDOM(childNodes[i]);
			var nsParts = rng_nsResolver.parseNodeNameOnElement(childNodes[i]);
			newElement.nodeName = nsParts.nodeName;
			newElement.localName = nsParts.localName;
			newElement.namespaceURI = nsParts.namespaceURI;
			newElement.prefix = nsParts.prefix;
			this.appendChild(newElement);
			newElement.parseChildren();

		} else if (childNodes[i].isRelaxNGElement("ref")) {
			//FIXME this can be done smarter... cache the defines.
			var grammarChild = this.node.ownerDocument.documentElement.childNodes;
			//dump ("ref: " + childNodes[i].getAttribute("name") +"\n");
			for (var j = 0; j < grammarChild.length; j++) {
				if (grammarChild[j].isRelaxNGElement("define") && grammarChild[j].getAttribute("name") == childNodes[i].getAttribute("name")) {
					//dump ("define" + grammarChild[j].getAttribute("name") +"\n");
					this.parseChildren(grammarChild[j]);
				}
			}
		}

		else if (childNodes[i].isRelaxNGElement("oneOrMore")) {
			newOneOrMore = new OneOrMoreVDOM(childNodes[i]);
			this.appendChild(newOneOrMore)
			newOneOrMore.parseChildren(childNodes[i]);

		} else if (childNodes[i].isRelaxNGElement("text")) {
			this.appendChild(new TextVDOM(childNodes[i]));

		} else if (childNodes[i].isRelaxNGElement("zeroOrMore")) {
			newOneOrMore = new OneOrMoreVDOM(childNodes[i]);
			this.appendChild(newOneOrMore);
			newOneOrMore.appendChild(new EmptyVDOM());
			newOneOrMore.parseChildren(childNodes[i]);

		} else if (childNodes[i].isRelaxNGElement("attribute")) {
			if (this.node.localName == "optional") {
				this.parentNode.addAttributeNode(new AttributeVDOM(childNodes[i]), "optional");
			} else {
				this.addAttributeNode(new AttributeVDOM(childNodes[i]), "optional");
			}
		} else if (childNodes[i].isRelaxNGElement("optional")) {
			newChoice = new ChoiceVDOM(childNodes[i]);
			this.appendChild(newChoice);
			newChoice.appendChild(new EmptyVDOM());
			newChoice.parseChildren();
		}
		else if (childNodes[i].isRelaxNGElement("choice")) {
			newChoice = new ChoiceVDOM(childNodes[i]);
			this.appendChild(newChoice);
			newChoice.parseChildren();
		}
	}
}

ChoiceVDOM.prototype = new NodeVDOM();

ChoiceVDOM.prototype.isValid = function(ctxt) {
	var child = this.firstChild;
	//dump ("Choice.isValid: " + this.nodeName+"\n");
	var hasEmpty = false;
	while (child) {
		//dump ("Choice.child.isValid: " + child.nodeName + "\n");
		if (child.type == "RELAXNG_EMPTY") {
			hasEmpty = true;
		}
		if (child.isValid(ctxt)) {
			ctxt.vdom = this;
			return true;
		}
		child= child.nextSibling;
	}
	if (hasEmpty) {
		var vdom = ctxt.nextVDOM();
		if (vdom) {
			return vdom.isValid(ctxt);
		}
	}
	return false;
}

function ChoiceVDOM(node) {
	this.node = node;
	this.type = "RELAXNG_CHOICE";
	this.nodeName = "RELAXNG_CHOICE";
	this.attributes = new Array();
}
EmptyVDOM.prototype = new NodeVDOM();

function EmptyVDOM(node) {
	this.node = node;
	this.type = "RELAXNG_EMPTY";
	this.nodeName = "RELAXNG_EMPTY";
}

EmptyVDOM.prototype.isValid  = function() {
	return false;
}
TextVDOM.prototype = new NodeVDOM();

function TextVDOM(node ) {
	this.node = node;
	this.type = "RELAXNG_TEXT";
	this.nodeName = "RELAXNG_TEXT";
}

TextVDOM.prototype.isValid = function(ctxt) {
	//dump("TextVDOM.isValid :" + ctxt.node.data + ":\n");
	if (ctxt.node.nodeType == 3) {
		return true;
	} else {
		return false;
	}

}

OneOrMoreVDOM.prototype = new NodeVDOM();

function OneOrMoreVDOM(node) {
	this.type = "RELAXNG_ONEORMORE";
	this.nodeName = "RELAXNG_ONEORMORE";
	this.node = node;
	this.hit = false;
}

OneOrMoreVDOM.prototype.isValid = function(ctxt) {
	var child = this.firstChild;
	//dump ("OneorMore.isValid:\n");
	while (child) {
		//dump ("OneorMore.child.isValid: " + child.nodeName + "\n");
		if (child.isValid(ctxt)) {
			ctxt.vdom = this;
			this.hit = true;
			return true;
		}
		child = child.nextSibling;
	}
	if (this.hit) {
		var vdom = ctxt.nextVDOM();
		if (vdom) {
			return vdom.isValid(ctxt);
		}
	}
	return false;
}

OneOrMoreVDOM.prototype.allowedElements = function() {
	var child = this.firstChild;
	var ac = new Array();

	while (child) {
		var subac = child.allowedElements();
		if (subac.nodeName) {
			ac.push(subac);
		} else {
			for (var i = 0; i < subac.length; i++) {
				ac.push(subac[i]);
			}
		}
		child = child.nextSibling;
	}
	return ac;

}

ChoiceVDOM.prototype.allowedElements = function() {
	var child = this.firstChild;
	var ac = new Array();

	while (child) {
		var subac = child.allowedElements();
		if (subac && subac.nodeName) {
			ac.push(subac);
		} else if (subac) {
			for (var i = 0; i < subac.length; i++) {
				ac.push(subac[i]);
			}
		}
		child = child.nextSibling;
	}

	return ac;

}

ElementVDOM.prototype.allowedElements = function() {
	var nodeName = "" ;
	if (this.prefix) {
		nodeName = this.prefix +":";
	}
	return {"nodeName":nodeName + this.localName, "namespaceURI": this.namespaceURI, "localName": this.localName};
}

ElementVDOM.prototype.__defineSetter__("nodeName", function(name) {
	var html = true;
	if (html) {
		this._xmlnodeName = name;
	}
}
)

ElementVDOM.prototype.__defineGetter__("nodeName", function(name) {
	return this._xmlnodeName;
}
)

DocumentVDOM.prototype.getStructure = function() {

	 return "\n"+ this.firstChild.getStructure();
}

NodeVDOM.prototype.getStructure = function(level) {
	var out = this.nodeName + " " + this.minOccurs + " " + this.maxOccurs + "\n";
	var child = this.firstChild;
	if (!level ) {
		level = 0;
	}
	var indent = "";
	for (var i = 0; i <= level; i++) {
		indent += "  ";
	}
	for ( i in this.attributes) {
		out += indent + "@" + i + " " + this.attributes[i].dataType + "\n";

	}
	if ( typeof child != "undefined") {
		while ( child != null && child != "undefined") {

			//out += indent + child.nodeName + "\n";

			out += indent + child.getStructure(level + 1);
			child = child.nextSibling;
		}

	}
	return out;
}

XMLNode.prototype.__defineGetter__(
"isWhitespaceOnly",
function()
{
	if (this.nodeType == 3) {
		if(/\S+/.test(this._node.nodeValue)) // any non white space visible characters
			return false;

		return true;
	} else {
		return false;
	}
}
);

<|endoftext|>


<commit_before>import $ from 'jquery';
import Dropzone from 'dropzone';
import _ from 'underscore';
import './preview_markdown';
import csrf from './lib/utils/csrf';
import axios from './lib/utils/axios_utils';

Dropzone.autoDiscover = false;

export default function dropzoneInput(form) {
  const divHover = '

';
  const iconPaperclip = '';
  const $attachButton = form.find('.button-attach-file');
  const $attachingFileMessage = form.find('.attaching-file-message');
  const $cancelButton = form.find('.button-cancel-uploading-files');
  const $retryLink = form.find('.retry-uploading-link');
  const $uploadProgress = form.find('.uploading-progress');
  const $uploadingErrorContainer = form.find('.uploading-error-container');
  const $uploadingErrorMessage = form.find('.uploading-error-message');
  const $uploadingProgressContainer = form.find('.uploading-progress-container');
  const uploadsPath = form.data('uploads-path') || window.uploads_path || null;
  const maxFileSize = gon.max_file_size || 10;
  const formTextarea = form.find('.js-gfm-input');
  let handlePaste;
  let pasteText;
  let addFileToForm;
  let updateAttachingMessage;
  let isImage;
  let getFilename;
  let uploadFile;

  formTextarea.wrap('

');
  formTextarea.on('paste', event => handlePaste(event));

  // Add dropzone area to the form.
  const $mdArea = formTextarea.closest('.md-area');
  form.setupMarkdownPreview();
  const $formDropzone = form.find('.div-dropzone');
  $formDropzone.parent().addClass('div-dropzone-wrapper');
  $formDropzone.append(divHover);
  $formDropzone.find('.div-dropzone-hover').append(iconPaperclip);

  if (!uploadsPath) {
    $formDropzone.addClass('js-invalid-dropzone');
    return;
  }

  const dropzone = $formDropzone.dropzone({
    url: uploadsPath,
    dictDefaultMessage: '',
    clickable: true,
    paramName: 'file',
    maxFilesize: maxFileSize,
    uploadMultiple: false,
    headers: csrf.headers,
    previewContainer: false,
    processing: () => $('.div-dropzone-alert').alert('close'),
    dragover: () => {
      $mdArea.addClass('is-dropzone-hover');
      form.find('.div-dropzone-hover').css('opacity', 0.7);
    },
    dragleave: () => {
      $mdArea.removeClass('is-dropzone-hover');
      form.find('.div-dropzone-hover').css('opacity', 0);
    },
    drop: () => {
      $mdArea.removeClass('is-dropzone-hover');
      form.find('.div-dropzone-hover').css('opacity', 0);
      formTextarea.focus();
    },
    success(header, response) {
      const processingFileCount = this.getQueuedFiles().length + this.getUploadingFiles().length;
      const shouldPad = processingFileCount >= 1;

      pasteText(response.link.markdown, shouldPad);
      // Show 'Attach a file' link only when all files have been uploaded.
      if (!processingFileCount) $attachButton.removeClass('hide');
      addFileToForm(response.link.url);
    },
    error: (file, errorMessage = 'Attaching the file failed.', xhr) => {
      // If 'error' event is fired by dropzone, the second parameter is error message.
      // If the 'errorMessage' parameter is empty, the default error message is set.
      // If the 'error' event is fired by backend (xhr) error response, the third parameter is
      // xhr object (xhr.responseText is error message).
      // On error we hide the 'Attach' and 'Cancel' buttons
      // and show an error.

      // If there's xhr error message, let's show it instead of dropzone's one.
      const message = xhr ? xhr.responseText : errorMessage;

      $uploadingErrorContainer.removeClass('hide');
      $uploadingErrorMessage.html(message);
      $attachButton.addClass('hide');
      $cancelButton.addClass('hide');
    },
    totaluploadprogress(totalUploadProgress) {
      updateAttachingMessage(this.files, $attachingFileMessage);
      $uploadProgress.text(`${Math.round(totalUploadProgress)}%`);
    },
    sending: () => {
      // DOM elements already exist.
      // Instead of dynamically generating them,
      // we just either hide or show them.
      $attachButton.addClass('hide');
      $uploadingErrorContainer.addClass('hide');
      $uploadingProgressContainer.removeClass('hide');
      $cancelButton.removeClass('hide');
    },
    removedfile: () => {
      $attachButton.removeClass('hide');
      $cancelButton.addClass('hide');
      $uploadingProgressContainer.addClass('hide');
      $uploadingErrorContainer.addClass('hide');
    },
    queuecomplete: () => {
      $('.dz-preview').remove();
      $('.markdown-area').trigger('input');

      $uploadingProgressContainer.addClass('hide');
      $cancelButton.addClass('hide');
    },
  });

  const child = $(dropzone[0]).children('textarea');

  // removeAllFiles(true) stops uploading files (if any)
  // and remove them from dropzone files queue.
  $cancelButton.on('click', (e) => {
    e.preventDefault();
    e.stopPropagation();
    Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true);
  });

  // If 'error' event is fired, we store a failed files,
  // clear dropzone files queue, change status of failed files to undefined,
  // and add that files to the dropzone files queue again.
  // addFile() adds file to dropzone files queue and upload it.
  $retryLink.on('click', (e) => {
    const dropzoneInstance = Dropzone.forElement(e.target.closest('.js-main-target-form').querySelector('.div-dropzone'));
    const failedFiles = dropzoneInstance.files;

    e.preventDefault();

    // 'true' parameter of removeAllFiles() cancels
    // uploading of files that are being uploaded at the moment.
    dropzoneInstance.removeAllFiles(true);

    failedFiles.map((failedFile) => {
      const file = failedFile;

      if (file.status === Dropzone.ERROR) {
        file.status = undefined;
        file.accepted = undefined;
      }

      return dropzoneInstance.addFile(file);
    });
  });
  // eslint-disable-next-line consistent-return
  handlePaste = (event) => {
    const pasteEvent = event.originalEvent;
    if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) {
      const image = isImage(pasteEvent);
      if (image) {
        event.preventDefault();
        const filename = getFilename(pasteEvent) || 'image.png';
        const text = `{{${filename}}}`;
        pasteText(text);
        return uploadFile(image.getAsFile(), filename);
      }
    }
  };

  isImage = (data) => {
    let i = 0;
    while (i < data.clipboardData.items.length) {
      const item = data.clipboardData.items[i];
      if (item.type.indexOf('image') !== -1) {
        return item;
      }
      i += 1;
    }
    return false;
  };

  pasteText = (text, shouldPad) => {
    let formattedText = text;
    if (shouldPad) {
      formattedText += '\n\n';
    }
    const textarea = child.get(0);
    const caretStart = textarea.selectionStart;
    const caretEnd = textarea.selectionEnd;
    const textEnd = $(child).val().length;
    const beforeSelection = $(child).val().substring(0, caretStart);
    const afterSelection = $(child).val().substring(caretEnd, textEnd);
    $(child).val(beforeSelection + formattedText + afterSelection);
    textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
    textarea.style.height = `${textarea.scrollHeight}px`;
    formTextarea.get(0).dispatchEvent(new Event('input'));
    return formTextarea.trigger('input');
  };

  addFileToForm = (path) => {
    $(form).append(`<input type="hidden" name="files[]" value="${_.escape(path)}">`);
  };

  getFilename = (e) => {
    let value;
    if (window.clipboardData && window.clipboardData.getData) {
      value = window.clipboardData.getData('Text');
    } else if (e.clipboardData && e.clipboardData.getData) {
      value = e.clipboardData.getData('text/plain');
    }
    value = value.split('\r');
    return value[0];
  };

  const showSpinner = () => $uploadingProgressContainer.removeClass('hide');

  const closeSpinner = () => $uploadingProgressContainer.addClass('hide');

  const showError = (message) => {
    $uploadingErrorContainer.removeClass('hide');
    $uploadingErrorMessage.html(message);
  };

  const closeAlertMessage = () => form.find('.div-dropzone-alert').alert('close');

  const insertToTextArea = (filename, url) => {
    const $child = $(child);
    $child.val((index, val) => val.replace(`{{${filename}}}`, url));

    $child.trigger('change');
  };

  uploadFile = (item, filename) => {
    const formData = new FormData();
    formData.append('file', item, filename);

    showSpinner();
    closeAlertMessage();

    axios.post(uploadsPath, formData)
      .then(({ data }) => {
        const md = data.link.markdown;

        insertToTextArea(filename, md);
        closeSpinner();
      })
      .catch((e) => {
        showError(e.response.data.message);
        closeSpinner();
      });
  };

  updateAttachingMessage = (files, messageContainer) => {
    let attachingMessage;
    const filesCount = files.filter(file => file.status === 'uploading' || file.status === 'queued').length;

    // Dinamycally change uploading files text depending on files number in
    // dropzone files queue.
    if (filesCount > 1) {
      attachingMessage = `Attaching ${filesCount} files -`;
    } else {
      attachingMessage = 'Attaching a file -';
    }

    messageContainer.text(attachingMessage);
  };

  form.find('.markdown-selector').click(function onMarkdownClick(e) {
    e.preventDefault();
    $(this).closest('.gfm-form').find('.div-dropzone').click();
    formTextarea.focus();
  });
}
<commit_msg>Fix error message handling in dropzone_input<commit_after>import $ from 'jquery';
import Dropzone from 'dropzone';
import _ from 'underscore';
import './preview_markdown';
import csrf from './lib/utils/csrf';
import axios from './lib/utils/axios_utils';

Dropzone.autoDiscover = false;

/**
 * Return the error message string from the given response.
 *
 * @param {String|Object} res
 */
function getErrorMessage(res) {
  if (!res || _.isString(res)) {
    return res;
  }

  return res.message;
}

export default function dropzoneInput(form) {
  const divHover = '

';
  const iconPaperclip = '';
  const $attachButton = form.find('.button-attach-file');
  const $attachingFileMessage = form.find('.attaching-file-message');
  const $cancelButton = form.find('.button-cancel-uploading-files');
  const $retryLink = form.find('.retry-uploading-link');
  const $uploadProgress = form.find('.uploading-progress');
  const $uploadingErrorContainer = form.find('.uploading-error-container');
  const $uploadingErrorMessage = form.find('.uploading-error-message');
  const $uploadingProgressContainer = form.find('.uploading-progress-container');
  const uploadsPath = form.data('uploads-path') || window.uploads_path || null;
  const maxFileSize = gon.max_file_size || 10;
  const formTextarea = form.find('.js-gfm-input');
  let handlePaste;
  let pasteText;
  let addFileToForm;
  let updateAttachingMessage;
  let isImage;
  let getFilename;
  let uploadFile;

  formTextarea.wrap('

');
  formTextarea.on('paste', event => handlePaste(event));

  // Add dropzone area to the form.
  const $mdArea = formTextarea.closest('.md-area');
  form.setupMarkdownPreview();
  const $formDropzone = form.find('.div-dropzone');
  $formDropzone.parent().addClass('div-dropzone-wrapper');
  $formDropzone.append(divHover);
  $formDropzone.find('.div-dropzone-hover').append(iconPaperclip);

  if (!uploadsPath) {
    $formDropzone.addClass('js-invalid-dropzone');
    return;
  }

  const dropzone = $formDropzone.dropzone({
    url: uploadsPath,
    dictDefaultMessage: '',
    clickable: true,
    paramName: 'file',
    maxFilesize: maxFileSize,
    uploadMultiple: false,
    headers: csrf.headers,
    previewContainer: false,
    processing: () => $('.div-dropzone-alert').alert('close'),
    dragover: () => {
      $mdArea.addClass('is-dropzone-hover');
      form.find('.div-dropzone-hover').css('opacity', 0.7);
    },
    dragleave: () => {
      $mdArea.removeClass('is-dropzone-hover');
      form.find('.div-dropzone-hover').css('opacity', 0);
    },
    drop: () => {
      $mdArea.removeClass('is-dropzone-hover');
      form.find('.div-dropzone-hover').css('opacity', 0);
      formTextarea.focus();
    },
    success(header, response) {
      const processingFileCount = this.getQueuedFiles().length + this.getUploadingFiles().length;
      const shouldPad = processingFileCount >= 1;

      pasteText(response.link.markdown, shouldPad);
      // Show 'Attach a file' link only when all files have been uploaded.
      if (!processingFileCount) $attachButton.removeClass('hide');
      addFileToForm(response.link.url);
    },
    error: (file, errorMessage = 'Attaching the file failed.', xhr) => {
      // If 'error' event is fired by dropzone, the second parameter is error message.
      // If the 'errorMessage' parameter is empty, the default error message is set.
      // If the 'error' event is fired by backend (xhr) error response, the third parameter is
      // xhr object (xhr.responseText is error message).
      // On error we hide the 'Attach' and 'Cancel' buttons
      // and show an error.
      const message = getErrorMessage(errorMessage || xhr.responseText);

      $uploadingErrorContainer.removeClass('hide');
      $uploadingErrorMessage.html(message);
      $attachButton.addClass('hide');
      $cancelButton.addClass('hide');
    },
    totaluploadprogress(totalUploadProgress) {
      updateAttachingMessage(this.files, $attachingFileMessage);
      $uploadProgress.text(`${Math.round(totalUploadProgress)}%`);
    },
    sending: () => {
      // DOM elements already exist.
      // Instead of dynamically generating them,
      // we just either hide or show them.
      $attachButton.addClass('hide');
      $uploadingErrorContainer.addClass('hide');
      $uploadingProgressContainer.removeClass('hide');
      $cancelButton.removeClass('hide');
    },
    removedfile: () => {
      $attachButton.removeClass('hide');
      $cancelButton.addClass('hide');
      $uploadingProgressContainer.addClass('hide');
      $uploadingErrorContainer.addClass('hide');
    },
    queuecomplete: () => {
      $('.dz-preview').remove();
      $('.markdown-area').trigger('input');

      $uploadingProgressContainer.addClass('hide');
      $cancelButton.addClass('hide');
    },
  });

  const child = $(dropzone[0]).children('textarea');

  // removeAllFiles(true) stops uploading files (if any)
  // and remove them from dropzone files queue.
  $cancelButton.on('click', (e) => {
    e.preventDefault();
    e.stopPropagation();
    Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true);
  });

  // If 'error' event is fired, we store a failed files,
  // clear dropzone files queue, change status of failed files to undefined,
  // and add that files to the dropzone files queue again.
  // addFile() adds file to dropzone files queue and upload it.
  $retryLink.on('click', (e) => {
    const dropzoneInstance = Dropzone.forElement(e.target.closest('.js-main-target-form').querySelector('.div-dropzone'));
    const failedFiles = dropzoneInstance.files;

    e.preventDefault();

    // 'true' parameter of removeAllFiles() cancels
    // uploading of files that are being uploaded at the moment.
    dropzoneInstance.removeAllFiles(true);

    failedFiles.map((failedFile) => {
      const file = failedFile;

      if (file.status === Dropzone.ERROR) {
        file.status = undefined;
        file.accepted = undefined;
      }

      return dropzoneInstance.addFile(file);
    });
  });
  // eslint-disable-next-line consistent-return
  handlePaste = (event) => {
    const pasteEvent = event.originalEvent;
    if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) {
      const image = isImage(pasteEvent);
      if (image) {
        event.preventDefault();
        const filename = getFilename(pasteEvent) || 'image.png';
        const text = `{{${filename}}}`;
        pasteText(text);
        return uploadFile(image.getAsFile(), filename);
      }
    }
  };

  isImage = (data) => {
    let i = 0;
    while (i < data.clipboardData.items.length) {
      const item = data.clipboardData.items[i];
      if (item.type.indexOf('image') !== -1) {
        return item;
      }
      i += 1;
    }
    return false;
  };

  pasteText = (text, shouldPad) => {
    let formattedText = text;
    if (shouldPad) {
      formattedText += '\n\n';
    }
    const textarea = child.get(0);
    const caretStart = textarea.selectionStart;
    const caretEnd = textarea.selectionEnd;
    const textEnd = $(child).val().length;
    const beforeSelection = $(child).val().substring(0, caretStart);
    const afterSelection = $(child).val().substring(caretEnd, textEnd);
    $(child).val(beforeSelection + formattedText + afterSelection);
    textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
    textarea.style.height = `${textarea.scrollHeight}px`;
    formTextarea.get(0).dispatchEvent(new Event('input'));
    return formTextarea.trigger('input');
  };

  addFileToForm = (path) => {
    $(form).append(`<input type="hidden" name="files[]" value="${_.escape(path)}">`);
  };

  getFilename = (e) => {
    let value;
    if (window.clipboardData && window.clipboardData.getData) {
      value = window.clipboardData.getData('Text');
    } else if (e.clipboardData && e.clipboardData.getData) {
      value = e.clipboardData.getData('text/plain');
    }
    value = value.split('\r');
    return value[0];
  };

  const showSpinner = () => $uploadingProgressContainer.removeClass('hide');

  const closeSpinner = () => $uploadingProgressContainer.addClass('hide');

  const showError = (message) => {
    $uploadingErrorContainer.removeClass('hide');
    $uploadingErrorMessage.html(message);
  };

  const closeAlertMessage = () => form.find('.div-dropzone-alert').alert('close');

  const insertToTextArea = (filename, url) => {
    const $child = $(child);
    $child.val((index, val) => val.replace(`{{${filename}}}`, url));

    $child.trigger('change');
  };

  uploadFile = (item, filename) => {
    const formData = new FormData();
    formData.append('file', item, filename);

    showSpinner();
    closeAlertMessage();

    axios.post(uploadsPath, formData)
      .then(({ data }) => {
        const md = data.link.markdown;

        insertToTextArea(filename, md);
        closeSpinner();
      })
      .catch((e) => {
        showError(e.response.data.message);
        closeSpinner();
      });
  };

  updateAttachingMessage = (files, messageContainer) => {
    let attachingMessage;
    const filesCount = files.filter(file => file.status === 'uploading' || file.status === 'queued').length;

    // Dinamycally change uploading files text depending on files number in
    // dropzone files queue.
    if (filesCount > 1) {
      attachingMessage = `Attaching ${filesCount} files -`;
    } else {
      attachingMessage = 'Attaching a file -';
    }

    messageContainer.text(attachingMessage);
  };

  form.find('.markdown-selector').click(function onMarkdownClick(e) {
    e.preventDefault();
    $(this).closest('.gfm-form').find('.div-dropzone').click();
    formTextarea.focus();
  });
}
<|endoftext|>


<commit_before>import CustomEvent from './CustomEvent';
import {helper} from './helper';
let slice = Array.prototype.slice;

/**
 * evented base component, use CustomEvent as compositor
 * @class com.sesamtv.core.util.BaseEvented
 * @requires com.sesamtv.core.util.CustomEvent
 */
export default class BaseEvented {
    constructor() {
        /**
         * @property event
         * @type {com.sesamtv.core.util.CustomEvent}
         */
        this.event = new CustomEvent();

        helper.applyIf(this, {
            /**
             * named listener handlers
             * @property evts
             * @type {Object}
             */
            evts: {},
            /**
             * anonymous listener handlers
             * @property connect
             * @type {Array}
             */
            connect: [],
            config: {}
        });
    }

    /**
     * @method on
     * @param {String} evt
     * @param {Function} fnc
     * @param {Boolean} [once]
     * @returns {{id: Number, remove: Function}}
     */
    on(evt, fnc, once) {
        return this.event[once ? 'once' : 'on'](evt, fnc.bind(this));
    }

    /**
     * @method once
     * @param {String} evt
     * @param {Function} fnc
     * @returns {{id: Number, remove: Function}}
     */
    once(evt, fnc) {
        return this.on(evt, fnc.bind(this), true);
    }

    /**
     * @method emit
     * @returns {Array}
     */
    emit() {
        return this.event.emit.apply(this.event, arguments);
    }

    /**
     * @method broadcast
     * @param {String} wildcard
     */
    broadcast(wildcard) {
        return this.event.broadcast.apply(this.event, arguments);
    }

    /**
     * set a property
     * @method set
     * @param k
     * @param v
     */
    set(k, v) {
        if (k === 'config') {
            return this.setConfigs(v);
        }
        if (k in this && this[k] === v) {
            return;
        }
        var res = {
            newValue: v
        };
        if (k in this) {
            res.oldValue = isObject(this[k]) ? helper.deepClone(this[k]) : this[k];
        }
        this[k] = v;
        this.emit(k, res);

    }

    /**
     * @method setConfigs
     * @param {Object} v
     */
    setConfigs(v) {
        Object.keys(v).forEach(function (k) {
            this.setConfig(k, v[k]);
        }, this);
    }

    /**
     * set a config property
     * @method setConfig
     * @param {String} k
     * @param {*} v
     */
    setConfig(k, v) {
        if (arguments.length === 1) {
            return this.setConfigs(k);
        }
        if (k in this.config && this.config[k] === v) {
            return;
        }
        var res = {
            key: k,
            newValue: v
        };
        if (k in this.config) {
            res.oldValue = isObject(this.config[k]) ? helper.deepClone(this.config[k]) : this.config[k];
        }
        this.config[k] = v;
        this.emit('config', res);
        this.emit('config/' + k, res);
    }

    /**
     * @method getConfig
     * @param {String} k
     * @returns {*}
     */
    getConfig(k) {
        var res = this.config[k], getter = k + 'Getter';
        if (this.config[getter]) {
            return this.config[getter](res, this);
        }
        return res;
    }

    removeEvts(evts) {
        evts.forEach(function (evt) {
            if (this.evts[evt]) {
                this.evts[evt].remove();
                delete this.evts[evt];
            }
        }, this);
    }

    /**
     * @method destroy
     */
    destroy() {

        this.connect.forEach(function (c) {
            c.remove();
        });
        this.connect.length = 0;
        Object.keys(this.evts).forEach(function (e) {
            this.evts[e].remove();
        }, this);
        this.evts = {};
        this.event.purgeListeners();
        this.$purgeSuper && this.$purgeSuper();
    }
}
/**
 * eventize an object by composition
 * @method eventize
 * @static
 * @param {Object} self
 * @returns {Object}
 */
BaseEvented.eventize = function (self) {
    self.event = new CustomEvent();
    helper.applyIf(self, {
        evts: {},
        connect: [],
        config: {}
    });
    var proto = BaseEvented.prototype, supers = {};
    Object.keys(proto).forEach(function (k) {
        if (k in self && typeof(self[k]) === 'function') {
            supers[k] = proto[k];
        } else {
            self[k] = proto[k];
        }
    });
    self.$super = function (k) {
        return supers[k] && supers[k].apply(this, slice.call(arguments, 1));
    };
    self.$purgeSuper = function () {
        supers = null;
    };
    return self;
};
<commit_msg>wrong import<commit_after>import CustomEvent from './CustomEvent';
import helper from './helper';
let slice = Array.prototype.slice;

/**
 * evented base component, use CustomEvent as compositor
 * @class com.sesamtv.core.util.BaseEvented
 * @requires com.sesamtv.core.util.CustomEvent
 */
export default class BaseEvented {
    constructor() {
        /**
         * @property event
         * @type {com.sesamtv.core.util.CustomEvent}
         */
        this.event = new CustomEvent();

        helper.applyIf(this, {
            /**
             * named listener handlers
             * @property evts
             * @type {Object}
             */
            evts: {},
            /**
             * anonymous listener handlers
             * @property connect
             * @type {Array}
             */
            connect: [],
            config: {}
        });
    }

    /**
     * @method on
     * @param {String} evt
     * @param {Function} fnc
     * @param {Boolean} [once]
     * @returns {{id: Number, remove: Function}}
     */
    on(evt, fnc, once) {
        return this.event[once ? 'once' : 'on'](evt, fnc.bind(this));
    }

    /**
     * @method once
     * @param {String} evt
     * @param {Function} fnc
     * @returns {{id: Number, remove: Function}}
     */
    once(evt, fnc) {
        return this.on(evt, fnc.bind(this), true);
    }

    /**
     * @method emit
     * @returns {Array}
     */
    emit() {
        return this.event.emit.apply(this.event, arguments);
    }

    /**
     * @method broadcast
     * @param {String} wildcard
     */
    broadcast(wildcard) {
        return this.event.broadcast.apply(this.event, arguments);
    }

    /**
     * set a property
     * @method set
     * @param k
     * @param v
     */
    set(k, v) {
        if (k === 'config') {
            return this.setConfigs(v);
        }
        if (k in this && this[k] === v) {
            return;
        }
        var res = {
            newValue: v
        };
        if (k in this) {
            res.oldValue = isObject(this[k]) ? helper.deepClone(this[k]) : this[k];
        }
        this[k] = v;
        this.emit(k, res);

    }

    /**
     * @method setConfigs
     * @param {Object} v
     */
    setConfigs(v) {
        Object.keys(v).forEach(function (k) {
            this.setConfig(k, v[k]);
        }, this);
    }

    /**
     * set a config property
     * @method setConfig
     * @param {String} k
     * @param {*} v
     */
    setConfig(k, v) {
        if (arguments.length === 1) {
            return this.setConfigs(k);
        }
        if (k in this.config && this.config[k] === v) {
            return;
        }
        var res = {
            key: k,
            newValue: v
        };
        if (k in this.config) {
            res.oldValue = isObject(this.config[k]) ? helper.deepClone(this.config[k]) : this.config[k];
        }
        this.config[k] = v;
        this.emit('config', res);
        this.emit('config/' + k, res);
    }

    /**
     * @method getConfig
     * @param {String} k
     * @returns {*}
     */
    getConfig(k) {
        var res = this.config[k], getter = k + 'Getter';
        if (this.config[getter]) {
            return this.config[getter](res, this);
        }
        return res;
    }

    removeEvts(evts) {
        evts.forEach(function (evt) {
            if (this.evts[evt]) {
                this.evts[evt].remove();
                delete this.evts[evt];
            }
        }, this);
    }

    /**
     * @method destroy
     */
    destroy() {

        this.connect.forEach(function (c) {
            c.remove();
        });
        this.connect.length = 0;
        Object.keys(this.evts).forEach(function (e) {
            this.evts[e].remove();
        }, this);
        this.evts = {};
        this.event.purgeListeners();
        this.$purgeSuper && this.$purgeSuper();
    }
}
/**
 * eventize an object by composition
 * @method eventize
 * @static
 * @param {Object} self
 * @returns {Object}
 */
BaseEvented.eventize = function (self) {
    self.event = new CustomEvent();
    helper.applyIf(self, {
        evts: {},
        connect: [],
        config: {}
    });
    var proto = BaseEvented.prototype, supers = {};
    Object.keys(proto).forEach(function (k) {
        if (k in self && typeof(self[k]) === 'function') {
            supers[k] = proto[k];
        } else {
            self[k] = proto[k];
        }
    });
    self.$super = function (k) {
        return supers[k] && supers[k].apply(this, slice.call(arguments, 1));
    };
    self.$purgeSuper = function () {
        supers = null;
    };
    return self;
};
<|endoftext|>