<commit_before>import Parchment from 'parchment';
import clone from 'clone';
import equal from 'deep-equal';
import Emitter from './emitter';
import logger from './logger';

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

class Range {
  constructor(index, length = 0) {
    this.index = index;
    this.length = length;
  }
}

class Selection {
  constructor(scroll, emitter) {
    this.emitter = emitter;
    this.scroll = scroll;
    this.composing = false;
    this.root = this.scroll.domNode;
    this.root.addEventListener('compositionstart', () => {
      this.composing = true;
    });
    this.root.addEventListener('compositionend', () => {
      this.composing = false;
    });
    this.cursor = Parchment.create('cursor', this);
    // savedRange is last non-null range
    this.lastRange = this.savedRange = new Range(0, 0);
    ['keyup', 'mouseup', 'mouseleave', 'touchend', 'touchleave', 'focus', 'blur'].forEach((eventName) => {
      this.root.addEventListener(eventName, () => {
        // When range used to be a selection and user click within the selection,
        // the range now being a cursor has not updated yet without setTimeout
        setTimeout(this.update.bind(this, Emitter.sources.USER), 100);
      });
    });
    this.emitter.on(Emitter.events.EDITOR_CHANGE, (type, delta) => {
      if (type === Emitter.events.TEXT_CHANGE && delta.length() > 0) {
        this.update(Emitter.sources.SILENT);
      }
    });
    this.emitter.on(Emitter.events.SCROLL_BEFORE_UPDATE, () => {
      if (!this.hasFocus()) return;
      let native = this.getNativeRange();
      if (native == null) return;
      if (native.start.node === this.cursor.textNode) return;  // cursor.restore() will handle
      // TODO unclear if this has negative side effects
      this.emitter.once(Emitter.events.SCROLL_UPDATE, () => {
        try {
          this.setNativeRange(native.start.node, native.start.offset, native.end.node, native.end.offset);
        } catch (ignored) {}
      });
    });
    this.update(Emitter.sources.SILENT);
  }

  focus() {
    if (this.hasFocus()) return;
    this.root.focus();
    this.setRange(this.savedRange);
  }

  format(format, value) {
    if (this.scroll.whitelist != null && !this.scroll.whitelist[format]) return;
    this.scroll.update();
    let nativeRange = this.getNativeRange();
    if (nativeRange == null || !nativeRange.native.collapsed || Parchment.query(format, Parchment.Scope.BLOCK)) return;
    if (nativeRange.start.node !== this.cursor.textNode) {
      let blot = Parchment.find(nativeRange.start.node, false);
      if (blot == null) return;
      // TODO Give blot ability to not split
      if (blot instanceof Parchment.Leaf) {
        let after = blot.split(nativeRange.start.offset);
        blot.parent.insertBefore(this.cursor, after);
      } else {
        blot.insertBefore(this.cursor, nativeRange.start.node);  // Should never happen
      }
      this.cursor.attach();
    }
    this.cursor.format(format, value);
    this.scroll.optimize();
    this.setNativeRange(this.cursor.textNode, this.cursor.textNode.data.length);
    this.update();
  }

  getBounds(index, length = 0) {
    let scrollLength = this.scroll.length();
    index = Math.min(index, scrollLength - 1);
    length = Math.min(index + length, scrollLength - 1) - index;
    let node, [leaf, offset] = this.scroll.leaf(index);
    if (leaf == null) return null;
    [node, offset] = leaf.position(offset, true);
    let range = document.createRange();
    if (length > 0) {
      range.setStart(node, offset);
      [leaf, offset] = this.scroll.leaf(index + length);
      if (leaf == null) return null;
      [node, offset] = leaf.position(offset, true);
      range.setEnd(node, offset);
      return range.getBoundingClientRect();
    } else {
      let side = 'left';
      let rect;
      if (node instanceof Text) {
        if (offset < node.data.length) {
          range.setStart(node, offset);
          range.setEnd(node, offset + 1);
        } else {
          range.setStart(node, offset - 1);
          range.setEnd(node, offset);
          side = 'right';
        }
        rect = range.getBoundingClientRect();
      } else {
        rect = leaf.domNode.getBoundingClientRect();
        if (offset > 0) side = 'right';
      }
      return {
        bottom: rect.top + rect.height,
        height: rect.height,
        left: rect[side],
        right: rect[side],
        top: rect.top,
        width: 0
      };
    }
  }

  getNativeRange() {
    let selection = document.getSelection();
    if (selection == null || selection.rangeCount <= 0) return null;
    let nativeRange = selection.getRangeAt(0);
    if (nativeRange == null) return null;
    let range = this.normalizeNative(nativeRange);
    debug.info('getNativeRange', range);
    return range;
  }

  getRange() {
    let normalized = this.getNativeRange();
    if (normalized == null) return [null, null];
    let range = this.normalizedToRange(normalized);
    return [range, normalized];
  }

  hasFocus() {
    return document.activeElement === this.root;
  }

  normalizedToRange(range) {
    let positions = [[range.start.node, range.start.offset]];
    if (!range.native.collapsed) {
      positions.push([range.end.node, range.end.offset]);
    }
    let indexes = positions.map((position) => {
      let [node, offset] = position;
      let blot = Parchment.find(node, true);
      let index = blot.offset(this.scroll);
      if (offset === 0) {
        return index;
      } else if (blot instanceof Parchment.Container) {
        return index + blot.length();
      } else {
        return index + blot.index(node, offset);
      }
    });
    let end = Math.min(Math.max(...indexes), this.scroll.length() - 1);
    let start = Math.min(end, ...indexes);
    return new Range(start, end-start);
  }

  normalizeNative(nativeRange) {
    if (!contains(this.root, nativeRange.startContainer) ||
        (!nativeRange.collapsed && !contains(this.root, nativeRange.endContainer))) {
      return null;
    }
    let range = {
      start: { node: nativeRange.startContainer, offset: nativeRange.startOffset },
      end: { node: nativeRange.endContainer, offset: nativeRange.endOffset },
      native: nativeRange
    };
    [range.start, range.end].forEach(function(position) {
      let node = position.node, offset = position.offset;
      while (!(node instanceof Text) && node.childNodes.length > 0) {
        if (node.childNodes.length > offset) {
          node = node.childNodes[offset];
          offset = 0;
        } else if (node.childNodes.length === offset) {
          node = node.lastChild;
          offset = node instanceof Text ? node.data.length : node.childNodes.length + 1;
        } else {
          break;
        }
      }
      position.node = node, position.offset = offset;
    });
    return range;
  }

  rangeToNative(range) {
    let indexes = range.collapsed ? [range.index] : [range.index, range.index + range.length];
    let args = [];
    let scrollLength = this.scroll.length();
    indexes.forEach((index, i) => {
      index = Math.min(scrollLength - 1, index);
      let node, [leaf, offset] = this.scroll.leaf(index);
      [node, offset] = leaf.position(offset, i !== 0);
      args.push(node, offset);
    });
    if (args.length < 2) {
      args = args.concat(args);
    }
    return args;
  }

  scrollIntoView(range = this.lastRange) {
    if (range == null) return;
    let bounds = this.getBounds(range.index, range.length);
    if (bounds == null) return;
    let limit = this.scroll.length()-1;
    let [first, ] = this.scroll.line(Math.min(range.index, limit));
    let last = first;
    if (range.length > 0) {
      [last, ] = this.scroll.line(Math.min(range.index + range.length, limit));
    }
    if (first == null || last == null) return;
    let scroller = this.scroll.scrollingContainer;
    //let scrollBounds = scroller.getBoundingClientRect();
    let headerHeight = 65;
    let windowHeight = window.innerHeight - 20;
    if (bounds.top < headerHeight) {
      scroller.scrollTop -= (headerHeight - bounds.top);
    } else if (bounds.bottom > windowHeight) {
      scroller.scrollTop += (bounds.bottom - windowHeight);
    }
  }

  setNativeRange(startNode, startOffset, endNode = startNode, endOffset = startOffset, force = false) {
    debug.info('setNativeRange', startNode, startOffset, endNode, endOffset);
    if (startNode != null && (this.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) {
      return;
    }
    let selection = document.getSelection();
    if (selection == null) return;
    if (startNode != null) {
      if (!this.hasFocus()) this.root.focus();
      let native = (this.getNativeRange() || {}).native;
      if (native == null || force ||
          startNode !== native.startContainer ||
          startOffset !== native.startOffset ||
          endNode !== native.endContainer ||
          endOffset !== native.endOffset) {

        if (startNode.tagName == "BR") {
          startOffset = [].indexOf.call(startNode.parentNode.childNodes, startNode);
          startNode = startNode.parentNode;
        }
        if (endNode.tagName == "BR") {
          endOffset = [].indexOf.call(endNode.parentNode.childNodes, endNode);
          endNode = endNode.parentNode;
        }
        let range = document.createRange();
        range.setStart(startNode, startOffset);
        range.setEnd(endNode, endOffset);
        selection.removeAllRanges();
        selection.addRange(range);
      }
    } else {
      selection.removeAllRanges();
      this.root.blur();
      document.body.focus();  // root.blur() not enough on IE11+Travis+SauceLabs (but not local VMs)
    }
  }

  setRange(range, force = false, source = Emitter.sources.API) {
    if (typeof force === 'string') {
      source = force;
      force = false;
    }
    debug.info('setRange', range);
    if (range != null) {
      let args = this.rangeToNative(range);
      this.setNativeRange(...args, force);
    } else {
      this.setNativeRange(null);
    }
    this.update(source);
  }

  update(source = Emitter.sources.USER) {
    let oldRange = this.lastRange;
    let [lastRange, nativeRange] = this.getRange();
    this.lastRange = lastRange;
    if (this.lastRange != null) {
      this.savedRange = this.lastRange;
    }
    if (!equal(oldRange, this.lastRange)) {
      if (!this.composing && nativeRange != null && nativeRange.native.collapsed && nativeRange.start.node !== this.cursor.textNode) {
        this.cursor.restore();
      }
      let args = [Emitter.events.SELECTION_CHANGE, clone(this.lastRange), clone(oldRange), source];
      this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args);
      if (source !== Emitter.sources.SILENT) {
        this.emitter.emit(...args);
      }
    }
  }
}

