<commit_before>import extend from 'extend';
import Delta from 'quill-delta';
import Parchment from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';

import { AlignAttribute, AlignStyle } from '../formats/align';
import { BackgroundStyle } from '../formats/background';
import { ColorStyle } from '../formats/color';
import { DirectionAttribute, DirectionStyle } from '../formats/direction';
import { FontStyle } from '../formats/font';
import { SizeStyle } from '../formats/size';

let debug = logger('quill:clipboard');

const DOM_KEY = '__ql-matcher';

const CLIPBOARD_CONFIG = [
  [Node.TEXT_NODE, matchText],
  [Node.TEXT_NODE, matchNewline],
  ['br', matchBreak],
  [Node.ELEMENT_NODE, matchNewline],
  [Node.ELEMENT_NODE, matchBlot],
  [Node.ELEMENT_NODE, matchSpacing],
  [Node.ELEMENT_NODE, matchAttributor],
  [Node.ELEMENT_NODE, matchStyles],
  ['li', matchIndent],
  ['b', matchAlias.bind(matchAlias, 'bold')],
  ['i', matchAlias.bind(matchAlias, 'italic')],
  ['style', matchIgnore]
];

const ATTRIBUTE_ATTRIBUTORS = [
  AlignAttribute,
  DirectionAttribute
].reduce(function(memo, attr) {
  memo[attr.keyName] = attr;
  return memo;
}, {});

const STYLE_ATTRIBUTORS = [
  AlignStyle,
  BackgroundStyle,
  ColorStyle,
  DirectionStyle,
  FontStyle,
  SizeStyle
].reduce(function(memo, attr) {
  memo[attr.keyName] = attr;
  return memo;
}, {});

class Clipboard extends Module {
  constructor(quill, options) {
    super(quill, options);
    this.quill.root.addEventListener('paste', this.onPaste.bind(this));
    this.container = this.quill.addContainer('ql-clipboard');
    this.container.setAttribute('contenteditable', true);
    this.container.setAttribute('tabindex', -1);
    this.matchers = [];
    CLIPBOARD_CONFIG.concat(this.options.matchers).forEach((pair) => {
      this.addMatcher(...pair);
    });
  }

  addMatcher(selector, matcher) {
    this.matchers.push([selector, matcher]);
  }

  convert(html) {
    if (typeof html === 'string') {
      this.container.innerHTML = html.replace(/\>\r?\n +\</g, '><'); // Remove spaces between tags
    }
    let [elementMatchers, textMatchers] = this.prepareMatching();
    let delta = traverse(this.container, elementMatchers, textMatchers);
    // Remove trailing newline
    if (deltaEndsWith(delta, '\n') && delta.ops[delta.ops.length - 1].attributes == null) {
      delta = delta.compose(new Delta().retain(delta.length() - 1).delete(1));
    }
    debug.log('convert', this.container.innerHTML, delta);
    this.container.innerHTML = '';
    return delta;
  }

  dangerouslyPasteHTML(index, html, source = Quill.sources.API) {
    if (typeof index === 'string') {
      return this.quill.setContents(this.convert(index), html);
    } else {
      let paste = this.convert(html);
      return this.quill.updateContents(new Delta().retain(index).concat(paste), source);
    }
  }

  onPaste(e) {
    if (e.defaultPrevented || !this.quill.isEnabled()) return;
    let range = this.quill.getSelection();
    let delta = new Delta().retain(range.index);
    let scrollTop = this.quill.scrollingContainer.scrollTop;
    this.container.focus();
    setTimeout(() => {
      this.quill.selection.update(Quill.sources.SILENT);
      delta = delta.concat(this.convert()).delete(range.length);
      this.quill.updateContents(delta, Quill.sources.USER);
      // range.length contributes to delta.length()
      this.quill.setSelection(delta.length() - range.length, Quill.sources.SILENT);
      this.quill.scrollingContainer.scrollTop = scrollTop;
      this.quill.selection.scrollIntoView();
    }, 1);
  }

  prepareMatching() {
    let elementMatchers = [], textMatchers = [];
    this.matchers.forEach((pair) => {
      let [selector, matcher] = pair;
      switch (selector) {
        case Node.TEXT_NODE:
          textMatchers.push(matcher);
          break;
        case Node.ELEMENT_NODE:
          elementMatchers.push(matcher);
          break;
        default:
          [].forEach.call(this.container.querySelectorAll(selector), (node) => {
            // TODO use weakmap
            node[DOM_KEY] = node[DOM_KEY] || [];
            node[DOM_KEY].push(matcher);
          });
          break;
      }
    });
    return [elementMatchers, textMatchers];
  }
}
Clipboard.DEFAULTS = {
  matchers: []
};

function applyFormat(delta, format, value) {
  if (typeof format === 'object') {
    return Object.keys(format).reduce(function(delta, key) {
      return applyFormat(delta, key, format[key]);
    }, delta);
  } else {
    return delta.reduce(function(delta, op) {
      if (op.attributes && op.attributes[format]) {
        return delta.push(op);
      } else {
        return delta.insert(op.insert, extend({}, {[format]: value}, op.attributes));
      }
    }, new Delta());
  }
}

function computeStyle(node) {
  if (node.nodeType !== Node.ELEMENT_NODE) return {};
  const DOM_KEY = '__ql-computed-style';
  return node[DOM_KEY] || (node[DOM_KEY] = window.getComputedStyle(node));
}

function deltaEndsWith(delta, text) {
  let endText = "";
  for (let i = delta.ops.length - 1; i >= 0 && endText.length < text.length; --i) {
    let op  = delta.ops[i];
    if (typeof op.insert !== 'string') break;
    endText = op.insert + endText;
  }
  return endText.slice(-1*text.length) === text;
}

function isLine(node) {
  if (node.childNodes.length === 0) return false;   // Exclude embed blocks
  let style = computeStyle(node);
  return ['block', 'list-item'].indexOf(style.display) > -1;
}

function traverse(node, elementMatchers, textMatchers) {  // Post-order
  if (node.nodeType === node.TEXT_NODE) {
    return textMatchers.reduce(function(delta, matcher) {
      return matcher(node, delta);
    }, new Delta());
  } else if (node.nodeType === node.ELEMENT_NODE) {
    return [].reduce.call(node.childNodes || [], (delta, childNode) => {
      let childrenDelta = traverse(childNode, elementMatchers, textMatchers);
      if (childNode.nodeType === node.ELEMENT_NODE) {
        childrenDelta = elementMatchers.reduce(function(childrenDelta, matcher) {
          return matcher(childNode, childrenDelta);
        }, childrenDelta);
        childrenDelta = (childNode[DOM_KEY] || []).reduce(function(childrenDelta, matcher) {
          return matcher(childNode, childrenDelta);
        }, childrenDelta);
      }
      return delta.concat(childrenDelta);
    }, new Delta());
  } else {
    return new Delta();
  }
}

function matchAlias(format, node, delta) {
  return applyFormat(delta, format, true);
}

function matchAttributor(node, delta) {
  let attributes = Parchment.Attributor.Attribute.keys(node);
  let classes = Parchment.Attributor.Class.keys(node);
  let styles = Parchment.Attributor.Style.keys(node);
  let formats = {};
  attributes.concat(classes).concat(styles).forEach((name) => {
    let attr = Parchment.query(name, Parchment.Scope.ATTRIBUTE);
    if (attr != null) {
      formats[attr.attrName] = attr.value(node);
      if (formats[attr.attrName]) return;
    }
    if (ATTRIBUTE_ATTRIBUTORS[name] != null) {
      attr = ATTRIBUTE_ATTRIBUTORS[name];
      formats[attr.attrName] = attr.value(node) || undefined;
    }
    if (STYLE_ATTRIBUTORS[name] != null) {
      attr = STYLE_ATTRIBUTORS[name];
      formats[attr.attrName] = attr.value(node) || undefined;
    }
  });
  if (Object.keys(formats).length > 0) {
    delta = applyFormat(delta, formats);
  }
  return delta;
}

function matchBlot(node, delta) {
  let match = Parchment.query(node);
  if (match == null) return delta;
  if (match.prototype instanceof Parchment.Embed) {
    let embed = {};
    let value = match.value(node);
    if (value != null) {
      embed[match.blotName] = value;
      delta = new Delta().insert(embed, match.formats(node));
    }
  } else if (typeof match.formats === 'function') {
    delta = applyFormat(delta, match.blotName, match.formats(node));
  }
  return delta;
}

function matchBreak(node, delta) {
  if (!deltaEndsWith(delta, '\n')) {
    delta.insert('\n');
  }
  return delta;
}

function matchIgnore() {
  return new Delta();
}

function matchIndent(node, delta) {
  let match = Parchment.query(node);
  if (match == null || match.blotName !== 'list-item' || !deltaEndsWith(delta, '\n')) {
    return delta;
  }
  let indent = -1, parent = node.parentNode;
  while (!parent.classList.contains('ql-clipboard')) {
    if ((Parchment.query(parent) || {}).blotName === 'list') {
      indent += 1;
    }
    parent = parent.parentNode;
  }
  if (indent <= 0) return delta;
  return delta.compose(new Delta().retain(delta.length() - 1).retain(1, { indent: indent}));
}

function matchNewline(node, delta) {
  if (!deltaEndsWith(delta, '\n')) {
    if (isLine(node) || (delta.length() > 0 && node.nextSibling && isLine(node.nextSibling))) {
      delta.insert('\n');
    }
  }
  return delta;
}

function matchSpacing(node, delta) {
  if (isLine(node) && node.nextElementSibling != null && !deltaEndsWith(delta, '\n\n')) {
    let nodeHeight = node.offsetHeight + parseFloat(computeStyle(node).marginTop) + parseFloat(computeStyle(node).marginBottom);
    if (node.nextElementSibling.offsetTop > node.offsetTop + nodeHeight*1.5) {
      delta.insert('\n');
    }
  }
  return delta;
}

