<commit_before>/**
 * Mobile QuickBug.
 **/

MODEL({
  name: 'CCListView',
  extendsModel: 'AutocompleteListView',
  properties: [
    {
      name: 'srcDAO',
      factory: function() { return this.X.PersonDAO; }
    },
    {
      name: 'queryFactory',
      defaultValue: function(data) {
        return STARTS_WITH_IC(IssuePerson.NAME, data);
      }
    }
  ]
});

/*
MODEL({
  name: 'LabelsListView',
  extendsModel: 'AutocompleteListView',
  properties: [
    {
      name: 'srcDAO',
      factory: function() { return this.X.LabelDAO; }
    },
    {
      name: 'queryFactory',
      defaultValue: function(data) {
        return CONTAINS_IC(IssueLabel.NAME, data);
      }
    }
  ]
});
*/

MODEL({
  name: 'MBug',
  description: 'Mobile QuickBug',
  traits: ['PositionedDOMViewTrait'],

  extendsModel: 'View',

  properties: [
    {
      name: 'qbug',
      label: 'QBug',
      subType: 'QBug',
      view: function() { return this.X.DetailView.create({model: QBug}); },
      factory: function() {
        return this.X.QBug.create({
          authClientId: '18229540903-cojf1q6g154dk5kpim4jnck3cfdvqe3u.apps.googleusercontent.com',
          authClientSecret: '<KEY>'
        });
      }
    },
    {
      name: 'project',
      subType: 'QProject',
      postSet: function(_, project) {
        var Y = project.X;
        Y.project     = project;
        Y.projectName = project.projectName;
        var localDao = LRUCachingDAO.create({
          delegate: MDAO.create({ model: Y.QIssue })
        });

        project.IssueNetworkDAO.batchSize = 25;

        Y.issueDAO = Y.QIssueSplitDAO.create({
          local: localDao,
          model: Y.QIssue,
          remote: project.IssueNetworkDAO,
          maxLimit: 25
        });

        var pc = Y.AppController.create({
          name: project.projectName,
          model: Y.QIssue,
          dao: Y.issueDAO,
          editableCitationViews: true,
          queryParser: Y.QueryParser,
          citationView: 'IssueCitationView',
          sortChoices: project.defaultSortChoices,
          filterChoices: project.defaultFilterChoices,
          menuFactory: function() {
            return this.X.ChangeProjectView.create({data: project.user});
          }
        });
        var view = FloatingView.create({
          view: Y.DetailView.create({data: pc})
        });
        this.stack.setTopView(view);
      }
    },
    {
      name: 'stack',
      subType: 'StackView',
      factory: function() { return this.X.StackView.create(); },
      postSet: function(old, v) {
        if ( old ) {
          Events.unfollow(this.width$, old.width$);
          Events.unfollow(this.height$, old.height$);
        }
        Events.follow(this.width$, v.width$);
        Events.follow(this.height$, v.height$);
      }
    }
  ],

  methods: {
    init: function() {
      this.SUPER();
      this.X.touchManager = this.X.TouchManager.create({});
      this.X.gestureManager = this.X.GestureManager.create({});
    },
    toHTML: function() { return this.stack.toHTML(); },
    projectContext: function() {
      return this.X.sub({
        mbug:              this,
        baseURL:           this.qbug.baseURL,
        user:              this.qbug.user,
        persistentContext: this.qbug.persistentContext,
        ProjectDAO:        this.qbug.ProjectDAO, // Is this needed?
        stack:             this.stack,
        DontSyncProjectData: true, // TODO(adamvy): This is a hack to prevent the project from creating its own caching daos.
      }, 'MBUG CONTEXT');
    },

    initHTML: function() {
      this.stack.initHTML();

      var self = this;
      this.qbug.userFuture.get(function(user) {
        self.qbug.findProject(user.defaultProject, {
          put: function(p) {
            self.project = p;
          }
        }, self.projectContext())
      });
    },
    editIssue: function(issue) {
      // TODO: clone issue, and add listener which saves on updates
      var v = this.project.X.FloatingView.create({
        view: this.project.X.IssueView.create({dao: this.project.X.issueDAO, data: issue})
      });
      this.stack.pushView(v, '');
    },
    setProject: function(projectName) {
      var self = this;
      this.qbug.findProject(projectName, function(project) {
        self.project = project;
        self.qbug.userFuture.get(function(user) {
          user.defaultProject = projectName;
        });
      }, this.projectContext());
      this.stack.back();
    }
  }
});