function contains(parent, descendant) {
  try {
    // Firefox inserts inaccessible nodes around video elements
    descendant.parentNode;
  } catch (e) {
    return false;
  }
  // IE11 has bug with Text nodes
  // https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect
  if (descendant instanceof Text) {
    descendant = descendant.parentNode;
  }
  return parent.contains(descendant);
}

export { Range, Selection as default };
<commit_msg>fixed scroll into view function<commit_after>import Parchment from 'parchment';
import clone from 'clone';
import equal from 'deep-equal';
import Emitter from './emitter';
import logger from './logger';

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

class Range {
  constructor(index, length = 0) {
    this.index = index;
    this.length = length;
  }
}

class Selection {
  constructor(scroll, emitter) {
    this.emitter = emitter;
    this.scroll = scroll;
    this.composing = false;
    this.root = this.scroll.domNode;
    this.root.addEventListener('compositionstart', () => {
      this.composing = true;
    });
    this.root.addEventListener('compositionend', () => {
      this.composing = false;
    });
    this.cursor = Parchment.create('cursor', this);
    // savedRange is last non-null range
    this.lastRange = this.savedRange = new Range(0, 0);
    ['keyup', 'mouseup', 'mouseleave', 'touchend', 'touchleave', 'focus', 'blur'].forEach((eventName) => {
      this.root.addEventListener(eventName, () => {
        // When range used to be a selection and user click within the selection,
        // the range now being a cursor has not updated yet without setTimeout
        setTimeout(this.update.bind(this, Emitter.sources.USER), 100);
      });
    });
    this.emitter.on(Emitter.events.EDITOR_CHANGE, (type, delta) => {
      if (type === Emitter.events.TEXT_CHANGE && delta.length() > 0) {
        this.update(Emitter.sources.SILENT);
      }
    });
    this.emitter.on(Emitter.events.SCROLL_BEFORE_UPDATE, () => {
      if (!this.hasFocus()) return;
      let native = this.getNativeRange();
      if (native == null) return;
      if (native.start.node === this.cursor.textNode) return;  // cursor.restore() will handle
      // TODO unclear if this has negative side effects
      this.emitter.once(Emitter.events.SCROLL_UPDATE, () => {
        try {
          this.setNativeRange(native.start.node, native.start.offset, native.end.node, native.end.offset);
        } catch (ignored) {}
      });
    });
    this.update(Emitter.sources.SILENT);
  }

  focus() {
    if (this.hasFocus()) return;
    this.root.focus();
    this.setRange(this.savedRange);
  }

  format(format, value) {
    if (this.scroll.whitelist != null && !this.scroll.whitelist[format]) return;
    this.scroll.update();
    let nativeRange = this.getNativeRange();
    if (nativeRange == null || !nativeRange.native.collapsed || Parchment.query(format, Parchment.Scope.BLOCK)) return;
    if (nativeRange.start.node !== this.cursor.textNode) {
      let blot = Parchment.find(nativeRange.start.node, false);
      if (blot == null) return;
      // TODO Give blot ability to not split
      if (blot instanceof Parchment.Leaf) {
        let after = blot.split(nativeRange.start.offset);
        blot.parent.insertBefore(this.cursor, after);
      } else {
        blot.insertBefore(this.cursor, nativeRange.start.node);  // Should never happen
      }
      this.cursor.attach();
    }
    this.cursor.format(format, value);
    this.scroll.optimize();
    this.setNativeRange(this.cursor.textNode, this.cursor.textNode.data.length);
    this.update();
  }

  getBounds(index, length = 0) {
    let scrollLength = this.scroll.length();
    index = Math.min(index, scrollLength - 1);
    length = Math.min(index + length, scrollLength - 1) - index;
    let node, [leaf, offset] = this.scroll.leaf(index);
    if (leaf == null) return null;
    [node, offset] = leaf.position(offset, true);
    let range = document.createRange();
    if (length > 0) {
      range.setStart(node, offset);
      [leaf, offset] = this.scroll.leaf(index + length);
      if (leaf == null) return null;
      [node, offset] = leaf.position(offset, true);
      range.setEnd(node, offset);
      return range.getBoundingClientRect();
    } else {
      let side = 'left';
      let rect;
      if (node instanceof Text) {
        if (offset < node.data.length) {
          range.setStart(node, offset);
          range.setEnd(node, offset + 1);
        } else {
          range.setStart(node, offset - 1);
          range.setEnd(node, offset);
          side = 'right';
        }
        rect = range.getBoundingClientRect();
      } else {
        rect = leaf.domNode.getBoundingClientRect();
        if (offset > 0) side = 'right';
      }
      return {
        bottom: rect.top + rect.height,
        height: rect.height,
        left: rect[side],
        right: rect[side],
        top: rect.top,
        width: 0
      };
    }
  }

  getNativeRange() {
    let selection = document.getSelection();
    if (selection == null || selection.rangeCount <= 0) return null;
    let nativeRange = selection.getRangeAt(0);
    if (nativeRange == null) return null;
    let range = this.normalizeNative(nativeRange);
    debug.info('getNativeRange', range);
    return range;
  }

  getRange() {
    let normalized = this.getNativeRange();
    if (normalized == null) return [null, null];
    let range = this.normalizedToRange(normalized);
    return [range, normalized];
  }

  hasFocus() {
    return document.activeElement === this.root;
  }

  normalizedToRange(range) {
    let positions = [[range.start.node, range.start.offset]];
    if (!range.native.collapsed) {
      positions.push([range.end.node, range.end.offset]);
    }
    let indexes = positions.map((position) => {
      let [node, offset] = position;
      let blot = Parchment.find(node, true);
      let index = blot.offset(this.scroll);
      if (offset === 0) {
        return index;
      } else if (blot instanceof Parchment.Container) {
        return index + blot.length();
      } else {
        return index + blot.index(node, offset);
      }
    });
    let end = Math.min(Math.max(...indexes), this.scroll.length() - 1);
    let start = Math.min(end, ...indexes);
    return new Range(start, end-start);
  }

  normalizeNative(nativeRange) {
    if (!contains(this.root, nativeRange.startContainer) ||
        (!nativeRange.collapsed && !contains(this.root, nativeRange.endContainer))) {
      return null;
    }
    let range = {
      start: { node: nativeRange.startContainer, offset: nativeRange.startOffset },
      end: { node: nativeRange.endContainer, offset: nativeRange.endOffset },
      native: nativeRange
    };
    [range.start, range.end].forEach(function(position) {
      let node = position.node, offset = position.offset;
      while (!(node instanceof Text) && node.childNodes.length > 0) {
        if (node.childNodes.length > offset) {
          node = node.childNodes[offset];
          offset = 0;
        } else if (node.childNodes.length === offset) {
          node = node.lastChild;
          offset = node instanceof Text ? node.data.length : node.childNodes.length + 1;
        } else {
          break;
        }
      }
      position.node = node, position.offset = offset;
    });
    return range;
  }

  rangeToNative(range) {
    let indexes = range.collapsed ? [range.index] : [range.index, range.index + range.length];
    let args = [];
    let scrollLength = this.scroll.length();
    indexes.forEach((index, i) => {
      index = Math.min(scrollLength - 1, index);
      let node, [leaf, offset] = this.scroll.leaf(index);
      [node, offset] = leaf.position(offset, i !== 0);
      args.push(node, offset);
    });
    if (args.length < 2) {
      args = args.concat(args);
    }
    return args;
  }

  scrollIntoView(range = this.lastRange) {
    if (range == null) return;
    let bounds = this.getBounds(range.index, range.length);
    if (bounds == null) return;
    let limit = this.scroll.length()-1;
    let [first, ] = this.scroll.line(Math.min(range.index, limit));
    let last = first;
    if (range.length > 0) {
      [last, ] = this.scroll.line(Math.min(range.index + range.length, limit));
    }
    if (first == null || last == null) return;
    let scroller = this.scroll.scrollingContainer;
    //let scrollBounds = scroller.getBoundingClientRect();
    let headerHeight = 65;
    let windowHeight = window.innerHeight - 20;
    if (!!bounds.top && !!bounds.bottom) {
      if (bounds.top < headerHeight) {
        scroller.scrollTop -= (headerHeight - bounds.top);
      } else if (bounds.bottom > windowHeight) {
        scroller.scrollTop += (bounds.bottom - windowHeight);
      }
    }
  }

  setNativeRange(startNode, startOffset, endNode = startNode, endOffset = startOffset, force = false) {
    debug.info('setNativeRange', startNode, startOffset, endNode, endOffset);
    if (startNode != null && (this.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) {
      return;
    }
    let selection = document.getSelection();
    if (selection == null) return;
    if (startNode != null) {
      if (!this.hasFocus()) this.root.focus();
      let native = (this.getNativeRange() || {}).native;
      if (native == null || force ||
          startNode !== native.startContainer ||
          startOffset !== native.startOffset ||
          endNode !== native.endContainer ||
          endOffset !== native.endOffset) {

        if (startNode.tagName == "BR") {
          startOffset = [].indexOf.call(startNode.parentNode.childNodes, startNode);
          startNode = startNode.parentNode;
        }
        if (endNode.tagName == "BR") {
          endOffset = [].indexOf.call(endNode.parentNode.childNodes, endNode);
          endNode = endNode.parentNode;
        }
        let range = document.createRange();
        range.setStart(startNode, startOffset);
        range.setEnd(endNode, endOffset);
        selection.removeAllRanges();
        selection.addRange(range);
      }
    } else {
      selection.removeAllRanges();
      this.root.blur();
      document.body.focus();  // root.blur() not enough on IE11+Travis+SauceLabs (but not local VMs)
    }
  }

  setRange(range, force = false, source = Emitter.sources.API) {
    if (typeof force === 'string') {
      source = force;
      force = false;
    }
    debug.info('setRange', range);
    if (range != null) {
      let args = this.rangeToNative(range);
      this.setNativeRange(...args, force);
    } else {
      this.setNativeRange(null);
    }
    this.update(source);
  }

  update(source = Emitter.sources.USER) {
    let oldRange = this.lastRange;
    let [lastRange, nativeRange] = this.getRange();
    this.lastRange = lastRange;
    if (this.lastRange != null) {
      this.savedRange = this.lastRange;
    }
    if (!equal(oldRange, this.lastRange)) {
      if (!this.composing && nativeRange != null && nativeRange.native.collapsed && nativeRange.start.node !== this.cursor.textNode) {
        this.cursor.restore();
      }
      let args = [Emitter.events.SELECTION_CHANGE, clone(this.lastRange), clone(oldRange), source];
      this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args);
      if (source !== Emitter.sources.SILENT) {
        this.emitter.emit(...args);
      }
    }
  }
}