function matchStyles(node, delta) {
  let formats = {};
  let style = node.style || {};
  if (style.fontStyle && computeStyle(node).fontStyle === 'italic') {
    formats.italic = true;
  }
  if (style.fontWeight && (computeStyle(node).fontWeight === 'bold' || computeStyle(node).fontWeight === '700')) {
    formats.bold = true;
  }
  if (Object.keys(formats).length > 0) {
    delta = applyFormat(delta, formats);
  }
  if (parseFloat(style.textIndent || 0) > 0) {  // Could be 0.5in
    delta = new Delta().insert('\t').concat(delta);
  }
  return delta;
}

function matchText(node, delta) {
  let text = node.data;
  // Word represents empty line with <o:p> </o:p>
  if (node.parentNode.tagName === 'O:P') {
    return delta.insert(text.trim());
  }
  if (text.trim().length === 0 && node.parentNode.classList.contains('ql-clipboard')) {
    return delta;
  }
  if (!computeStyle(node.parentNode).whiteSpace.startsWith('pre')) {
    // eslint-disable-next-line func-style
    let replacer = function(collapse, match) {
      match = match.replace(/[^\u00a0]/g, '');    // \u00a0 is nbsp;
      return match.length < 1 && collapse ? ' ' : match;
    };
    text = text.replace(/\r\n/g, ' ').replace(/\n/g, ' ');
    text = text.replace(/\s\s+/g, replacer.bind(replacer, true));  // collapse whitespace
    if ((node.previousSibling == null && isLine(node.parentNode)) ||
        (node.previousSibling != null && isLine(node.previousSibling))) {
      text = text.replace(/^\s+/, replacer.bind(replacer, false));
    }
    if ((node.nextSibling == null && isLine(node.parentNode)) ||
        (node.nextSibling != null && isLine(node.nextSibling))) {
      text = text.replace(/\s+$/, replacer.bind(replacer, false));
    }
  }
  return delta.insert(text);
}

export { Clipboard as default, matchAttributor, matchBlot, matchNewline, matchSpacing, matchText };
<commit_msg>handle bolder weights<commit_after>import extend from 'extend';
import Delta from 'quill-delta';
import Parchment from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';

import { AlignAttribute, AlignStyle } from '../formats/align';
import { BackgroundStyle } from '../formats/background';
import { ColorStyle } from '../formats/color';
import { DirectionAttribute, DirectionStyle } from '../formats/direction';
import { FontStyle } from '../formats/font';
import { SizeStyle } from '../formats/size';

let debug = logger('quill:clipboard');

const DOM_KEY = '__ql-matcher';

const CLIPBOARD_CONFIG = [
  [Node.TEXT_NODE, matchText],
  [Node.TEXT_NODE, matchNewline],
  ['br', matchBreak],
  [Node.ELEMENT_NODE, matchNewline],
  [Node.ELEMENT_NODE, matchBlot],
  [Node.ELEMENT_NODE, matchSpacing],
  [Node.ELEMENT_NODE, matchAttributor],
  [Node.ELEMENT_NODE, matchStyles],
  ['li', matchIndent],
  ['b', matchAlias.bind(matchAlias, 'bold')],
  ['i', matchAlias.bind(matchAlias, 'italic')],
  ['style', matchIgnore]
];

const ATTRIBUTE_ATTRIBUTORS = [
  AlignAttribute,
  DirectionAttribute
].reduce(function(memo, attr) {
  memo[attr.keyName] = attr;
  return memo;
}, {});

const STYLE_ATTRIBUTORS = [
  AlignStyle,
  BackgroundStyle,
  ColorStyle,
  DirectionStyle,
  FontStyle,
  SizeStyle
].reduce(function(memo, attr) {
  memo[attr.keyName] = attr;
  return memo;
}, {});

class Clipboard extends Module {
  constructor(quill, options) {
    super(quill, options);
    this.quill.root.addEventListener('paste', this.onPaste.bind(this));
    this.container = this.quill.addContainer('ql-clipboard');
    this.container.setAttribute('contenteditable', true);
    this.container.setAttribute('tabindex', -1);
    this.matchers = [];
    CLIPBOARD_CONFIG.concat(this.options.matchers).forEach((pair) => {
      this.addMatcher(...pair);
    });
  }

  addMatcher(selector, matcher) {
    this.matchers.push([selector, matcher]);
  }

  convert(html) {
    if (typeof html === 'string') {
      this.container.innerHTML = html.replace(/\>\r?\n +\</g, '><'); // Remove spaces between tags
    }
    let [elementMatchers, textMatchers] = this.prepareMatching();
    let delta = traverse(this.container, elementMatchers, textMatchers);
    // Remove trailing newline
    if (deltaEndsWith(delta, '\n') && delta.ops[delta.ops.length - 1].attributes == null) {
      delta = delta.compose(new Delta().retain(delta.length() - 1).delete(1));
    }
    debug.log('convert', this.container.innerHTML, delta);
    this.container.innerHTML = '';
    return delta;
  }

  dangerouslyPasteHTML(index, html, source = Quill.sources.API) {
    if (typeof index === 'string') {
      return this.quill.setContents(this.convert(index), html);
    } else {
      let paste = this.convert(html);
      return this.quill.updateContents(new Delta().retain(index).concat(paste), source);
    }
  }

  onPaste(e) {
    if (e.defaultPrevented || !this.quill.isEnabled()) return;
    let range = this.quill.getSelection();
    let delta = new Delta().retain(range.index);
    let scrollTop = this.quill.scrollingContainer.scrollTop;
    this.container.focus();
    setTimeout(() => {
      this.quill.selection.update(Quill.sources.SILENT);
      delta = delta.concat(this.convert()).delete(range.length);
      this.quill.updateContents(delta, Quill.sources.USER);
      // range.length contributes to delta.length()
      this.quill.setSelection(delta.length() - range.length, Quill.sources.SILENT);
      this.quill.scrollingContainer.scrollTop = scrollTop;
      this.quill.selection.scrollIntoView();
    }, 1);
  }

  prepareMatching() {
    let elementMatchers = [], textMatchers = [];
    this.matchers.forEach((pair) => {
      let [selector, matcher] = pair;
      switch (selector) {
        case Node.TEXT_NODE:
          textMatchers.push(matcher);
          break;
        case Node.ELEMENT_NODE:
          elementMatchers.push(matcher);
          break;
        default:
          [].forEach.call(this.container.querySelectorAll(selector), (node) => {
            // TODO use weakmap
            node[DOM_KEY] = node[DOM_KEY] || [];
            node[DOM_KEY].push(matcher);
          });
          break;
      }
    });
    return [elementMatchers, textMatchers];
  }
}
Clipboard.DEFAULTS = {
  matchers: []
};

function applyFormat(delta, format, value) {
  if (typeof format === 'object') {
    return Object.keys(format).reduce(function(delta, key) {
      return applyFormat(delta, key, format[key]);
    }, delta);
  } else {
    return delta.reduce(function(delta, op) {
      if (op.attributes && op.attributes[format]) {
        return delta.push(op);
      } else {
        return delta.insert(op.insert, extend({}, {[format]: value}, op.attributes));
      }
    }, new Delta());
  }
}

function computeStyle(node) {
  if (node.nodeType !== Node.ELEMENT_NODE) return {};
  const DOM_KEY = '__ql-computed-style';
  return node[DOM_KEY] || (node[DOM_KEY] = window.getComputedStyle(node));
}

function deltaEndsWith(delta, text) {
  let endText = "";
  for (let i = delta.ops.length - 1; i >= 0 && endText.length < text.length; --i) {
    let op  = delta.ops[i];
    if (typeof op.insert !== 'string') break;
    endText = op.insert + endText;
  }
  return endText.slice(-1*text.length) === text;
}

function isLine(node) {
  if (node.childNodes.length === 0) return false;   // Exclude embed blocks
  let style = computeStyle(node);
  return ['block', 'list-item'].indexOf(style.display) > -1;
}

function traverse(node, elementMatchers, textMatchers) {  // Post-order
  if (node.nodeType === node.TEXT_NODE) {
    return textMatchers.reduce(function(delta, matcher) {
      return matcher(node, delta);
    }, new Delta());
  } else if (node.nodeType === node.ELEMENT_NODE) {
    return [].reduce.call(node.childNodes || [], (delta, childNode) => {
      let childrenDelta = traverse(childNode, elementMatchers, textMatchers);
      if (childNode.nodeType === node.ELEMENT_NODE) {
        childrenDelta = elementMatchers.reduce(function(childrenDelta, matcher) {
          return matcher(childNode, childrenDelta);
        }, childrenDelta);
        childrenDelta = (childNode[DOM_KEY] || []).reduce(function(childrenDelta, matcher) {
          return matcher(childNode, childrenDelta);
        }, childrenDelta);
      }
      return delta.concat(childrenDelta);
    }, new Delta());
  } else {
    return new Delta();
  }
}

function matchAlias(format, node, delta) {
  return applyFormat(delta, format, true);
}

function matchAttributor(node, delta) {
  let attributes = Parchment.Attributor.Attribute.keys(node);
  let classes = Parchment.Attributor.Class.keys(node);
  let styles = Parchment.Attributor.Style.keys(node);
  let formats = {};
  attributes.concat(classes).concat(styles).forEach((name) => {
    let attr = Parchment.query(name, Parchment.Scope.ATTRIBUTE);
    if (attr != null) {
      formats[attr.attrName] = attr.value(node);
      if (formats[attr.attrName]) return;
    }
    if (ATTRIBUTE_ATTRIBUTORS[name] != null) {
      attr = ATTRIBUTE_ATTRIBUTORS[name];
      formats[attr.attrName] = attr.value(node) || undefined;
    }
    if (STYLE_ATTRIBUTORS[name] != null) {
      attr = STYLE_ATTRIBUTORS[name];
      formats[attr.attrName] = attr.value(node) || undefined;
    }
  });
  if (Object.keys(formats).length > 0) {
    delta = applyFormat(delta, formats);
  }
  return delta;
}

