<commit_before>/*
 Copyright 2013 by PeopleWare n.v.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

define(["dojo/_base/declare", "dijit/_WidgetBase", "dijit/_TemplatedMixin", "dijit/_WidgetsInTemplateMixin", "dojo/text!./onePropertyCrdList.html", "dojo/store/Memory",
  "ppwcode-util-oddsAndEnds/_PropagationMixin", "dojox/mobile/ListItem", "dojox/mobile/Icon", "dojo/dom-style", "dojo/dom-class", "dojo/Stateful", "dojo/dom-construct",
  "dijit/form/Button", "dojo/i18n!./nls/labels", "dijit/form/ComboBox", "../../log/logger!", "dojo/Deferred",

  "dojox/mobile/Container", "dojox/mobile/EdgeToEdgeList",
  "xstyle/css!dojox/mobile/themes/iphone/iphone.css",
  "xstyle/css!./onePropertyCrdList.css"],
  function (declare, _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, template, Memory,
            _PropagationMixin, ListItem, Icon, domStyle, domClass, Stateful, domConstruct,
            Button, labels, ComboBox, logger, Deferred) {

    return declare([Stateful, _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, _PropagationMixin], {
      // summary:
      //   Widget that is specially made to represent a list with only 1 property per element.
      //   Elements can be added or removed.
      //   When the getData() function is overridden, the input box will support auto-complete.

      templateString: template,
      labels: labels,

      // _ulNode: UL HTML element in template
      _ulNode: null,

      _ulNodeWrapper: null,

      _txtAdd: null,

      _addTextWrapperNode: null,

      _propertySelect: null,

      // getData: function
      // summary:
      //   Function that gets the data to use in the auto-complete.
      // description:
      //   This function should return an array with objects that have a displayValue.
      //   This is a property that is used to search on when trying to auto-complete.
      getData: function() {return new Deferred().resolve([]).promise},

      placeHolder: "",

      height: null,

      // value: Array
      value: [],
      // disabled: Boolean
      disabled: null,

      "-propagate-": {
        value: [
          {path: "_propagateValue", exec: true}
        ]
      },

      _propagateValue: function(/*Array*/ valueArray) {
        var self = this;
        if (self._ulNode) {
          domConstruct.empty(self._ulNode.domNode);
          if (valueArray && valueArray.length > 0) {
            valueArray.forEach(function (element) {
              var li = new ListItem({label: self.format(element)});
              if (!self.get("disabled")) {
                var deleteIcon = new Button({showLabel: false, iconClass: "dijitIconDelete"});
                domClass.add(deleteIcon.domNode, "deleteIcon");
                li.own(deleteIcon.on("click", function() {
                  if (!self.get("disabled")) {
                    if (confirm(labels.confirmDelete1 + self.format(element) + labels.confirmDelete2)) {
                      var arr = self.get("value");
                      var newArr = [];
                      arr.forEach(function (arrElement) {
                        if (element !== arrElement) {
                          newArr.push(arrElement);
                        }
                      });
                      self.set("value", newArr);
                    }
                  }
                }));
                li.addChild(deleteIcon);
              }
              if (!self.get("disabled")) {
                domClass.remove(li.domNode, "disabled");
              }
              else {
                domClass.add(li.domNode, "disabled");
              }
              self._ulNode.addChild(li);
            });
          }
        }
      },

      _getValueAttr: function() {
        if (this.value) {
          return this.value.slice();
        } else {
          return undefined;
        }
      },

      _getDisabledAttr: function() {
        return !!this.disabled;
      },

      format: function(val) {
        return val ? val.toString() : "";
      },

      parse: function(formattedValue) {
        return formattedValue ? formattedValue.toString() : "";
      },

      _setHeightAttr: function(height) {
        this.inherited(arguments);
        domStyle.set(this._ulNodeWrapper, "height", height);
      },

      _setDisabledAttr: function(disabled) {
        this.inherited(arguments);
        this.disabled = disabled;
        if (!disabled) {
          this._initAutoCompleteInputField();
        }
        else {
          this._destroyAutoCompleteInputField();
        }
        domStyle.set(this._addTextWrapperNode, "display", (!!disabled ? "none" : "block"));
        this._propagateValue(this._getValueAttr());
      },

      _initAutoCompleteInputField: function() {
        var self = this;
        var loaded = self.getData && self.getData();
        if (loaded) {
          loaded.then(
            function (result) {
              var myStore = new Memory({data: result});
              self._txtAdd = new ComboBox({
                  id: "propertyComboBox",
                  store: myStore,
                  autocomplete: result && result.length > 0,
                  searchAttr: "displayValue",
                  placeholder: self.placeHolder
                },
                self._propertySelect);
              self._txtAdd.startup();
            },
            function (err) {
              logger.error("Error loading emergency numbers used");
            }
          );
        }
        else {
          logger.error("No getData function specified");
        }
      },

      // _destroyAutoCompleteInputField: function
      // summary:
      //   Destroys the auto-complete input box and re-creates the original DIV element that was replaced on creation.
      _destroyAutoCompleteInputField: function() {
        if (this._txtAdd) {
          this._txtAdd.destroy();
          this._txtAdd = null;
          domConstruct.destroy("propertyComboBox");
          this._propertySelect = domConstruct.create("div", {}, this._addTextWrapperNode, "first");
        }
      },

      addClicked: function() {
        var fieldValue = this._txtAdd.get("value");
        var valueToAdd = this.parse(fieldValue);
        if (valueToAdd && valueToAdd.trim() !== "") {
          var arr = this.get("value");
          arr.push(valueToAdd);
          this.set("value", arr);
          this._txtAdd.set("value", "");
        }
      }

    });
  }
)
;
<commit_msg>PICTOPERFECT-631 Ervoor zorgen dat hetzelfde emergencyNumber geen meerdere keren voorkomt in de lijst van de licensePane.<commit_after>/*
 Copyright 2013 by PeopleWare n.v.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

define(["dojo/_base/declare", "dijit/_WidgetBase", "dojo/_base/array", "dijit/_TemplatedMixin", "dijit/_WidgetsInTemplateMixin", "dojo/text!./onePropertyCrdList.html", "dojo/store/Memory",
  "ppwcode-util-oddsAndEnds/_PropagationMixin", "dojox/mobile/ListItem", "dojox/mobile/Icon", "dojo/dom-style", "dojo/dom-class", "dojo/Stateful", "dojo/dom-construct",
  "dijit/form/Button", "dojo/i18n!./nls/labels", "dijit/form/ComboBox", "../../log/logger!", "dojo/Deferred",

  "dojox/mobile/Container", "dojox/mobile/EdgeToEdgeList",
  "xstyle/css!dojox/mobile/themes/iphone/iphone.css",
  "xstyle/css!./onePropertyCrdList.css"],
  function (declare, _WidgetBase, array, _TemplatedMixin, _WidgetsInTemplateMixin, template, Memory,
            _PropagationMixin, ListItem, Icon, domStyle, domClass, Stateful, domConstruct,
            Button, labels, ComboBox, logger, Deferred) {

    return declare([Stateful, _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, _PropagationMixin], {
      // summary:
      //   Widget that is specially made to represent a list with only 1 property per element.
      //   Elements can be added or removed.
      //   When the getData() function is overridden, the input box will support auto-complete.

      templateString: template,
      labels: labels,

      // _ulNode: UL HTML element in template
      _ulNode: null,

      _ulNodeWrapper: null,

      _txtAdd: null,

      _addTextWrapperNode: null,

      _propertySelect: null,

      // getData: function
      // summary:
      //   Function that gets the data to use in the auto-complete.
      // description:
      //   This function should return an array with objects that have a displayValue.
      //   This is a property that is used to search on when trying to auto-complete.
      getData: function() {return new Deferred().resolve([]).promise},

      placeHolder: "",

      height: null,

      // value: Array
      value: [],
      // disabled: Boolean
      disabled: null,

      "-propagate-": {
        value: [
          {path: "_propagateValue", exec: true}
        ]
      },

      _propagateValue: function(/*Array*/ valueArray) {
        var self = this;
        if (self._ulNode) {
          domConstruct.empty(self._ulNode.domNode);
          if (valueArray && valueArray.length > 0) {
            valueArray.forEach(function (element) {
              var li = new ListItem({label: self.format(element)});
              if (!self.get("disabled")) {
                var deleteIcon = new Button({showLabel: false, iconClass: "dijitIconDelete"});
                domClass.add(deleteIcon.domNode, "deleteIcon");
                li.own(deleteIcon.on("click", function() {
                  if (!self.get("disabled")) {
                    if (confirm(labels.confirmDelete1 + self.format(element) + labels.confirmDelete2)) {
                      var arr = self.get("value");
                      var newArr = [];
                      arr.forEach(function (arrElement) {
                        if (element !== arrElement) {
                          newArr.push(arrElement);
                        }
                      });
                      self.set("value", newArr);
                    }
                  }
                }));
                li.addChild(deleteIcon);
              }
              if (!self.get("disabled")) {
                domClass.remove(li.domNode, "disabled");
              }
              else {
                domClass.add(li.domNode, "disabled");
              }
              self._ulNode.addChild(li);
            });
          }
        }
      },

      _getValueAttr: function() {
        if (this.value) {
          return this.value.slice();
        } else {
          return undefined;
        }
      },

      _getDisabledAttr: function() {
        return !!this.disabled;
      },

      format: function(val) {
        return val ? val.toString() : "";
      },

      parse: function(formattedValue) {
        return formattedValue ? formattedValue.toString() : "";
      },

      _setHeightAttr: function(height) {
        this.inherited(arguments);
        domStyle.set(this._ulNodeWrapper, "height", height);
      },

      _setDisabledAttr: function(disabled) {
        this.inherited(arguments);
        this.disabled = disabled;
        if (!disabled) {
          this._initAutoCompleteInputField();
        }
        else {
          this._destroyAutoCompleteInputField();
        }
        domStyle.set(this._addTextWrapperNode, "display", (!!disabled ? "none" : "block"));
        this._propagateValue(this._getValueAttr());
      },

      _initAutoCompleteInputField: function() {
        var self = this;
        var loaded = self.getData && self.getData();
        if (loaded) {
          loaded.then(
            function (result) {
              var myStore = new Memory({data: result});
              self._txtAdd = new ComboBox({
                  id: "propertyComboBox",
                  store: myStore,
                  autocomplete: result && result.length > 0,
                  searchAttr: "displayValue",
                  placeholder: self.placeHolder
                },
                self._propertySelect);
              self._txtAdd.startup();
            },
            function (err) {
              logger.error("Error loading emergency numbers used");
            }
          );
        }
        else {
          logger.error("No getData function specified");
        }
      },

      // _destroyAutoCompleteInputField: function
      // summary:
      //   Destroys the auto-complete input box and re-creates the original DIV element that was replaced on creation.
      _destroyAutoCompleteInputField: function() {
        if (this._txtAdd) {
          this._txtAdd.destroy();
          this._txtAdd = null;
          domConstruct.destroy("propertyComboBox");
          this._propertySelect = domConstruct.create("div", {}, this._addTextWrapperNode, "first");
        }
      },

      addClicked: function() {
        var fieldValue = this._txtAdd.get("value");
        var valueToAdd = this.parse(fieldValue);
        if (valueToAdd && valueToAdd.trim() !== "") {
          var arr = this.get("value");
          if (array.indexOf(arr, valueToAdd) < 0) {
            arr.push(valueToAdd);
            this.set("value", arr);
          }
          this._txtAdd.set("value", "");
        }
      }

    });
  }
)
;
<|endoftext|>


<commit_before>'use strict';
const book = require('../books');
const empty = require('../emptyBook');
const indexInstance = new Index();

//write test to read book data
describe("Read book data", () => {

  it("should verify that the files are not empty", () => {
    expect(book.length > 0).toBeTruthy();
  });

  it("should check if all properties in the file are strings", () => {
    book.forEach(function (document) {
      expect(typeof document.title === "string").toBeTruthy();
      expect(typeof document.text === "string").toBeTruthy();
    });

  });

  it("should verify that the file content is a JSON array", () => {
    expect(Array.isArray(book)).toBeTruthy();
  });

});

describe("Check Class properties", () => {

  it("should have get index property", () => {
    expect(typeof indexInstance.getIndex === 'function').toBeTruthy();
  });

  it("should have create index property", () => {
    expect(typeof indexInstance.createIndex === 'function').toBeTruthy();
  });

  it("should have search property", () => {
    expect(typeof indexInstance.searchIndex === 'function').toBeTruthy();
  });

});