function contains(parent, descendant) {
  try {
    // Firefox inserts inaccessible nodes around video elements
    descendant.parentNode;
  } catch (e) {
    return false;
  }
  // IE11 has bug with Text nodes
  // https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect
  if (descendant instanceof Text) {
    descendant = descendant.parentNode;
  }
  return parent.contains(descendant);
}

export { Range, Selection as default };
<|endoftext|>


<commit_before>// Handles related-objects functionality: lookup link for raw_id_admin=True
// and Add Another links.

function showRelatedObjectLookupPopup(triggeringLink) {
    var name = triggeringLink.id.replace(/^lookup_/, '');
    // IE doesn't like periods in the window name, so convert temporarily.
    name = name.replace(/\./g, '___');
    var href;
    if (triggeringLink.href.search(/\?/) >= 0) {
        href = triggeringLink.href + '&pop=1';
    } else {
        href = triggeringLink.href + '?pop=1';
    }
    var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
    win.focus();
    return false;
}

function dismissRelatedLookupPopup(win, chosenId) {
    var name = win.name.replace(/___/g, '.');
    var elem = document.getElementById(name);
    if (elem.className.indexOf('vRawIdAdminField') != -1 && elem.value) {
        elem.value += ',' + chosenId;
    } else {
        document.getElementById(name).value = chosenId;
    }
    win.close();
}

function showAddAnotherPopup(triggeringLink) {
    var name = triggeringLink.id.replace(/^add_/, '');
    name = name.replace(/\./g, '___');
    href = triggeringLink.href
    if (href.indexOf('?') == -1)
	href += '?_popup=1';
    else
	href += '&_popup=1';
    var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
    win.focus();
    return false;
}

function dismissAddAnotherPopup(win, newId, newRepr) {
    var name = win.name.replace(/___/g, '.');
    var elem = document.getElementById(name);
    if (elem) {
        if (elem.nodeName == 'SELECT') {
            var o = new Option(newRepr, newId);
            elem.options[elem.options.length] = o;
            o.selected = true;
        } else if (elem.nodeName == 'INPUT') {
            elem.value = newId;
        }
    } else {
        var toId = name + "_to";
        elem = document.getElementById(toId);
        var o = new Option(newRepr, newId);
        SelectBox.add_to_cache(toId, o);
        SelectBox.redisplay(toId);
    }
    win.close();
}
<commit_msg>Fixed #4556 -- Stylistic changes to [5500]. Thanks, glin@seznam.cz.<commit_after>// Handles related-objects functionality: lookup link for raw_id_admin=True
// and Add Another links.

function showRelatedObjectLookupPopup(triggeringLink) {
    var name = triggeringLink.id.replace(/^lookup_/, '');
    // IE doesn't like periods in the window name, so convert temporarily.
    name = name.replace(/\./g, '___');
    var href;
    if (triggeringLink.href.search(/\?/) >= 0) {
        href = triggeringLink.href + '&pop=1';
    } else {
        href = triggeringLink.href + '?pop=1';
    }
    var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
    win.focus();
    return false;
}

function dismissRelatedLookupPopup(win, chosenId) {
    var name = win.name.replace(/___/g, '.');
    var elem = document.getElementById(name);
    if (elem.className.indexOf('vRawIdAdminField') != -1 && elem.value) {
        elem.value += ',' + chosenId;
    } else {
        document.getElementById(name).value = chosenId;
    }
    win.close();
}

function showAddAnotherPopup(triggeringLink) {
    var name = triggeringLink.id.replace(/^add_/, '');
    name = name.replace(/\./g, '___');
    href = triggeringLink.href
    if (href.indexOf('?') == -1) {
        href += '?_popup=1';
    } else {
        href  += '&_popup=1';
    }
    var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
    win.focus();
    return false;
}

function dismissAddAnotherPopup(win, newId, newRepr) {
    var name = win.name.replace(/___/g, '.');
    var elem = document.getElementById(name);
    if (elem) {
        if (elem.nodeName == 'SELECT') {
            var o = new Option(newRepr, newId);
            elem.options[elem.options.length] = o;
            o.selected = true;
        } else if (elem.nodeName == 'INPUT') {
            elem.value = newId;
        }
    } else {
        var toId = name + "_to";
        elem = document.getElementById(toId);
        var o = new Option(newRepr, newId);
        SelectBox.add_to_cache(toId, o);
        SelectBox.redisplay(toId);
    }
    win.close();
}
<|endoftext|>


<commit_before>'use strict';

var memoize   = require('memoizee/plain')
  , validDbjs = require('dbjs/valid-dbjs');

module.exports = memoize(function (db) {
	return validDbjs(db).DateTime.extend('Date', {
		step: { value: 1000 * 60 * 60 * 24 },
		_validateCreate_: { value: function (value/*[, mth[, d[, h]]]*/) {
			var l = arguments.length, year, month, day;
			if (!l) {
				value = new Date();
				year = value.getFullYear();
				month = value.getMonth();
				day = value.getDate();
				value.setUTCFullYear(year);
				value.setUTCMonth(month);
				value.setUTCDate(day);
			} else if (l === 1) {
				value = new Date(value);
			} else {
				value = new Date(Date.UTC(value, arguments[1], (l > 2) ? arguments[2] : 1,
					(l > 3) ? arguments[3] : 0));
			}
			return [this.validate(value)];
		} },
		normalize: { value: function (value/*, descriptor*/) {
			var year, month, date;
			if (!value) return this.database.DateTime.normalize.apply(this, arguments);
			if (value instanceof this) return this.database.DateTime.normalize.apply(this, arguments);
			if (Object.prototype.toString.call(value) !== '[object Date]') {
				return this.database.DateTime.normalize.apply(this, arguments);
			}
			year = value.getFullYear();
			month = value.getMonth();
			date = value.getDate();
			value = new Date(value);
			value.setUTCFullYear(year);
			value.setUTCMonth(month);
			value.setUTCDate(date);
			return this.database.DateTime.normalize.call(this, value, arguments[1]);
		} },
		validate: { value: function (value/*, descriptor*/) {
			var year, month, date;
			if (!value) return this.database.DateTime.validate.apply(this, arguments);
			if (value instanceof this) return this.database.DateTime.validate.apply(this, arguments);
			if (Object.prototype.toString.call(value) !== '[object Date]') {
				return this.database.DateTime.validate.apply(this, arguments);
			}
			year = value.getFullYear();
			month = value.getMonth();
			date = value.getDate();
			value = new Date(value);
			value.setUTCFullYear(year);
			value.setUTCMonth(month);
			value.setUTCDate(date);
			return this.database.DateTime.validate.call(this, value, arguments[1]);
		} }
	}, {
		toString: { value: function (descriptor) {
			return (new Date(this.getUTCFullYear(), this.getUTCMonth(),
				this.getUTCDate())).toLocaleDateString(this.database.locale);
		} }
	});
}, { normalizer: require('memoizee/normalizers/get-1')() });
<commit_msg>Assure right arguments resolution<commit_after>'use strict';

var memoize   = require('memoizee/plain')
  , validDbjs = require('dbjs/valid-dbjs');

module.exports = memoize(function (db) {
	return validDbjs(db).DateTime.extend('Date', {
		step: { value: 1000 * 60 * 60 * 24 },
		_validateCreate_: { value: function (value/*[, mth[, d[, h]]]*/) {
			var l = arguments.length, year, month, day;
			if (!l) {
				value = new Date();
				year = value.getFullYear();
				month = value.getMonth();
				day = value.getDate();
				value.setUTCFullYear(year);
				value.setUTCMonth(month);
				value.setUTCDate(day);
			} else if (l === 1) {
				value = new Date(value);
			} else {
				value = new Date(Date.UTC(value, arguments[1], (l > 2) ? arguments[2] : 1,
					(l > 3) ? arguments[3] : 0));
			}
			return [this.database.DateTime.validate.call(this, value)];
		} },
		normalize: { value: function (value/*, descriptor*/) {
			var year, month, date;
			if (!value) return this.database.DateTime.normalize.apply(this, arguments);
			if (value instanceof this) return this.database.DateTime.normalize.apply(this, arguments);
			if (Object.prototype.toString.call(value) !== '[object Date]') {
				return this.database.DateTime.normalize.apply(this, arguments);
			}
			year = value.getFullYear();
			month = value.getMonth();
			date = value.getDate();
			value = new Date(value);
			value.setUTCFullYear(year);
			value.setUTCMonth(month);
			value.setUTCDate(date);
			return this.database.DateTime.normalize.call(this, value, arguments[1]);
		} },
		validate: { value: function (value/*, descriptor*/) {
			var year, month, date;
			if (!value) return this.database.DateTime.validate.apply(this, arguments);
			if (value instanceof this) return this.database.DateTime.validate.apply(this, arguments);
			if (Object.prototype.toString.call(value) !== '[object Date]') {
				return this.database.DateTime.validate.apply(this, arguments);
			}
			year = value.getFullYear();
			month = value.getMonth();
			date = value.getDate();
			value = new Date(value);
			value.setUTCFullYear(year);
			value.setUTCMonth(month);
			value.setUTCDate(date);
			return this.database.DateTime.validate.call(this, value, arguments[1]);
		} }
	}, {
		toString: { value: function (descriptor) {
			return (new Date(this.getUTCFullYear(), this.getUTCMonth(),
				this.getUTCDate())).toLocaleDateString(this.database.locale);
		} }
	});
}, { normalizer: require('memoizee/normalizers/get-1')() });
<|endoftext|>