function matchBlot(node, delta) {
  let match = Parchment.query(node);
  if (match == null) return delta;
  if (match.prototype instanceof Parchment.Embed) {
    let embed = {};
    let value = match.value(node);
    if (value != null) {
      embed[match.blotName] = value;
      delta = new Delta().insert(embed, match.formats(node));
    }
  } else if (typeof match.formats === 'function') {
    delta = applyFormat(delta, match.blotName, match.formats(node));
  }
  return delta;
}

function matchBreak(node, delta) {
  if (!deltaEndsWith(delta, '\n')) {
    delta.insert('\n');
  }
  return delta;
}

function matchIgnore() {
  return new Delta();
}

function matchIndent(node, delta) {
  let match = Parchment.query(node);
  if (match == null || match.blotName !== 'list-item' || !deltaEndsWith(delta, '\n')) {
    return delta;
  }
  let indent = -1, parent = node.parentNode;
  while (!parent.classList.contains('ql-clipboard')) {
    if ((Parchment.query(parent) || {}).blotName === 'list') {
      indent += 1;
    }
    parent = parent.parentNode;
  }
  if (indent <= 0) return delta;
  return delta.compose(new Delta().retain(delta.length() - 1).retain(1, { indent: indent}));
}

function matchNewline(node, delta) {
  if (!deltaEndsWith(delta, '\n')) {
    if (isLine(node) || (delta.length() > 0 && node.nextSibling && isLine(node.nextSibling))) {
      delta.insert('\n');
    }
  }
  return delta;
}

function matchSpacing(node, delta) {
  if (isLine(node) && node.nextElementSibling != null && !deltaEndsWith(delta, '\n\n')) {
    let nodeHeight = node.offsetHeight + parseFloat(computeStyle(node).marginTop) + parseFloat(computeStyle(node).marginBottom);
    if (node.nextElementSibling.offsetTop > node.offsetTop + nodeHeight*1.5) {
      delta.insert('\n');
    }
  }
  return delta;
}

function matchStyles(node, delta) {
  let formats = {};
  let style = node.style || {};
  if (style.fontStyle && computeStyle(node).fontStyle === 'italic') {
    formats.italic = true;
  }
  if (style.fontWeight && (computeStyle(node).fontWeight.startsWith('bold') ||
                           parseInt(computeStyle(node).fontWeight) >= 700)) {
    formats.bold = true;
  }
  if (Object.keys(formats).length > 0) {
    delta = applyFormat(delta, formats);
  }
  if (parseFloat(style.textIndent || 0) > 0) {  // Could be 0.5in
    delta = new Delta().insert('\t').concat(delta);
  }
  return delta;
}

function matchText(node, delta) {
  let text = node.data;
  // Word represents empty line with <o:p> </o:p>
  if (node.parentNode.tagName === 'O:P') {
    return delta.insert(text.trim());
  }
  if (text.trim().length === 0 && node.parentNode.classList.contains('ql-clipboard')) {
    return delta;
  }
  if (!computeStyle(node.parentNode).whiteSpace.startsWith('pre')) {
    // eslint-disable-next-line func-style
    let replacer = function(collapse, match) {
      match = match.replace(/[^\u00a0]/g, '');    // \u00a0 is nbsp;
      return match.length < 1 && collapse ? ' ' : match;
    };
    text = text.replace(/\r\n/g, ' ').replace(/\n/g, ' ');
    text = text.replace(/\s\s+/g, replacer.bind(replacer, true));  // collapse whitespace
    if ((node.previousSibling == null && isLine(node.parentNode)) ||
        (node.previousSibling != null && isLine(node.previousSibling))) {
      text = text.replace(/^\s+/, replacer.bind(replacer, false));
    }
    if ((node.nextSibling == null && isLine(node.parentNode)) ||
        (node.nextSibling != null && isLine(node.nextSibling))) {
      text = text.replace(/\s+$/, replacer.bind(replacer, false));
    }
  }
  return delta.insert(text);
}

export { Clipboard as default, matchAttributor, matchBlot, matchNewline, matchSpacing, matchText };
<|endoftext|>


<commit_before>import { json as d3_json } from 'd3-fetch';
import { data as _data } from '../../data';  // prebundled data

//
// The coreData module fetches data from JSON files
//
export function coreData(context) {
  let _this = {};
  let _inflight = {};
  let _fileMap = {
    'address_formats': 'data/address_formats.min.json',
    'deprecated': 'data/deprecated.min.json',
    'discarded': 'data/discarded.min.json',
    'imagery': 'data/imagery.min.json',
    'intro_graph': 'data/intro_graph.min.json',
    'languages': 'data/languages.min.json',
    'locales': 'data/locales.min.json',
    'nsi_brands': 'https://cdn.jsdelivr.net/npm/name-suggestion-index@3/dist/brands.min.json',
    'nsi_filters': 'https://cdn.jsdelivr.net/npm/name-suggestion-index@3/dist/filters.min.json',
    'oci_features': 'https://cdn.jsdelivr.net/npm/osm-community-index@2/dist/features.min.json',
    'oci_resources': 'https://cdn.jsdelivr.net/npm/osm-community-index@2/dist/resources.min.json',
    'preset_categories': 'data/preset_categories.min.json',
    'preset_defaults': 'data/preset_defaults.min.json',
    'preset_fields': 'data/fields.min.json',
    'preset_presets': 'data/presets.min.json',
    'phone_formats': 'data/phone_formats.min.json',
    'shortcuts': 'data/shortcuts.min.json',
    'territory_languages': 'data/territory_languages.min.json',
    'wmf_sitematrix': 'https://cdn.jsdelivr.net/npm/wmf-sitematrix@0.1/wikipedia.min.json'
  };

  // Returns a Promise to fetch data
  // (resolved with the data if we have it already)
  _this.get = (which) => {
    if (_data[which]) {
      return Promise.resolve(_data[which]);
    }

    const file = _fileMap[which];
    const url = file && context.asset(file);
    if (!url) {
      return Promise.reject(`Unknown data file for "${which}"`);
    }

    let prom = _inflight[url];
    if (!prom) {
      _inflight[url] = prom = d3_json(url)
        .then(result => {
          delete _inflight[url];
          if (!result) {
            throw new Error(`No data loaded for "${which}"`);
          }
          _data[which] = result;
          return result;
        })
        .catch(err => {
          delete _inflight[url];
          throw err;
        });
    }

    return prom;
  };

  // Accessor for the file map
  _this.fileMap = function(val) {
    if (!arguments.length) return _fileMap;
    _fileMap = val;
    return _this;
  };

  return _this;
}
<commit_msg>Fix data urls<commit_after>import { json as d3_json } from 'd3-fetch';
import { data as _data } from '../../data';  // prebundled data

//
// The coreData module fetches data from JSON files
//
export function coreData(context) {
  let _this = {};
  let _inflight = {};
  let _fileMap = {
    'address_formats': 'data/address_formats.min.json',
    'deprecated': 'data/deprecated.min.json',
    'discarded': 'data/discarded.min.json',
    'imagery': 'data/imagery.min.json',
    'intro_graph': 'data/intro_graph.min.json',
    'languages': 'data/languages.min.json',
    'locales': 'data/locales.min.json',
    'nsi_brands': 'https://cdn.jsdelivr.net/npm/name-suggestion-index@3/dist/brands.json',
    'nsi_filters': 'https://cdn.jsdelivr.net/npm/name-suggestion-index@3/dist/filters.json',
    'oci_features': 'https://cdn.jsdelivr.net/npm/osm-community-index@2/dist/features.min.json',
    'oci_resources': 'https://cdn.jsdelivr.net/npm/osm-community-index@2/dist/resources.min.json',
    'preset_categories': 'data/preset_categories.min.json',
    'preset_defaults': 'data/preset_defaults.min.json',
    'preset_fields': 'data/preset_fields.min.json',
    'preset_presets': 'data/preset_presets.min.json',
    'phone_formats': 'data/phone_formats.min.json',
    'shortcuts': 'data/shortcuts.min.json',
    'territory_languages': 'data/territory_languages.min.json',
    'wmf_sitematrix': 'https://cdn.jsdelivr.net/npm/wmf-sitematrix@0.1/wikipedia.min.json'
  };

  // Returns a Promise to fetch data
  // (resolved with the data if we have it already)
  _this.get = (which) => {
    if (_data[which]) {
      return Promise.resolve(_data[which]);
    }

    const file = _fileMap[which];
    const url = file && context.asset(file);
    if (!url) {
      return Promise.reject(`Unknown data file for "${which}"`);
    }

    let prom = _inflight[url];
    if (!prom) {
      _inflight[url] = prom = d3_json(url)
        .then(result => {
          delete _inflight[url];
          if (!result) {
            throw new Error(`No data loaded for "${which}"`);
          }
          _data[which] = result;
          return result;
        })
        .catch(err => {
          delete _inflight[url];
          throw err;
        });
    }

    return prom;
  };

  // Accessor for the file map
  _this.fileMap = function(val) {
    if (!arguments.length) return _fileMap;
    _fileMap = val;
    return _this;
  };

  return _this;
}
<|endoftext|>


<commit_before>/* Waterbot -- Module "http-urls"
 *   Spouts page title when someone posts a link.
 *
 * Copyright 2014 Augustin Cavalier (waddlesplash).
 * Licensed under the MIT license.
 */

var http = require("http");
var https = require("https");