describe("Populate Index", () => {

  it('should return false when empty book is passed', () => {
    const emptyFile = {
      name: 'emptyBook.json',
      docs: empty
    };

    expect(indexInstance.createIndex(empty)).toBeFalsy();
  });

  it("should create an index of documents as an object", () => {
    const details = {
      name: 'book.json',
      docs: book
    };

    expect(typeof indexInstance.getIndex(details)).toBe('object');
  });

  it("should create index and return a valid result", () => {
    const result = {
      a: [0, 1],
      alice: [0],
      alliance: [1],
      an: [1],
      and: [0, 1],
      destroy: [1],
      dwarf: [1],
      elf: [1],
      enters: [0],
      falls: [0],
      full: [0],
      hobbit: [1],
      hole: [0],
      imagination: [0],
      into: [0],
      man: [1],
      of: [0, 1],
      oobit: [1],
      powerful: [1],
      rabbit: [0],
      ring: [1],
      seek: [1],
      shit: [0],
      to: [1],
      unusual: [1],
      wizard: [1],
      world: [0]
    };

    expect(indexInstance.createIndex(book)).toEqual(result);
  });

});

describe('Search Index', () => {

  it('should search and return an object that contains result', () => {
    const term = 'We are very unusual in alliance';
    expect(typeof indexInstance.searchIndex(term, indexInstance.createIndex(book))).toBe('object');
  });

  it('should return accurate search result', () => {
    const term = 'We are very unusual in alliance';
    const searchResult = {
      we:'Not Found',
      are: 'Not Found',
      very: 'Not Found',
      unusual: [1],
      in: 'Not Found',
      alliance: [1]
    };

    expect(indexInstance.searchIndex(term, indexInstance.createIndex(book))).toEqual(searchResult);
  });

  it('should return false when term is not string', () => {
    const invalidTerm = 12;
    expect(indexInstance.searchIndex(invalidTerm, indexInstance.createIndex(book))).toBeFalsy();
  });

});<commit_msg>Character per line limit fix<commit_after>'use strict';
const book = require('../books');
const empty = require('../emptyBook');
const indexInstance = new Index();

//write test to read book data
describe("Read book data", () => {

  it("should verify that the files are not empty", () => {
    expect(book.length > 0).toBeTruthy();
  });

  it("should check if all properties in the file are strings", () => {
    book.forEach(function (document) {
      expect(typeof document.title === "string").toBeTruthy();
      expect(typeof document.text === "string").toBeTruthy();
    });

  });

  it("should verify that the file content is a JSON array", () => {
    expect(Array.isArray(book)).toBeTruthy();
  });

});

describe("Check Class properties", () => {

  it("should have get index property", () => {
    expect(typeof indexInstance.getIndex === 'function').toBeTruthy();
  });

  it("should have create index property", () => {
    expect(typeof indexInstance.createIndex === 'function').toBeTruthy();
  });

  it("should have search property", () => {
    expect(typeof indexInstance.searchIndex === 'function').toBeTruthy();
  });

});

describe("Populate Index", () => {

  it('should return false when empty book is passed', () => {
    const emptyFile = {
      name: 'emptyBook.json',
      docs: empty
    };

    expect(indexInstance.createIndex(empty)).toBeFalsy();
  });

  it("should create an index of documents as an object", () => {
    const details = {
      name: 'book.json',
      docs: book
    };

    expect(typeof indexInstance.getIndex(details)).toBe('object');
  });

  it("should create index and return a valid result", () => {
    const result = {
      a: [0, 1],
      alice: [0],
      alliance: [1],
      an: [1],
      and: [0, 1],
      destroy: [1],
      dwarf: [1],
      elf: [1],
      enters: [0],
      falls: [0],
      full: [0],
      hobbit: [1],
      hole: [0],
      imagination: [0],
      into: [0],
      man: [1],
      of: [0, 1],
      oobit: [1],
      powerful: [1],
      rabbit: [0],
      ring: [1],
      seek: [1],
      shit: [0],
      to: [1],
      unusual: [1],
      wizard: [1],
      world: [0]
    };

    expect(indexInstance.createIndex(book)).toEqual(result);
  });

});

describe('Search Index', () => {

  it('should search and return an object that contains result', () => {
    const term = 'We are very unusual in alliance';
    expect(typeof indexInstance.searchIndex(term, indexInstance
     .createIndex(book))).toBe('object');
  });

  it('should return accurate search result', () => {
    const term = 'We are very unusual in alliance';
    const searchResult = {
      we:'Not Found',
      are: 'Not Found',
      very: 'Not Found',
      unusual: [1],
      in: 'Not Found',
      alliance: [1]
    };

    expect(indexInstance.searchIndex(term, indexInstance
     .createIndex(book))).toEqual(searchResult);
  });

  it('should return false when term is not string', () => {
    const invalidTerm = 12;

    expect(indexInstance.searchIndex(invalidTerm, indexInstance
     .createIndex(book))).toBeFalsy();
  });

});<|endoftext|>


<commit_before>"use strict";

mft = {
  // CONFIG ITEMS:

  STEMMING: 'porter', // doesn't do anything yet
  TOKENIZING: 'basic',// doesn't do anything yet

  EXTRACTED_TERMS_FIELD: '_extracted_terms',
  SEARCH_ALL_PSEUDO_FIELD: '$search', // magic "field name" that specifies we want a fulltext search
            // (abusing the '$' notation somewhat, which is often for search operators)
  SEARCH_ANY_PSEUDO_FIELD: '$searchany', // magic "field name" that specifies we want a fulltext search matching any, not all

  // WORKHORSE VARS:
  _STEM_FUNCTION: null,
  _TOKENIZE_FUNCTION: null
};

mft.indexedFieldsAndWeights = function(coll_name) {
  // we expect a special collection named '_fulltext_config', with items having elems 'collection_name', and 'fields',
  // with 'fields' having keys being the field name, and the values being the weight.
  //eg:
      // {
      //   collection_name: 'test',
      //   fields: {
      //     title: 5,
      //     content: 1
      //   }
      // }

  collection_conf = db.fulltext_config.findOne({collection_name: coll_name});
  return collection_conf.fields;
};

mft.search = function(coll_name, query_obj) {
  // check for $search member on query_obj
  // if it doesn't exist, pass through to regular .find
  // if it does, parse the ft query string, and add the appropriate filter
  // clause to the non-ft-search components, execute that, then
  // score every remaining document, and put those sorted IDs and scores in a record in
  // a private collection (hashed by the whole query obj, which we can check next time around)
  // then iterate through the IDs and scores and return the corrpesponding records with the IDs
  // attached to them, in a way that emulates a cursor object.
  var search_query_string;
  var require_all;
  if (query_obj[mft.SEARCH_ALL_PSEUDO_FIELD]) {
    search_query_string = query_obj[mft.SEARCH_ALL_PSEUDO_FIELD];
    require_all = true;
  } else if (query_obj[mft.SEARCH_ALL_PSEUDO_FIELD]) {
    search_query_string = query_obj[mft.SEARCH_ANY_PSEUDO_FIELD];
    require_all = false;
  } else {
    return db[coll_name].find(query_obj); // no need to call search, you chump
  }
  print("DEBUG: query string is " + search_query_string);
  var query_terms = mft.processQueryString(search_query_string);
  print("DEBUG: query terms is " + (query_terms.join(',') + " with length " + query_terms.length));
  query_obj[mft.EXTRACTED_TERMS_FIELD] = mft.filterArg(coll_name, query_terms, require_all);
  if (require_all) {
    delete(query_obj[mft.SEARCH_ALL_PSEUDO_FIELD]); // need to get rid f pseudo args, as they stop .find() from returning anything
  } else {
    delete(query_obj[mft.SEARCH_ANY_PSEUDO_FIELD]);
  }
  print("DEBUG: query_obj=" + tojson(query_obj));
  var filtered = db[coll_name].find(query_obj);
  var scores_and_ids = Array();
  print("DEBUG: num recs found: " + filtered.count());
  filtered.forEach(
    function(record) {
      var score = mft.scoreRecordAgainstQuery(coll_name, record, query_terms);
      scores_and_ids.push([score, record._id]);
    });
  return new mft.SearchPseudoCursor(coll_name, scores_and_ids);
  // scores_and_ids.sort(mft.sortNumericFirstDescending); // need to provide a custom search function anyway, as JS does sorts alphabetically
  // var scored_records = Array();
  // // this is the dodgy way - need to do a cursor in the future
  // for (var i = 0; i < scores_and_ids.length; i++) {
  //   var score_and_id = scores_and_ids[i];
  //   record = db[coll_name].findOne({_id: score_and_id[1]});
  //   record.score = score_and_id[0];
  //   scored_records.push(record);
  // }
  // return scored_records;
};

mft.sortNumericFirstDescending = function(a, b) {
  return b[0] - a[0];
};

mft.scoreRecordAgainstQuery = function(coll_name, record, query_terms) {
  var record_terms = record[mft.EXTRACTED_TERMS_FIELD];
  print("DEBUG: record=" + record);
  var query_terms_set = {};
  var score = 0.0;
  for (var i = 0; i < query_terms.length; i++) {
    query_terms_set[query_terms[i]] = true; // to avoid needing to iterate
  }
  print("DEBUG: query_terms_set=" + tojson(query_terms_set));
  var idf_cache = {};
  for (var j = 0; j < record_terms.length; j++) {
    var term = record_terms[j];
    if (term in query_terms_set) {
      var term_idf = idf_cache[term];
      if (term_idf === undefined) {
        term_idf = mft.getTermIdf(coll_name, term);
        idf_cache[term] = term_idf;
      }
      score += term_idf;
    }
  }
  return score/Math.sqrt(record_terms.length);
  // for cosine similarity, we should be normalizing the document vector against the sqrt of the sums of the sqares of all term
  // but that would require knowing the IDF scores of all terms, rather than just the ones in the query which is potentially
  // expensive unless we precompute (either the IDFs or normalized vector for each doc).
  /// However that would make updating the index a much bigger can of worms.
  // This should provide a nice approximation that will give decent results at least relative to the query, which is all we care about.
};

mft.getTermIdf = function(coll_name, term) {
  // this currently doesn't have any caching smarts.
  // we could cache the IDF for each doc in the collection, but that would make updating more complicated
  // for the moment I'll gamble on mongodb being quick enough to make it not a problem
  //
  var term_filter_obj = {};
  term_filter_obj[mft.EXTRACTED_TERMS_FIELD] = mft.filterArg(coll_name, [term], true);
  var term_count = db[coll_name].find(term_filter_obj).count();
  if (term_count === 0) { return 0.0; }
  var num_docs = db[coll_name].find().count(); // TODO: memoize, or find a better method for getting this
  return Math.log(num_docs) - Math.log(term_count);
};

mft.filterArg = function(coll_name, query_terms, require_all) {
  if (require_all === undefined) {
    require_all = true;
  }
  var filter_obj = {};
  filter_obj[require_all ? '$all' : '$in'] = query_terms;
  return filter_obj;
};

mft.processQueryString = function(query_string) {
  return mft.stemAndTokenize(query_string); // maybe tokenizing should be different for queries?
};

mft.indexAll = function(coll_name) {
  print("DEBUG: indexing all records in " + coll_name);
  var cur = db[coll_name].find();
  indexed_fields = mft.indexedFieldsAndWeights(coll_name);
  print("DEBUG: indexed fields and weights: " + tojson(indexed_fields));
  cur.forEach(function(x) { mft.indexSingleRecord(coll_name, x, indexed_fields); });
};

mft.indexSingleRecord = function(coll_name, record, indexed_fields) {
  if (typeof indexed_fields === undefined) {// we can pass this in to save CPU in bulk indexing, but might not
    indexed_fields = mft.indexedFieldsAndWeights(coll_name);
  }
  var all_extracted_terms = Array();
  for (var field in indexed_fields) {
    all_extracted_terms = all_extracted_terms.concat(mft.extractFieldTokens(coll_name, record, field, indexed_fields[field]));
  }
  record[mft.EXTRACTED_TERMS_FIELD] = all_extracted_terms;
  print("DEBUG: record is now: " + tojson(record));
  db[coll_name].save(record);
};

mft.indexSingleRecordFromId = function(coll_name, record_id) {
  var rec = db[coll_name].findOne({'_id': record_id});
  mft.indexSingleRecord(coll_name, rec);
};

mft.extractFieldTokens = function(coll_name, record, field, upweighting) {
  // extracts tokens in stemmed and tokenised form and upweights them as specified in the config if necessary
  var contents = record[field];
  print("DEBUG: contents for field " + field + ": " + contents);
  if (!contents) { // eg the field doesn't exist on this particular record, we silently fail
    return;
  }
  var processed_contents = mft.stemAndTokenize(contents);
  if (upweighting == 1) { // special -casing for the common case - may be slightly quicker avoiding the array copy
    return processed_contents;
  } else {
    var upweighted_contents = processed_contents;
    for (var i = 1; i < upweighting; i++) {
      upweighted_contents = upweighted_contents.concat(processed_contents);
    }
    return upweighted_contents; // this upweighting shouldn't damage our scores as long as we TF IDF, since IDF won't be affect by linear multipliers
  }
};