<commit_before>angular.module('ui.bootstrap.datetimepicker', ["ui.bootstrap.dateparser", "ui.bootstrap.datepicker", "ui.bootstrap.timepicker"])
  .directive('datepickerPopup', function () {
    return {
      restrict: 'EAC',
      require: 'ngModel',
      link: function (scope, element, attr, controller) {
        //remove the default formatter from the input directive to prevent conflict
        controller.$formatters.shift();
      }
    }
  })
  .directive('datetimepicker', [
    function () {

      function versionCheck(){
        return (angular.version.major === 1 && (angular.version.minor > 4 || (angular.version.minor === 4 && angular.version.dot >= 4)));
      }

      if (!versionCheck()) {
        return {
          restrict: 'EA',
          template: "
Angular 1.4.4 or above is required for datetimepicker to work correctly
"
        };
      }
      return {
        restrict: 'EA',
        require: 'ngModel',
        scope: {
          ngModel: '=',
          ngChange: '&',
          dayFormat: "=",
          monthFormat: "=",
          yearFormat: "=",
          minTime: "=",
          maxTime: "=",
          dayHeaderFormat: "=",
          dayTitleFormat: "=",
          monthTitleFormat: "=",
          yearRange: "=",
          dateOptions: "=?",
          dateDisabled: "&",
          dateNgClick: "&",
          hourStep: "=",
          dateOpened: "=",
          minuteStep: "=",
          showMeridian: "=",
          meredians: "=",
          mousewheel: "=",
          readonlyTime: "=",
          readonlyDate: "=",
          disabledDate: "=",
          hiddenTime: "=",
          hiddenDate: "="
        },
        template: function (elem, attrs) {
          function dashCase(name) {
            return name.replace(/[A-Z]/g, function (letter, pos) {
              return (pos ? '-' : '') + letter.toLowerCase();
            });
          }

          function createAttr(innerAttr, dateTimeAttrOpt) {
            var dateTimeAttr = angular.isDefined(dateTimeAttrOpt) ? dateTimeAttrOpt : innerAttr;
            if (attrs[dateTimeAttr]) {
              return dashCase(innerAttr) + "=\"" + dateTimeAttr + "\" ";
            } else {
              return '';
            }
          }

          function createFuncAttr(innerAttr, funcArgs, dateTimeAttrOpt, defaultImpl) {
            var dateTimeAttr = angular.isDefined(dateTimeAttrOpt) ? dateTimeAttrOpt : innerAttr;
            if (attrs[dateTimeAttr]) {
              return dashCase(innerAttr) + "=\"" + dateTimeAttr + "({" + funcArgs + "})\" ";
            } else {
              return angular.isDefined(defaultImpl) ? dashCase(innerAttr) + "=\"" + defaultImpl + "\"" : "";
            }
          }

          function createEvalAttr(innerAttr, dateTimeAttrOpt) {
            var dateTimeAttr = angular.isDefined(dateTimeAttrOpt) ? dateTimeAttrOpt : innerAttr;
            if (attrs[dateTimeAttr]) {
              return dashCase(innerAttr) + "=\"" + attrs[dateTimeAttr] + "\" ";
            } else {
              return dashCase(innerAttr) + " ";
            }
          }

          function createAttrConcat(previousAttrs, attr) {
            return previousAttrs + createAttr.apply(null, attr)
          }

          var dateTmpl = "
" +
            "<input class=\"form-control\" type=\"text\" " +
            "name=\"datepicker\"" +
            "ng-change=\"date_change($event)\" " +
            "is-open=\"innerDateOpened\" " +
            "datepicker-options=\"dateOptions\" " +
            "uib-datepicker-popup=\"{{dateFormat}}\"" +
            "ng-model=\"ngModel\" " + [
              ["dayFormat"],
              ["monthFormat"],
              ["yearFormat"],
              ["dayHeaderFormat"],
              ["dayTitleFormat"],
              ["monthTitleFormat"],
              ["yearRange"],
              ["showButtonBar"],
              ["ngHide", "hiddenDate"],
              ["ngReadonly", "readonlyDate"],
              ["ngDisabled", "disabledDate"]
            ].reduce(createAttrConcat, '') +
            createFuncAttr("ngClick",
              "$event: $event, opened: opened",
              "dateNgClick",
              "open($event)") +
            createEvalAttr("currentText", "currentText") +
            createEvalAttr("clearText", "clearText") +
            createEvalAttr("datepickerAppendToBody", "datepickerAppendToBody") +
            createEvalAttr("closeText", "closeText") +
            createEvalAttr("placeholder", "placeholder") +
            "/>\n" +
            "
\n";
          var timeTmpl = "
\n" +
            "

\n" +
            "
";
          // form is isolated so the directive is registered as one component in the parent form (not date and time)
          var tmpl = "<ng-form name=\"datetimepickerForm\" isolate-form>" + dateTmpl + timeTmpl + "</ng-form>";
          return tmpl;
        },
        controller: ['$scope', '$attrs',
          function ($scope, $attrs) {
            $scope.date_change = function () {
              // If we changed the date only, set the time (h,m) on it.
              // This is important in case the previous date was null.
              // This solves the issue when the user set a date and time, cleared the date, and chose another date,
              // and then, the time was cleared too - which is unexpected
              var time = $scope.time;
              if ($scope.ngModel && $scope.time) { // if ngModel is null, that's because the user cleared the date field
                $scope.ngModel.setHours(time.getHours(), time.getMinutes(), 0, 0);
              }
            };
            $scope.time_change = function () {
              if ($scope.ngModel && $scope.time) {
                // convert from ISO format to Date
                if (!($scope.ngModel instanceof Date)) $scope.ngModel = new Date($scope.ngModel);
                $scope.ngModel.setHours($scope.time.getHours(), $scope.time.getMinutes(), 0, 0);
              }  // else the time is invalid, keep the current Date value in datepicker
            };
            $scope.open = function ($event) {
              $event.preventDefault();
              $event.stopPropagation();
              $scope.innerDateOpened = true;
            };
            $attrs.$observe('dateFormat', function(newDateFormat, oldValue) {
              $scope.dateFormat = newDateFormat;
            });
            $scope.dateOptions = angular.isDefined($scope.dateOptions) ? $scope.dateOptions : {};
            $scope.dateOptions.dateDisabled = $scope.dateDisabled;
          }
        ],
        link: function (scope, element, attrs, ctrl) {
          var updateMinTime = function() {
            if (!scope.ngModel) {
              return;
            }
            if (scope.minTime) {
              scope.minDate = new Date(scope.ngModel.getFullYear(),
                                       scope.ngModel.getMonth(),
                                       scope.ngModel.getDate(),
                                       scope.minTime.getHours(),
                                       scope.minTime.getMinutes(),
                                       0);
              if (scope.dateOptions.minDate && scope.dateOptions.minDate > scope.minDate) {
                scope.minDate = scope.dateOptions.minDate;
              }
            } else {
              scope.minDate = scope.dateOptions.minDate;
            }
          };
          var updateMaxTime = function() {
            if (!scope.ngModel) {
              return;
            }
            if (scope.maxTime) {
              scope.maxDate = new Date(scope.ngModel.getFullYear(),
                                       scope.ngModel.getMonth(),
                                       scope.ngModel.getDate(),
                                       scope.maxTime.getHours(),
                                       scope.maxTime.getMinutes(),
                                       0);
              if (scope.dateOptions.maxDate && scope.dateOptions.maxDate < scope.maxDate) {
                scope.maxDate = scope.dateOptions.maxDate;
              }
            } else {
              scope.maxDate = scope.dateOptions.maxDate;
            }
          };

          var firstTimeAssign = true;

          scope.$watch(function () {
            return scope.ngModel;
          }, function (newTime) {
            var timeElement = element[0].querySelector('[name=timepicker]');

            // if a time element is focused, updating its model will cause hours/minutes to be formatted by padding with leading zeros
            if (timeElement && !timeElement.contains(document.activeElement)) {

              if (newTime === null || newTime === '') { // if the newTime is not defined
                if (firstTimeAssign) { // if it's the first time we assign the time value
                  // create a new default time where the hours, minutes, seconds and milliseconds are set to 0.
                  newTime = new Date();
                  newTime.setHours(0, 0, 0, 0);
                } else { // clear the time
                  scope.time = null;
                  if (scope.ngChange) scope.$eval(scope.ngChange);
                  return;
                }
              }
              // Update timepicker (watch on ng-model in timepicker does not use object equality),
              // also if the ngModel was not a Date, convert it to date
              newTime = new Date(newTime);

              if (isNaN(newTime.getTime()) === false) {
                scope.time = newTime; // change the time in timepicker
                if (firstTimeAssign) {
                  firstTimeAssign = false;
                }
                else if (scope.ngChange) {
                  scope.$eval(scope.ngChange);
                }
              }
            }
            updateMinTime();
            updateMaxTime();
          }, true);

          scope.$watch(function () {
            return scope.datetimepickerForm && scope.datetimepickerForm.$error;
          }, function (errors) {
            if (angular.isUndefined(errors)) {
              return;
            }
            Object.keys(ctrl.$error).forEach(function (error) {
              ctrl.$setValidity(error, true);
            });
            Object.keys(errors).forEach(function (error) {
              ctrl.$setValidity(error, false);
            });
          }, true);

          scope.$watch(function () {
            return scope.datetimepickerForm && (scope.datetimepickerForm.timepicker.$touched || scope.datetimepickerForm.datepicker.$touched);
          }, function (touched) {
            if (touched) {
              ctrl.$setTouched();
            }
          });

          scope.$watch(function () {
            return scope.datetimepickerForm && scope.datetimepickerForm.$dirty;
          }, function (dirty) {
            if (dirty) {
              ctrl.$setDirty();
            }
          });

          scope.$watch('dateOpened', function (value) {
            scope.innerDateOpened = value;
          });
          scope.$watch('innerDateOpened', function (value) {
            if (angular.isDefined(scope.dateOpened)) {
              scope.dateOpened = value;
            }
          });
          scope.$watch('dateOptions.minDate', function (value) {
            updateMinTime();
          });
          scope.$watch('timeMin', function (value) {
            updateMinTime();
          });
          scope.$watch('dateOptions.maxDate', function (value) {
            updateMaxTime();
          });
          scope.$watch('timeMax', function (value) {
            updateMaxTime();
          });
        }
      }
    }
  ]).directive('isolateForm', [function () {
  return {
    restrict: 'A',
    require: '?form',
    link: function (scope, elm, attrs, ctrl) {
      if (!ctrl) {
        return;
      }
      // Do a copy of the controller
      var ctrlCopy = {};
      angular.copy(ctrl, ctrlCopy);

      // Get the parent of the form
      var parent = elm.parent().controller('form');
      if (!parent) {
        return;
      }
      // Remove parent link to the controller
      parent.$removeControl(ctrl);

      // Replace form controller with an "isolated form"
      var isolatedFormCtrl = {
        $setValidity: function (validationToken, isValid, control) {
          ctrlCopy.$setValidity(validationToken, isValid, control);
          parent.$setValidity(validationToken, true, ctrl);
        }
      };
      angular.extend(ctrl, isolatedFormCtrl);
    }
  };
}]);
<commit_msg>Support for string date<commit_after>angular.module('ui.bootstrap.datetimepicker', ["ui.bootstrap.dateparser", "ui.bootstrap.datepicker", "ui.bootstrap.timepicker"])
  .directive('datepickerPopup', function () {
    return {
      restrict: 'EAC',
      require: 'ngModel',
      link: function (scope, element, attr, controller) {
        //remove the default formatter from the input directive to prevent conflict
        controller.$formatters.shift();
      }
    }
  })
  .directive('datetimepicker', [
    function () {

      function versionCheck(){
        return (angular.version.major === 1 && (angular.version.minor > 4 || (angular.version.minor === 4 && angular.version.dot >= 4)));
      }

      if (!versionCheck()) {
        return {
          restrict: 'EA',
          template: "
Angular 1.4.4 or above is required for datetimepicker to work correctly
"
        };
      }
      return {
        restrict: 'EA',
        require: 'ngModel',
        scope: {
          ngModel: '=',
          ngChange: '&',
          dayFormat: "=",
          monthFormat: "=",
          yearFormat: "=",
          minTime: "=",
          maxTime: "=",
          dayHeaderFormat: "=",
          dayTitleFormat: "=",
          monthTitleFormat: "=",
          yearRange: "=",
          dateOptions: "=?",
          dateDisabled: "&",
          dateNgClick: "&",
          hourStep: "=",
          dateOpened: "=",
          minuteStep: "=",
          showMeridian: "=",
          meredians: "=",
          mousewheel: "=",
          readonlyTime: "=",
          readonlyDate: "=",
          disabledDate: "=",
          hiddenTime: "=",
          hiddenDate: "="
        },
        template: function (elem, attrs) {
          function dashCase(name) {
            return name.replace(/[A-Z]/g, function (letter, pos) {
              return (pos ? '-' : '') + letter.toLowerCase();
            });
          }

          function createAttr(innerAttr, dateTimeAttrOpt) {
            var dateTimeAttr = angular.isDefined(dateTimeAttrOpt) ? dateTimeAttrOpt : innerAttr;
            if (attrs[dateTimeAttr]) {
              return dashCase(innerAttr) + "=\"" + dateTimeAttr + "\" ";
            } else {
              return '';
            }
          }

          function createFuncAttr(innerAttr, funcArgs, dateTimeAttrOpt, defaultImpl) {
            var dateTimeAttr = angular.isDefined(dateTimeAttrOpt) ? dateTimeAttrOpt : innerAttr;
            if (attrs[dateTimeAttr]) {
              return dashCase(innerAttr) + "=\"" + dateTimeAttr + "({" + funcArgs + "})\" ";
            } else {
              return angular.isDefined(defaultImpl) ? dashCase(innerAttr) + "=\"" + defaultImpl + "\"" : "";
            }
          }

          function createEvalAttr(innerAttr, dateTimeAttrOpt) {
            var dateTimeAttr = angular.isDefined(dateTimeAttrOpt) ? dateTimeAttrOpt : innerAttr;
            if (attrs[dateTimeAttr]) {
              return dashCase(innerAttr) + "=\"" + attrs[dateTimeAttr] + "\" ";
            } else {
              return dashCase(innerAttr) + " ";
            }
          }

          function createAttrConcat(previousAttrs, attr) {
            return previousAttrs + createAttr.apply(null, attr)
          }

          var dateTmpl = "
" +
            "<input class=\"form-control\" type=\"text\" " +
            "name=\"datepicker\"" +
            "ng-change=\"date_change($event)\" " +
            "is-open=\"innerDateOpened\" " +
            "datepicker-options=\"dateOptions\" " +
            "uib-datepicker-popup=\"{{dateFormat}}\"" +
            "ng-model=\"ngModel\" " + [
              ["dayFormat"],
              ["monthFormat"],
              ["yearFormat"],
              ["dayHeaderFormat"],
              ["dayTitleFormat"],
              ["monthTitleFormat"],
              ["yearRange"],
              ["showButtonBar"],
              ["ngHide", "hiddenDate"],
              ["ngReadonly", "readonlyDate"],
              ["ngDisabled", "disabledDate"]
            ].reduce(createAttrConcat, '') +
            createFuncAttr("ngClick",
              "$event: $event, opened: opened",
              "dateNgClick",
              "open($event)") +
            createEvalAttr("currentText", "currentText") +
            createEvalAttr("clearText", "clearText") +
            createEvalAttr("datepickerAppendToBody", "datepickerAppendToBody") +
            createEvalAttr("closeText", "closeText") +
            createEvalAttr("placeholder", "placeholder") +
            "/>\n" +
            "
\n";
          var timeTmpl = "
\n" +
            "

\n" +
            "
";
          // form is isolated so the directive is registered as one component in the parent form (not date and time)
          var tmpl = "<ng-form name=\"datetimepickerForm\" isolate-form>" + dateTmpl + timeTmpl + "</ng-form>";
          return tmpl;
        },
        controller: ['$scope', '$attrs',
          function ($scope, $attrs) {
            $scope.date_change = function () {
              // If we changed the date only, set the time (h,m) on it.
              // This is important in case the previous date was null.
              // This solves the issue when the user set a date and time, cleared the date, and chose another date,
              // and then, the time was cleared too - which is unexpected
              var time = $scope.time;
              if ($scope.ngModel && $scope.time) { // if ngModel is null, that's because the user cleared the date field
                $scope.ngModel.setHours(time.getHours(), time.getMinutes(), 0, 0);
              }
            };
            $scope.time_change = function () {
              if ($scope.ngModel && $scope.time) {
                $scope.ngModel.setHours($scope.time.getHours(), $scope.time.getMinutes(), 0, 0);
              }  // else the time is invalid, keep the current Date value in datepicker
            };
            $scope.open = function ($event) {
              $event.preventDefault();
              $event.stopPropagation();
              $scope.innerDateOpened = true;
            };
            $attrs.$observe('dateFormat', function(newDateFormat, oldValue) {
              $scope.dateFormat = newDateFormat;
            });
            $scope.dateOptions = angular.isDefined($scope.dateOptions) ? $scope.dateOptions : {};
            $scope.dateOptions.dateDisabled = $scope.dateDisabled;
          }
        ],
        link: function (scope, element, attrs, ctrl) {
          var updateMinTime = function() {
            if (!scope.ngModel) {
              return;
            }
            if (scope.minTime) {
              scope.minDate = new Date(scope.ngModel.getFullYear(),
                                       scope.ngModel.getMonth(),
                                       scope.ngModel.getDate(),
                                       scope.minTime.getHours(),
                                       scope.minTime.getMinutes(),
                                       0);
              if (scope.dateOptions.minDate && scope.dateOptions.minDate > scope.minDate) {
                scope.minDate = scope.dateOptions.minDate;
              }
            } else {
              scope.minDate = scope.dateOptions.minDate;
            }
          };
          var updateMaxTime = function() {
            if (!scope.ngModel) {
              return;
            }
            if (scope.maxTime) {
              scope.maxDate = new Date(scope.ngModel.getFullYear(),
                                       scope.ngModel.getMonth(),
                                       scope.ngModel.getDate(),
                                       scope.maxTime.getHours(),
                                       scope.maxTime.getMinutes(),
                                       0);
              if (scope.dateOptions.maxDate && scope.dateOptions.maxDate < scope.maxDate) {
                scope.maxDate = scope.dateOptions.maxDate;
              }
            } else {
              scope.maxDate = scope.dateOptions.maxDate;
            }
          };

          var firstTimeAssign = true;

          scope.$watch(function () {
            return scope.ngModel;
          }, function (newTime) {
            if (scope.ngModel && !(scope.ngModel instanceof Date)) {
                // convert from ISO format to Date
                scope.ngModel = new Date(scope.ngModel);
            }

            var timeElement = element[0].querySelector('[name=timepicker]');

            // if a time element is focused, updating its model will cause hours/minutes to be formatted by padding with leading zeros
            if (timeElement && !timeElement.contains(document.activeElement)) {

              if (newTime === null || newTime === '') { // if the newTime is not defined
                if (firstTimeAssign) { // if it's the first time we assign the time value
                  // create a new default time where the hours, minutes, seconds and milliseconds are set to 0.
                  newTime = new Date();
                  newTime.setHours(0, 0, 0, 0);
                } else { // clear the time
                  scope.time = null;
                  if (scope.ngChange) scope.$eval(scope.ngChange);
                  return;
                }
              }
              // Update timepicker (watch on ng-model in timepicker does not use object equality),
              // also if the ngModel was not a Date, convert it to date
              newTime = new Date(newTime);

              if (isNaN(newTime.getTime()) === false) {
                scope.time = newTime; // change the time in timepicker
                if (firstTimeAssign) {
                  firstTimeAssign = false;
                }
                else if (scope.ngChange) {
                  scope.$eval(scope.ngChange);
                }
              }
            }
            updateMinTime();
            updateMaxTime();
          }, true);

          scope.$watch(function () {
            return scope.datetimepickerForm && scope.datetimepickerForm.$error;
          }, function (errors) {
            if (angular.isUndefined(errors)) {
              return;
            }
            Object.keys(ctrl.$error).forEach(function (error) {
              ctrl.$setValidity(error, true);
            });
            Object.keys(errors).forEach(function (error) {
              ctrl.$setValidity(error, false);
            });
          }, true);

          scope.$watch(function () {
            return scope.datetimepickerForm && (scope.datetimepickerForm.timepicker.$touched || scope.datetimepickerForm.datepicker.$touched);
          }, function (touched) {
            if (touched) {
              ctrl.$setTouched();
            }
          });

          scope.$watch(function () {
            return scope.datetimepickerForm && scope.datetimepickerForm.$dirty;
          }, function (dirty) {
            if (dirty) {
              ctrl.$setDirty();
            }
          });

          scope.$watch('dateOpened', function (value) {
            scope.innerDateOpened = value;
          });
          scope.$watch('innerDateOpened', function (value) {
            if (angular.isDefined(scope.dateOpened)) {
              scope.dateOpened = value;
            }
          });
          scope.$watch('dateOptions.minDate', function (value) {
            updateMinTime();
          });
          scope.$watch('timeMin', function (value) {
            updateMinTime();
          });
          scope.$watch('dateOptions.maxDate', function (value) {
            updateMaxTime();
          });
          scope.$watch('timeMax', function (value) {
            updateMaxTime();
          });
        }
      }
    }
  ]).directive('isolateForm', [function () {
  return {
    restrict: 'A',
    require: '?form',
    link: function (scope, elm, attrs, ctrl) {
      if (!ctrl) {
        return;
      }
      // Do a copy of the controller
      var ctrlCopy = {};
      angular.copy(ctrl, ctrlCopy);

      // Get the parent of the form
      var parent = elm.parent().controller('form');
      if (!parent) {
        return;
      }
      // Remove parent link to the controller
      parent.$removeControl(ctrl);

      // Replace form controller with an "isolated form"
      var isolatedFormCtrl = {
        $setValidity: function (validationToken, isValid, control) {
          ctrlCopy.$setValidity(validationToken, isValid, control);
          parent.$setValidity(validationToken, true, ctrl);
        }
      };
      angular.extend(ctrl, isolatedFormCtrl);
    }
  };
}]);
<|endoftext|>