MODEL({
  name: 'PriorityView',
  extendsModel: 'View',
  properties: [
    { name: 'data', postSet: function() { this.updateHTML(); } },
    {
      name: 'map',
      defaultValue: {
        'Low': 3,
        'Medium': 2,
        'High': 1,
        'Critical': 0
      }
    }
  ],
  methods: {
    dataToPriority: function(data) {
      var numeric = parseInt(data);
      if ( ! Number.isNaN(numeric) ) return numeric;
      if ( this.map[data] !== undefined ) return this.map[data];
      console.warn('Unknown priority ', data);
      return 3;
    }
  },
  templates: [
    function toInnerHTML() {/*
      <div class="priority priority-<%= this.dataToPriority(this.data) %>">P<%= this.dataToPriority(this.data) %></div>
    */}
  ]
});


MODEL({
  name: 'PriorityCitationView',
  extendsModel: 'PriorityView',
  methods: {
    updateHTML: function() {
      if ( ! this.$ ) return;
      this.invokeDestructors();
      this.$.outerHTML = this.toHTML();
      this.initHTML();
    }
  },
  templates: [
    function toHTML() {/*
      <span id="%%id" class="priority priority-<%= this.dataToPriority(this.data) %>">Pri <%= this.dataToPriority(this.data) %></span>
    */}
  ]
});