mft.stemAndTokenize = function(field_contents) {
  return mft.stem(mft.tokenize(field_contents.toLowerCase())); //TODO: actually stem as promised
};

mft.tokenizeBasic = function(field_contents) {
  var token_re = /\b(\w[\w'-]*\w|\w)\b/g;
  return field_contents.match(token_re);
};

mft.stem = function(field_tokens) {
  var stem_fn = mft.getStemFunction();
  var stemmed = Array();
  for (var i = 0; i < field_tokens.length; i++) {
    stemmed.push(stem_fn(field_tokens[i]));
  }
  return stemmed;
};

mft.tokenize = function(field_contents) {
  var tokenize_fn = mft.getTokenizeFunction();
  return tokenize_fn(field_contents);
};

mft.getStemFunction = function() {
  if (mft._STEM_FUNCTION) {
    return mft._STEM_FUNCTION;
  } else {
    if (mft.STEMMING == 'porter') { // no others available
      return (mft._STEM_FUNCTION = mft_stemming.porterStemmerCreator()); // porterStemmer() returns a porter stemming fn when called
    }
  }
};

mft.getTokenizeFunction = function() {
  if (mft._TOKENIZE_FUNCTION) {
    return mft._TOKENIZE_FUNCTION;
  } else {
    if (mft.TOKENIZING == 'basic') { // no others available
      return (mft._TOKENIZE_FUNCTION = mft.tokenizeBasic);
    }
  }
};

mft.SearchPseudoCursor = function(coll_name, scores_and_ids) {
  // class to vaguely efficiently act as a store for the the retrived records while not chewing up lots of
  // memory, and not taking lots of time to sort results we may not need - hence the heap
  this.coll_name = coll_name;
  var scores_and_ids_heap = new mft_heap.BinaryHeap(function(x) { return x[0] });
  print("DEBUG: heap is: " + tojson(scores_and_ids_heap.prototype));
  // print("DEBUG: score functino running: " + scores_and_ids_heap.scoreFunction([[1, 2], [3,1]]);
  scores_and_ids.forEach( function(x) {
    print("DEBUG: pushing rec d " + x[1] + " onto heap");
    scores_and_ids_heap.push(x); // in-place would be better, but let's leave that unless we think it would be useful
  });
  this.scores_and_ids_heap = scores_and_ids_heap;
};

mft.SearchPseudoCursor.prototype = {

  hasNext: function() {
    return this.scores_and_ids_heap.size() > 0;
  },

  next: function() {
    return fetchScoredRecord(this.scores_and_ids_heap.pop());
  },

  toArray: function() {
    output = [];
    while (this.hasNext()) {
      output.push(this.next());
    }
    return output;
  },

  fetchById: function(record_id) {
    return db[this.coll_name].findOne({_id: score_and_id[1]});
  },

  fetchScoredRecord: function(score_and_id) {
    rec = fetchById(score_and_id[1]);
    rec.score = score_and_id[0];
    return rec;
  }
};

_all = {
  mft: mft
};
  <commit_msg>fixed heap to be in the right order, and now define functions int he constructor instead of as the prototype which doesnt work it seems<commit_after>"use strict";

mft = {
  // CONFIG ITEMS:

  STEMMING: 'porter', // doesn't do anything yet
  TOKENIZING: 'basic',// doesn't do anything yet

  EXTRACTED_TERMS_FIELD: '_extracted_terms',
  SEARCH_ALL_PSEUDO_FIELD: '$search', // magic "field name" that specifies we want a fulltext search
            // (abusing the '$' notation somewhat, which is often for search operators)
  SEARCH_ANY_PSEUDO_FIELD: '$searchany', // magic "field name" that specifies we want a fulltext search matching any, not all

  // WORKHORSE VARS:
  _STEM_FUNCTION: null,
  _TOKENIZE_FUNCTION: null
};

mft.indexedFieldsAndWeights = function(coll_name) {
  // we expect a special collection named '_fulltext_config', with items having elems 'collection_name', and 'fields',
  // with 'fields' having keys being the field name, and the values being the weight.
  //eg:
      // {
      //   collection_name: 'test',
      //   fields: {
      //     title: 5,
      //     content: 1
      //   }
      // }

  collection_conf = db.fulltext_config.findOne({collection_name: coll_name});
  return collection_conf.fields;
};

mft.search = function(coll_name, query_obj) {
  // check for $search member on query_obj
  // if it doesn't exist, pass through to regular .find
  // if it does, parse the ft query string, and add the appropriate filter
  // clause to the non-ft-search components, execute that, then
  // score every remaining document, and put those sorted IDs and scores in a record in
  // a private collection (hashed by the whole query obj, which we can check next time around)
  // then iterate through the IDs and scores and return the corrpesponding records with the IDs
  // attached to them, in a way that emulates a cursor object.
  var search_query_string;
  var require_all;
  if (query_obj[mft.SEARCH_ALL_PSEUDO_FIELD]) {
    search_query_string = query_obj[mft.SEARCH_ALL_PSEUDO_FIELD];
    require_all = true;
  } else if (query_obj[mft.SEARCH_ALL_PSEUDO_FIELD]) {
    search_query_string = query_obj[mft.SEARCH_ANY_PSEUDO_FIELD];
    require_all = false;
  } else {
    return db[coll_name].find(query_obj); // no need to call search, you chump
  }
  print("DEBUG: query string is " + search_query_string);
  var query_terms = mft.processQueryString(search_query_string);
  print("DEBUG: query terms is " + (query_terms.join(',') + " with length " + query_terms.length));
  query_obj[mft.EXTRACTED_TERMS_FIELD] = mft.filterArg(coll_name, query_terms, require_all);
  if (require_all) {
    delete(query_obj[mft.SEARCH_ALL_PSEUDO_FIELD]); // need to get rid f pseudo args, as they stop .find() from returning anything
  } else {
    delete(query_obj[mft.SEARCH_ANY_PSEUDO_FIELD]);
  }
  print("DEBUG: query_obj=" + tojson(query_obj));
  var filtered = db[coll_name].find(query_obj);
  var scores_and_ids = Array();
  print("DEBUG: num recs found: " + filtered.count());
  filtered.forEach(
    function(record) {
      var score = mft.scoreRecordAgainstQuery(coll_name, record, query_terms);
      scores_and_ids.push([score, record._id]);
    });
  return new mft.SearchPseudoCursor(coll_name, scores_and_ids);
  // scores_and_ids.sort(mft.sortNumericFirstDescending); // need to provide a custom search function anyway, as JS does sorts alphabetically
  // var scored_records = Array();
  // // this is the dodgy way - need to do a cursor in the future
  // for (var i = 0; i < scores_and_ids.length; i++) {
  //   var score_and_id = scores_and_ids[i];
  //   record = db[coll_name].findOne({_id: score_and_id[1]});
  //   record.score = score_and_id[0];
  //   scored_records.push(record);
  // }
  // return scored_records;
};

mft.sortNumericFirstDescending = function(a, b) {
  return b[0] - a[0];
};

mft.scoreRecordAgainstQuery = function(coll_name, record, query_terms) {
  var record_terms = record[mft.EXTRACTED_TERMS_FIELD];
  print("DEBUG: record=" + record);
  var query_terms_set = {};
  var score = 0.0;
  for (var i = 0; i < query_terms.length; i++) {
    query_terms_set[query_terms[i]] = true; // to avoid needing to iterate
  }
  print("DEBUG: query_terms_set=" + tojson(query_terms_set));
  var idf_cache = {};
  for (var j = 0; j < record_terms.length; j++) {
    var term = record_terms[j];
    if (term in query_terms_set) {
      var term_idf = idf_cache[term];
      if (term_idf === undefined) {
        term_idf = mft.getTermIdf(coll_name, term);
        idf_cache[term] = term_idf;
      }
      score += term_idf;
    }
  }
  return score/Math.sqrt(record_terms.length);
  // for cosine similarity, we should be normalizing the document vector against the sqrt of the sums of the sqares of all term
  // but that would require knowing the IDF scores of all terms, rather than just the ones in the query which is potentially
  // expensive unless we precompute (either the IDFs or normalized vector for each doc).
  /// However that would make updating the index a much bigger can of worms.
  // This should provide a nice approximation that will give decent results at least relative to the query, which is all we care about.
};

mft.getTermIdf = function(coll_name, term) {
  // this currently doesn't have any caching smarts.
  // we could cache the IDF for each doc in the collection, but that would make updating more complicated
  // for the moment I'll gamble on mongodb being quick enough to make it not a problem
  //
  var term_filter_obj = {};
  term_filter_obj[mft.EXTRACTED_TERMS_FIELD] = mft.filterArg(coll_name, [term], true);
  var term_count = db[coll_name].find(term_filter_obj).count();
  if (term_count === 0) { return 0.0; }
  var num_docs = db[coll_name].find().count(); // TODO: memoize, or find a better method for getting this
  return Math.log(num_docs) - Math.log(term_count);
};

mft.filterArg = function(coll_name, query_terms, require_all) {
  if (require_all === undefined) {
    require_all = true;
  }
  var filter_obj = {};
  filter_obj[require_all ? '$all' : '$in'] = query_terms;
  return filter_obj;
};

mft.processQueryString = function(query_string) {
  return mft.stemAndTokenize(query_string); // maybe tokenizing should be different for queries?
};

mft.indexAll = function(coll_name) {
  print("DEBUG: indexing all records in " + coll_name);
  var cur = db[coll_name].find();
  indexed_fields = mft.indexedFieldsAndWeights(coll_name);
  print("DEBUG: indexed fields and weights: " + tojson(indexed_fields));
  cur.forEach(function(x) { mft.indexSingleRecord(coll_name, x, indexed_fields); });
};

mft.indexSingleRecord = function(coll_name, record, indexed_fields) {
  if (typeof indexed_fields === undefined) {// we can pass this in to save CPU in bulk indexing, but might not
    indexed_fields = mft.indexedFieldsAndWeights(coll_name);
  }
  var all_extracted_terms = Array();
  for (var field in indexed_fields) {
    all_extracted_terms = all_extracted_terms.concat(mft.extractFieldTokens(coll_name, record, field, indexed_fields[field]));
  }
  record[mft.EXTRACTED_TERMS_FIELD] = all_extracted_terms;
  print("DEBUG: record is now: " + tojson(record));
  db[coll_name].save(record);
};

mft.indexSingleRecordFromId = function(coll_name, record_id) {
  var rec = db[coll_name].findOne({'_id': record_id});
  mft.indexSingleRecord(coll_name, rec);
};

mft.extractFieldTokens = function(coll_name, record, field, upweighting) {
  // extracts tokens in stemmed and tokenised form and upweights them as specified in the config if necessary
  var contents = record[field];
  print("DEBUG: contents for field " + field + ": " + contents);
  if (!contents) { // eg the field doesn't exist on this particular record, we silently fail
    return;
  }
  var processed_contents = mft.stemAndTokenize(contents);
  if (upweighting == 1) { // special -casing for the common case - may be slightly quicker avoiding the array copy
    return processed_contents;
  } else {
    var upweighted_contents = processed_contents;
    for (var i = 1; i < upweighting; i++) {
      upweighted_contents = upweighted_contents.concat(processed_contents);
    }
    return upweighted_contents; // this upweighting shouldn't damage our scores as long as we TF IDF, since IDF won't be affect by linear multipliers
  }
};

mft.stemAndTokenize = function(field_contents) {
  return mft.stem(mft.tokenize(field_contents.toLowerCase())); //TODO: actually stem as promised
};

mft.tokenizeBasic = function(field_contents) {
  var token_re = /\b(\w[\w'-]*\w|\w)\b/g;
  return field_contents.match(token_re);
};

mft.stem = function(field_tokens) {
  var stem_fn = mft.getStemFunction();
  var stemmed = Array();
  for (var i = 0; i < field_tokens.length; i++) {
    stemmed.push(stem_fn(field_tokens[i]));
  }
  return stemmed;
};

mft.tokenize = function(field_contents) {
  var tokenize_fn = mft.getTokenizeFunction();
  return tokenize_fn(field_contents);
};

mft.getStemFunction = function() {
  if (mft._STEM_FUNCTION) {
    return mft._STEM_FUNCTION;
  } else {
    if (mft.STEMMING == 'porter') { // no others available
      return (mft._STEM_FUNCTION = mft_stemming.porterStemmerCreator()); // porterStemmer() returns a porter stemming fn when called
    }
  }
};

mft.getTokenizeFunction = function() {
  if (mft._TOKENIZE_FUNCTION) {
    return mft._TOKENIZE_FUNCTION;
  } else {
    if (mft.TOKENIZING == 'basic') { // no others available
      return (mft._TOKENIZE_FUNCTION = mft.tokenizeBasic);
    }
  }
};

mft.SearchPseudoCursor = function(coll_name, scores_and_ids) {
  // class to vaguely efficiently act as a store for the the retrived records while not chewing up lots of
  // memory, and not taking lots of time to sort results we may not need - hence the heap
  this.coll_name = coll_name;
  var scores_and_ids_heap = new mft_heap.BinaryHeap(function(x) { return -x[0] });
  // print("DEBUG: score functino running: " + scores_and_ids_heap.scoreFunction([[1, 2], [3,1]]);
  scores_and_ids.forEach( function(x) {
    scores_and_ids_heap.push(x); // in-place would be better, but let's leave that unless we think it would be useful
  });
  this.scores_and_ids_heap = scores_and_ids_heap;

  this.hasNext = function() {
    return this.scores_and_ids_heap.size() > 0;
  };

  this.next = function() {
    return this.fetchScoredRecord(this.scores_and_ids_heap.pop());
  };

  this.toArray = function() {
    output = [];
    while (this.hasNext()) {
      output.push(this.next());
    }
    return output;
  };

  this.fetchById = function(record_id) {
    return db[this.coll_name].findOne({_id: record_id});
  };

  this.fetchScoredRecord = function(score_and_id) {
    rec = this.fetchById(score_and_id[1]);
    rec.score = score_and_id[0];
    return rec;
  };

};

_all = {
  mft: mft
};
  <|endoftext|>


<commit_before>//Show tutorial completion projects
function showTutorials()
{
	document.getElementById("location").innerHTML = '
Stevenpg.GitHub.io/Projects/Tutorials
';
	document.getElementById("projContent").innerHTML =
	'\

\

\
		 - My First Widget (Clock)\

\
		\

\

\
	\

\

\
		 - Android Flashlight Application\

\
		\

\

\
	\

\

\
		 - My-First-App (via developer.android.com)\

\
		\

\

\
	\

\
	';
}

//Show independent projects
function showMainProjects()
{
	document.getElementById("location").innerHTML = '
Stevenpg.GitHub.io/Projects/Independents
';
	document.getElementById("projContent").innerHTML =
	'\
	\
	 - Core Removal Script on GitHub
\

\
	\

\
	 - Project Euler Problem listing on GitHub
\

\
	\

\
	\
	 - First Github Project "Simple Calculator"
\

\
	\

\
	\
	';
}

//  Independent Projects

// Show books
function showBooks(){
	document.getElementById("books").innerHTML = '\
	 Owned Books\

\

\
			Title (Edition & year)	\
			Author(s)	\

\

\
			Android Programming: The Big Nerd Ranch Guide (2013 PDF)	\
			Bill Phillips & Brian Hardy	\

\

\
			Introduction to Java programming Comprehensive (9th edition 2013 PDF)	\
			Y. Daniel Yang	\

\

\
			The Elements of Java Style (2007)	\
			Al Vermuelen, Scott W. Ambler & Others	\

\

\
			Data Abstraction & Problem Solving with C++ (6th edition 2013)	\
			Frank M. Carrano & Timonthy Henry	\

\

\
			C++ Primer (3rd edition 1998)	\
			Stanley B. Lippman & Josee Lajoie	\

\

\
			Data Structures and Problem Solving Using C++ (2000)	\
			Mark Allen Weiss	\

\

\
			Python Essential Reference (2009 PDF)	\
			D. Beazley	\

\

\
			An Introduction to Programming Using Visual Basic (2010)	\
			David I. Schneider	\

\

\
			PHP for the Web (4th edition 2011)	\
			Larry Ullman	\

\

\
			Operating System Concepts Essentials (2014)	\
			Wiley Custom Learning Solutions	\

\

\
			Programming Language Pragmatics (3rd edition 2009)	\
			Micheal L. Scott	\

\

\
			Outdated/Legacy Texts	\
				\

\

\
			DOS FOR DUMMIES (1st and 2nd Edition 1991)	\
			Dan Gookin 	\

\

\
			Guide to Popular Operating Systems (1986)	\
			Gerry Kane	\

\

\
			The handbook of computers and computing (1985)	\
			Arthur H. Seidman & Ivan Flores	\

\

\
			Elements of Computer Mathematics (1985)	\
			Sanda Talbot, Harold Baker, and Lawrence Gilligan	\

\

\
			The Complete MICROCOMPUTER HANDBOOK With Tested Basic Programs (1984)	\
			Robert C. Genn, Jr. & E.L. Genn 	\

\

\
			DATA FILE PROGRAMMING IN BASIC (A SELF TEACHING GUIDE, 1981)	\
			LeROY FINKEL & JERALD R. BROWN	\

\

\
			Intel Memory Handbook (1978)	\
			Radio Shack	\

\

\
			Microcomputer Primer (1976)	\
			Radio Shack	\

\

\
			Intel 8080 Users Manual (1975)	\
			Intel Systems	\

\

\
			Computer Dictionary and Handbook (1972)	\
			Charles J. Sippl & Charles P. Sippl	\

\

\
			digital logic handbook (1972)	\
			Digital Equiptment Corp	\

\

\
			basic basic (1970)	\
			James S. Coan	\

\

\
	';
}

// Hide books
function hideBooks(){
	document.getElementById("books").innerHTML = '\
	 Owned Books\
	';
}<commit_msg>Added book to Contacts/ownedbooks<commit_after>//Show tutorial completion projects
function showTutorials()
{
	document.getElementById("location").innerHTML = '
Stevenpg.GitHub.io/Projects/Tutorials
';
	document.getElementById("projContent").innerHTML =
	'\

\

\
		 - My First Widget (Clock)\

\
		\

\

\
	\

\

\
		 - Android Flashlight Application\

\
		\

\

\
	\

\

\
		 - My-First-App (via developer.android.com)\

\
		\

\

\
	\

\
	';
}

//Show independent projects
function showMainProjects()
{
	document.getElementById("location").innerHTML = '
Stevenpg.GitHub.io/Projects/Independents
';
	document.getElementById("projContent").innerHTML =
	'\
	\
	 - Core Removal Script on GitHub
\

\
	\

\
	 - Project Euler Problem listing on GitHub
\

\
	\

\
	\
	 - First Github Project "Simple Calculator"
\

\
	\

\
	\
	';
}

//  Independent Projects

// Show books
function showBooks(){
	document.getElementById("books").innerHTML = '\
	 Owned Books\

\

\
			Title (Edition & year)	\
			Author(s)	\

\

\
			Android Programming: The Big Nerd Ranch Guide (2013 PDF)	\
			Bill Phillips & Brian Hardy	\

\

\
			Introduction to Java programming Comprehensive (9th edition 2013 PDF)	\
			Y. Daniel Yang	\

\

\
			The Elements of Java Style (2007)	\
			Al Vermuelen, Scott W. Ambler & Others	\

\

\
			Data Abstraction & Problem Solving with C++ (6th edition 2013)	\
			Frank M. Carrano & Timonthy Henry	\

\

\
			C++ Primer (3rd edition 1998)	\
			Stanley B. Lippman & Josee Lajoie	\

\

\
			Data Structures and Problem Solving Using C++ (2000)	\
			Mark Allen Weiss	\

\

\
			UNIX for Programmers and Users (2003)	\
			Graham Glass & King Ables	\

\

\
			Python Essential Reference (2009 PDF)	\
			D. Beazley	\

\

\
			An Introduction to Programming Using Visual Basic (2010)	\
			David I. Schneider	\

\

\
			PHP for the Web (4th edition 2011)	\
			Larry Ullman	\

\

\
			Operating System Concepts Essentials (2014)	\
			Wiley Custom Learning Solutions	\

\

\
			Programming Language Pragmatics (3rd edition 2009)	\
			Micheal L. Scott	\

\

\
			Outdated/Legacy Texts	\
				\

\

\
			DOS FOR DUMMIES (1st and 2nd Edition 1991)	\
			Dan Gookin 	\

\

\
			Guide to Popular Operating Systems (1986)	\
			Gerry Kane	\

\

\
			The handbook of computers and computing (1985)	\
			Arthur H. Seidman & Ivan Flores	\

\

\
			Elements of Computer Mathematics (1985)	\
			Sanda Talbot, Harold Baker, and Lawrence Gilligan	\

\

\
			The Complete MICROCOMPUTER HANDBOOK With Tested Basic Programs (1984)	\
			Robert C. Genn, Jr. & E.L. Genn 	\

\

\
			DATA FILE PROGRAMMING IN BASIC (A SELF TEACHING GUIDE, 1981)	\
			LeROY FINKEL & JERALD R. BROWN	\

\

\
			Intel Memory Handbook (1978)	\
			Radio Shack	\

\

\
			Microcomputer Primer (1976)	\
			Radio Shack	\

\

\
			Intel 8080 Users Manual (1975)	\
			Intel Systems	\

\

\
			Computer Dictionary and Handbook (1972)	\
			Charles J. Sippl & Charles P. Sippl	\

\

\
			digital logic handbook (1972)	\
			Digital Equiptment Corp	\

\

\
			basic basic (1970)	\
			James S. Coan	\

\

\
	';
}

// Hide books
function hideBooks(){
	document.getElementById("books").innerHTML = '\
	 Owned Books\
	';
}<|endoftext|>


<commit_before>var mimetypes = [{
  "mime": "application/octet-stream",
  "desc": ""
}, {
  "mime": "image/png",
  "desc": ""
}, {
  "mime": "video/mpeg",
  "desc": ""
}]

var mimetypesStore = Ext.define('CF.store.Mimetype', {
  extend: 'Ext.data.Store',
  model: 'CF.model.Mimetype',
  data: mimetypes
});<commit_msg>kmz mime-type added<commit_after>var mimetypes = [{
  "mime": "application/octet-stream",
  "desc": ""
}, {
  "mime": "image/png",
  "desc": ""
}, {
  "mime": "video/mpeg",
  "desc": ""
},
{
	  "mime": "application/vnd.google-earth.kmz",
	  "desc": ""
	}

]

var mimetypesStore = Ext.define('CF.store.Mimetype', {
  extend: 'Ext.data.Store',
  model: 'CF.model.Mimetype',
  data: mimetypes
});<|endoftext|>


<commit_before>/**
 * CSS load tracker.
 *

 * Keeps track of page CSS, adding and removing CSS as ExtensionPoint components are
 * mounted and unmounted.
 */

// The CSS resources (and array) to be added for each
// Extension point.
// Key:     Extension point name.
// Value:   An array of CSS adjunct URLs that need to be activated when the extension point is rendered.
var pointCSSs = {};

// Active CSS.
// Key:     CSS URL.
// Value:   Counter of the number of mounted Extension Points that need the CSS to be active.
//          The onMount and onUnmount functions increment and decrement the counter. When the
//          counter gets back to zero, the CSS can be removed from the page.
var activeCSSs = {};

const jsModules = require('@jenkins-cd/js-modules');

/**
 * Initialize the loader witht he extension point information.
 * @param extensionPointList The Extension point list. An array containing ExtensionPoint
 * metadata for all plugins that define such. It's an aggregation of
 * of the /jenkins-js-extension.json files found on the server classpath.
 */
exports.setExtensionPointMetadata = function(extensionPointList) {
    // Reset - for testing.
    pointCSSs = {};
    activeCSSs = {};

    // Iterate through each plugin /jenkins-js-extension.json
    for(var i1 = 0; i1 < extensionPointList.length; i1++) {
        var pluginMetadata = extensionPointList[i1];
        var extensions = pluginMetadata.extensions; // All the extensions defined on the plugin
        var pluginCSS = pluginMetadata.extensionCSS; // The plugin CSS URL (adjunct URL).

        // Iterate through the ExtensionPoints defined in each plugin
        for (var i2 = 0; i2 < extensions.length; i2++) {
            var extensionPoint = extensions[i2].extensionPoint; // The extension point name.
            var pointCSS = pointCSSs[extensionPoint]; // The current list of CSS URLs for the named extension point.

            if (!pointCSS) {
                pointCSS = [];
                pointCSSs[extensionPoint] = pointCSS;
            }

            // Add the plugin CSS if it's not already in the list.
            if (pointCSS.indexOf(pluginCSS) === -1) {
                pointCSS.push(pluginCSS);
            }
        }
    }
};

/**
 * Called when a Jenskins ExtensionPoint is mounted.
 *

 * If the extension point implementations use CSS (comes from plugins that define CSS)
 * then this method will use requireCSS, and then addCSS, for each CSS. addCSS only
 * gets called for a CSS by the first extension point to "require" that CSS.
 *
 * @param extensionPointName The extension point name.
 */
exports.onMount = function(extensionPointName) {
    const pointCSS = pointCSSs[extensionPointName];
    if (pointCSS) {
        for (var i = 0; i < pointCSS.length; i++) {
            requireCSS(pointCSS[i]);
        }
    }
};

/**
 * Called when a Jenskins ExtensionPoint is unmounted.
 *

 * If the extension point implementations use CSS (comes from plugins that define CSS)
 * then this method will use unrequireCSS, and then removeCSS, for each CSS. removeCSS only
 * gets called for a CSS by the last extension point to "unrequire" that CSS.
 *
 * @param extensionPointName The extension point name.
 */
exports.onUnmount = function(extensionPointName) {
    const pointCSS = pointCSSs[extensionPointName];
    if (pointCSS) {
        for (var i = 0; i < pointCSS.length; i++) {
            unrequireCSS(pointCSS[i]);
        }
    }
};

function requireCSS(url) {
    var activeCount = activeCSSs[url];

    if (!activeCount) {
        activeCount = 0;
        addCSS(url);
    }
    activeCount++;
    activeCSSs[url] = activeCount;
}

function unrequireCSS(url) {
    var activeCount = activeCSSs[url];

    if (!activeCount) {
        // Huh?
        console.warn('Unexpected call to deactivate an inactive Jenkins Extension Point CSS: ' + url);
        // Does this mean that react calls unmount multiple times for a given component instance?
        // That would sound like a bug, no?
    } else {
        activeCount--;
        if (activeCount === 0) {
            // All extension points using this CSS have been unmounted.
            delete activeCSSs[url];
            removeCSS(url);
        } else {
            activeCSSs[url] = activeCount;
        }
    }
}

function addCSS(url) {
    const cssURLPrefix = jsModules.getAdjunctURL();
    jsModules.addCSSToPage(cssURLPrefix + '/' + url);
}

function removeCSS(url) {
    const cssURLPrefix = jsModules.getAdjunctURL();
    const cssURL = cssURLPrefix + '/' + url;
    const linkElId = jsModules.toCSSId(cssURL);
    const linkEl = document.getElementById(linkElId);

    if (linkEl) {
        linkEl.parentNode.removeChild(linkEl);
    }
}
<commit_msg>cssloadtracker doc typo fixes<commit_after>/**
 * CSS load tracker.
 *

 * Keeps track of page CSS, adding and removing CSS as ExtensionPoint components are
 * mounted and unmounted.
 */

// The CSS resources to be added for each Extension point.
// Key:     Extension point name.
// Value:   An array of CSS adjunct URLs that need to be activated when the extension point is rendered.
var pointCSSs = {};

// Active CSS.
// Key:     CSS URL.
// Value:   Counter of the number of mounted Extension Points that need the CSS to be active.
//          The onMount and onUnmount functions increment and decrement the counter. When the
//          counter gets back to zero, the CSS can be removed from the page.
var activeCSSs = {};

const jsModules = require('@jenkins-cd/js-modules');

/**
 * Initialize the loader with the extension point information.
 * @param extensionPointList The Extension point list. An array containing ExtensionPoint
 * metadata for all plugins that define such. It's an aggregation of
 * of the /jenkins-js-extension.json files found on the server classpath.
 */
exports.setExtensionPointMetadata = function(extensionPointList) {
    // Reset - for testing.
    pointCSSs = {};
    activeCSSs = {};

    // Iterate through each plugin /jenkins-js-extension.json
    for(var i1 = 0; i1 < extensionPointList.length; i1++) {
        var pluginMetadata = extensionPointList[i1];
        var extensions = pluginMetadata.extensions; // All the extensions defined on the plugin
        var pluginCSS = pluginMetadata.extensionCSS; // The plugin CSS URL (adjunct URL).

        // Iterate through the ExtensionPoints defined in each plugin
        for (var i2 = 0; i2 < extensions.length; i2++) {
            var extensionPoint = extensions[i2].extensionPoint; // The extension point name.
            var pointCSS = pointCSSs[extensionPoint]; // The current list of CSS URLs for the named extension point.

            if (!pointCSS) {
                pointCSS = [];
                pointCSSs[extensionPoint] = pointCSS;
            }

            // Add the plugin CSS if it's not already in the list.
            if (pointCSS.indexOf(pluginCSS) === -1) {
                pointCSS.push(pluginCSS);
            }
        }
    }
};

/**
 * Called when a Jenkins ExtensionPoint is mounted.
 *

 * If the extension point implementations use CSS (comes from plugins that define CSS)
 * then this method will use requireCSS, and then addCSS, for each CSS. addCSS only
 * gets called for a CSS by the first extension point to "require" that CSS.
 *
 * @param extensionPointName The extension point name.
 */
exports.onMount = function(extensionPointName) {
    const pointCSS = pointCSSs[extensionPointName];
    if (pointCSS) {
        for (var i = 0; i < pointCSS.length; i++) {
            requireCSS(pointCSS[i]);
        }
    }
};

/**
 * Called when a Jenkins ExtensionPoint is unmounted.
 *

 * If the extension point implementations use CSS (comes from plugins that define CSS)
 * then this method will use unrequireCSS, and then removeCSS, for each CSS. removeCSS only
 * gets called for a CSS by the last extension point to "unrequire" that CSS.
 *
 * @param extensionPointName The extension point name.
 */
exports.onUnmount = function(extensionPointName) {
    const pointCSS = pointCSSs[extensionPointName];
    if (pointCSS) {
        for (var i = 0; i < pointCSS.length; i++) {
            unrequireCSS(pointCSS[i]);
        }
    }
};

function requireCSS(url) {
    var activeCount = activeCSSs[url];

    if (!activeCount) {
        activeCount = 0;
        addCSS(url);
    }
    activeCount++;
    activeCSSs[url] = activeCount;
}

function unrequireCSS(url) {
    var activeCount = activeCSSs[url];

    if (!activeCount) {
        // Huh?
        console.warn('Unexpected call to deactivate an inactive Jenkins Extension Point CSS: ' + url);
        // Does this mean that react calls unmount multiple times for a given component instance?
        // That would sound like a bug, no?
    } else {
        activeCount--;
        if (activeCount === 0) {
            // All extension points using this CSS have been unmounted.
            delete activeCSSs[url];
            removeCSS(url);
        } else {
            activeCSSs[url] = activeCount;
        }
    }
}

function addCSS(url) {
    const cssURLPrefix = jsModules.getAdjunctURL();
    jsModules.addCSSToPage(cssURLPrefix + '/' + url);
}

function removeCSS(url) {
    const cssURLPrefix = jsModules.getAdjunctURL();
    const cssURL = cssURLPrefix + '/' + url;
    const linkElId = jsModules.toCSSId(cssURL);
    const linkEl = document.getElementById(linkElId);

    if (linkEl) {
        linkEl.parentNode.removeChild(linkEl);
    }
}
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
import transitions from '../styles/transitions';

class ExpandTransitionChild extends Component {
  static propTypes = {
    children: PropTypes.node,
    enterDelay: PropTypes.number,
    style: PropTypes.object,
    transitionDelay: PropTypes.number,
    transitionDuration: PropTypes.number,
  };

  static defaultProps = {
    enterDelay: 0,
    transitionDelay: 0,
    transitionDuration: 450,
  };

  static contextTypes = {
    muiTheme: PropTypes.object.isRequired,
  };

  componentDidUpdate() {
    this.open();
  }

  componentWillUnmount() {
    clearTimeout(this.enterTimer);
    clearTimeout(this.leaveTimer);
  }

  componentWillAppear(callback) {
    this.open();
    callback();
  }

  componentWillEnter(callback) {
    const {enterDelay} = this.props;
    const {style} = ReactDOM.findDOMNode(this);
    style.height = 0;
    this.enterTimer = setTimeout(() => callback(), enterDelay);
  }

  componentDidEnter() {
    this.open();
  }

  componentWillLeave(callback) {
    const {transitionDuration} = this.props;
    const {style} = ReactDOM.findDOMNode(this);
    style.height = 0;
    this.leaveTimer = setTimeout(() => callback(), transitionDuration);
  }

  open() {
    const {style} = ReactDOM.findDOMNode(this);
    style.height = `${this.refs.wrapper.clientHeight}px`;
  }

  render() {
    const {
      children,
      style,
      transitionDelay, // eslint-disable-line no-unused-vars
      transitionDuration,
      ...other,
    } = this.props;

    const {prepareStyles} = this.context.muiTheme;

    const mergedRootStyles = Object.assign({
      position: 'relative',
      height: 0,
      width: '100%',
      top: 0,
      left: 0,
      overflow: 'hidden',
      transition: transitions.easeOut(
        `${transitionDuration}ms`,
        ['height'],
        `${transitionDelay}ms`,
      ),
    }, style);

    return (

{children}

    );
  }
}

export default ExpandTransitionChild;
<commit_msg>[ExpandTransitionChild][Stepper] Set height to "auto" in componentDidAppear and componentDidEnter, resolves #4179<commit_after>import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
import transitions from '../styles/transitions';

class ExpandTransitionChild extends Component {
  static propTypes = {
    children: PropTypes.node,
    enterDelay: PropTypes.number,
    style: PropTypes.object,
    transitionDelay: PropTypes.number,
    transitionDuration: PropTypes.number,
  };

  static defaultProps = {
    enterDelay: 0,
    transitionDelay: 0,
    transitionDuration: 450,
  };

  static contextTypes = {
    muiTheme: PropTypes.object.isRequired,
  };

  componentDidUpdate() {
    this.open();
  }

  componentWillUnmount() {
    clearTimeout(this.enterTimer);
    clearTimeout(this.enteredTimer);
    clearTimeout(this.leaveTimer);
  }

  componentWillAppear(callback) {
    this.open();
    callback();
  }

  componentDidAppear() {
    this.setAutoHeight();
  }

  componentWillEnter(callback) {
    const {enterDelay, transitionDelay, transitionDuration} = this.props;
    const {style} = ReactDOM.findDOMNode(this);
    style.height = 0;
    this.enterTimer = setTimeout(() => this.open(), enterDelay);
    this.enteredTimer = setTimeout(() => callback(), enterDelay + transitionDelay + transitionDuration);
  }

  componentDidEnter() {
    this.setAutoHeight();
  }

  componentWillLeave(callback) {
    const {transitionDelay, transitionDuration} = this.props;
    const {style} = ReactDOM.findDOMNode(this);
    style.height = `${this.refs.wrapper.clientHeight}px`;
    style.height = 0;
    this.leaveTimer = setTimeout(() => callback(), transitionDelay + transitionDuration);
  }

  setAutoHeight() {
    const {style} = ReactDOM.findDOMNode(this);
    style.height = 'auto';
  }

  open() {
    const {style} = ReactDOM.findDOMNode(this);
    style.height = `${this.refs.wrapper.clientHeight}px`;
  }

  render() {
    const {
      children,
      style,
      transitionDelay, // eslint-disable-line no-unused-vars
      transitionDuration,
      ...other,
    } = this.props;

    const {prepareStyles} = this.context.muiTheme;

    const mergedRootStyles = Object.assign({
      position: 'relative',
      height: 0,
      width: '100%',
      top: 0,
      left: 0,
      overflow: 'hidden',
      transition: transitions.easeOut(
        `${transitionDuration}ms`,
        ['height'],
        `${transitionDelay}ms`,
      ),
    }, style);

    return (

{children}

    );
  }
}

export default ExpandTransitionChild;
<|endoftext|>


<commit_before>define(["modules/data-types/multivalue"], function (createType){
	"use strict";
	var Utf8String = function (string){
		this.value = string;
	};
	return createType(Utf8String, "utf8string");
});<commit_msg>Utf8String.fromString / toString()<commit_after>define(["modules/data-types/multivalue"], function (createType) {
    "use strict";
    var Utf8String = function (string) {
        this.value = string;
    };
    var mixin = {
        isEqualTo: function (other) {
            if (this.constructor.typeName !== other.constructor.typeName) { return false; }
            return this.value === other.value;
        },
        toString: function () {
            return this.value;
        }
    };

    Utf8String.fromString = function (str) {
        return new Utf8String(str);
    };

    return createType(Utf8String, "utf8string");
});<|endoftext|>


<commit_before><commit_msg>add countmissingdata fun<commit_after><|endoftext|>


<commit_before>/**
 * Defines a drawer that will 'pop out' of the side of the browser window
 * and provide ways for the user to interact with the content they have
 * selected.
 *
 */
betterlink_user_interface.createModule("Action Drawer", function(api, apiInternal) {
	api.requireModules( ["Util", "Util.DOM", "Drawer Reset CSS", "Mouseboundary", "Neglected", "Draggable", "Drawer Slider", "Drawer Dropzone", "Dropzone.Nexus"] );

	var HTML5_CSS = "article, aside, footer, header, nav, section { display: block; }";
	var DRAWER_ID = extractId(apiInternal.drawerSelector);
	var DRAWER_HEADER_ID = DRAWER_ID + "-header";
	var DRAWER_CENTER_ID = DRAWER_ID + "-center";
	var DRAWER_FOOTER_ID = DRAWER_ID + "-footer";

	var TOP_LIST_ID = "betterlink-top-list";
	var HEADER_PROP_ID = 'betterlink-header-prop';
	var FOOTER_LINKS_ID = 'betterlink-footer-links';
	var FOOTER_ACTIVE_CLASS = 'betterlink-footer-active';
	var GITHUB_CLASS = 'betterlink-footer-github';

	// Drawer CSS
	// Full-height layout dervied from Steven Sanderson
	// http://blog.stevensanderson.com/2011/10/05/full-height-app-layouts-a-css-trick-to-make-it-easier/
	var DRAWER_CSS =
		[   ".betterlink-row, .betterlink-col { overflow: hidden; position: absolute; }",
			".betterlink-row { width: 100%; }",
			".betterlink-col { top: 0; bottom: 0; }",
			".betterlink-scroll-x { overflow-x: auto; }",
			".betterlink-scroll-y { overflow-y: auto; }",

			".betterlink-left.betterlink-col { width: 230px; }",
			".betterlink-right.betterlink-col { width: 230px; right: 0; }",

			apiInternal.drawerSelector + ".betterlink-header.betterlink-row { height: 75px; }",
			apiInternal.drawerSelector + ".betterlink-center.betterlink-row { top: 75px; bottom: 50px; }",
			apiInternal.drawerSelector + ".betterlink-footer.betterlink-row { height: 50px; bottom: 0; }",

			apiInternal.drawerSelector + ".betterlink-top-to-bottom { width: 100%; }",
			apiInternal.drawerSelector + ".betterlink-bottom-to-top { bottom: 0; position: absolute; width: 100%; }",

			apiInternal.drawerSelector + "#" + TOP_LIST_ID + " { margin: 0; padding: 0; }",
			apiInternal.drawerSelector + "#" + TOP_LIST_ID + ">li { list-style: none; }",

			"#" + DRAWER_ID + " { background: #E9E9E9; position: fixed; }",
			"#" + DRAWER_ID + ":after { content: ''; position: absolute; width: 1px; height: 100%; top: 75px; background: #DDD; }"
		].join(' ');

	var HEADER_CSS =
		[   apiInternal.drawerSelector + "#" + DRAWER_HEADER_ID + ">h1 { margin: 10px 0 3px 10px; }",
			apiInternal.drawerSelector + "#" + DRAWER_HEADER_ID + ">p { margin: 0 0 5px 10px; font-size: 80%; }",
			apiInternal.drawerSelector + "#" + HEADER_PROP_ID + " { height: 100%; float: left; width: 5px; background: #3299BB; }"].join(' ');

	var FOOTER_CSS =
		[	apiInternal.drawerSelector + "#" + DRAWER_FOOTER_ID + " { font-size: 70%; font-family: 'Open Sans', Arial, sans-serif; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + " { position: absolute; margin-top: 5px; margin-right: 5px; right: 0; text-align: right; width: auto; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + ">a { display: block; color: #424242; opacity: .5; text-decoration: none; -webkit-transition: opacity 0.3s ease; transition: opacity 0.3s ease; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + ">a:hover { text-decoration: underline; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + "." + FOOTER_ACTIVE_CLASS + ">a { opacity: 1; }",
			apiInternal.drawerSelector + "." + GITHUB_CLASS + " { width: 14px; height: 14px; margin-left: 3px; vertical-align: text-bottom; border: none; }"].join(' ');

	var drawer;
	var submissionFunction;

	apiInternal.drawer = {
		create: initializeDrawer,
		show: showDrawer,
		hide: hideDrawer
	};

	/****************************************************************************************************/

	function initializeDrawer(submissionFn) {
		if(apiInternal.drawer.initialized)
			return;

		apiInternal.drawer.initialized = true;
		submissionFunction = submissionFn;

		insertDrawerStyles();
		createDrawer();
		toggleDrawerOnDrag();
		addDrawerToDOM();
	}

	function insertDrawerStyles() {
		var fullCss = apiInternal.drawerResetCss + ' ' + DRAWER_CSS + ' ' + HEADER_CSS + ' ' + FOOTER_CSS;

		apiInternal.util.dom.createAndAppendStyleElement(HTML5_CSS);
		apiInternal.util.dom.createAndAppendStyleElement(fullCss);
	}

	function addDrawerToDOM() {
		apiInternal.slider.initialize(drawer, '230px');
		apiInternal.util.dom.registerAndAppend(document.body, drawer);
	}

	// Use the Neglected event to indicate when the user has stopped interacting
	// with the drawer. We'll start watching once the drawer is open (to keep
	// accurate track of the mouse position), but only allow the drawer to close
	// once the user has stopped dragging their element and is no longer interacting
	// with the drawer.
	function showDrawer() {
		apiInternal.slider.slideDrawerIn();
		apiInternal.neglected.watchTarget(drawer, hideDrawer);
	}

	function hideDrawer() {
		apiInternal.slider.slideDrawerAway();
		apiInternal.neglected.stopWatchingTarget(drawer, hideDrawer);
	}

	function allowDrawerToClose() {
		apiInternal.neglected.actOnTarget(drawer, hideDrawer);
	}

	// Show and hide the Action Drawer when the user starts/stops dragging selected
	// content
	function toggleDrawerOnDrag() {
		apiInternal.draggable.subscribeGlobal.dragstart(showDrawer);
		apiInternal.draggable.subscribeGlobal.dragend(allowDrawerToClose);
	}

	function createDrawer() {
		drawer = document.createElement('aside');
		drawer.id = DRAWER_ID;
		drawer.className = 'betterlink-right betterlink-col';

		var header = createHeader();

		var center = document.createElement('section');
		center.id = DRAWER_CENTER_ID;
		center.className = 'betterlink-center betterlink-row';
		var center_top = document.createElement('div');
		var center_bottom = document.createElement('div');
		center_top.className = 'betterlink-top-to-bottom';
		center_bottom.className = 'betterlink-bottom-to-top';

		addTopList(center_top);
		// center_bottom.appendChild();
		center.appendChild(center_top);
		center.appendChild(center_bottom);

		var footer = createFooter();

		drawer.appendChild(header);
		drawer.appendChild(center);
		drawer.appendChild(footer);
	}

	// Add a list of elements to the provided parent element
	function addTopList(element) {
		var list = document.createElement('ul');
		list.id = TOP_LIST_ID;
		list.appendChild(createNexus());

		element.appendChild(list);
	}

	function createNexus() {
		var li = document.createElement('li');

		var nexus = apiInternal.dropzone.sharingNexus.create(submissionFunction);
		li.appendChild(nexus);

		return li;
	}

	function createHeader() {
		var header = document.createElement('section');
		header.id = DRAWER_HEADER_ID;
		header.className = 'betterlink-header betterlink-row';

		var prop = document.createElement('div');
		prop.id = HEADER_PROP_ID;
		var h1 = document.createElement('h1');
		h1.appendChild(document.createTextNode("Betterlink"));
		var subtitle = document.createElement('p');
		subtitle.appendChild(document.createTextNode("drag & drop to share content"));

		header.appendChild(prop);
		header.appendChild(h1);
		header.appendChild(subtitle);

		return header;
	}

	function createFooter() {
		var footer = document.createElement('section');
		footer.id = DRAWER_FOOTER_ID;
		footer.className = 'betterlink-footer betterlink-row';

		var links = document.createElement('div');
		links.id = FOOTER_LINKS_ID;

		var homeLink = apiInternal.util.dom.createAnchorElement('http://betterlink.io', 'http://betterlink.io', '_blank');
		var contributeLink = apiInternal.util.dom.createAnchorElement('Open Source on GitHub', 'https://github.com/betterlink/betterlink-interface', '_blank');
		appendGithubLogo(contributeLink);

		links.appendChild(homeLink);
		links.appendChild(contributeLink);
		footer.appendChild(links);

		apiInternal.mouseboundary.subscribe.mouseenter(footer, activateFooter);
		apiInternal.mouseboundary.subscribe.mouseleave(footer, deactivateFooter);

		return footer;
	}

	function appendGithubLogo(element) {
		if(apiInternal.svg.supported) {
			var githubImg = apiInternal.svg.createElement('github');
			apiInternal.util.dom.applyClassToElement(githubImg, GITHUB_CLASS);
			element.appendChild(githubImg);
		}
	}

	function activateFooter() {
		var links = document.getElementById(FOOTER_LINKS_ID);
		apiInternal.util.dom.applyClassToElement(links, FOOTER_ACTIVE_CLASS);
	}

	function deactivateFooter() {
		var links = document.getElementById(FOOTER_LINKS_ID);
		apiInternal.util.dom.removeClassFromElement(links, FOOTER_ACTIVE_CLASS);
	}

	// selector is assumed to be a simple CSS ID selector
	// ex: "#betterlink-drawer "
	function extractId(selector) {
		return selector.replace(/[#\s]/g, '');
	}
});
<commit_msg>animate sliding in/out the drawer<commit_after>/**
 * Defines a drawer that will 'pop out' of the side of the browser window
 * and provide ways for the user to interact with the content they have
 * selected.
 *
 */
betterlink_user_interface.createModule("Action Drawer", function(api, apiInternal) {
	api.requireModules( ["Util", "Util.DOM", "Drawer Reset CSS", "Mouseboundary", "Neglected", "Draggable", "Drawer Slider", "Drawer Dropzone", "Dropzone.Nexus"] );

	var HTML5_CSS = "article, aside, footer, header, nav, section { display: block; }";
	var DRAWER_ID = extractId(apiInternal.drawerSelector);
	var DRAWER_HEADER_ID = DRAWER_ID + "-header";
	var DRAWER_CENTER_ID = DRAWER_ID + "-center";
	var DRAWER_FOOTER_ID = DRAWER_ID + "-footer";

	var TOP_LIST_ID = "betterlink-top-list";
	var HEADER_PROP_ID = 'betterlink-header-prop';
	var FOOTER_LINKS_ID = 'betterlink-footer-links';
	var FOOTER_ACTIVE_CLASS = 'betterlink-footer-active';
	var GITHUB_CLASS = 'betterlink-footer-github';

	// Drawer CSS
	// Full-height layout dervied from Steven Sanderson
	// http://blog.stevensanderson.com/2011/10/05/full-height-app-layouts-a-css-trick-to-make-it-easier/
	var DRAWER_CSS =
		[   ".betterlink-row, .betterlink-col { overflow: hidden; position: absolute; }",
			".betterlink-row { width: 100%; }",
			".betterlink-col { top: 0; bottom: 0; }",
			".betterlink-scroll-x { overflow-x: auto; }",
			".betterlink-scroll-y { overflow-y: auto; }",

			".betterlink-left.betterlink-col { width: 230px; }",
			".betterlink-right.betterlink-col { width: 230px; right: 0; }",

			apiInternal.drawerSelector + ".betterlink-header.betterlink-row { height: 75px; }",
			apiInternal.drawerSelector + ".betterlink-center.betterlink-row { top: 75px; bottom: 50px; }",
			apiInternal.drawerSelector + ".betterlink-footer.betterlink-row { height: 50px; bottom: 0; }",

			apiInternal.drawerSelector + ".betterlink-top-to-bottom { width: 100%; }",
			apiInternal.drawerSelector + ".betterlink-bottom-to-top { bottom: 0; position: absolute; width: 100%; }",

			apiInternal.drawerSelector + "#" + TOP_LIST_ID + " { margin: 0; padding: 0; }",
			apiInternal.drawerSelector + "#" + TOP_LIST_ID + ">li { list-style: none; }",

			"#" + DRAWER_ID + " { background: #E9E9E9; position: fixed; }",
			"#" + DRAWER_ID + ":after { content: ''; position: absolute; width: 1px; height: 100%; top: 75px; background: #DDD; }"
		].join(' ');

	var HEADER_CSS =
		[   apiInternal.drawerSelector + "#" + DRAWER_HEADER_ID + ">h1 { margin: 10px 0 3px 10px; }",
			apiInternal.drawerSelector + "#" + DRAWER_HEADER_ID + ">p { margin: 0 0 5px 10px; font-size: 80%; }",
			apiInternal.drawerSelector + "#" + HEADER_PROP_ID + " { height: 100%; float: left; width: 5px; background: #3299BB; }"].join(' ');

	var FOOTER_CSS =
		[	apiInternal.drawerSelector + "#" + DRAWER_FOOTER_ID + " { font-size: 70%; font-family: 'Open Sans', Arial, sans-serif; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + " { position: absolute; margin-top: 5px; margin-right: 5px; right: 0; text-align: right; width: auto; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + ">a { display: block; color: #424242; opacity: .5; text-decoration: none; -webkit-transition: opacity 0.3s ease; transition: opacity 0.3s ease; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + ">a:hover { text-decoration: underline; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + "." + FOOTER_ACTIVE_CLASS + ">a { opacity: 1; }",
			apiInternal.drawerSelector + "." + GITHUB_CLASS + " { width: 14px; height: 14px; margin-left: 3px; vertical-align: text-bottom; border: none; }"].join(' ');

	var drawer;
	var submissionFunction;

	apiInternal.drawer = {
		create: initializeDrawer,
		show: showDrawer,
		hide: hideDrawer
	};

	/****************************************************************************************************/

	function initializeDrawer(submissionFn) {
		if(apiInternal.drawer.initialized)
			return;

		apiInternal.drawer.initialized = true;
		submissionFunction = submissionFn;

		insertDrawerStyles();
		createDrawer();
		toggleDrawerOnDrag();
		addDrawerToDOM();
	}

	function insertDrawerStyles() {
		var fullCss = apiInternal.drawerResetCss + ' ' + DRAWER_CSS + ' ' + HEADER_CSS + ' ' + FOOTER_CSS;

		apiInternal.util.dom.createAndAppendStyleElement(HTML5_CSS);
		apiInternal.util.dom.createAndAppendStyleElement(fullCss);
	}

	function addDrawerToDOM() {
		apiInternal.slider.initialize(drawer, '230px', true);
		apiInternal.util.dom.registerAndAppend(document.body, drawer);
	}

	// Use the Neglected event to indicate when the user has stopped interacting
	// with the drawer. We'll start watching once the drawer is open (to keep
	// accurate track of the mouse position), but only allow the drawer to close
	// once the user has stopped dragging their element and is no longer interacting
	// with the drawer.
	function showDrawer() {
		apiInternal.slider.slideDrawerIn();
		apiInternal.neglected.watchTarget(drawer, hideDrawer);
	}

	function hideDrawer() {
		apiInternal.slider.slideDrawerAway();
		apiInternal.neglected.stopWatchingTarget(drawer, hideDrawer);
	}

	function allowDrawerToClose() {
		apiInternal.neglected.actOnTarget(drawer, hideDrawer);
	}

	// Show and hide the Action Drawer when the user starts/stops dragging selected
	// content
	function toggleDrawerOnDrag() {
		apiInternal.draggable.subscribeGlobal.dragstart(showDrawer);
		apiInternal.draggable.subscribeGlobal.dragend(allowDrawerToClose);
	}

	function createDrawer() {
		drawer = document.createElement('aside');
		drawer.id = DRAWER_ID;
		drawer.className = 'betterlink-right betterlink-col';

		var header = createHeader();

		var center = document.createElement('section');
		center.id = DRAWER_CENTER_ID;
		center.className = 'betterlink-center betterlink-row';
		var center_top = document.createElement('div');
		var center_bottom = document.createElement('div');
		center_top.className = 'betterlink-top-to-bottom';
		center_bottom.className = 'betterlink-bottom-to-top';

		addTopList(center_top);
		// center_bottom.appendChild();
		center.appendChild(center_top);
		center.appendChild(center_bottom);

		var footer = createFooter();

		drawer.appendChild(header);
		drawer.appendChild(center);
		drawer.appendChild(footer);
	}

	// Add a list of elements to the provided parent element
	function addTopList(element) {
		var list = document.createElement('ul');
		list.id = TOP_LIST_ID;
		list.appendChild(createNexus());

		element.appendChild(list);
	}

	function createNexus() {
		var li = document.createElement('li');

		var nexus = apiInternal.dropzone.sharingNexus.create(submissionFunction);
		li.appendChild(nexus);

		return li;
	}

	function createHeader() {
		var header = document.createElement('section');
		header.id = DRAWER_HEADER_ID;
		header.className = 'betterlink-header betterlink-row';

		var prop = document.createElement('div');
		prop.id = HEADER_PROP_ID;
		var h1 = document.createElement('h1');
		h1.appendChild(document.createTextNode("Betterlink"));
		var subtitle = document.createElement('p');
		subtitle.appendChild(document.createTextNode("drag & drop to share content"));

		header.appendChild(prop);
		header.appendChild(h1);
		header.appendChild(subtitle);

		return header;
	}

	function createFooter() {
		var footer = document.createElement('section');
		footer.id = DRAWER_FOOTER_ID;
		footer.className = 'betterlink-footer betterlink-row';

		var links = document.createElement('div');
		links.id = FOOTER_LINKS_ID;

		var homeLink = apiInternal.util.dom.createAnchorElement('http://betterlink.io', 'http://betterlink.io', '_blank');
		var contributeLink = apiInternal.util.dom.createAnchorElement('Open Source on GitHub', 'https://github.com/betterlink/betterlink-interface', '_blank');
		appendGithubLogo(contributeLink);

		links.appendChild(homeLink);
		links.appendChild(contributeLink);
		footer.appendChild(links);

		apiInternal.mouseboundary.subscribe.mouseenter(footer, activateFooter);
		apiInternal.mouseboundary.subscribe.mouseleave(footer, deactivateFooter);

		return footer;
	}

	function appendGithubLogo(element) {
		if(apiInternal.svg.supported) {
			var githubImg = apiInternal.svg.createElement('github');
			apiInternal.util.dom.applyClassToElement(githubImg, GITHUB_CLASS);
			element.appendChild(githubImg);
		}
	}

	function activateFooter() {
		var links = document.getElementById(FOOTER_LINKS_ID);
		apiInternal.util.dom.applyClassToElement(links, FOOTER_ACTIVE_CLASS);
	}

	function deactivateFooter() {
		var links = document.getElementById(FOOTER_LINKS_ID);
		apiInternal.util.dom.removeClassFromElement(links, FOOTER_ACTIVE_CLASS);
	}

	// selector is assumed to be a simple CSS ID selector
	// ex: "#betterlink-drawer "
	function extractId(selector) {
		return selector.replace(/[#\s]/g, '');
	}
});
<|endoftext|>


<commit_before>/**
 * Defines a drawer that will 'pop out' of the side of the browser window
 * and provide ways for the user to interact with the content they have
 * selected.
 *
 */
betterlink_user_interface.createModule("Action Drawer", function(api, apiInternal) {
	api.requireModules( ["Util", "Util.DOM", "Drawer Reset CSS", "Mouseboundary", "Neglected", "Draggable", "Drawer Dropzone", "Dropzone.Nexus"] );

	var HTML5_CSS = "article, aside, footer, header, nav, section { display: block; }";
	var DRAWER_ID = extractId(apiInternal.drawerSelector);
	var DRAWER_HEADER_ID = DRAWER_ID + "-header";
	var DRAWER_CENTER_ID = DRAWER_ID + "-center";
	var DRAWER_FOOTER_ID = DRAWER_ID + "-footer";

	var DRAWER_HIDDEN_CLASS = "betterlink-drawer-hidden";
	var TOP_LIST_ID = "betterlink-top-list";
	var HEADER_PROP_ID = 'betterlink-header-prop';
	var FOOTER_LINKS_ID = 'betterlink-footer-links';
	var FOOTER_ACTIVE_CLASS = 'betterlink-footer-active';

	// Drawer Animation
	// http://www.berriart.com/sidr/
	// http://jpanelmenu.com/

	// Drawer CSS
	// Full-height layout dervied from Steven Sanderson
	// http://blog.stevensanderson.com/2011/10/05/full-height-app-layouts-a-css-trick-to-make-it-easier/
	var DRAWER_CSS =
		[   ".betterlink-row, .betterlink-col { overflow: hidden; position: absolute; }",
			".betterlink-row { width: 100%; }",
			".betterlink-col { top: 0; bottom: 0; }",
			".betterlink-scroll-x { overflow-x: auto; }",
			".betterlink-scroll-y { overflow-y: auto; }",

			".betterlink-left.betterlink-col { width: 230px; }",
			".betterlink-right.betterlink-col { width: 230px; right: 0; }",

			apiInternal.drawerSelector + ".betterlink-header.betterlink-row { height: 75px; }",
			apiInternal.drawerSelector + ".betterlink-center.betterlink-row { top: 75px; bottom: 50px; }",
			apiInternal.drawerSelector + ".betterlink-footer.betterlink-row { height: 50px; bottom: 0; }",

			apiInternal.drawerSelector + ".betterlink-top-to-bottom { width: 100%; }",
			apiInternal.drawerSelector + ".betterlink-bottom-to-top { bottom: 0; position: absolute; width: 100%; }",

			apiInternal.drawerSelector + "#" + TOP_LIST_ID + " { margin: 0; padding: 0; }",
			apiInternal.drawerSelector + "#" + TOP_LIST_ID + ">li { list-style: none; }",

			"#" + DRAWER_ID + " { background: #E9E9E9; position: fixed; }",
			"#" + DRAWER_ID + ":after { content: ''; position: absolute; width: 1px; height: 100%; top: 75px; background: #DDD; }",
			"." + DRAWER_HIDDEN_CLASS + " { display: none; }"].join(' ');

	var HEADER_CSS =
		[   apiInternal.drawerSelector + "#" + DRAWER_HEADER_ID + ">h1 { margin: 10px 0 3px 10px; }",
			apiInternal.drawerSelector + "#" + DRAWER_HEADER_ID + ">p { margin: 0 0 5px 10px; font-size: 80%; }",
			apiInternal.drawerSelector + "#" + HEADER_PROP_ID + " { height: 100%; float: left; width: 5px; background: #3299BB; }"].join(' ');

	var FOOTER_CSS =
		[	apiInternal.drawerSelector + "#" + DRAWER_FOOTER_ID + " { font-size: 70%; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + " { position: absolute; margin-top: 5px; margin-right: 5px; right: 0; text-align: right; width: auto; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + ">a { display: block; color: #424242; opacity: .5; text-decoration: none; -webkit-transition: opacity 0.3s ease; transition: opacity 0.3s ease; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + ">a:hover { text-decoration: underline; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + "." + FOOTER_ACTIVE_CLASS + ">a { opacity: 1; }"].join(' ');

	var drawer;
	var submissionFunction;

	apiInternal.drawer = {
		create: initializeDrawer,
		show: showDrawer,
		hide: hideDrawer
	};

	/****************************************************************************************************/

	function initializeDrawer(submissionFn) {
		if(apiInternal.drawer.initialized)
			return;

		apiInternal.drawer.initialized = true;
		submissionFunction = submissionFn;

		insertDrawerStyles();
		createDrawer();
		toggleDrawerOnDrag();
	}

	function insertDrawerStyles() {
		var fullCss = apiInternal.drawerResetCss + ' ' + DRAWER_CSS + ' ' + HEADER_CSS + ' ' + FOOTER_CSS;

		apiInternal.util.dom.createAndAppendStyleElement(HTML5_CSS);
		apiInternal.util.dom.createAndAppendStyleElement(fullCss);
	}

	// Use the Neglected event to indicate when the user has stopped interacting
	// with the drawer. We'll start watching once the drawer is open (to keep
	// accurate track of the mouse position), but only allow the drawer to close
	// once the user has stopped dragging their element and is no longer interacting
	// with the drawer.
	function showDrawer() {
		apiInternal.util.dom.removeClassFromElement(drawer, DRAWER_HIDDEN_CLASS);
		apiInternal.neglected.watchTarget(drawer, hideDrawer);
	}

	function hideDrawer() {
		apiInternal.util.dom.applyClassToElement(drawer, DRAWER_HIDDEN_CLASS);
		apiInternal.neglected.stopWatchingTarget(drawer, hideDrawer);
	}

	function allowDrawerToClose() {
		apiInternal.neglected.actOnTarget(drawer, hideDrawer);
	}

	// Show and hide the Action Drawer when the user starts/stops dragging selected
	// content
	function toggleDrawerOnDrag() {
		apiInternal.draggable.subscribeGlobal.dragstart(showDrawer);
		apiInternal.draggable.subscribeGlobal.dragend(allowDrawerToClose);
	}

	function createDrawer() {
		drawer = document.createElement('aside');
		drawer.id = DRAWER_ID;
		drawer.className = 'betterlink-right betterlink-col';

		var header = createHeader();

		var center = document.createElement('section');
		center.id = DRAWER_CENTER_ID;
		center.className = 'betterlink-center betterlink-row';
		var center_top = document.createElement('div');
		var center_bottom = document.createElement('div');
		center_top.className = 'betterlink-top-to-bottom';
		center_bottom.className = 'betterlink-bottom-to-top';

		addTopList(center_top);
		// center_bottom.appendChild();
		center.appendChild(center_top);
		center.appendChild(center_bottom);

		var footer = createFooter();

		drawer.appendChild(header);
		drawer.appendChild(center);
		drawer.appendChild(footer);

		hideDrawer();
		apiInternal.util.dom.registerAndAppend(document.body, drawer);
	}

	// Add a list of elements to the provided parent element
	function addTopList(element) {
		var list = document.createElement('ul');
		list.id = TOP_LIST_ID;
		list.appendChild(createNexus());

		element.appendChild(list);
	}

	function createNexus() {
		var li = document.createElement('li');

		var nexus = apiInternal.dropzone.sharingNexus.create(submissionFunction);
		li.appendChild(nexus);

		return li;
	}

	function createHeader() {
		var header = document.createElement('section');
		header.id = DRAWER_HEADER_ID;
		header.className = 'betterlink-header betterlink-row';

		var prop = document.createElement('div');
		prop.id = HEADER_PROP_ID;
		var h1 = document.createElement('h1');
		h1.appendChild(document.createTextNode("Betterlink"));
		var subtitle = document.createElement('p');
		subtitle.appendChild(document.createTextNode("drag & drop to share content"));

		header.appendChild(prop);
		header.appendChild(h1);
		header.appendChild(subtitle);

		return header;
	}

	function createFooter() {
		var footer = document.createElement('section');
		footer.id = DRAWER_FOOTER_ID;
		footer.className = 'betterlink-footer betterlink-row';

		var links = document.createElement('div');
		links.id = FOOTER_LINKS_ID;

		var homeLink = apiInternal.util.dom.createAnchorElement('http://betterlink.io', 'http://betterlink.io', '_blank');
		var contributeLink = apiInternal.util.dom.createAnchorElement('Open Source on GitHub', 'https://github.com/betterlink/betterlink-interface', '_blank');
		links.appendChild(homeLink);
		links.appendChild(contributeLink);

		footer.appendChild(links);
		apiInternal.mouseboundary.subscribe.mouseenter(footer, activateFooter);
		apiInternal.mouseboundary.subscribe.mouseleave(footer, deactivateFooter);

		return footer;
	}

	function activateFooter() {
		var links = document.getElementById(FOOTER_LINKS_ID);
		apiInternal.util.dom.applyClassToElement(links, FOOTER_ACTIVE_CLASS);
	}

	function deactivateFooter() {
		var links = document.getElementById(FOOTER_LINKS_ID);
		apiInternal.util.dom.removeClassFromElement(links, FOOTER_ACTIVE_CLASS);
	}

	// selector is assumed to be a simple CSS ID selector
	// ex: "#betterlink-drawer "
	function extractId(selector) {
		return selector.replace(/[#\s]/g, '');
	}
});
<commit_msg>add the GitHub logo to the footer<commit_after>/**
 * Defines a drawer that will 'pop out' of the side of the browser window
 * and provide ways for the user to interact with the content they have
 * selected.
 *
 */
betterlink_user_interface.createModule("Action Drawer", function(api, apiInternal) {
	api.requireModules( ["Util", "Util.DOM", "Drawer Reset CSS", "Mouseboundary", "Neglected", "Draggable", "Drawer Dropzone", "Dropzone.Nexus"] );

	var HTML5_CSS = "article, aside, footer, header, nav, section { display: block; }";
	var DRAWER_ID = extractId(apiInternal.drawerSelector);
	var DRAWER_HEADER_ID = DRAWER_ID + "-header";
	var DRAWER_CENTER_ID = DRAWER_ID + "-center";
	var DRAWER_FOOTER_ID = DRAWER_ID + "-footer";

	var DRAWER_HIDDEN_CLASS = "betterlink-drawer-hidden";
	var TOP_LIST_ID = "betterlink-top-list";
	var HEADER_PROP_ID = 'betterlink-header-prop';
	var FOOTER_LINKS_ID = 'betterlink-footer-links';
	var FOOTER_ACTIVE_CLASS = 'betterlink-footer-active';
	var GITHUB_CLASS = 'betterlink-footer-github';

	// Drawer Animation
	// http://www.berriart.com/sidr/
	// http://jpanelmenu.com/

	// Drawer CSS
	// Full-height layout dervied from Steven Sanderson
	// http://blog.stevensanderson.com/2011/10/05/full-height-app-layouts-a-css-trick-to-make-it-easier/
	var DRAWER_CSS =
		[   ".betterlink-row, .betterlink-col { overflow: hidden; position: absolute; }",
			".betterlink-row { width: 100%; }",
			".betterlink-col { top: 0; bottom: 0; }",
			".betterlink-scroll-x { overflow-x: auto; }",
			".betterlink-scroll-y { overflow-y: auto; }",

			".betterlink-left.betterlink-col { width: 230px; }",
			".betterlink-right.betterlink-col { width: 230px; right: 0; }",

			apiInternal.drawerSelector + ".betterlink-header.betterlink-row { height: 75px; }",
			apiInternal.drawerSelector + ".betterlink-center.betterlink-row { top: 75px; bottom: 50px; }",
			apiInternal.drawerSelector + ".betterlink-footer.betterlink-row { height: 50px; bottom: 0; }",

			apiInternal.drawerSelector + ".betterlink-top-to-bottom { width: 100%; }",
			apiInternal.drawerSelector + ".betterlink-bottom-to-top { bottom: 0; position: absolute; width: 100%; }",

			apiInternal.drawerSelector + "#" + TOP_LIST_ID + " { margin: 0; padding: 0; }",
			apiInternal.drawerSelector + "#" + TOP_LIST_ID + ">li { list-style: none; }",

			"#" + DRAWER_ID + " { background: #E9E9E9; position: fixed; }",
			"#" + DRAWER_ID + ":after { content: ''; position: absolute; width: 1px; height: 100%; top: 75px; background: #DDD; }",
			"." + DRAWER_HIDDEN_CLASS + " { display: none; }"].join(' ');

	var HEADER_CSS =
		[   apiInternal.drawerSelector + "#" + DRAWER_HEADER_ID + ">h1 { margin: 10px 0 3px 10px; }",
			apiInternal.drawerSelector + "#" + DRAWER_HEADER_ID + ">p { margin: 0 0 5px 10px; font-size: 80%; }",
			apiInternal.drawerSelector + "#" + HEADER_PROP_ID + " { height: 100%; float: left; width: 5px; background: #3299BB; }"].join(' ');

	var FOOTER_CSS =
		[	apiInternal.drawerSelector + "#" + DRAWER_FOOTER_ID + " { font-size: 70%; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + " { position: absolute; margin-top: 5px; margin-right: 5px; right: 0; text-align: right; width: auto; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + ">a { display: block; color: #424242; opacity: .5; text-decoration: none; -webkit-transition: opacity 0.3s ease; transition: opacity 0.3s ease; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + ">a:hover { text-decoration: underline; }",
			apiInternal.drawerSelector + "#" + FOOTER_LINKS_ID + "." + FOOTER_ACTIVE_CLASS + ">a { opacity: 1; }",
			apiInternal.drawerSelector + "." + GITHUB_CLASS + " { width: 14px; height: 14px; margin-left: 3px; vertical-align: text-bottom; }"].join(' ');

	var drawer;
	var submissionFunction;

	apiInternal.drawer = {
		create: initializeDrawer,
		show: showDrawer,
		hide: hideDrawer
	};

	/****************************************************************************************************/

	function initializeDrawer(submissionFn) {
		if(apiInternal.drawer.initialized)
			return;

		apiInternal.drawer.initialized = true;
		submissionFunction = submissionFn;

		insertDrawerStyles();
		createDrawer();
		toggleDrawerOnDrag();
	}

	function insertDrawerStyles() {
		var fullCss = apiInternal.drawerResetCss + ' ' + DRAWER_CSS + ' ' + HEADER_CSS + ' ' + FOOTER_CSS;

		apiInternal.util.dom.createAndAppendStyleElement(HTML5_CSS);
		apiInternal.util.dom.createAndAppendStyleElement(fullCss);
	}

	// Use the Neglected event to indicate when the user has stopped interacting
	// with the drawer. We'll start watching once the drawer is open (to keep
	// accurate track of the mouse position), but only allow the drawer to close
	// once the user has stopped dragging their element and is no longer interacting
	// with the drawer.
	function showDrawer() {
		apiInternal.util.dom.removeClassFromElement(drawer, DRAWER_HIDDEN_CLASS);
		apiInternal.neglected.watchTarget(drawer, hideDrawer);
	}

	function hideDrawer() {
		apiInternal.util.dom.applyClassToElement(drawer, DRAWER_HIDDEN_CLASS);
		apiInternal.neglected.stopWatchingTarget(drawer, hideDrawer);
	}

	function allowDrawerToClose() {
		apiInternal.neglected.actOnTarget(drawer, hideDrawer);
	}

	// Show and hide the Action Drawer when the user starts/stops dragging selected
	// content
	function toggleDrawerOnDrag() {
		apiInternal.draggable.subscribeGlobal.dragstart(showDrawer);
		apiInternal.draggable.subscribeGlobal.dragend(allowDrawerToClose);
	}

	function createDrawer() {
		drawer = document.createElement('aside');
		drawer.id = DRAWER_ID;
		drawer.className = 'betterlink-right betterlink-col';

		var header = createHeader();

		var center = document.createElement('section');
		center.id = DRAWER_CENTER_ID;
		center.className = 'betterlink-center betterlink-row';
		var center_top = document.createElement('div');
		var center_bottom = document.createElement('div');
		center_top.className = 'betterlink-top-to-bottom';
		center_bottom.className = 'betterlink-bottom-to-top';

		addTopList(center_top);
		// center_bottom.appendChild();
		center.appendChild(center_top);
		center.appendChild(center_bottom);

		var footer = createFooter();

		drawer.appendChild(header);
		drawer.appendChild(center);
		drawer.appendChild(footer);

		hideDrawer();
		apiInternal.util.dom.registerAndAppend(document.body, drawer);
	}

	// Add a list of elements to the provided parent element
	function addTopList(element) {
		var list = document.createElement('ul');
		list.id = TOP_LIST_ID;
		list.appendChild(createNexus());

		element.appendChild(list);
	}

	function createNexus() {
		var li = document.createElement('li');

		var nexus = apiInternal.dropzone.sharingNexus.create(submissionFunction);
		li.appendChild(nexus);

		return li;
	}

	function createHeader() {
		var header = document.createElement('section');
		header.id = DRAWER_HEADER_ID;
		header.className = 'betterlink-header betterlink-row';

		var prop = document.createElement('div');
		prop.id = HEADER_PROP_ID;
		var h1 = document.createElement('h1');
		h1.appendChild(document.createTextNode("Betterlink"));
		var subtitle = document.createElement('p');
		subtitle.appendChild(document.createTextNode("drag & drop to share content"));

		header.appendChild(prop);
		header.appendChild(h1);
		header.appendChild(subtitle);

		return header;
	}

	function createFooter() {
		var footer = document.createElement('section');
		footer.id = DRAWER_FOOTER_ID;
		footer.className = 'betterlink-footer betterlink-row';

		var links = document.createElement('div');
		links.id = FOOTER_LINKS_ID;

		var homeLink = apiInternal.util.dom.createAnchorElement('http://betterlink.io', 'http://betterlink.io', '_blank');
		var contributeLink = apiInternal.util.dom.createAnchorElement('Open Source on GitHub', 'https://github.com/betterlink/betterlink-interface', '_blank');
		appendGithubLogo(contributeLink);

		links.appendChild(homeLink);
		links.appendChild(contributeLink);
		footer.appendChild(links);

		apiInternal.mouseboundary.subscribe.mouseenter(footer, activateFooter);
		apiInternal.mouseboundary.subscribe.mouseleave(footer, deactivateFooter);

		return footer;
	}

	function appendGithubLogo(element) {
		if(apiInternal.svg.supported) {
			var githubImg = apiInternal.svg.createElement('github');
			apiInternal.util.dom.applyClassToElement(githubImg, GITHUB_CLASS);
			element.appendChild(githubImg);
		}
	}

	function activateFooter() {
		var links = document.getElementById(FOOTER_LINKS_ID);
		apiInternal.util.dom.applyClassToElement(links, FOOTER_ACTIVE_CLASS);
	}

	function deactivateFooter() {
		var links = document.getElementById(FOOTER_LINKS_ID);
		apiInternal.util.dom.removeClassFromElement(links, FOOTER_ACTIVE_CLASS);
	}

	// selector is assumed to be a simple CSS ID selector
	// ex: "#betterlink-drawer "
	function extractId(selector) {
		return selector.replace(/[#\s]/g, '');
	}
});
<|endoftext|>