<commit_before>/* Copyright 2014 by Krzysztof Daniel

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.*/

var db = require('./db').database;
var logger = require('./util/log.js').log.getLogger('user');
var request = require('request');

logger.setLevel('ALL');

var user = function() {

	var self = this;

	this.fetchStormPathProviderData = function(account, res, next) {
		account.getProviderData(function(err, providerData) {
			if (err) {
				logger.error('error while getting data for', account.href, err);
				return;
			}
			if (!providerData) {
				logger.warn("no data for " + account.href);
				return;
			}
			if (providerData.providerId === 'google') {
				logger.debug('got google stormpath data for ' + account.href);
				account.providerData = providerData;
				self.fetchAndStoreGoogleProfile(account, account.providerData.accessToken, next);
			} else {
				logger.error('support for ' + providerData.providerId + ' not implemented');
				next();
			}
		});
	};

	this.migrateMapsFromOldLogin = function(account, next){
		var aggregatedID = account.providerData.providerId + account.customData.googleProfile.id;
		db.users.find({
			aggregatedID : aggregatedID
		}).toArray(function(err, obj) {
			if(err){
				logger.error('could not find aggregatedID', aggregatedID, err);
				return;
			}
			if(obj.length > 0){
				var legacyId = obj[0]._id;
				logger.debug('migrating maps', aggregatedID, err);
				var updateResult = db.maps.update({
					userId : legacyId
				}, {
					$set : {
						userIdGoogle : account.href
					}
				}, {
					multi : true
				}, function(error, value){
					logger.debug('Migrated maps', error, value);
					next();
				});
			} else {
				next();
			}
		});
	};

	this.fetchAndStoreGoogleProfile = function(account, token, next) {
		logger.debug('requested google profile  for ' + account);
		request('https://www.googleapis.com/oauth2/v2/userinfo?access_token='
				+ token, function(err, res1, body) {
			if (err) {
				logger.error(err);
				return;
			}
			if (res1.statusCode !== 200) {
				return logger.error('Invalid access token: ' + body);
			} else {
				account.customData.googleProfile = JSON.parse(body);
				account.save(function(err) {
					if (err) {
						logger.error('error while getting user profile' + err);
					}
					logger.debug('stored google profile  for ' + account);
					self.migrateMapsFromOldLogin(account, next);
				});
			}
		});
	};

	this.normalizeLoginInfo = function(account, res, next) {
		self.fetchStormPathProviderData(account, res, next);
	};

};