MODEL({
  name: 'IssueView',
  extendsModel: 'UpdateDetailView',
  properties: [
    {
      name: 'commentsView',
      factory: function() {
        return DAOListView.create({mode: 'read-only', rowView: 'CommentView', dao: this.X.project.issueCommentDAO(this.data.id).orderBy(QIssueComment.SEQ_NO) });
      }
    },
    {
      name: 'scroller$',
      getter: function() { return this.X.$(this.id + '-scroller'); }
    },
    {
      name: 'scrollHeight'
    },
    {
      name: 'viewportHeight',
      defaultValueFn: function() {
        return this.$ && this.$.offsetHeight;
      }
    },
    {
      name: 'scrollTop',
      hidden: true,
      defaultValue: 0,
      postSet: function(_, nu) {
        var s = this.$.getElementsByClassName('body')[0];
        if ( s ) s.scrollTop = nu;
      }
    },
    {
      name: 'verticalScrollbarView',
      defaultValue: 'VerticalScrollbarView'
    },
    {
      name: 'scrollGesture',
      hidden: true,
      transient: true,
      factory: function() {
        var self = this;
        return this.X.GestureTarget.create({
          container: {
            containsPoint: function(x, y, e) {
              var s = self.scroller$;
              while ( e ) {
                if ( e === s ) return true;
                e = e.parentNode;
              }
              return false;
            }
          },
          handler: this,
          gesture: 'verticalScrollMomentum'
        });
      }
    }
  ],
  // TODO: Make traits for DOM (overflow: scroll) and abspos scrolling.
  listeners: [
    {
      name: 'verticalScrollMove',
      code: function(dy, ty, y) {
        this.scrollTop -= dy;
      }
    },
    {
      name: 'updateScrollHeight',
      code: function() {
        this.scrollHeight = parseFloat(this.scroller$.style.height);
      }
    }
  ],
  methods: {
    initHTML: function() {
      this.SUPER();
      this.X.gestureManager.install(this.scrollGesture);

      /*
      var verticalScrollbar = FOAM.lookup(this.verticalScrollbarView, this.X).create({
        height$: this.viewportHeight$,
        scrollTop$: this.scrollTop$,
        scrollHeight$: this.scrollHeight$
      });

      this.$.getElementsByClassName('body')[0].insertAdjacentHTML('beforeend', verticalScrollbar.toHTML());
      this.X.setTimeout(function() { verticalScrollbar.initHTML(); }, 0);
      */
    },

    destroy: function() {
      this.SUPER();
      this.X.gestureManager.uninstall(this.scrollGesture);
    }
  },
  actions: [
    {
      name: 'back',
      label: '',
      iconUrl: 'images/ic_arrow_back_24dp.png'
    },
    {
      name: 'cancel',
      label: '',
      iconUrl: 'images/ic_clear_24dp.png'
    },
    {
      name: 'save',
      label: '',
      iconUrl: 'images/ic_done_24dp.png'
    },
    {
      name: 'addCc',
      label: '',
      iconUrl: 'images/ic_add_24dp.png',
      action: function() {
        var innerView = this.X.IssueOwnerEditView.create(this.model.CC);
        var view = this.X.FloatingView.create({
          view: innerView
        });
        this.X.stack.pushView(view);
        innerView.focus();
        var self = this;
        innerView.subscribe(['finished'], function() {
          self.data.cc = self.data.cc.concat(innerView.data);
        });
      }
    }
  ],
  templates: [
    function headerToHTML() {/*
      <div class="header">
        <div class="toolbar">
          $$back
          $$cancel
          <span class="expand"></span>
          $$save
          $$starred{
            model_: 'ImageBooleanView',
            className:  'star',
            trueImage:  'images/ic_star_white_24dp.png',
            falseImage: 'images/ic_star_outline_white_24dp.png'
          }
        </div>
        <div class="title">
          #&nbsp;$$id{mode: 'read-only'} $$summary{mode: 'read-only'}
        </div>
      </div>
    */},

    // TODO: get options from QProject
    function bodyToHTML() {/*
      <div class="body">
        <div id="<%= this.id %>-scroller" class="body-scroller">
          <div class="choice">
          <% if ( this.data.pri ) { %>
            $$pri{ model_: 'PriorityView' }
            $$pri{
              model_: 'PopupChoiceView',
              iconUrl: 'images/ic_arrow_drop_down_24dp.png',
              showValue: true
            }
          <% } else { %>
            $$priority{ model_: 'PriorityView' }
            $$priority{
              model_: 'PopupChoiceView',
              iconUrl: 'images/ic_arrow_drop_down_24dp.png',
              showValue: true
            }
          <% } %>
          </div>
          <div class="choice">
            <img src="images/ic_keep_24dp.png" class="status-icon">
            <%=
              this.createTemplateView('STATUS', {
                model_: 'PopupChoiceView',
                iconUrl: 'images/ic_arrow_drop_down_24dp.png',
                showValue: true,
                dao: this.X.StatusDAO,
                objToChoice: function(o) { return [o.status, o.status]; }
              })
            %>
          </div>
          <div class="separator separator1"></div>
          <div class="owner">
            <div class="owner-header">Owner</div>
            $$owner{model_: 'IssueOwnerView', className: 'owner-info'}
          </div>
          <div class="separator separator1"></div>
          <div class="cc">
            <div class="cc-header"><div class="cc-header-text">Cc</div>$$addCc</div>
            $$cc{xmodel_: 'CCListView', model_: 'IssueEmailArrayView'}
          </div>
          <%= this.commentsView %>
        </div>
      </div>
    */},

    function toHTML() {/*
      <div id="<%= this.id %>" class="issue-edit">
        <%= this.headerToHTML() %>
        <%= this.bodyToHTML() %>
      </div>
    */}
  ]
});


MODEL({
  name: 'IssueEmailArrayView',
  extendsModel: 'View',
  properties: [
    { name: 'data', postSet: function() { this.updateHTML(); } }
  ],
  templates: [
    function toHTML() {/* <div id="<%= this.id %>"><%= this.toInnerHTML() %></div> */},
    function toInnerHTML() {/*
<% for ( var i = 0; i < this.data.length; i++ ) { %>
  <%
    var view = this.X.IssueEmailCitationView.create({ data: this.data[i], showDelete: true });
    out(view);
    view.subscribe(['delete'], function(index) {
      this.data = this.data.filter(function(x, i) { return i !== index; });
    }.bind(this, i));
  %>
<% } %>
    */}
  ]
});


MODEL({
  name: 'IssuePersonCitationView',
  extendsModel: 'DetailView',
  templates: [
    function toHTML() {/* $$name{model_: 'IssueEmailCitationView'} */}
  ]
});