/* From PHPJS */
function htmlspecialchars_decode(string, quote_style) {
  var optTemp = 0,
  i = 0,
  noquotes = false;
  if (typeof quote_style === 'undefined') {
    quote_style = 2;
  }
  string = string.toString()
  .replace(/</g, '<')
  .replace(/>/g, '>');
  var OPTS = {
    'ENT_NOQUOTES': 0,
    'ENT_HTML_QUOTE_SINGLE': 1,
    'ENT_HTML_QUOTE_DOUBLE': 2,
    'ENT_COMPAT': 2,
    'ENT_QUOTES': 3,
    'ENT_IGNORE': 4
  };
  if (quote_style === 0) {
    noquotes = true;
  }
  if (typeof quote_style !== 'number') { // Allow for a single string or an array of string flags
    quote_style = [].concat(quote_style);
    for (i = 0; i < quote_style.length; i++) {
      // Resolve string input to bitwise e.g. 'PATHINFO_EXTENSION' becomes 4
      if (OPTS[quote_style[i]] === 0) {
        noquotes = true;
      } else if (OPTS[quote_style[i]]) {
        optTemp = optTemp | OPTS[quote_style[i]];
      }
    }
    quote_style = optTemp;
  }
  if (quote_style & OPTS.ENT_HTML_QUOTE_SINGLE) {
    string = string.replace(/&#0*39;/g, "'"); // PHP doesn't currently escape if more than one 0, but it should
    // string = string.replace(/'|&#x0*27;/g, "'"); // This would also be useful here, but not a part of PHP
  }
  if (!noquotes) {
    string = string.replace(/"/g, '"');
  }
  // Put this in last place to avoid escape being double-decoded
  string = string.replace(/&/g, '&');

  return string;
}

exports.onMessage = function(channelSettings, globalSettings, parameters) {
  var urls = [], match;
  var regexp = /(https?:\/\/[^\s]+)/g;
  console.log(parameters.message);
  while(matches = regexp.exec(parameters.message)) {
    if((matches[0].indexOf("127.0.0.1") == -1) && (matches[0].indexOf("localhost") == -1))
      urls.push(matches[0]);
    else {
      parameters.bot.say(parameters.channel, "I'm not that stupid. :P");
      console.log("[http-urls.js] Someone tried to request localhost!");
    }
  }

  for(var urli in urls) {
    var url = urls[urli];
    var callback = function(response) {
      var data = '';
      function findTitle() {
        var pos = data.toLowerCase().indexOf("<title>") + "<title>".length;
        if(pos == -1) return;

        var title = data.slice(pos);
        pos = title.toLowerCase().indexOf("</title");
        if(pos == -1) return;

        title = title.slice(0, pos);
        var prepend = '';
        if(urls.length > 1)
          prepend = url.replace("https://", '').replace("http://", '').slice(0, 10) + "..." + url.slice(-6) + ': ';
        parameters.bot.say(parameters.channel, prepend + htmlspecialchars_decode(title.trim()));
      }
      response.on('data', function (chunk) {
        data += chunk;
        if(data.indexOf("</title") != -1) {
          response.destroy();
        }
      });
      response.on('end', findTitle);
    }

    if(url.indexOf("https") == 0)
      https.request(url, callback).end();
    else
      http.request(url, callback).end();
  }
};
<commit_msg>http-urls: fix de-escaping.<commit_after>/* Waterbot -- Module "http-urls"
 *   Spouts page title when someone posts a link.
 *
 * Copyright 2014 Augustin Cavalier (waddlesplash).
 * Licensed under the MIT license.
 */

var http = require("http");
var https = require("https");

function unHTMLify(string) {
  return string.trim()
    .replace(new RegExp("&", 'g'), "&")
    .replace(new RegExp("<", 'g'), "<")
    .replace(new RegExp(">", 'g'), ">")
    .replace(new RegExp(""", 'g'), '"')
    .replace(new RegExp("'", 'g'), "'")
    .replace(new RegExp("—", 'g'), "–")

    .replace(new RegExp("\r\n", 'g'), " ")
    .replace(new RegExp("\r", 'g'), " ")
    .replace(new RegExp("\n", 'g'), " ");
}

exports.onMessage = function(channelSettings, globalSettings, parameters) {
  var urls = [], match;
  var regexp = /(https?:\/\/[^\s]+)/g;
  console.log(parameters.message);
  while(matches = regexp.exec(parameters.message)) {
    if((matches[0].indexOf("127.0.0.1") == -1) && (matches[0].indexOf("localhost") == -1))
      urls.push(matches[0]);
    else {
      parameters.bot.say(parameters.channel, "I'm not that stupid. :P");
      console.log("[http-urls.js] Someone tried to request localhost!");
    }
  }

  for(var urli in urls) {
    var url = urls[urli];
    var callback = function(response) {
      var data = '';
      function findTitle() {
        var pos = data.toLowerCase().indexOf("<title>") + "<title>".length;
        if(pos == -1) return;

        var title = data.slice(pos);
        pos = title.toLowerCase().indexOf("</title");
        if(pos == -1) return;

        title = title.slice(0, pos);
        var prepend = '';
        if(urls.length > 1)
          prepend = url.replace("https://", '').replace("http://", '').slice(0, 10) + "..." + url.slice(-6) + ': ';
        parameters.bot.say(parameters.channel, prepend + unHTMLify(title));
      }
      response.on('data', function (chunk) {
        data += chunk;
        if(data.indexOf("</title") != -1) {
          response.destroy();
        }
      });
      response.on('end', findTitle);
    }

    if(url.indexOf("https") == 0)
      https.request(url, callback).end();
    else
      http.request(url, callback).end();
  }
};
<|endoftext|>


<commit_before>
//
// Standard module stuff
//

// module object
var l_module = {};

// a pool for all message handlers
var l_handlers = exports.handlers = {};
var l_checkers = exports.checkers = {};
var l_api = exports.api = {};

var l_name = 'SR.Module.reporting';

//
// module start/stop
//

/*
	config: {
		ip_port:	'string'		// target server's IP & port to connect
	}
*/

l_module.start = function (config, onDone) {

	// do not connect if this is a monitor server
	if (SR.Settings.SERVER_INFO.type === 'monitor') {
		LOG.warn('monitor server need not initialize connection to monitor', l_name);
		return UTIL.safeCall(onDone);
	}

	// set IP & port for monitor server
	var ip = (SR.Settings.IP_MONITOR instanceof Array ?
			  SR.Settings.IP_MONITOR[0] :
			  SR.Settings.IP_MONITOR);

	l_ip_port = config.ip_port ||
		{IP: ip,
		 port: SR.Settings.PORT_MONITOR + SR.Settings.PORT_INC_SOCKET};

	LOG.warn('monitor to connect:', l_name);
	LOG.warn(l_ip_port, l_name);

	l_connect(function () {

		// 1st time reporting
		l_report({
			type:		'ServerStatus',
			status:		{
				server: SR.Settings.SERVER_INFO,
				admin:  UTIL.userSettings('adminMail'),
				ports:  UTIL.getLocalPort()
			}
		});

		UTIL.safeCall(onDone);
	});
}

// stop / shutdown this module
l_module.stop = function (onDone) {

	// shutdown reporting
	l_report({
		type:		'ServerStatus',
		status:		{
			server: SR.Settings.SERVER_INFO,
			status: 'shutdown'
		}
	})

	UTIL.safeCall(onDone);
}

// prepare content of the periodic reporting
var onReport = function () {

	// TODO: should update changing status (such as login user count)
	// currently SERVER_INFO is pretty static
	var status = {
		//server: SR.Settings.SERVER_INFO,
		server: undefined
	}

	return status;
}

//
//	Remote Server Management (for Monitor)
//

// server status indexed by 'serverID', each status is indexed by 'type'
var l_status = {};

// mapping from connID to serverID
var l_conn2server = {};

// receiving status report and store it, indexed by: 1) serverID 2) type
/*
	status: {
		serverID:	'string'
		type:		'string',
		data:		'object'
	}
*/
l_handlers.SR_REPORT_STATUS = function (event) {

	// do not respond unless I'm a monitor
	if (SR.Settings.SERVER_INFO.type !== 'monitor') {
		event.done();
		return;
	}

	var status = event.data;
	//LOG.warn('status received:');
	//LOG.warn(status);

	if (l_status.hasOwnProperty(status.serverID) === false) {
		l_status[status.serverID] = {};
		l_conn2server[event.conn.connID] = status.serverID;
	}
	if (l_status[status.serverID].hasOwnProperty(status.type) === false) {
		l_status[status.serverID][status.type] = {};
	}

	//LOG.warn('before update:');
	//LOG.warn(l_status);

	// NOTE: we only update what's new, while keeping all the other existing fields
	l_status[status.serverID][status.type] = UTIL.mixin(l_status[status.serverID][status.type], status.data);

	//LOG.warn('after update:');
	//LOG.warn(l_status);

	LOG.sys('SR_REPORT_STATUS id: ' + status.serverID + ' after update:', l_name);
	LOG.sys(l_status[status.serverID][status.type], l_name);

	event.done('SR_REPORT_STATUS', {id: status.serverID});
}

//
// public API
//

// get log name for a given server
//exports.getLog = function ()

// get a list of servers based on server info
// info: 'string' ||
//		 {owner: 'string', project: 'string', name: 'string'}
exports.getStat = function (info, onDone) {
	var query_name = undefined;
	var query_id = undefined;

	if (typeof info === 'object') {
		query_name = (info.owner ? info.owner : '') +
			(info.project ? ('-' + info.project) : '') +
			(info.name ? ('-' + info.name) : '');
	}
	else if (typeof info === 'string')
		query_id = info;

	LOG.sys('query_name [' + query_name + '] query_id: [' + query_id + ']', l_name);

	var list = [];

	for (var id in l_status) {
		if (!l_status[id]) {
			LOG.error('no server info found for id: ' + id);
			continue;
		}

		var servers = l_status[id];
		for (var type in servers) {

			var server = servers[type].server;
			var server_name = server.owner + '-' + server.project + '-' + server.name;
			LOG.warn('[' + id + '] type: ' + type + ' server name: ' + server_name, l_name);

			if ((query_id && id === query_id) ||
				 server_name.startsWith(query_name)) {
				LOG.sys('match found: ' + server_name, l_name);
				list.push(server);
			}
		}

	}
	LOG.sys('returning ' + list.length + ' server info', l_name);

	UTIL.safeCall(onDone, list);
	return list;
}

//
// connection management
//

// when a server disconnects, remove related records
SR.Callback.onDisconnect(function (conn) {

	// check if connection belongs to a server in contact
	if (l_conn2server.hasOwnProperty(conn.connID) === false)
		return;

	var serverID = l_conn2server[conn.connID];

	LOG.warn('server [' + serverID + '] disconnected', l_name);
	delete l_status[serverID];

	// TODO: send out system-wide notify?
});

//
//	Connection Management (to Monitor)
//
const l_timeoutConnectRetry = 3000;
var l_connector = undefined;
var l_ip_port = undefined;
var l_onConnect = undefined;		// what to do after first connect

var l_config = {
	onConnect: function () {
		LOG.warn('Monitor Server connected', l_name);
	},
	onDisconnect: function () {
		LOG.warn('Monitor Server disconnected');
		// re-init connection
		LOG.warn('attempt to re-connect in: ' + l_timeoutConnectRetry + 'ms', l_name);
		setTimeout(l_connect, l_timeoutConnectRetry);
	}
};

// connect to remote server
var l_connect = function (onDone) {

	// store what to do after connected
	// NOTE: may be called repeatedly (if initial attempts fail or disconnect happens)
	if (typeof onDone === 'function')
		l_onConnect = onDone;

	if (l_ip_port === undefined) {
		LOG.warn('not init (or already disposed), cannot connect to server');
		return;
	}

	if (l_connector === undefined)
		l_connector = new SR.Connector(l_config);

    // establish connection
	LOG.warn('connecting to: ' + l_ip_port, l_name);
	l_connector.connect(l_ip_port, function (err, socket) {

		if (err) {
			// try-again later
			LOG.warn('attempt to re-connect in: ' + l_timeoutConnectRetry + 'ms', l_name);
			setTimeout(l_connect, l_timeoutConnectRetry);
			return;
		}

		LOG.warn('connection to: ' + socket.host + ':' + socket.port + ' established', l_name);

		UTIL.safeCall(l_onConnect);
	});
}

// send report to remote server
/* 	args: {
		target: 'string', 	// 'monitor', 'app', 'lobby'
		type: 	'string',
		status: 'object' || 'function',
		interval: 'number'
	}
*/
var l_report = function (args, onDone) {

	// check if we've init / connected already
	if (!l_connector) {
		var err = 'connection to monitor not yet established, cannot report';
		LOG.error(err, l_name);
		return UTIL.safeCall(onDone, err);
	}

	var serverID = SR.Settings.SERVER_INFO.id;

	var send_once = function (onSent) {
		var data = (typeof args.status === 'function' ? args.status() : args.status);

		// send a report (including status type & sender's serverID)
		// TODO: error handling?
		l_connector.send('SR_REPORT_STATUS', {
			serverID: serverID,
			type: args.type,
			data: data
		}, 'SR_REGISTER_STATUS', function (res) {
			UTIL.safeCall(onSent);
		});
	}

	// send once only
	send_once(function () {
		UTIL.safeCall(onDone, null);
	});
}

// add handlers
SR.Handler.add(exports);

// register this module
SR.Module.add('reporting', l_module);

<commit_msg>add: get PATH_LIB from monitor server<commit_after>
//
// Standard module stuff
//

// module object
var l_module = {};

// a pool for all message handlers
var l_handlers = exports.handlers = {};
var l_checkers = exports.checkers = {};
var l_api = exports.api = {};

var l_name = 'SR.Module.reporting';

//
// module start/stop
//

/*
	config: {
		ip_port:	'string'		// target server's IP & port to connect
	}
*/

l_module.start = function (config, onDone) {

	// do not connect if this is a monitor server
	if (SR.Settings.SERVER_INFO.type === 'monitor') {
		LOG.warn('monitor server need not initialize connection to monitor', l_name);
		return UTIL.safeCall(onDone);
	}

	// set IP & port for monitor server
	var ip = (SR.Settings.IP_MONITOR instanceof Array ?
			  SR.Settings.IP_MONITOR[0] :
			  SR.Settings.IP_MONITOR);

	l_ip_port = config.ip_port ||
		{IP: ip,
		 port: SR.Settings.PORT_MONITOR + SR.Settings.PORT_INC_SOCKET};

	LOG.warn('monitor to connect:', l_name);
	LOG.warn(l_ip_port, l_name);

	l_connect(function () {

		// 1st time reporting
		l_report({
			type:		'ServerStatus',
			status:		{
				server: SR.Settings.SERVER_INFO,
				admin:  UTIL.userSettings('adminMail'),
				ports:  UTIL.getLocalPort()
			}
		});

		// add monitor as a remote API endpoint
		SR.API.addRemote({name: 'monitor', host: {IP: ip, port: SR.Settings.PORT_MONITOR}});

		// get parameters from monitor, if available
		SR.API['monitor']('_SYS_PATH', {type: 'PATH_LIB'}, function (err, result) {
			if (!err) {
				LOG.warn('PATH_LIB: ' + result, l_name);
				SR.Settings.PATH_LIB = result;
			}
		});

		UTIL.safeCall(onDone);
	});
}

// stop / shutdown this module
l_module.stop = function (onDone) {

	// shutdown reporting
	l_report({
		type:		'ServerStatus',
		status:		{
			server: SR.Settings.SERVER_INFO,
			status: 'shutdown'
		}
	})

	UTIL.safeCall(onDone);
}

// prepare content of the periodic reporting
var onReport = function () {

	// TODO: should update changing status (such as login user count)
	// currently SERVER_INFO is pretty static
	var status = {
		//server: SR.Settings.SERVER_INFO,
		server: undefined
	}

	return status;
}

//
//	Remote Server Management (for Monitor)
//

// server status indexed by 'serverID', each status is indexed by 'type'
var l_status = {};

// mapping from connID to serverID
var l_conn2server = {};

// receiving status report and store it, indexed by: 1) serverID 2) type
/*
	status: {
		serverID:	'string'
		type:		'string',
		data:		'object'
	}
*/
l_handlers.SR_REPORT_STATUS = function (event) {

	// do not respond unless I'm a monitor
	if (SR.Settings.SERVER_INFO.type !== 'monitor') {
		event.done();
		return;
	}

	var status = event.data;
	//LOG.warn('status received:');
	//LOG.warn(status);

	if (l_status.hasOwnProperty(status.serverID) === false) {
		l_status[status.serverID] = {};
		l_conn2server[event.conn.connID] = status.serverID;
	}
	if (l_status[status.serverID].hasOwnProperty(status.type) === false) {
		l_status[status.serverID][status.type] = {};
	}

	//LOG.warn('before update:');
	//LOG.warn(l_status);

	// NOTE: we only update what's new, while keeping all the other existing fields
	l_status[status.serverID][status.type] = UTIL.mixin(l_status[status.serverID][status.type], status.data);

	//LOG.warn('after update:');
	//LOG.warn(l_status);

	LOG.sys('SR_REPORT_STATUS id: ' + status.serverID + ' after update:', l_name);
	LOG.sys(l_status[status.serverID][status.type], l_name);

	event.done('SR_REPORT_STATUS', {id: status.serverID});
}

//
// public API
//

// get log name for a given server
//exports.getLog = function ()

// get a list of servers based on server info
// info: 'string' ||
//		 {owner: 'string', project: 'string', name: 'string'}
exports.getStat = function (info, onDone) {
	var query_name = undefined;
	var query_id = undefined;

	if (typeof info === 'object') {
		query_name = (info.owner ? info.owner : '') +
			(info.project ? ('-' + info.project) : '') +
			(info.name ? ('-' + info.name) : '');
	}
	else if (typeof info === 'string')
		query_id = info;

	LOG.sys('query_name [' + query_name + '] query_id: [' + query_id + ']', l_name);

	var list = [];

	for (var id in l_status) {
		if (!l_status[id]) {
			LOG.error('no server info found for id: ' + id);
			continue;
		}

		var servers = l_status[id];
		for (var type in servers) {

			var server = servers[type].server;
			var server_name = server.owner + '-' + server.project + '-' + server.name;
			LOG.warn('[' + id + '] type: ' + type + ' server name: ' + server_name, l_name);

			if ((query_id && id === query_id) ||
				 server_name.startsWith(query_name)) {
				LOG.sys('match found: ' + server_name, l_name);
				list.push(server);
			}
		}

	}
	LOG.sys('returning ' + list.length + ' server info', l_name);

	UTIL.safeCall(onDone, list);
	return list;
}

//
// connection management
//

// when a server disconnects, remove related records
SR.Callback.onDisconnect(function (conn) {

	// check if connection belongs to a server in contact
	if (l_conn2server.hasOwnProperty(conn.connID) === false)
		return;

	var serverID = l_conn2server[conn.connID];

	LOG.warn('server [' + serverID + '] disconnected', l_name);
	delete l_status[serverID];

	// TODO: send out system-wide notify?
});

//
//	Connection Management (to Monitor)
//
const l_timeoutConnectRetry = 3000;
var l_connector = undefined;
var l_ip_port = undefined;
var l_onConnect = undefined;		// what to do after first connect

var l_config = {
	onConnect: function () {
		LOG.warn('Monitor Server connected', l_name);
	},
	onDisconnect: function () {
		LOG.warn('Monitor Server disconnected');
		// re-init connection
		LOG.warn('attempt to re-connect in: ' + l_timeoutConnectRetry + 'ms', l_name);
		setTimeout(l_connect, l_timeoutConnectRetry);
	}
};

// connect to remote server
var l_connect = function (onDone) {

	// store what to do after connected
	// NOTE: may be called repeatedly (if initial attempts fail or disconnect happens)
	if (typeof onDone === 'function')
		l_onConnect = onDone;

	if (l_ip_port === undefined) {
		LOG.warn('not init (or already disposed), cannot connect to server');
		return;
	}

	if (l_connector === undefined)
		l_connector = new SR.Connector(l_config);

    // establish connection
	LOG.warn('connecting to: ' + l_ip_port, l_name);
	l_connector.connect(l_ip_port, function (err, socket) {

		if (err) {
			// try-again later
			LOG.warn('attempt to re-connect in: ' + l_timeoutConnectRetry + 'ms', l_name);
			setTimeout(l_connect, l_timeoutConnectRetry);
			return;
		}

		LOG.warn('connection to: ' + socket.host + ':' + socket.port + ' established', l_name);

		UTIL.safeCall(l_onConnect);
	});
}

// send report to remote server
/* 	args: {
		target: 'string', 	// 'monitor', 'app', 'lobby'
		type: 	'string',
		status: 'object' || 'function',
		interval: 'number'
	}
*/
var l_report = function (args, onDone) {

	// check if we've init / connected already
	if (!l_connector) {
		var err = 'connection to monitor not yet established, cannot report';
		LOG.error(err, l_name);
		return UTIL.safeCall(onDone, err);
	}

	var serverID = SR.Settings.SERVER_INFO.id;

	var send_once = function (onSent) {
		var data = (typeof args.status === 'function' ? args.status() : args.status);

		// send a report (including status type & sender's serverID)
		// TODO: error handling?
		l_connector.send('SR_REPORT_STATUS', {
			serverID: serverID,
			type: args.type,
			data: data
		}, 'SR_REGISTER_STATUS', function (res) {
			UTIL.safeCall(onSent);
		});
	}

	// send once only
	send_once(function () {
		UTIL.safeCall(onDone, null);
	});
}

// add handlers
SR.Handler.add(exports);

// register this module
SR.Module.add('reporting', l_module);
<|endoftext|>


<commit_before>var HTTP = require('q-io/http');
var cheerio = require('cheerio');
var humanize = require('humanize');
var moment = require('moment-timezone');

var abbrHash = require('./abbrHash.json');

var bold = function(str) {
  return '\u0002' + str + '\u000F';
};

var int = function(str) {
  return parseInt(str, 10);
};

var toAbbr = function(team) {
  return Object.keys(abbrHash).filter(function(key) {
    return abbrHash[key].toLowerCase() === team.toLowerCase();
  })[0];
};

module.exports = {
  "mls": function(data, user, target) {
    var date, ds = data ? data.split(" ") : [];
    if(ds.length > 0) {
      // requested a date!
      date = ds[0];
      if(!date.match(/^[\d]{8}$/)) {
        this.client.say(target, "The date specified is incorrectly formatted. Your request should look like ,mls [YYYYMMDD]");
        return user.nickname + " asked for the MLS schedule for '" + data + "', but the date was badly formatted.";
      }
      date = moment(date, "YYYYMMDD");
    } else {
      date = moment();
    }
    var url = 'http://www.espnfc.com/scores/_/date/' + date.format('YYYYMMDD') + '/league/usa.1';
    HTTP.read(url).then(function(b) {
      var $ = cheerio.load(b.toString());

      var rows = $('.scores-top .teams');
      if(rows.length === 0) {
        return 'No games found.';
      }
      return Array.prototype.map.call(rows, function(row) {
        var dateStr = $(row.children[0]).text().trim();
        if(!dateStr.match(/(FT|HT)/)) {
          if(dateStr.split(', ').length === 1) {
            dateStr = moment().format('MMMM D') + ', ' + dateStr;
          }
          dateStr = moment(dateStr, 'MMMM D, HH:mm').tz('America/New_York').format('h:mm A') + ' ET';
        }
        var scores = $(row.children[2]).text().replace(/[^0-9\-]/g,'').split('-').map(int);
        if(scores.length > 1) {
          var tm1 = $(row.children[1]).text().trim() + ' ' + scores[0];
          var tm2 = $(row.children[3]).text().trim() + ' ' + scores[1];
          if(scores[0] < scores[1]) {
            tm2 = bold(tm2);
          } else if(scores[0] > scores[1]) {
            tm1 = bold(tm1);
          }
          return tm1 + ' ' + tm2 + ' (' + dateStr + ')';
        } else {
          return $(row.children[1]).text().trim() + ' vs ' + $(row.children[3]).text().trim() + ' (' + dateStr + ')';
        }
      }).join("\n");
    }).then(function(sched) {
      this.client.say(target, sched);
      return user.nickname + ' asked for the MLS schedule.';
    }.bind(this));
  },
  "nhl": function(data, user, target) {
    var date;
    if (data === 'tomorrow') {
      date = moment().tz('America/New_York').add(moment.duration(1, 'day'));
    } else {
      date = moment().tz('America/New_York');
    }
    HTTP.read('http://live.nhle.com/GameData/RegularSeasonScoreboardv3.jsonp').then(function(b) {
      var games = JSON.parse(b.toString('utf8').replace('loadScoreboard(','').slice(0,-1)).games;
      return games.filter(function(game) {
        var d = game.ts.split(' ').pop();
        if(d.toLowerCase() === 'today') {
          d = moment().tz('America/New_York');
        } else {
          d = moment(d, 'M/D', 'America/New_York');
        }
        return d.format('YYYYMMDD') === date.format('YYYYMMDD');
      }).map(function(game) {
        return toAbbr(game.atv) + " @ " + toAbbr(game.htv) + ' (' + game.bs + ' ET) [' + game.ustv.split(',').concat(game.catv.split(',')).join(', ') + ']';
      }).join("\n");
    }).then(function(sched) {
      this.client.say(target, sched);
      return user.nickname + ' asked for the NHL schedule.';
    }.bind(this));
  }
};<commit_msg>Add NBA schedules<commit_after>var HTTP = require('q-io/http');
var cheerio = require('cheerio');
var humanize = require('humanize');
var moment = require('moment-timezone');

var abbrHash = require('./abbrHash.json');

var bold = function(str) {
  return '\u0002' + str + '\u000F';
};

var int = function(str) {
  return parseInt(str, 10);
};

var toAbbr = function(team) {
  return Object.keys(abbrHash).filter(function(key) {
    return abbrHash[key].toLowerCase() === team.toLowerCase();
  })[0];
};

var zipHash = function(keys, values) {
  var obj = {};
  keys.forEach(function(key, i) {
    obj[key] = values[i];
  });
  return obj;
};

module.exports = {
  "mls": function(data, user, target) {
    var date, ds = data ? data.split(" ") : [];
    if(ds.length > 0) {
      // requested a date!
      date = ds[0];
      if(!date.match(/^[\d]{8}$/)) {
        this.client.say(target, "The date specified is incorrectly formatted. Your request should look like ,mls [YYYYMMDD]");
        return user.nickname + " asked for the MLS schedule for '" + data + "', but the date was badly formatted.";
      }
      date = moment(date, "YYYYMMDD");
    } else {
      date = moment();
    }
    var url = 'http://www.espnfc.com/scores/_/date/' + date.format('YYYYMMDD') + '/league/usa.1';
    HTTP.read(url).then(function(b) {
      var $ = cheerio.load(b.toString());

      var rows = $('.scores-top .teams');
      if(rows.length === 0) {
        return 'No games found.';
      }
      return Array.prototype.map.call(rows, function(row) {
        var dateStr = $(row.children[0]).text().trim();
        if(!dateStr.match(/(FT|HT)/)) {
          if(dateStr.split(', ').length === 1) {
            dateStr = moment().format('MMMM D') + ', ' + dateStr;
          }
          dateStr = moment(dateStr, 'MMMM D, HH:mm').tz('America/New_York').format('h:mm A') + ' ET';
        }
        var scores = $(row.children[2]).text().replace(/[^0-9\-]/g,'').split('-').map(int);
        if(scores.length > 1) {
          var tm1 = $(row.children[1]).text().trim() + ' ' + scores[0];
          var tm2 = $(row.children[3]).text().trim() + ' ' + scores[1];
          if(scores[0] < scores[1]) {
            tm2 = bold(tm2);
          } else if(scores[0] > scores[1]) {
            tm1 = bold(tm1);
          }
          return tm1 + ' ' + tm2 + ' (' + dateStr + ')';
        } else {
          return $(row.children[1]).text().trim() + ' vs ' + $(row.children[3]).text().trim() + ' (' + dateStr + ')';
        }
      }).join("\n");
    }).then(function(sched) {
      this.client.say(target, sched);
      return user.nickname + ' asked for the MLS schedule.';
    }.bind(this));
  },
  "nhl": function(data, user, target) {
    var date;
    if (data === 'tomorrow') {
      date = moment().tz('America/New_York').add(moment.duration(1, 'day'));
    } else {
      date = moment().tz('America/New_York');
    }
    HTTP.read('http://live.nhle.com/GameData/RegularSeasonScoreboardv3.jsonp').then(function(b) {
      var games = JSON.parse(b.toString('utf8').replace('loadScoreboard(','').slice(0,-1)).games;
      return games.filter(function(game) {
        var d = game.ts.split(' ').pop();
        if(d.toLowerCase() === 'today') {
          d = moment().tz('America/New_York');
        } else {
          d = moment(d, 'M/D', 'America/New_York');
        }
        return d.format('YYYYMMDD') === date.format('YYYYMMDD');
      }).map(function(game) {
        return toAbbr(game.atv) + " @ " + toAbbr(game.htv) + ' (' + game.bs + ' ET) [' + game.ustv.split(',').concat(game.catv.split(',')).join(', ') + ']';
      }).join("\n");
    }).then(function(sched) {
      this.client.say(target, sched);
      return user.nickname + ' asked for the NHL schedule.';
    }.bind(this));
  },
  "nba": function(data, user, target) {
    var date;
    if (data === 'tomorrow') {
      date = moment().tz('America/New_York').add(moment.duration(1, 'day'));
    } else if(data.match(/^[\d]{8}$/)) {
      date = moment(data, 'YYYYMMDD', 'America/New_York');
    } else {
      date = moment().tz('America/New_York');
    }
    HTTP.read('http://stats.nba.com/stats/scoreboard/?LeagueID=00&DayOffset=0&gameDate=' + date.format('MM/DD/YYYY')).then(function(b) {
      var json = JSON.parse(b.toString('utf8'));
      // construct team id hash
      var teams = {};
      var lines = json.resultSets[1];
      lines.rowSet.forEach(function(row) {
        var hash = zipHash(lines.headers, row);
        teams[hash.TEAM_ID] = hash;
      });
      // actually load games
      var games = json.resultSets[0];
      return games.rowSet.map(function(row) {
        var hash = zipHash(games.headers, row);
        var homeTeam = teams[hash.HOME_TEAM_ID];
        var awayTeam = teams[hash.VISITOR_TEAM_ID];
        var futureTime = moment(hash.GAME_STATUS_TEXT, 'h:mm a \\E\\T', 'America/New_York', true);
        var dateStr, tm1, tm2;
        if(futureTime.isValid()) {
          dateStr = futureTime.format('h:mm A') + ' ET';
          tm1 = awayTeam.TEAM_ABBREVIATION;
          tm2 = homeTeam.TEAM_ABBREVIATION;
        } else {
          dateStr = hash.GAME_STATUS_TEXT;
          var s1 = awayTeam.PTS, s2 = homeTeam.PTS;
          tm1 = awayTeam.TEAM_ABBREVIATION + ' ' + s1;
          tm2 = homeTeam.TEAM_ABBREVIATION + ' ' + s2;
          if(s1 < s2) {
            tm2 = bold(tm2);
          } else if(s1 > s2) {
            tm1 = bold(tm1);
          }
        }
        return tm1 + " @ " + tm2 + ' (' + dateStr + ') [' + hash.NATL_TV_BROADCASTER_ABBREVIATION + ']';
      }).join("\n");
    }).then(function(sched) {
      this.client.say(target, sched);
      return user.nickname + ' asked for the NBA schedule.';
    }.bind(this));
  },
};<|endoftext|>


<commit_before>var ldap = require('ldapjs');
var fs = require('fs');
var _ = require('underscore');

var Employee = require('../models/employee');

var config = JSON.parse(fs.readFileSync('config.json', 'utf8'))
var ldap_server = config.suse_ldap_server
var searchBase = 'ou=people,dc=suse,dc=de';

var client = ldap.createClient({
  url: ldap_server,
  reconnect: true
});

client.on('error', function(err) {
  console.error('ERROR:' + ' No connection to LDAP server');
  console.error(err)
  return false;
});

exports.findAll = function (request, response) {
  var search = request.query.search

  if(search) {
    exports._search(request, response)
  } else {
    exports._all(request, response)
  }
};

exports._all = function (request, response) {
  var opts = {
    filter:'(!(ou=people))',
    scope: 'sub'
  };

  exports._query(opts, request, response)
};

exports._search = function (request, response) {
  var search = request.query.search
  var opts = {
    scope: 'sub',
    filter: '(|(givenName=*' + search + '*)(sn=*' + search + '*)(uid=*' + search + '*)(mail=*' + search + '*)(cn=*' + search + '*))'
  };

  exports._query(opts, request, response)
};

exports._query = function (opts, request, response) {
  var page = parseInt(request.query.page) || 0
  var limit = parseInt(request.query.limit) || 20
  var start = page * limit;
  var end = start + limit;

  client.search(searchBase, opts, function(req, res) {
    var staff = [];

    res.on('searchEntry', function (entry) {
      staff.push(entry.object)
    });

    res.on('end', function(result) {
      var employees = []
      staff = _.sortBy(staff, 'cn').slice(start, end);
      for(i in staff) { employees.push(new Employee(staff[i])) }
      response.send(employees);
    });
  })

}

exports.findById = function (id, callback) {
  var id = id.startsWith('0') ? id.substring(1) : id;
  var opts = {
    filter: "(employeeNumber=" + id + ")",
    scope: 'one'
  };

  client.search(searchBase, opts, function(req, res) {
    var employee = null;

    res.on('searchEntry', function (entry) {
      employee = entry.object
    });

    res.on('end', function(result) {
      callback(new Employee(employee));
    });
  })
}

exports.newcomers = function(request, response) {
  var opts = {
    scope: 'sub',
    filter:'(&(!(ou=people))(employeeNumber=*))'
  }

  client.search(searchBase, opts, function(req, res, next) {
    var staff = [];

    res.on('searchEntry', function (entry) {
      staff.push(entry.object)
    });

    res.on('end', function(result) {
      var employees = []

      staff = _.sortBy(staff, 'suseid').reverse().slice(0,50)

      for(i in staff) {
        employees.push(new Employee(staff[i]))
      }

      response.send(employees);
    });
  })
};

exports.count = function (request, response) {
  client.search(searchBase, {attributes: 'id', scope: 'sub'}, function (req, res, next) {
    var count = 0;

    res.on('searchEntry', function (entry) {
      count++;
    });

    res.on('end', function (result) {
      response.send({count: count});
    });
  })
};

<commit_msg>entries with employee id only<commit_after>var ldap = require('ldapjs');
var fs = require('fs');
var _ = require('underscore');

var Employee = require('../models/employee');

var config = JSON.parse(fs.readFileSync('config.json', 'utf8'))
var ldap_server = config.suse_ldap_server
var searchBase = 'ou=people,dc=suse,dc=de';

var client = ldap.createClient({
  url: ldap_server,
  reconnect: true
});

client.on('error', function(err) {
  console.error('ERROR:' + ' No connection to LDAP server');
  console.error(err)
  return false;
});

exports.findAll = function (request, response) {
  var search = request.query.search

  if(search) {
    exports._search(request, response)
  } else {
    exports._all(request, response)
  }
};

exports._all = function (request, response) {
  var opts = {
    filter:'(&(!(ou=people))(employeeNumber=*))',
    scope: 'sub'
  };

  exports._query(opts, request, response)
};

exports._search = function (request, response) {
  var search = request.query.search
  var opts = {
    scope: 'sub',
    filter: '(|(givenName=*' + search + '*)(sn=*' + search + '*)(uid=*' + search + '*)(mail=*' + search + '*)(cn=*' + search + '*))'
  };

  exports._query(opts, request, response)
};

exports._query = function (opts, request, response) {
  var page = parseInt(request.query.page) || 0
  var limit = parseInt(request.query.limit) || 20
  var start = page * limit;
  var end = start + limit;

  client.search(searchBase, opts, function(req, res) {
    var staff = [];

    res.on('searchEntry', function (entry) {
      staff.push(entry.object)
    });

    res.on('end', function(result) {
      var employees = []
      staff = _.sortBy(staff, 'cn').slice(start, end);
      for(i in staff) { employees.push(new Employee(staff[i])) }
      response.send(employees);
    });
  })

}

exports.findById = function (id, callback) {
  var id = id.startsWith('0') ? id.substring(1) : id;
  var opts = {
    filter: "(employeeNumber=" + id + ")",
    scope: 'one'
  };

  client.search(searchBase, opts, function(req, res) {
    var employee = null;

    res.on('searchEntry', function (entry) {
      employee = entry.object
    });

    res.on('end', function(result) {
      callback(new Employee(employee));
    });
  })
}

exports.newcomers = function(request, response) {
  var opts = {
    scope: 'sub',
    filter:'(&(!(ou=people))(employeeNumber=*))'
  }

  client.search(searchBase, opts, function(req, res, next) {
    var staff = [];

    res.on('searchEntry', function (entry) {
      staff.push(entry.object)
    });

    res.on('end', function(result) {
      var employees = []

      staff = _.sortBy(staff, 'suseid').reverse().slice(0,50)

      for(i in staff) {
        employees.push(new Employee(staff[i]))
      }

      response.send(employees);
    });
  })
};

exports.count = function (request, response) {
  client.search(searchBase, {attributes: 'id', scope: 'sub'}, function (req, res, next) {
    var count = 0;

    res.on('searchEntry', function (entry) {
      count++;
    });

    res.on('end', function (result) {
      response.send({count: count});
    });
  })
};

<|endoftext|>


<commit_before>// ==========================================================================
// Project:   The M-Project - Mobile HTML5 Application Framework
// Copyright: 2010 M-Way Solutions GmbH. All rights reserved.
// Creator:   Dominik
// Date:      02.11.2010
// License:   Dual licensed under the MIT or GPL Version 2 licenses.
//            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
//            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
// ==========================================================================

m_require('../core/foundation/view.js');

/**
 * @class
 *
 * The root object for ButtonViews.
 *
 */
M.ButtonView = M.View.extend({

    /**
     * The type of this object.
     *
     * @property {String}
     */
    type: 'M.ButtonView',

    useOnClick: NO,

    /**
     * Mapping to value attribute.
     * text property is mixed in when extended.
     */
    //value: this.text,

    /**
     * Renders a button as an input tag. Input is automatically converted by jQuery mobile.
     */
    render: function() {
        //var html = '<input type="button" id="' + this.id + '" value="' + this.value + '" ' + this.style() + ' />';
        var html = '' + this.value + '';
        if(this.renderToDOM) {
            document.write(html);
        } else {
            return html;
        }
    },

    /**
     * Updates the value of the button with DOM access by jQuery.
     */
    renderUpdate: function() {
        $('#' + this.id).attr('value', this.value);
        this.applyTheme();
    },

    /**
     * Triggers rendering engine, e.g. jQuery mobile, to style the button.
     */
    applyTheme: function() {
        $('#' + this.id).button();
    },

    /**
     * Applies some style-attributes to the label.
     */
    style: function() {
        var html = '';
        if(this.isInline) {
            html += 'data-inline="true"';
        }
        if(this.icon) {
            html += 'data-icon="' + this.icon + '"';
        }
        return html;
    },

    /**
     * Applies events by using the onclick property if set.
     */
    event: function() {
        var html = '';
        if(this.useOnClick) {
            html += 'onclick="M.EventDispatcher.simulateEvent(\'click\', \'' + this.id + '\', null, \'' + this.modelId + '\')"';
        }
        return html;
    }

});

<commit_msg>Removed custom onClick events, since this is now handled directly by the event dispatcher<commit_after>// ==========================================================================
// Project:   The M-Project - Mobile HTML5 Application Framework
// Copyright: 2010 M-Way Solutions GmbH. All rights reserved.
// Creator:   Dominik
// Date:      02.11.2010
// License:   Dual licensed under the MIT or GPL Version 2 licenses.
//            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
//            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
// ==========================================================================

m_require('../core/foundation/view.js');

/**
 * @class
 *
 * The root object for ButtonViews.
 *
 */
M.ButtonView = M.View.extend({

    /**
     * The type of this object.
     *
     * @property {String}
     */
    type: 'M.ButtonView',

    useOnClick: NO,

    /**
     * Mapping to value attribute.
     * text property is mixed in when extended.
     */
    //value: this.text,

    /**
     * Renders a button as an input tag. Input is automatically converted by jQuery mobile.
     */
    render: function() {
        var html = '' + this.value + '';
        if(this.renderToDOM) {
            document.write(html);
            this.applyTheme();
        } else {
            return html;
        }
    },

    /**
     * Updates the value of the button with DOM access by jQuery.
     */
    renderUpdate: function() {
        $('#' + this.id).attr('value', this.value);
        this.applyTheme();
    },

    /**
     * Triggers rendering engine, e.g. jQuery mobile, to style the button.
     */
    applyTheme: function() {
        $('#' + this.id).button();
    },

    /**
     * Applies some style-attributes to the label.
     */
    style: function() {
        var html = '';
        if(this.isInline) {
            html += 'data-inline="true"';
        }
        if(this.icon) {
            html += 'data-icon="' + this.icon + '"';
        }
        return html;
    }

});

<|endoftext|>


<commit_before>// ==========================================================================
// Project:   The M-Project - Mobile HTML5 Application Framework
// Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved.
// Creator:   Dominik
// Date:      02.11.2010
// License:   Dual licensed under the MIT or GPL Version 2 licenses.
//            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
//            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
// ==========================================================================

m_require('ui/label.js');

/**
 * @class
 *
 * This defines the prototype for any button view. A button is a view element that is
 * typically used for triggering an action, e.g. switching to another page, firing a
 * request or opening a dialog.
 *
 * @extends M.View
 */
M.ButtonView = M.View.extend(
/** @scope M.ButtonView.prototype */ {

    /**
     * The type of this object.
     *
     * @type String
     */
    type: 'M.ButtonView',

    /**
     * Determines whether this button is active or not.
     *
     * Note: This property is only used if the button is part of a button group (M.ButtonGroupView).
     *
     * @type Boolean
     */
    isActive: NO,

    /**
     * Determines whether to display the button ony with an icon but no text or not.
     *
     * @type Boolean
     */
    isIconOnly: NO,

    /**
     * This property can be used to specify a certain hyperlink type for this button. It only
     * works in combination with the hyperlinkTarget property.
     *
     * @type String
     */
    hyperlinkType: null,

    /**
     * This property can be used to specify a hyperlink target for this button. It only
     * works in combination with the hyperlinkType property.
     *
     * @type String
     */
    hyperlinkTarget: null,

    /**
     * This property specifies the recommended events for this type of view.
     *
     * @type Array
     */
    recommendedEvents: ['click'],

    /**
     * Renders a button as an input tag. Input is automatically converted by jQuery mobile.
     *
     * @private
     * @returns {String} The button view's html representation.
     */
    render: function() {
        this.computeValue();
        this.html += '<a data-role="button" id="' + this.id + '"' + this.style() + ' ';

        if(this.hyperlinkTarget && this.hyperlinkType) {
            switch (this.hyperlinkType) {
                case M.HYPERLINK_EMAIL:
                    this.html += 'rel="external" href="mailto:' + this.hyperlinkTarget + '"';
                    break;
                case M.HYPERLINK_WEBSITE:
                    this.html += 'rel="external" target="_blank" href="' + this.hyperlinkTarget + '"';
                    break;
                case M.HYPERLINK_PHONE:
                    this.html += 'rel="external" href="tel:' + this.hyperlinkTarget + '"';
                    break;
            }
        } else {
            this.html += 'href="#"';
        }

        this.html += '>' + this.value + '';

        return this.html;
    },

    /**
     * Updates the value of the button with DOM access by jQuery.
     *
     * @private
     */
    renderUpdate: function() {
        if(this.applyTheme){
            this.theme();
            $('#' + this.id).parent().find('.ui-btn-text').text(this.value);
        }else{
            $('#' + this.id).find('.ui-btn-text').text(this.value);
        }
    },

    /**
     * Sets the button's value and calls renderUpdate() to make the value update visible.
     *
     * @param {String} value The button's new value.
     */
    setValue: function(value) {
        this.value = value;
        this.renderUpdate();
    },

    /**
     * Triggers the rendering engine, jQuery mobile, to style the button.
     *
     * @private
     */
    theme: function() {
        $('#' + this.id).button();
    },

    /**
     * Applies some style-attributes to the button.
     *
     * @private
     * @returns {String} The button's styling as html representation.
     */
    style: function() {
        var html = '';
        if(this.isInline) {
            html += ' data-inline="true"';
        }
        if(this.icon) {
            html += ' data-icon="' + this.icon + '"';
        }
        if(this.cssClass) {
            html += ' data-theme="' + this.cssClass + '"';
        }
        if(this.isIconOnly) {
            html += ' data-iconpos="notext"';
        }
        if(this.cssStyle) {
            html += 'style="' + this.cssStyle + '"';
        }
        return html;
    }

});<commit_msg>added TAP to recommended events<commit_after>// ==========================================================================
// Project:   The M-Project - Mobile HTML5 Application Framework
// Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved.
// Creator:   Dominik
// Date:      02.11.2010
// License:   Dual licensed under the MIT or GPL Version 2 licenses.
//            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
//            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
// ==========================================================================

m_require('ui/label.js');

/**
 * @class
 *
 * This defines the prototype for any button view. A button is a view element that is
 * typically used for triggering an action, e.g. switching to another page, firing a
 * request or opening a dialog.
 *
 * @extends M.View
 */
M.ButtonView = M.View.extend(
/** @scope M.ButtonView.prototype */ {

    /**
     * The type of this object.
     *
     * @type String
     */
    type: 'M.ButtonView',

    /**
     * Determines whether this button is active or not.
     *
     * Note: This property is only used if the button is part of a button group (M.ButtonGroupView).
     *
     * @type Boolean
     */
    isActive: NO,

    /**
     * Determines whether to display the button ony with an icon but no text or not.
     *
     * @type Boolean
     */
    isIconOnly: NO,

    /**
     * This property can be used to specify a certain hyperlink type for this button. It only
     * works in combination with the hyperlinkTarget property.
     *
     * @type String
     */
    hyperlinkType: null,

    /**
     * This property can be used to specify a hyperlink target for this button. It only
     * works in combination with the hyperlinkType property.
     *
     * @type String
     */
    hyperlinkTarget: null,

    /**
     * This property specifies the recommended events for this type of view.
     *
     * @type Array
     */
    recommendedEvents: ['click', 'tap'],

    /**
     * Renders a button as an input tag. Input is automatically converted by jQuery mobile.
     *
     * @private
     * @returns {String} The button view's html representation.
     */
    render: function() {
        this.computeValue();
        this.html += '<a data-role="button" id="' + this.id + '"' + this.style() + ' ';

        if(this.hyperlinkTarget && this.hyperlinkType) {
            switch (this.hyperlinkType) {
                case M.HYPERLINK_EMAIL:
                    this.html += 'rel="external" href="mailto:' + this.hyperlinkTarget + '"';
                    break;
                case M.HYPERLINK_WEBSITE:
                    this.html += 'rel="external" target="_blank" href="' + this.hyperlinkTarget + '"';
                    break;
                case M.HYPERLINK_PHONE:
                    this.html += 'rel="external" href="tel:' + this.hyperlinkTarget + '"';
                    break;
            }
        } else {
            this.html += 'href="#"';
        }

        this.html += '>' + this.value + '';

        return this.html;
    },

    /**
     * Updates the value of the button with DOM access by jQuery.
     *
     * @private
     */
    renderUpdate: function() {
        if(this.applyTheme){
            this.theme();
            $('#' + this.id).parent().find('.ui-btn-text').text(this.value);
        }else{
            $('#' + this.id).find('.ui-btn-text').text(this.value);
        }
    },

    /**
     * Sets the button's value and calls renderUpdate() to make the value update visible.
     *
     * @param {String} value The button's new value.
     */
    setValue: function(value) {
        this.value = value;
        this.renderUpdate();
    },

    /**
     * Triggers the rendering engine, jQuery mobile, to style the button.
     *
     * @private
     */
    theme: function() {
        $('#' + this.id).button();
    },

    /**
     * Applies some style-attributes to the button.
     *
     * @private
     * @returns {String} The button's styling as html representation.
     */
    style: function() {
        var html = '';
        if(this.isInline) {
            html += ' data-inline="true"';
        }
        if(this.icon) {
            html += ' data-icon="' + this.icon + '"';
        }
        if(this.cssClass) {
            html += ' data-theme="' + this.cssClass + '"';
        }
        if(this.isIconOnly) {
            html += ' data-iconpos="notext"';
        }
        if(this.cssStyle) {
            html += 'style="' + this.cssStyle + '"';
        }
        return html;
    }

});<|endoftext|>