exports.user = new user();<commit_msg>improve formatting and remove the warning.<commit_after>/* Copyright 2014 by Krzysztof Daniel

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.*/

var db = require('./db').database;
var logger = require('./util/log.js').log.getLogger('user');
var request = require('request');

logger.setLevel('ALL');

var user = function() {

	var self = this;

	this.fetchStormPathProviderData = function(account, res, next) {
		account.getProviderData(function(err, providerData) {
			if (err) {
				logger.error('error while getting data for', account.href, err);
				return;
			}
			if (!providerData) {
				logger.warn("no data for " + account.href);
				return;
			}
			if (providerData.providerId === 'google') {
				logger.debug('got google stormpath data for ' + account.href);
				account.providerData = providerData;
				self.fetchAndStoreGoogleProfile(account, account.providerData.accessToken, next);
			} else {
				logger.error('support for ' + providerData.providerId + ' not implemented');
				next();
			}
		});
	};

	this.migrateMapsFromOldLogin = function(account, next){
		var aggregatedID = account.providerData.providerId + account.customData.googleProfile.id;
		db.users.find({
			aggregatedID : aggregatedID
		}).toArray(function(err, obj) {
			if(err){
				logger.error('could not find aggregatedID', aggregatedID, err);
				return;
			}
			if(obj.length > 0){
				var legacyId = obj[0]._id;
				logger.debug('migrating maps', aggregatedID, err);
				var updateResult = db.maps.update({
					userId : legacyId
				}, {
					$set : {
						userIdGoogle : account.href
					}
				}, {
					multi : true
				}, function(error, value){
					logger.debug('Migrated maps', error, value);
					next();
				});
			} else {
				next();
			}
		});
	};

	this.fetchAndStoreGoogleProfile = function(account, token, next) {
		logger.debug('requested google profile  for ' + account);
		request('https://www.googleapis.com/oauth2/v2/userinfo?access_token=' + token,
			function(err, res1, body) {
				if (err) {
					logger.error(err);
					return;
				}
				if (res1.statusCode !== 200) {
					return logger.error('Invalid access token: ' + body);
				} else {
					account.customData.googleProfile = JSON.parse(body);
					account.save(function(err) {
						if (err) {
							logger.error('error while getting user profile' + err);
						}
						logger.debug('stored google profile  for ' + account);
						self.migrateMapsFromOldLogin(account, next);
					});
				}
			});
	};

	this.normalizeLoginInfo = function(account, res, next) {
		self.fetchStormPathProviderData(account, res, next);
	};

};

exports.user = new user();<|endoftext|>


<commit_before>
const expect = require('chai').expect;
const RG = require('../../../client/src/battles');

/* Updates given systems in given order.*/
const updateSystems = function(systems) {
    for (let i = 0; i < systems.length; i++) {
        systems[i].update();
    }
};

describe('How hunger system works', function() {
    it('Subtracts energy from actors with hunger', function() {
        const system = new RG.System.Hunger('Hunger', ['Hunger', 'Action']);
        const hunger = new RG.Component.Hunger(2000);
        const action = new RG.Component.Action();
        const player = RG.FACT.createPlayer('Player', {});
        player.add('Hunger', hunger);
        player.add('Action', action);
        action.addEnergy(100);
        expect(player.has('Hunger')).to.equal(true);
        expect(system.entities[player.getID()]).to.equal(player);
        expect(player.get('Action').getEnergy()).to.equal(100);
        system.update();
        expect(player.get('Hunger').getEnergy()).to.equal(2000 - 100);
    });
});

describe('How items/loot is dropped by monsters', function() {
    it('Drops loot when lethal damage is dealt', function() {
        const level = RG.FACT.createLevel('arena', 20, 20);

        const monsterStats = {hp: 5, att: 1, def: 1, prot: 1};
        const monster = RG.FACT.createActor('TestMonster', monsterStats);
        const humanStats = {hp: 5, att: 1, def: 1, prot: 1};
        const human = RG.FACT.createActor('Human', humanStats);

        const dSystem = new RG.System.Damage('Damage', ['Damage']);
        const systems = [dSystem];

        const lootItem = new RG.Item.Base('Loot item');
        const loot = new RG.Component.Loot(lootItem);

        const invItem = new RG.Item.Weapon('Sword');

        monster.getInvEq().addItem(invItem);
        monster.add('Loot', loot);
        const dmgComp = new RG.Component.Damage(6, 'fire');
        dmgComp.setSource(human);
        monster.add('Damage', dmgComp);
        expect(dSystem.entities.hasOwnProperty(monster.getID())).to.equal(true);

        const lootCell = level.getMap().getCell(3, 6);
        level.addActor(monster, 3, 6);
        expect(lootItem.getOwner()).to.equal(null);
        expect(lootCell.hasProp('items')).to.equal(false);
        updateSystems(systems);
        expect(monster.get('Health').getHP()).to.equal(0);
        expect(lootItem.getOwner()).to.equal(lootCell);
        expect(lootCell.hasProp('items')).to.equal(true);

        // Check for the dropped inventory item
        const items = lootCell.getProp(RG.TYPE_ITEM);
        expect(items).to.have.length(2);
    });
});

describe('System.Damage', () => {
    it('handles adding components on hit', () => {
        const level = RG.FACT.createLevel('arena', 10, 10);
        const dSystem = new RG.System.Damage('Damage', ['Damage']);
        const systems = [dSystem];

        const poisonSword = new RG.Item.Weapon('Sword of Poison');
        const addOnHit = new RG.Component.AddOnHit();
        const poisonComp = new RG.Component.Poison();
        addOnHit.addComp(poisonComp);
        poisonComp.duration = '1d6 + 5';
        poisonSword.add('AddOnHit', addOnHit);
        const human = new RG.Actor.Rogue('Human');
        human.getInvEq().addItem(poisonSword);
        human.getInvEq().equipItem(poisonSword);
        const beast = new RG.Actor.Rogue('Beast');

        const dmgComp = new RG.Component.Damage(10, 'slash');
        dmgComp.setSource(human);
        dmgComp.setWeapon(poisonSword);
        beast.add('Damage', dmgComp);

        updateSystems(systems);
        expect(beast.has('Poison')).to.equal(true);
    });
});
<commit_msg>Renamed addComp -> setComp.<commit_after>
const expect = require('chai').expect;
const RG = require('../../../client/src/battles');