MODEL({
  name: 'IssueEmailCitationView',
  extendsModel: 'View',
  properties: [
    { name: 'data', postSet: function() { this.updateHTML(); } },
    { name: 'tagName', defaultValue: 'div' },
    { name: 'className', defaultValue: 'owner-info' },
    { name: 'showDelete', defaultValue: false }
  ],
  actions: [
    {
      name: 'delete',
      label: '',
      iconUrl: 'images/ic_clear_black_24dp.png',
      action: function() {
        this.publish(['delete']);
      }
    }
  ],
  templates: [
    function toHTML() {/* <div %%cssClassAttr() id="<%= this.id %>"><%= this.toInnerHTML() %></div> */},
    function toInnerHTML() {/*
      <%= this.X.IssueOwnerAvatarView.create({ data: this.data }) %>
      <div class="owner-name"><%= escapeHTML(this.data) %></div>
      <% if ( this.showDelete ) { %>
        $$delete
      <% } %>
    */}
  ]
});


MODEL({
  name: 'IssueOwnerAvatarView',
  extendsModel: 'View',
  properties: [
    { name: 'data', postSet: function() { this.updateHTML(); } },
  ],
  methods: {
    colors: 'e8ad62 9b26af 6639b6 4184f3 02a8f3 00bbd3 009587 0e9c57 9e9c57 8ac249 ccdb38 ffea3a f3b300 ff9700 ff5621 785447'.split(' '),
    generateColor: function() {
      if ( ! this.data ) return 'url(images/silhouette.png)';

      return '#' + this.colors[Math.abs(this.data.hashCode()) % this.colors.length];
    },
    updateHTML: function() {
      if ( this.$ ) this.$.style.background = this.generateColor();
      return this.SUPER();
    },
  },
  templates: [
    function toInnerHTML() {/* {{{this.data[0] && this.data[0].toUpperCase()}}} */},
    // TODO: replace use of data-tip with Tooltip Model
    function toHTML() {/*
      <div id="<%= this.id %>" data-tip="<%= this.data %>" class="owner-avatar" style="background:<%= this.generateColor() %>"><%= this.toInnerHTML() %></div>
    */}
  ]
});


MODEL({
  name: 'IssueCitationView',
  extendsModel: 'DetailView',
  templates: [
    function toHTML() {/*
      <div id="<%= this.on('click', function() { this.X.mbug.editIssue(this.data); }) %>" class="issue-citation">
        $$owner{model_: "IssueOwnerAvatarView"}
        <div class="middle">
          $$id{mode: 'read-only', className: 'id'}
        <% if ( this.data.pri ) { %>
          $$pri{ model_: 'PriorityCitationView' }
        <% } else { %>
          $$priority{ model_: 'PriorityCitationView' }
        <% } %><br>
          $$summary{mode: 'read-only'}
        </div>
        $$starred{
          model_: 'ImageBooleanView',
          className:  'star',
          trueImage:  'images/ic_star_24dp.png',
          falseImage: 'images/ic_star_outline_24dp.png'
        }
      </div>
    */}
   ]
});


MODEL({
  name: 'CommentView',
  extendsModel: 'DetailView',
  templates: [ function toHTML() {/*
    <div class="separator"></div>
    <div id="<%= this.id %>" class="comment-view">
       <span class="owner">
         <%= IssueOwnerAvatarView.create({data: this.data.author.name}) %>
       </span>
       <span class="content">
         Commented by $$author<br>
         <span class="published"><%= this.data.published.toRelativeDateString() %></span> <br><br>
         $$content{mode: 'read-only', escapeHTML: false}
       </span>
    </div>
  */} ]
});