/* Updates given systems in given order.*/
const updateSystems = function(systems) {
    for (let i = 0; i < systems.length; i++) {
        systems[i].update();
    }
};

describe('How hunger system works', function() {
    it('Subtracts energy from actors with hunger', function() {
        const system = new RG.System.Hunger('Hunger', ['Hunger', 'Action']);
        const hunger = new RG.Component.Hunger(2000);
        const action = new RG.Component.Action();
        const player = RG.FACT.createPlayer('Player', {});
        player.add('Hunger', hunger);
        player.add('Action', action);
        action.addEnergy(100);
        expect(player.has('Hunger')).to.equal(true);
        expect(system.entities[player.getID()]).to.equal(player);
        expect(player.get('Action').getEnergy()).to.equal(100);
        system.update();
        expect(player.get('Hunger').getEnergy()).to.equal(2000 - 100);
    });
});

describe('How items/loot is dropped by monsters', function() {
    it('Drops loot when lethal damage is dealt', function() {
        const level = RG.FACT.createLevel('arena', 20, 20);

        const monsterStats = {hp: 5, att: 1, def: 1, prot: 1};
        const monster = RG.FACT.createActor('TestMonster', monsterStats);
        const humanStats = {hp: 5, att: 1, def: 1, prot: 1};
        const human = RG.FACT.createActor('Human', humanStats);

        const dSystem = new RG.System.Damage('Damage', ['Damage']);
        const systems = [dSystem];

        const lootItem = new RG.Item.Base('Loot item');
        const loot = new RG.Component.Loot(lootItem);

        const invItem = new RG.Item.Weapon('Sword');

        monster.getInvEq().addItem(invItem);
        monster.add('Loot', loot);
        const dmgComp = new RG.Component.Damage(6, 'fire');
        dmgComp.setSource(human);
        monster.add('Damage', dmgComp);
        expect(dSystem.entities.hasOwnProperty(monster.getID())).to.equal(true);

        const lootCell = level.getMap().getCell(3, 6);
        level.addActor(monster, 3, 6);
        expect(lootItem.getOwner()).to.equal(null);
        expect(lootCell.hasProp('items')).to.equal(false);
        updateSystems(systems);
        expect(monster.get('Health').getHP()).to.equal(0);
        expect(lootItem.getOwner()).to.equal(lootCell);
        expect(lootCell.hasProp('items')).to.equal(true);

        // Check for the dropped inventory item
        const items = lootCell.getProp(RG.TYPE_ITEM);
        expect(items).to.have.length(2);
    });
});

describe('System.Damage', () => {
    it('handles adding components on hit', () => {
        const level = RG.FACT.createLevel('arena', 10, 10);
        const dSystem = new RG.System.Damage('Damage', ['Damage']);
        const systems = [dSystem];

        const poisonSword = new RG.Item.Weapon('Sword of Poison');
        const addOnHit = new RG.Component.AddOnHit();
        const poisonComp = new RG.Component.Poison();
        addOnHit.setComp(poisonComp);
        poisonComp.duration = '1d6 + 5';
        poisonSword.add('AddOnHit', addOnHit);
        const human = new RG.Actor.Rogue('Human');
        human.getInvEq().addItem(poisonSword);
        human.getInvEq().equipItem(poisonSword);
        const beast = new RG.Actor.Rogue('Beast');

        const dmgComp = new RG.Component.Damage(10, 'slash');
        dmgComp.setSource(human);
        dmgComp.setWeapon(poisonSword);
        beast.add('Damage', dmgComp);

        updateSystems(systems);
        expect(beast.has('Poison')).to.equal(true);
    });
});
<|endoftext|>


<commit_before>// Client side code for the Problem Builder Dashboard XBlock
// So far, this code is only used to generate a downloadable report.
function ExportBase(runtime, element, initData) {
    "use strict";

    var reportTemplate = initData.reportTemplate;

    var generateDataUriFromImageURL = function(imgURL) {
        // Given the URL to an image, IF the image has already been cached by the browser,
        // returns a data: URI with the contents of the image (image will be converted to PNG)

        // Expand relative urls and urls without an explicit protocol into absolute urls
        var a = document.createElement('a');
        a.href = imgURL;
        imgURL = a.href;

        // If the image is from another domain, just return its URL. We can't
        // create a data URL from cross-domain images:
        // https://html.spec.whatwg.org/multipage/scripting.html#dom-canvas-todataurl
        if (a.origin !== window.location.origin)
            return imgURL;

        var img = new Image();
        img.src = imgURL;
        if (!img.complete)
            return imgURL;

        // Create an in-memory canvas from which we can extract a data URL:
        var canvas = document.createElement("canvas");
        canvas.width = img.naturalWidth;
        canvas.height = img.naturalHeight;
        // Draw the image onto our temporary canvas:
        canvas.getContext('2d').drawImage(img, 0, 0);
        return canvas.toDataURL("image/png");
    };

    var unicodeStringToBase64 = function(str) {
        // Convert string to base64. A bit weird in order to support unicode, per
        // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/btoa
        return window.btoa(unescape(encodeURIComponent(str)));
    };

    var downloadReport = function(ev) {
        // Download Report:
        // Change the URL to a data: URI before continuing with the click event.
        if ($(this).attr('href').charAt(0) == '#') {
            var $report = $(initData.reportContentSelector, element).clone();
            // Convert all images in $report to data URIs:
            $report.find('image').each(function() {
                var origURL = $(this).attr('xlink:href');
                $(this).attr('xlink:href', generateDataUriFromImageURL(origURL));
            });
            // Take the resulting HTML and put it into the template we have:
            var mentoringTableContainer = $report.find('.mentoring-table-container');
            console.log(mentoringTableContainer.context);
            var cache_width = mentoringTableContainer.width();
            var wrapperHTML = reportTemplate.replace('REPORT_GOES_HERE', $report.html());

            // createPDF(mentoringTableContainer, cache_width);
            var doc = new jsPDF({
              unit:'px',
              format:'a4'
            });

            doc.fromHTML(mentoringTableContainer.context, 15, 15, {
                'width': 170
            });

            doc.save('report.pdf');

            var dataURI = "data:text/html;base64," + unicodeStringToBase64(wrapperHTML);
            $(this).attr('href', dataURI);
        }
    };

    function createPDF(html, cache_width){
     getCanvas(html).then(function(canvas){
      var
      img = canvas.toDataURL("image/png"),
      doc = new jsPDF({
              unit:'px',
              format:'a4'
            });
            doc.addImage(img, 'JPEG', 20, 20);
            doc.save('report.pdf');
            html.width(cache_width);
     });
    }

    function getCanvas(html){
     var a4  =[ 595.28,  841.89];
     html.width((a4[0]*1.33333) -80).css('max-width','none');
     console.log(html.context);
     return html2canvas(html.context,{
         imageTimeout:2000,
         removeContainer:true
        });
    }

    var $downloadLink = $('.report-download-link', element);
    $downloadLink.on('click', downloadReport);
}

function PBDashboardBlock(runtime, element, initData) {
    new ExportBase(runtime, element, initData);
}

function MentoringTableBlock(runtime, element, initData) {
    // Display an excerpt for long answers, with a "more" link to display the full text

    var $element = $(element),
        $shareButton = $element.find('.mentoring-share-button'),
        $doShareButton = $element.find('.do-share-button'),
        $shareMenu = $element.find('.mentoring-share-with'),
        $displayDropdown = $element.find('.mentoring-display-dropdown'),
        $errorHolder = $element.find('.share-errors'),
        $deleteShareButton = $element.find('.remove-share'),
        $newShareContainer = $($element.find('.new-share-container')[0]),
        $addShareField = $($element.find('.add-share-field')[0]),
        $notification = $($element.find('.share-notification')),
        $closeNotification = $($element.find('.notification-close')),
        tableLoadURL = runtime.handlerUrl(element, 'table_render'),
        deleteShareUrl = runtime.handlerUrl(element, 'remove_share'),
        sharedListLoadUrl = runtime.handlerUrl(element, 'get_shared_list'),
        clearNotificationUrl = runtime.handlerUrl(element, 'clear_notification'),
        shareResultsUrl = runtime.handlerUrl(element, 'share_results');

    function loadTable(data) {
        $element.find('.mentoring-table-target').html(data['content']);
        $('.answer-table', element).shorten({
            moreText: 'more',
            lessText: 'less',
            showChars: '500'
        });
    }

    function errorMessage(event) {
        $errorHolder.text(JSON.parse(event.responseText)['error'])
    }

    function sharedRefresh(data) {
        $element.find('.shared-with-container').html(data['content']);
        $deleteShareButton = $($deleteShareButton.selector);
        $deleteShareButton.on('click', deleteShare);
    }

    function postShareRefresh(data) {
        sharedRefresh(data);
        $element.find(".new-share-container").each(function(index, container) {
            if (index === 0) {
                var $container = $(container);
                $container.find('.add-share-username').val('');
                $container.find('.add-share-field').show();
                return;
            }
            $(container).remove()
        });
        $errorHolder.html('');
    }

    function postShare() {
        $.ajax({
            type: "POST",
            url: sharedListLoadUrl,
            data: JSON.stringify({}),
            success: postShareRefresh,
            error: errorMessage
        });
    }

    function updateShare() {
        var usernames = [];
        $element.find('.add-share-username').each(function(index, username) {
            usernames.push($(username).val())
        });
        $.ajax({
            type: "POST",
            url: shareResultsUrl,
            data: JSON.stringify({'usernames': usernames}),
            success: postShare,
            error: errorMessage
        });
    }

    function menuHider(event) {
        if (!$(event.target).closest($shareMenu).length) {
            // We're clicking outside of the menu, so hide it.
            $shareMenu.hide();
            $(document).off('click.mentoring_share_menu_hide');
        }
    }

    $shareButton.on('click', function (event) {
        if (!$shareMenu.is(':visible')){
            event.stopPropagation();
            $(document).on('click.mentoring_share_menu_hide', menuHider);
            $shareMenu.show();
        }
    });
    $doShareButton.on('click', updateShare);

    function postLoad(data) {
        loadTable(data);
        new ExportBase(runtime, element, initData);
    }

    $.ajax({
        type: "POST",
        url: tableLoadURL,
        data: JSON.stringify({'target_username': $displayDropdown.val()}),
        success: postLoad
    });

    $.ajax({
        type: "POST",
        url: sharedListLoadUrl,
        data: JSON.stringify({}),
        success: sharedRefresh
    });

    $displayDropdown.on('change', function () {
        if ($displayDropdown[0].selectedIndex !== 0) {
            $shareButton.prop('disabled', true);
            $element.find('.report-download-container').hide();
        } else {
            $shareButton.prop('disabled', false);
            $element.find('.report-download-container').show();
        }
        $.ajax({
            type: "POST",
            url: tableLoadURL,
            data: JSON.stringify({'target_username': $displayDropdown.val()}),
            success: loadTable
        })
    });

    function addShare() {
        var container = $newShareContainer.clone();
        container.find('.add-share-username').val('');
        container.insertAfter($element.find('.new-share-container').last());
        container.find('.add-share-field').on('click', addShare);
        var buttons = $element.find('.new-share-container .add-share-field');
        buttons.hide();
        buttons.last().show();
    }

    function deleteShare(event) {
        event.preventDefault();
        $.ajax({
            type: "POST",
            url: deleteShareUrl,
            data: JSON.stringify({'username': $(event.target).parent().prev()[0].innerHTML}),
            success: function () {
                $(event.target).parent().parent().remove();
                $errorHolder.html('');
            },
            error: errorMessage
        });
    }

    $closeNotification.on('click', function () {
        // Don't need server approval to hide it.
        $notification.hide();
        $.ajax({
            type: "POST",
            url: clearNotificationUrl,
            data: JSON.stringify({'usernames': $notification.data('shared')})
        })
    });

    $addShareField.on('click', addShare);
}
<commit_msg>use the whole report as problem builder gives it<commit_after>// Client side code for the Problem Builder Dashboard XBlock
// So far, this code is only used to generate a downloadable report.
function ExportBase(runtime, element, initData) {
    "use strict";

    var reportTemplate = initData.reportTemplate;

    var generateDataUriFromImageURL = function(imgURL) {
        // Given the URL to an image, IF the image has already been cached by the browser,
        // returns a data: URI with the contents of the image (image will be converted to PNG)

        // Expand relative urls and urls without an explicit protocol into absolute urls
        var a = document.createElement('a');
        a.href = imgURL;
        imgURL = a.href;

        // If the image is from another domain, just return its URL. We can't
        // create a data URL from cross-domain images:
        // https://html.spec.whatwg.org/multipage/scripting.html#dom-canvas-todataurl
        if (a.origin !== window.location.origin)
            return imgURL;

        var img = new Image();
        img.src = imgURL;
        if (!img.complete)
            return imgURL;

        // Create an in-memory canvas from which we can extract a data URL:
        var canvas = document.createElement("canvas");
        canvas.width = img.naturalWidth;
        canvas.height = img.naturalHeight;
        // Draw the image onto our temporary canvas:
        canvas.getContext('2d').drawImage(img, 0, 0);
        return canvas.toDataURL("image/png");
    };

    var unicodeStringToBase64 = function(str) {
        // Convert string to base64. A bit weird in order to support unicode, per
        // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/btoa
        return window.btoa(unescape(encodeURIComponent(str)));
    };

    var downloadReport = function(ev) {
        // Download Report:
        // Change the URL to a data: URI before continuing with the click event.
        if ($(this).attr('href').charAt(0) == '#') {
            var $report = $(initData.reportContentSelector, element).clone();
            // Convert all images in $report to data URIs:
            $report.find('image').each(function() {
                var origURL = $(this).attr('xlink:href');
                $(this).attr('xlink:href', generateDataUriFromImageURL(origURL));
            });
            // Take the resulting HTML and put it into the template we have:
            var mentoringTableContainer = $report.find('.mentoring-table-container');
            console.log($report.html());
            var cache_width = mentoringTableContainer.width();
            var wrapperHTML = reportTemplate.replace('REPORT_GOES_HERE', $report.html());

            // createPDF(mentoringTableContainer, cache_width);
            var doc = new jsPDF({
              unit:'px',
              format:'a4'
            });

            doc.fromHTML($report.html()t, 15, 15, {
                'width': 170
            });

            doc.save('report.pdf');

            var dataURI = "data:text/html;base64," + unicodeStringToBase64(wrapperHTML);
            $(this).attr('href', dataURI);
        }
    };

    function createPDF(html, cache_width){
     getCanvas(html).then(function(canvas){
      var
      img = canvas.toDataURL("image/png"),
      doc = new jsPDF({
              unit:'px',
              format:'a4'
            });
            doc.addImage(img, 'JPEG', 20, 20);
            doc.save('report.pdf');
            html.width(cache_width);
     });
    }

    function getCanvas(html){
     var a4  =[ 595.28,  841.89];
     html.width((a4[0]*1.33333) -80).css('max-width','none');
     console.log(html.context);
     return html2canvas(html.context,{
         imageTimeout:2000,
         removeContainer:true
        });
    }

    var $downloadLink = $('.report-download-link', element);
    $downloadLink.on('click', downloadReport);
}

function PBDashboardBlock(runtime, element, initData) {
    new ExportBase(runtime, element, initData);
}

function MentoringTableBlock(runtime, element, initData) {
    // Display an excerpt for long answers, with a "more" link to display the full text

    var $element = $(element),
        $shareButton = $element.find('.mentoring-share-button'),
        $doShareButton = $element.find('.do-share-button'),
        $shareMenu = $element.find('.mentoring-share-with'),
        $displayDropdown = $element.find('.mentoring-display-dropdown'),
        $errorHolder = $element.find('.share-errors'),
        $deleteShareButton = $element.find('.remove-share'),
        $newShareContainer = $($element.find('.new-share-container')[0]),
        $addShareField = $($element.find('.add-share-field')[0]),
        $notification = $($element.find('.share-notification')),
        $closeNotification = $($element.find('.notification-close')),
        tableLoadURL = runtime.handlerUrl(element, 'table_render'),
        deleteShareUrl = runtime.handlerUrl(element, 'remove_share'),
        sharedListLoadUrl = runtime.handlerUrl(element, 'get_shared_list'),
        clearNotificationUrl = runtime.handlerUrl(element, 'clear_notification'),
        shareResultsUrl = runtime.handlerUrl(element, 'share_results');

    function loadTable(data) {
        $element.find('.mentoring-table-target').html(data['content']);
        $('.answer-table', element).shorten({
            moreText: 'more',
            lessText: 'less',
            showChars: '500'
        });
    }

    function errorMessage(event) {
        $errorHolder.text(JSON.parse(event.responseText)['error'])
    }

    function sharedRefresh(data) {
        $element.find('.shared-with-container').html(data['content']);
        $deleteShareButton = $($deleteShareButton.selector);
        $deleteShareButton.on('click', deleteShare);
    }

    function postShareRefresh(data) {
        sharedRefresh(data);
        $element.find(".new-share-container").each(function(index, container) {
            if (index === 0) {
                var $container = $(container);
                $container.find('.add-share-username').val('');
                $container.find('.add-share-field').show();
                return;
            }
            $(container).remove()
        });
        $errorHolder.html('');
    }

    function postShare() {
        $.ajax({
            type: "POST",
            url: sharedListLoadUrl,
            data: JSON.stringify({}),
            success: postShareRefresh,
            error: errorMessage
        });
    }

    function updateShare() {
        var usernames = [];
        $element.find('.add-share-username').each(function(index, username) {
            usernames.push($(username).val())
        });
        $.ajax({
            type: "POST",
            url: shareResultsUrl,
            data: JSON.stringify({'usernames': usernames}),
            success: postShare,
            error: errorMessage
        });
    }

    function menuHider(event) {
        if (!$(event.target).closest($shareMenu).length) {
            // We're clicking outside of the menu, so hide it.
            $shareMenu.hide();
            $(document).off('click.mentoring_share_menu_hide');
        }
    }

    $shareButton.on('click', function (event) {
        if (!$shareMenu.is(':visible')){
            event.stopPropagation();
            $(document).on('click.mentoring_share_menu_hide', menuHider);
            $shareMenu.show();
        }
    });
    $doShareButton.on('click', updateShare);

    function postLoad(data) {
        loadTable(data);
        new ExportBase(runtime, element, initData);
    }

    $.ajax({
        type: "POST",
        url: tableLoadURL,
        data: JSON.stringify({'target_username': $displayDropdown.val()}),
        success: postLoad
    });

    $.ajax({
        type: "POST",
        url: sharedListLoadUrl,
        data: JSON.stringify({}),
        success: sharedRefresh
    });

    $displayDropdown.on('change', function () {
        if ($displayDropdown[0].selectedIndex !== 0) {
            $shareButton.prop('disabled', true);
            $element.find('.report-download-container').hide();
        } else {
            $shareButton.prop('disabled', false);
            $element.find('.report-download-container').show();
        }
        $.ajax({
            type: "POST",
            url: tableLoadURL,
            data: JSON.stringify({'target_username': $displayDropdown.val()}),
            success: loadTable
        })
    });

    function addShare() {
        var container = $newShareContainer.clone();
        container.find('.add-share-username').val('');
        container.insertAfter($element.find('.new-share-container').last());
        container.find('.add-share-field').on('click', addShare);
        var buttons = $element.find('.new-share-container .add-share-field');
        buttons.hide();
        buttons.last().show();
    }

    function deleteShare(event) {
        event.preventDefault();
        $.ajax({
            type: "POST",
            url: deleteShareUrl,
            data: JSON.stringify({'username': $(event.target).parent().prev()[0].innerHTML}),
            success: function () {
                $(event.target).parent().parent().remove();
                $errorHolder.html('');
            },
            error: errorMessage
        });
    }

    $closeNotification.on('click', function () {
        // Don't need server approval to hide it.
        $notification.hide();
        $.ajax({
            type: "POST",
            url: clearNotificationUrl,
            data: JSON.stringify({'usernames': $notification.data('shared')})
        })
    });

    $addShareField.on('click', addShare);
}
<|endoftext|>