// Is actually a DetailView on User, but is only really
// used to show and select available projects.
MODEL({
  name: 'ChangeProjectView',
  traits: ['PositionedDOMViewTrait'],
  extendsModel: 'DetailView',

  properties: [
    { name: 'preferredWidth', defaultValue: 304 },
    { name: 'className', defaultValue: 'change-project-view' },
  ],<commit_msg>Continue porting MBug to new AutocompleteListView.<commit_after>/**
 * Mobile QuickBug.
 **/

MODEL({
  name: 'CCListView',
  extendsModel: 'AutocompleteListView',
  properties: [
    {
      name: 'srcDAO',
      factory: function() { return this.X.PersonDAO; }
    },
    {
      name: 'queryFactory',
      defaultValue: function(data) {
        return STARTS_WITH_IC(IssuePerson.NAME, data);
      }
    },
    {
      name: 'rowView',
      defaultValue: 'IssueEmailCitationView2'
    }
  ]
});


MODEL({
  name: 'IssueEmailCitationView2',
  extendsModel: 'DefaultRowView',
  properties: [
    {
      name: 'className',
      defaultValue: 'IssueEmailCitationView'
    }
  ],
  templates: [
    function CSS() {/*
      .IssueEmailCitationView {
        padding: 12px 0;
        display: flex;
        flex-direction: row;
        align-items: center;
        color: #575757;
      }
    */},
    function toInnerHTML() {/*
      <%= this.X.IssueOwnerAvatarView.create({ data: this.data }) %>
      <div class="owner-name"><%= escapeHTML(this.data) %></div>
      $$removeRow
    */}
  ]
});


/*
MODEL({
  name: 'LabelsListView',
  extendsModel: 'AutocompleteListView',
  properties: [
    {
      name: 'srcDAO',
      factory: function() { return this.X.LabelDAO; }
    },
    {
      name: 'queryFactory',
      defaultValue: function(data) {
        return CONTAINS_IC(IssueLabel.NAME, data);
      }
    }
  ]
});
*/

MODEL({
  name: 'MBug',
  description: 'Mobile QuickBug',
  traits: ['PositionedDOMViewTrait'],

  extendsModel: 'View',

  properties: [
    {
      name: 'qbug',
      label: 'QBug',
      subType: 'QBug',
      view: function() { return this.X.DetailView.create({model: QBug}); },
      factory: function() {
        return this.X.QBug.create({
          authClientId: '18229540903-cojf1q6g154dk5kpim4jnck3cfdvqe3u.apps.googleusercontent.com',
          authClientSecret: '<KEY>'
        });
      }
    },
    {
      name: 'project',
      subType: 'QProject',
      postSet: function(_, project) {
        var Y = project.X;
        Y.project     = project;
        Y.projectName = project.projectName;
        var localDao = LRUCachingDAO.create({
          delegate: MDAO.create({ model: Y.QIssue })
        });

        project.IssueNetworkDAO.batchSize = 25;

        Y.issueDAO = Y.QIssueSplitDAO.create({
          local: localDao,
          model: Y.QIssue,
          remote: project.IssueNetworkDAO,
          maxLimit: 25
        });

        var pc = Y.AppController.create({
          name: project.projectName,
          model: Y.QIssue,
          dao: Y.issueDAO,
          editableCitationViews: true,
          queryParser: Y.QueryParser,
          citationView: 'IssueCitationView',
          sortChoices: project.defaultSortChoices,
          filterChoices: project.defaultFilterChoices,
          menuFactory: function() {
            return this.X.ChangeProjectView.create({data: project.user});
          }
        });
        var view = FloatingView.create({
          view: Y.DetailView.create({data: pc})
        });
        this.stack.setTopView(view);
      }
    },
    {
      name: 'stack',
      subType: 'StackView',
      factory: function() { return this.X.StackView.create(); },
      postSet: function(old, v) {
        if ( old ) {
          Events.unfollow(this.width$, old.width$);
          Events.unfollow(this.height$, old.height$);
        }
        Events.follow(this.width$, v.width$);
        Events.follow(this.height$, v.height$);
      }
    }
  ],

  methods: {
    init: function() {
      this.SUPER();
      this.X.touchManager = this.X.TouchManager.create({});
      this.X.gestureManager = this.X.GestureManager.create({});
    },
    toHTML: function() { return this.stack.toHTML(); },
    projectContext: function() {
      return this.X.sub({
        mbug:              this,
        baseURL:           this.qbug.baseURL,
        user:              this.qbug.user,
        persistentContext: this.qbug.persistentContext,
        ProjectDAO:        this.qbug.ProjectDAO, // Is this needed?
        stack:             this.stack,
        DontSyncProjectData: true, // TODO(adamvy): This is a hack to prevent the project from creating its own caching daos.
      }, 'MBUG CONTEXT');
    },

    initHTML: function() {
      this.stack.initHTML();

      var self = this;
      this.qbug.userFuture.get(function(user) {
        self.qbug.findProject(user.defaultProject, {
          put: function(p) {
            self.project = p;
          }
        }, self.projectContext())
      });
    },
    editIssue: function(issue) {
      // TODO: clone issue, and add listener which saves on updates
      var v = this.project.X.FloatingView.create({
        view: this.project.X.IssueView.create({dao: this.project.X.issueDAO, data: issue})
      });
      this.stack.pushView(v, '');
    },
    setProject: function(projectName) {
      var self = this;
      this.qbug.findProject(projectName, function(project) {
        self.project = project;
        self.qbug.userFuture.get(function(user) {
          user.defaultProject = projectName;
        });
      }, this.projectContext());
      this.stack.back();
    }
  }
});


MODEL({
  name: 'PriorityView',
  extendsModel: 'View',
  properties: [
    { name: 'data', postSet: function() { this.updateHTML(); } },
    {
      name: 'map',
      defaultValue: {
        'Low': 3,
        'Medium': 2,
        'High': 1,
        'Critical': 0
      }
    }
  ],
  methods: {
    dataToPriority: function(data) {
      var numeric = parseInt(data);
      if ( ! Number.isNaN(numeric) ) return numeric;
      if ( this.map[data] !== undefined ) return this.map[data];
      console.warn('Unknown priority ', data);
      return 3;
    }
  },
  templates: [
    function toInnerHTML() {/*
      <div class="priority priority-<%= this.dataToPriority(this.data) %>">P<%= this.dataToPriority(this.data) %></div>
    */}
  ]
});


MODEL({
  name: 'PriorityCitationView',
  extendsModel: 'PriorityView',
  methods: {
    updateHTML: function() {
      if ( ! this.$ ) return;
      this.invokeDestructors();
      this.$.outerHTML = this.toHTML();
      this.initHTML();
    }
  },
  templates: [
    function toHTML() {/*
      <span id="%%id" class="priority priority-<%= this.dataToPriority(this.data) %>">Pri <%= this.dataToPriority(this.data) %></span>
    */}
  ]
});


MODEL({
  name: 'IssueView',
  extendsModel: 'UpdateDetailView',
  properties: [
    {
      name: 'commentsView',
      factory: function() {
        return DAOListView.create({mode: 'read-only', rowView: 'CommentView', dao: this.X.project.issueCommentDAO(this.data.id).orderBy(QIssueComment.SEQ_NO) });
      }
    },
    {
      name: 'scroller$',
      getter: function() { return this.X.$(this.id + '-scroller'); }
    },
    {
      name: 'scrollHeight'
    },
    {
      name: 'viewportHeight',
      defaultValueFn: function() {
        return this.$ && this.$.offsetHeight;
      }
    },
    {
      name: 'scrollTop',
      hidden: true,
      defaultValue: 0,
      postSet: function(_, nu) {
        var s = this.$.getElementsByClassName('body')[0];
        if ( s ) s.scrollTop = nu;
      }
    },
    {
      name: 'verticalScrollbarView',
      defaultValue: 'VerticalScrollbarView'
    },
    {
      name: 'scrollGesture',
      hidden: true,
      transient: true,
      factory: function() {
        var self = this;
        return this.X.GestureTarget.create({
          container: {
            containsPoint: function(x, y, e) {
              var s = self.scroller$;
              while ( e ) {
                if ( e === s ) return true;
                e = e.parentNode;
              }
              return false;
            }
          },
          handler: this,
          gesture: 'verticalScrollMomentum'
        });
      }
    }
  ],
  // TODO: Make traits for DOM (overflow: scroll) and abspos scrolling.
  listeners: [
    {
      name: 'verticalScrollMove',
      code: function(dy, ty, y) {
        this.scrollTop -= dy;
      }
    },
    {
      name: 'updateScrollHeight',
      code: function() {
        this.scrollHeight = parseFloat(this.scroller$.style.height);
      }
    }
  ],
  methods: {
    initHTML: function() {
      this.SUPER();
      this.X.gestureManager.install(this.scrollGesture);

      /*
      var verticalScrollbar = FOAM.lookup(this.verticalScrollbarView, this.X).create({
        height$: this.viewportHeight$,
        scrollTop$: this.scrollTop$,
        scrollHeight$: this.scrollHeight$
      });

      this.$.getElementsByClassName('body')[0].insertAdjacentHTML('beforeend', verticalScrollbar.toHTML());
      this.X.setTimeout(function() { verticalScrollbar.initHTML(); }, 0);
      */
    },

    destroy: function() {
      this.SUPER();
      this.X.gestureManager.uninstall(this.scrollGesture);
    }
  },
  actions: [
    {
      name: 'back',
      label: '',
      iconUrl: 'images/ic_arrow_back_24dp.png'
    },
    {
      name: 'cancel',
      label: '',
      iconUrl: 'images/ic_clear_24dp.png'
    },
    {
      name: 'save',
      label: '',
      iconUrl: 'images/ic_done_24dp.png'
    },
    {
      name: 'addCc',
      label: '',
      iconUrl: 'images/ic_add_24dp.png',
      action: function() {
        var innerView = this.X.IssueOwnerEditView.create(this.model.CC);
        var view = this.X.FloatingView.create({
          view: innerView
        });
        this.X.stack.pushView(view);
        innerView.focus();
        var self = this;
        innerView.subscribe(['finished'], function() {
          self.data.cc = self.data.cc.concat(innerView.data);
        });
      }
    }
  ],
  templates: [
    function headerToHTML() {/*
      <div class="header">
        <div class="toolbar">
          $$back
          $$cancel
          <span class="expand"></span>
          $$save
          $$starred{
            model_: 'ImageBooleanView',
            className:  'star',
            trueImage:  'images/ic_star_white_24dp.png',
            falseImage: 'images/ic_star_outline_white_24dp.png'
          }
        </div>
        <div class="title">
          #&nbsp;$$id{mode: 'read-only'} $$summary{mode: 'read-only'}
        </div>
      </div>
    */},

    // TODO: get options from QProject
    function bodyToHTML() {/*
      <div class="body">
        <div id="<%= this.id %>-scroller" class="body-scroller">
          <div class="choice">
          <% if ( this.data.pri ) { %>
            $$pri{ model_: 'PriorityView' }
            $$pri{
              model_: 'PopupChoiceView',
              iconUrl: 'images/ic_arrow_drop_down_24dp.png',
              showValue: true
            }
          <% } else { %>
            $$priority{ model_: 'PriorityView' }
            $$priority{
              model_: 'PopupChoiceView',
              iconUrl: 'images/ic_arrow_drop_down_24dp.png',
              showValue: true
            }
          <% } %>
          </div>
          <div class="choice">
            <img src="images/ic_keep_24dp.png" class="status-icon">
            <%=
              this.createTemplateView('STATUS', {
                model_: 'PopupChoiceView',
                iconUrl: 'images/ic_arrow_drop_down_24dp.png',
                showValue: true,
                dao: this.X.StatusDAO,
                objToChoice: function(o) { return [o.status, o.status]; }
              })
            %>
          </div>
          <div class="separator separator1"></div>
          <div class="owner">
            <div class="owner-header">Owner</div>
            $$owner{model_: 'IssueOwnerView', className: 'owner-info'}
          </div>
          <!-- 
          <div class="separator separator1"></div>
          cc{model_: 'CCListView'}
          -->
          <div class="separator separator1"></div>
          <div class="cc">
            <div class="cc-header"><div class="cc-header-text">Cc</div>$$addCc</div>
            $$cc{model_: 'IssueEmailArrayView'}
          </div>
          <%= this.commentsView %>
        </div>
      </div>
    */},

    function toHTML() {/*
      <div id="<%= this.id %>" class="issue-edit">
        <%= this.headerToHTML() %>
        <%= this.bodyToHTML() %>
      </div>
    */}
  ]
});


MODEL({
  name: 'IssueEmailArrayView',
  extendsModel: 'View',
  properties: [
    { name: 'data', postSet: function() { this.updateHTML(); } }
  ],
  templates: [
    function toHTML() {/* <div id="<%= this.id %>"><%= this.toInnerHTML() %></div> */},
    function toInnerHTML() {/*
<% for ( var i = 0; i < this.data.length; i++ ) { %>
  <%
    var view = this.X.IssueEmailCitationView.create({ data: this.data[i], showDelete: true });
    out(view);
    view.subscribe(['delete'], function(index) {
      this.data = this.data.filter(function(x, i) { return i !== index; });
    }.bind(this, i));
  %>
<% } %>
    */}
  ]
});


MODEL({
  name: 'IssuePersonCitationView',
  extendsModel: 'DetailView',
  templates: [
    function toHTML() {/* $$name{model_: 'IssueEmailCitationView'} */}
  ]
});


MODEL({
  name: 'IssueEmailCitationView',
  extendsModel: 'View',
  properties: [
    { name: 'data', postSet: function() { this.updateHTML(); } },
    { name: 'tagName', defaultValue: 'div' },
    { name: 'className', defaultValue: 'owner-info' },
    { name: 'showDelete', defaultValue: false }
  ],
  actions: [
    {
      name: 'delete',
      label: '',
      iconUrl: 'images/ic_clear_black_24dp.png',
      action: function() {
        this.publish(['delete']);
      }
    }
  ],
  templates: [
    function toHTML() {/* <div %%cssClassAttr() id="<%= this.id %>"><%= this.toInnerHTML() %></div> */},
    function toInnerHTML() {/*
      <%= this.X.IssueOwnerAvatarView.create({ data: this.data }) %>
      <div class="owner-name"><%= escapeHTML(this.data) %></div>
      <% if ( this.showDelete ) { %>
        $$delete
      <% } %>
    */}
  ]
});


MODEL({
  name: 'IssueOwnerAvatarView',
  extendsModel: 'View',
  properties: [
    { name: 'data', postSet: function() { this.updateHTML(); } },
  ],
  methods: {
    colors: 'e8ad62 9b26af 6639b6 4184f3 02a8f3 00bbd3 009587 0e9c57 9e9c57 8ac249 ccdb38 ffea3a f3b300 ff9700 ff5621 785447'.split(' '),
    generateColor: function() {
      if ( ! this.data ) return 'url(images/silhouette.png)';

      return '#' + this.colors[Math.abs(this.data.hashCode()) % this.colors.length];
    },
    updateHTML: function() {
      if ( this.$ ) this.$.style.background = this.generateColor();
      return this.SUPER();
    },
  },
  templates: [
    function toInnerHTML() {/* {{{this.data[0] && this.data[0].toUpperCase()}}} */},
    // TODO: replace use of data-tip with Tooltip Model
    function toHTML() {/*
      <div id="<%= this.id %>" data-tip="<%= this.data %>" class="owner-avatar" style="background:<%= this.generateColor() %>"><%= this.toInnerHTML() %></div>
    */}
  ]
});


MODEL({
  name: 'IssueCitationView',
  extendsModel: 'DetailView',
  templates: [
    function toHTML() {/*
      <div id="<%= this.on('click', function() { this.X.mbug.editIssue(this.data); }) %>" class="issue-citation">
        $$owner{model_: "IssueOwnerAvatarView"}
        <div class="middle">
          $$id{mode: 'read-only', className: 'id'}
        <% if ( this.data.pri ) { %>
          $$pri{ model_: 'PriorityCitationView' }
        <% } else { %>
          $$priority{ model_: 'PriorityCitationView' }
        <% } %><br>
          $$summary{mode: 'read-only'}
        </div>
        $$starred{
          model_: 'ImageBooleanView',
          className:  'star',
          trueImage:  'images/ic_star_24dp.png',
          falseImage: 'images/ic_star_outline_24dp.png'
        }
      </div>
    */}
   ]
});


MODEL({
  name: 'CommentView',
  extendsModel: 'DetailView',
  templates: [ function toHTML() {/*
    <div class="separator"></div>
    <div id="<%= this.id %>" class="comment-view">
       <span class="owner">
         <%= IssueOwnerAvatarView.create({data: this.data.author.name}) %>
       </span>
       <span class="content">
         Commented by $$author<br>
         <span class="published"><%= this.data.published.toRelativeDateString() %></span> <br><br>
         $$content{mode: 'read-only', escapeHTML: false}
       </span>
    </div>
  */} ]
});


// Is actually a DetailView on User, but is only really
// used to show and select available projects.
MODEL({
  name: 'ChangeProjectView',
  traits: ['PositionedDOMViewTrait'],
  extendsModel: 'DetailView',

  properties: [
    { name: 'preferredWidth', defaultValue: 304 },
    { name: 'className',      defaultValue: 'change-project-view' },
  ],
{'fraction_non_alphanumeric': 0.16322632324850575, 'fraction_numerical': 0.008318442294657712, 'mean_word_length': 2.13159671972986, 'pattern_counts': {'":': 0, '<': 210, '<?xml version=': 0, '>': 208, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6153961', 'n_tokens_mistral': 10792, 'n_tokens_neox': 10287, 'n_words': 1907}