<commit_before>import {Fragment, Slice} from "../model"

import {Transform} from "./transform"
import {Step, StepResult} from "./step"
import {ShiftStep} from "./shift"
import {PosMap} from "./map"

// ;; Replace a part of the document with a slice of new content.
class ReplaceStep extends Step {
  // :: (number, number, Slice)
  // The given `slice` should fit the 'gap' between `from` and
  // `to`—the depths must line up, and the surrounding nodes must be
  // able to be joined with the open sides of the slice.
  constructor(from, to, slice) {
    super()
    this.from = from
    this.to = to
    this.slice = slice
  }

  apply(doc) {
    return StepResult.fromReplace(doc, this.from, this.to, this.slice)
  }

  posMap() {
    return new PosMap([this.from, this.to - this.from, this.slice.size])
  }

  invert(doc) {
    return new ReplaceStep(this.from, this.from + this.slice.size, doc.slice(this.from, this.to))
  }

  map(mapping) {
    let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
    if (from.deleted && to.deleted) return null
    return new ReplaceStep(from.pos, Math.max(from.pos, to.pos), this.slice)
  }

  static fromJSON(schema, json) {
    return new ReplaceStep(json.from, json.to, Slice.fromJSON(schema, json.slice))
  }
}

Step.register("replace", ReplaceStep)

// :: (number, number) → Transform
// Delete the content between the given positions.
Transform.prototype.delete = function(from, to) {
  if (from != to) this.replace(from, to, Slice.empty)
  return this
}

// :: (number, ?number, ?Slice) → Transform
// Replace the part of the document between `from` and `to` with the
// part of the `source` between `start` and `end`.
Transform.prototype.replace = function(from, to = from, slice = Slice.empty) {
  let $from = this.doc.resolve(from), $to = this.doc.resolve(to)
  let {fitted, distAfter} = fitSliceInto($from, $to, slice), fSize = fitted.size
  if (from == to && !fSize) return this
  this.step(new ReplaceStep(from, to, fitted))

  // If the endpoints of the replacement don't end right next to each
  // other, we may need to move text that occurs directly after the
  // slice to fit onto the inserted content. But only if there is text
  // before and after the cut, and if those endpoints aren't already
  // next to each other.
  if (!fSize || !$to.parent.isTextblock) return this
  let after = from + fSize
  let inner = !slice.size ? from : distAfter < 0 ? -1 : after - distAfter, $inner
  if (inner == -1 || inner == after ||
      !($inner = this.doc.resolve(inner)).parent.isTextblock ||
      !$inner.parent.canAppendFragment($to.parent.content))
    return this
  mergeTextblockAfter(this, $inner, this.doc.resolve(after))
  return this
}

// :: (number, number, union<Fragment, Node, [Node]>) → Transform
// Replace the given range with the given content, which may be a
// fragment, node, or array of nodes.
Transform.prototype.replaceWith = function(from, to, content) {
  return this.replace(from, to, new Slice(Fragment.from(content), 0, 0))
}

// :: (number, union<Fragment, Node, [Node]>) → Transform
// Insert the given content at the given position.
Transform.prototype.insert = function(pos, content) {
  return this.replaceWith(pos, pos, content)
}

// :: (number, string) → Transform
// Insert the given text at `pos`, inheriting the marks of the
// existing content at that position.
Transform.prototype.insertText = function(pos, text) {
  return this.insert(pos, this.doc.type.schema.text(text, this.doc.marksAt(pos)))
}

// :: (number, Node) → Transform
// Insert the given node at `pos`, inheriting the marks of the
// existing content at that position.
Transform.prototype.insertInline = function(pos, node) {
  return this.insert(pos, node.mark(this.doc.marksAt(pos)))
}

// This is an output variable for closeFragment and friends, used to
// track the distance between the end of the resulting slice and the
// end of the inserted content, so that we can find back the position
// afterwards.
let distAfter = 0

// : (ResolvedPos, ResolvedPos, Slice) → {fitted: Slice, distAfter: number}
// Mangle the content of a slice so that it fits between the given
// positions.
function fitSliceInto($from, $to, slice) {
  let base = $from.sameDepth($to)
  let placed = placeSlice($from, slice), outer = outerPlaced(placed)
  if (outer) base = Math.min(outer.depth, base)

  // distAfter starts negative, and is set to a positive value when
  // the end of the inserted content is placed.
  distAfter = -1e10 // FIXME kludge
  let fragment = closeFragment($from.node(base), fillBetween($from, $to, base, placed), $from, $to, base)
  return {fitted: new Slice(fragment, $from.depth - base, $to.depth - base),
          distAfter: distAfter - ($to.depth - base)}
}

function outerPlaced(placed) {
  for (let i = 0; i < placed.length; i++) if (placed[i]) return placed[i]
}

// Given a replaced range and a set of placements, produce a fragment
// that represents the fitted content. Recursive, called once for each
// depth where both sides are open, until the first placement.
function fillBetween($from, $to, depth, placed) {
  let fromNext = $from.depth > depth && $from.node(depth + 1)
  let toNext = $to.depth > depth && $to.node(depth + 1)
  let placedHere = placed[depth]

  // If there's depth left on both sides, the two are compatible, and
  // nothing is placed here, make a recursive call.
  if (fromNext && toNext && toNext.type.compatibleContent(fromNext.type) && !placedHere)
    return Fragment.from(closeNode(fromNext, fillBetween($from, $to, depth + 1, placed),
                                   $from, $to, depth + 1))

  // Otherwise, build up the content by combining the open pieces to
  // the left and right with the inserted content placed here.

  let content = Fragment.empty
  if (placedHere) {
    content = closeLeft(placedHere.content, placedHere.openLeft)
    if (placedHere.isEnd) distAfter = placedHere.openRight
  }

  distAfter--
  if (fromNext)
    content = content.addToStart(closeNode(fromNext, fillFrom($from, depth + 1, placed),
                                           $from, null, depth + 1))
  if (toNext)
    content = closeTo(content, $to, depth + 1, placedHere ? placedHere.openRight : 0)
  else if (placedHere)
    content = closeRight(content, placedHere.openRight)
  distAfter++

  return content
}

function fillFrom($from, depth, placed) {
  let placedHere = placed[depth], content = Fragment.empty
  if (placedHere) {
    content = closeRight(placedHere.content, placedHere.openRight)
    if (placedHere.isEnd) distAfter = placedHere.openRight
  }

  distAfter--
  if ($from.depth > depth)
    content = content.addToStart(closeNode($from.node(depth + 1), fillFrom($from, depth + 1, placed),
                                           $from, null, depth + 1))
  distAfter++

  return content
}

function closeTo(content, $to, depth, openDepth) {
  let after = $to.node(depth)
  if (openDepth == 0 || !content.lastChild.type.compatibleContent(after.type)) {
    let finish = closeNode(after, fillTo($to, depth), null, $to, depth)
    distAfter += finish.nodeSize
    return closeRight(content, openDepth).addToEnd(finish)
  }
  let inner = content.lastChild.content
  if (depth < $to.depth) inner = closeTo(inner, $to, depth + 1, openDepth - 1)
  return content.replaceChild(content.childCount - 1, after.copy(inner))
}

function fillTo(to, depth) {
  if (to.depth == depth) return Fragment.empty
  return Fragment.from(closeNode(to.node(depth + 1), fillTo(to, depth + 1), null, to, depth + 1))
}

// Closing nodes is the process of ensuring that they contain valid
// content, optionally changing the content (that is inside of the
// replace) to make sure.

function closeRight(content, openDepth) {
  if (openDepth == 0) return content
  let last = content.lastChild, closed = closeNode(last, closeRight(last.content, openDepth - 1))
  return closed == last ? content : content.replaceChild(content.childCount - 1, closed)
}

function closeLeft(content, openDepth) {
  if (openDepth == 0) return content
  let first = content.firstChild, closed = closeNode(first, first.content)
  return closed == first ? content : content.replaceChild(0, closed)
}

function closeFragment(refNode, content, $to, $from, depth) {
  let before = !$to ? Fragment.empty
      : $to.depth == depth ? $to.parent.content.cut(0, $to.parentOffset)
      : $to.node(depth).content.cutByIndex(0, $to.index(depth))
  let after = !$from ? Fragment.empty
      : $from.depth == depth ? $from.parent.content.cut($from.parentOffset)
      : $from.node(depth).content.cutByIndex($from.index(depth) + 1)
  let filled = refNode.type.contentExpr.fillContent(refNode.attrs, before, content, after)
  // FIXME what do we do when this fails?
  return filled.left.append(content).append(filled.right)
}

function closeNode(node, content, $to, $from, depth) {
  return node.copy(closeFragment(node, content, $to, $from, depth))
}

// Algorithm for 'placing' the elements of a slice into a gap:
//
// We consider the content of each node that is open to the left to be
// independently placeable. I.e. in <p("foo"), p("bar")>, when the
// paragraph on the left is open, "foo" can be placed (somewhere on
// the left side of the replacement gap) independently from p("bar").
//
// So placeSlice splits up a slice into a number of sub-slices,
// along with information on where they can be placed on the given
// left-side edge. It works by walking the open side of the slice,
// from the inside out, and trying to find a landing spot for each
// element, by simultaneously scanning over the gap side. When no
// place is found for an open node's content, it is left in that node.
//
// If the outer content can't be placed, a set of wrapper nodes is
// made up for it (by rooting it in the document node type using
// findWrapping), and the algorithm continues to iterate over those.
// This is guaranteed to find a fit, since both stacks now start with
// the same node type (doc).

function nodeLeft(slice, depth) {
  let content = slice.content
  for (let i = 1; i < depth; i++) content = content.firstChild.content
  return content.firstChild
}

function placeSlice($from, slice) {
  let dFrom = $from.depth, unplaced = null, openLeftUnplaced = 0
  let placed = [], parents = null

  for (let dSlice = slice.openLeft;; --dSlice) {
    let curType, curAttrs, curFragment
    if (dSlice >= 0) {
      if (dSlice > 0) { // Inside slice
        ;({type: curType, attrs: curAttrs, content: curFragment} = nodeLeft(slice, dSlice))
      } else if (dSlice == 0) { // Top of slice
        curFragment = slice.content
      }
      if (dSlice < slice.openLeft) curFragment = curFragment.cut(curFragment.firstChild.nodeSize)
    } else { // Outside slice
      curFragment = Fragment.empty
      curType = parents[parents.length + dSlice - 1]
    }
    if (unplaced)
      curFragment = curFragment.addToStart(unplaced)

    if (curFragment.size == 0 && dSlice <= 0) break

    let found = findPlacement(curFragment, $from, dFrom)
    if (found > -1) {
      if (curFragment.size > 0)
        placed[found] = {content: curFragment,
                         openLeft: openLeftUnplaced,
                         openRight: dSlice > 0 ? 0 : slice.openRight - dSlice,
                         isEnd: dSlice <= 0,
                         depth: found}
      if (dSlice <= 0) break
      unplaced = null
      openLeftUnplaced = 0
      dFrom = Math.max(0, found - 1)
    } else {
      if (dSlice == 0) {
        // FIXME this is dodgy -- might not find a fit, even if one
        // exists, because it searches based only on the first child.
        parents = $from.node(0).findWrappingAt($from.index(0), curFragment.child(0).type)
        if (!parents || !parents[parents.length - 1].contentExpr.matches(parents[parents.length - 1].defaultAttrs, curFragment)) break
        parents = [$from.node(0).type].concat(parents)
        curType = parents[parents.length - 1]
      }
      unplaced = curType.create(curAttrs, curFragment)
      openLeftUnplaced++
    }
  }

  return placed
}

function findPlacement(fragment, $from, start) {
  for (let d = start; d >= 0; d--) if ($from.node(d).canAppendFragment(fragment)) return d
  return -1
}

// When a replace ends in an (open) textblock, and the content that
// ends up before it also ends in an open textblock, the textblock
// after is moved to and connected with the one before it. This
// influences content outside of the replaced range, so it is not done
// as part of the replace step itself, but instead tacked on as a
// shift step.

function mergeTextblockAfter(tr, $inside, $after) {
  let base = $inside.sameDepth($after), dInside = $inside.depth - base, dAfter = $after.depth - base
  let delDepth = $after.depth - 1
  while (delDepth > base && $after.index(delDepth) + 1 == $after.node(delDepth).childCount)
    --delDepth

  tr.step(new ShiftStep($after.pos, $after.pos + $after.parent.content.size,
                        {overwrite: dInside + dAfter, close: 0, open: []},
                        {overwrite: $after.depth - delDepth, close: dInside, open: delDepth - base}))
}
<commit_msg>Change the interface for filling in content<commit_after>import {Fragment, Slice} from "../model"

import {Transform} from "./transform"
import {Step, StepResult} from "./step"
import {ShiftStep} from "./shift"
import {PosMap} from "./map"

// ;; Replace a part of the document with a slice of new content.
class ReplaceStep extends Step {
  // :: (number, number, Slice)
  // The given `slice` should fit the 'gap' between `from` and
  // `to`—the depths must line up, and the surrounding nodes must be
  // able to be joined with the open sides of the slice.
  constructor(from, to, slice) {
    super()
    this.from = from
    this.to = to
    this.slice = slice
  }

  apply(doc) {
    return StepResult.fromReplace(doc, this.from, this.to, this.slice)
  }

  posMap() {
    return new PosMap([this.from, this.to - this.from, this.slice.size])
  }

  invert(doc) {
    return new ReplaceStep(this.from, this.from + this.slice.size, doc.slice(this.from, this.to))
  }

  map(mapping) {
    let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
    if (from.deleted && to.deleted) return null
    return new ReplaceStep(from.pos, Math.max(from.pos, to.pos), this.slice)
  }

  static fromJSON(schema, json) {
    return new ReplaceStep(json.from, json.to, Slice.fromJSON(schema, json.slice))
  }
}

Step.register("replace", ReplaceStep)

// :: (number, number) → Transform
// Delete the content between the given positions.
Transform.prototype.delete = function(from, to) {
  if (from != to) this.replace(from, to, Slice.empty)
  return this
}

// :: (number, ?number, ?Slice) → Transform
// Replace the part of the document between `from` and `to` with the
// part of the `source` between `start` and `end`.
Transform.prototype.replace = function(from, to = from, slice = Slice.empty) {
  let $from = this.doc.resolve(from), $to = this.doc.resolve(to)
  let {fitted, distAfter} = fitSliceInto($from, $to, slice), fSize = fitted.size
  if (from == to && !fSize) return this
  this.step(new ReplaceStep(from, to, fitted))

  // If the endpoints of the replacement don't end right next to each
  // other, we may need to move text that occurs directly after the
  // slice to fit onto the inserted content. But only if there is text
  // before and after the cut, and if those endpoints aren't already
  // next to each other.
  if (!fSize || !$to.parent.isTextblock) return this
  let after = from + fSize
  let inner = !slice.size ? from : distAfter < 0 ? -1 : after - distAfter, $inner
  if (inner == -1 || inner == after ||
      !($inner = this.doc.resolve(inner)).parent.isTextblock ||
      !$inner.parent.canAppendFragment($to.parent.content))
    return this
  mergeTextblockAfter(this, $inner, this.doc.resolve(after))
  return this
}

// :: (number, number, union<Fragment, Node, [Node]>) → Transform
// Replace the given range with the given content, which may be a
// fragment, node, or array of nodes.
Transform.prototype.replaceWith = function(from, to, content) {
  return this.replace(from, to, new Slice(Fragment.from(content), 0, 0))
}

// :: (number, union<Fragment, Node, [Node]>) → Transform
// Insert the given content at the given position.
Transform.prototype.insert = function(pos, content) {
  return this.replaceWith(pos, pos, content)
}

// :: (number, string) → Transform
// Insert the given text at `pos`, inheriting the marks of the
// existing content at that position.
Transform.prototype.insertText = function(pos, text) {
  return this.insert(pos, this.doc.type.schema.text(text, this.doc.marksAt(pos)))
}

// :: (number, Node) → Transform
// Insert the given node at `pos`, inheriting the marks of the
// existing content at that position.
Transform.prototype.insertInline = function(pos, node) {
  return this.insert(pos, node.mark(this.doc.marksAt(pos)))
}

// This is an output variable for closeFragment and friends, used to
// track the distance between the end of the resulting slice and the
// end of the inserted content, so that we can find back the position
// afterwards.
let distAfter = 0

// : (ResolvedPos, ResolvedPos, Slice) → {fitted: Slice, distAfter: number}
// Mangle the content of a slice so that it fits between the given
// positions.
function fitSliceInto($from, $to, slice) {
  let base = $from.sameDepth($to)
  let placed = placeSlice($from, slice), outer = outerPlaced(placed)
  if (outer) base = Math.min(outer.depth, base)

  // distAfter starts negative, and is set to a positive value when
  // the end of the inserted content is placed.
  distAfter = -1e10 // FIXME kludge
  let fragment = closeFragment($from.node(base), fillBetween($from, $to, base, placed), $from, $to, base)
  return {fitted: new Slice(fragment, $from.depth - base, $to.depth - base),
          distAfter: distAfter - ($to.depth - base)}
}

function outerPlaced(placed) {
  for (let i = 0; i < placed.length; i++) if (placed[i]) return placed[i]
}

// Given a replaced range and a set of placements, produce a fragment
// that represents the fitted content. Recursive, called once for each
// depth where both sides are open, until the first placement.
function fillBetween($from, $to, depth, placed) {
  let fromNext = $from.depth > depth && $from.node(depth + 1)
  let toNext = $to.depth > depth && $to.node(depth + 1)
  let placedHere = placed[depth]

  // If there's depth left on both sides, the two are compatible, and
  // nothing is placed here, make a recursive call.
  if (fromNext && toNext && toNext.type.compatibleContent(fromNext.type) && !placedHere)
    return Fragment.from(closeNode(fromNext, fillBetween($from, $to, depth + 1, placed),
                                   $from, $to, depth + 1))

  // Otherwise, build up the content by combining the open pieces to
  // the left and right with the inserted content placed here.

  let content = Fragment.empty
  if (placedHere) {
    content = closeLeft(placedHere.content, placedHere.openLeft)
    if (placedHere.isEnd) distAfter = placedHere.openRight
  }

  distAfter--
  if (fromNext)
    content = content.addToStart(closeNode(fromNext, fillFrom($from, depth + 1, placed),
                                           $from, null, depth + 1))
  if (toNext)
    content = closeTo(content, $to, depth + 1, placedHere ? placedHere.openRight : 0)
  else if (placedHere)
    content = closeRight(content, placedHere.openRight)
  distAfter++

  return content
}

function fillFrom($from, depth, placed) {
  let placedHere = placed[depth], content = Fragment.empty
  if (placedHere) {
    content = closeRight(placedHere.content, placedHere.openRight)
    if (placedHere.isEnd) distAfter = placedHere.openRight
  }

  distAfter--
  if ($from.depth > depth)
    content = content.addToStart(closeNode($from.node(depth + 1), fillFrom($from, depth + 1, placed),
                                           $from, null, depth + 1))
  distAfter++

  return content
}

function closeTo(content, $to, depth, openDepth) {
  let after = $to.node(depth)
  if (openDepth == 0 || !content.lastChild.type.compatibleContent(after.type)) {
    let finish = closeNode(after, fillTo($to, depth), null, $to, depth)
    distAfter += finish.nodeSize
    return closeRight(content, openDepth).addToEnd(finish)
  }
  let inner = content.lastChild.content
  if (depth < $to.depth) inner = closeTo(inner, $to, depth + 1, openDepth - 1)
  return content.replaceChild(content.childCount - 1, after.copy(inner))
}

function fillTo(to, depth) {
  if (to.depth == depth) return Fragment.empty
  return Fragment.from(closeNode(to.node(depth + 1), fillTo(to, depth + 1), null, to, depth + 1))
}

// Closing nodes is the process of ensuring that they contain valid
// content, optionally changing the content (that is inside of the
// replace) to make sure.

function closeRight(content, openDepth) {
  if (openDepth == 0) return content
  let last = content.lastChild, closed = closeNode(last, closeRight(last.content, openDepth - 1))
  return closed == last ? content : content.replaceChild(content.childCount - 1, closed)
}

function closeLeft(content, openDepth) {
  if (openDepth == 0) return content
  let first = content.firstChild, closed = closeNode(first, first.content)
  return closed == first ? content : content.replaceChild(0, closed)
}

function closeFragment(refNode, content, $to, $from, depth) {
  let before = !$to ? Fragment.empty
      : $to.depth == depth ? $to.parent.content.cut(0, $to.parentOffset)
      : $to.node(depth).content.cutByIndex(0, $to.index(depth))
  let after = !$from ? Fragment.empty
      : $from.depth == depth ? $from.parent.content.cut($from.parentOffset)
      : $from.node(depth).content.cutByIndex($from.index(depth) + 1)
  let expr = refNode.type.contentExpr
  content = expr.getMatchAt(refNode.attrs, before).fillBefore(content).append(content)
  return content.append(expr.getMatchAt(refNode.attrs, before.append(content)).fillBefore(after, true))
  // FIXME verify
}

function closeNode(node, content, $to, $from, depth) {
  return node.copy(closeFragment(node, content, $to, $from, depth))
}

// Algorithm for 'placing' the elements of a slice into a gap:
//
// We consider the content of each node that is open to the left to be
// independently placeable. I.e. in <p("foo"), p("bar")>, when the
// paragraph on the left is open, "foo" can be placed (somewhere on
// the left side of the replacement gap) independently from p("bar").
//
// So placeSlice splits up a slice into a number of sub-slices,
// along with information on where they can be placed on the given
// left-side edge. It works by walking the open side of the slice,
// from the inside out, and trying to find a landing spot for each
// element, by simultaneously scanning over the gap side. When no
// place is found for an open node's content, it is left in that node.
//
// If the outer content can't be placed, a set of wrapper nodes is
// made up for it (by rooting it in the document node type using
// findWrapping), and the algorithm continues to iterate over those.
// This is guaranteed to find a fit, since both stacks now start with
// the same node type (doc).

function nodeLeft(slice, depth) {
  let content = slice.content
  for (let i = 1; i < depth; i++) content = content.firstChild.content
  return content.firstChild
}

function placeSlice($from, slice) {
  let dFrom = $from.depth, unplaced = null, openLeftUnplaced = 0
  let placed = [], parents = null

  for (let dSlice = slice.openLeft;; --dSlice) {
    let curType, curAttrs, curFragment
    if (dSlice >= 0) {
      if (dSlice > 0) { // Inside slice
        ;({type: curType, attrs: curAttrs, content: curFragment} = nodeLeft(slice, dSlice))
      } else if (dSlice == 0) { // Top of slice
        curFragment = slice.content
      }
      if (dSlice < slice.openLeft) curFragment = curFragment.cut(curFragment.firstChild.nodeSize)
    } else { // Outside slice
      curFragment = Fragment.empty
      curType = parents[parents.length + dSlice - 1]
    }
    if (unplaced)
      curFragment = curFragment.addToStart(unplaced)

    if (curFragment.size == 0 && dSlice <= 0) break

    let found = findPlacement(curFragment, $from, dFrom)
    if (found > -1) {
      if (curFragment.size > 0)
        placed[found] = {content: curFragment,
                         openLeft: openLeftUnplaced,
                         openRight: dSlice > 0 ? 0 : slice.openRight - dSlice,
                         isEnd: dSlice <= 0,
                         depth: found}
      if (dSlice <= 0) break
      unplaced = null
      openLeftUnplaced = 0
      dFrom = Math.max(0, found - 1)
    } else {
      if (dSlice == 0) {
        // FIXME this is dodgy -- might not find a fit, even if one
        // exists, because it searches based only on the first child.
        parents = $from.node(0).findWrappingAt($from.index(0), curFragment.child(0).type)
        if (!parents || !parents[parents.length - 1].contentExpr.matches(parents[parents.length - 1].defaultAttrs, curFragment)) break
        parents = [$from.node(0).type].concat(parents)
        curType = parents[parents.length - 1]
      }
      unplaced = curType.create(curAttrs, curFragment)
      openLeftUnplaced++
    }
  }

  return placed
}

function findPlacement(fragment, $from, start) {
  for (let d = start; d >= 0; d--) if ($from.node(d).canAppendFragment(fragment)) return d
  return -1
}

// When a replace ends in an (open) textblock, and the content that
// ends up before it also ends in an open textblock, the textblock
// after is moved to and connected with the one before it. This
// influences content outside of the replaced range, so it is not done
// as part of the replace step itself, but instead tacked on as a
// shift step.

function mergeTextblockAfter(tr, $inside, $after) {
  let base = $inside.sameDepth($after), dInside = $inside.depth - base, dAfter = $after.depth - base
  let delDepth = $after.depth - 1
  while (delDepth > base && $after.index(delDepth) + 1 == $after.node(delDepth).childCount)
    --delDepth

  tr.step(new ShiftStep($after.pos, $after.pos + $after.parent.content.size,
                        {overwrite: dInside + dAfter, close: 0, open: []},
                        {overwrite: $after.depth - delDepth, close: dInside, open: delDepth - base}))
}
<|endoftext|>


<commit_before>define([
	"dcl/dcl",
	"dojo/window", // winUtils.scrollIntoView
	"dojo/dom-style", // domStyle
	"./Widget",
	"./Invalidating"
], function (dcl, winUtils, domStyle, Widget, Invalidating) {

	// module:
	//		delite/FormWidget

	return dcl([Widget, Invalidating], {
		// summary:
		//		Mixin for widgets that extend HTMLElement, but conceptually correspond
		//		to native HTML elements such as `<checkbox>` or `<button>`,
		//		which can be children of a `<form>` node or a `deliteful/Form` widget.
		//
		// description:
		//		Represents a single HTML element.
		//		All these widgets should have these attributes just like native HTML input elements.
		//		You can set them during widget construction or afterwards, via `delite/Widget.set()`.
		//
		//		They also share some common methods.

		// name: [const] String
		//		Name used when submitting form; same as "name" attribute or plain HTML elements
		name: "",

		// alt: String
		//		Corresponds to the native HTML `<input>` element's attribute.
		alt: "",

		// value: String
		//		Corresponds to the native HTML `<input>` element's attribute.
		value: "",

		// scrollOnFocus: Boolean
		//		On focus, should this widget scroll into view?
		scrollOnFocus: true,

		// tabIndex: Number
		//        The order in which fields are traversed when user hits the tab key
		tabIndex: 0,

		// tabStops: [const] String
		//        Concatenated list of node names that can receive focus during tab operations
		tabStops: "focusNode", // should be "" if the widget's only tab stop is the outer root node

		// disabled: Boolean
		//		Should this widget respond to user input?
		//		In markup, this is specified as "disabled='disabled'", or just "disabled".
		disabled: false,

		preCreate: function () {
			this.addInvalidatingProperties(
				"disabled",
				"tabStops",
				"tabIndex"
			);
		},

		refreshRendering: dcl.after(function (args) {
			// summary:
			//		Handle disabled and tabIndex, across the tabStops and root node.
			//		No special processing is needed for tabStops other than just to refresh disable and tabIndex.
			var props = args[0];
			var self = this;
			var tabStops = this.tabStops.split(/[ ,]/);
			if (props.tabStops || props.disabled) {
				var isDisabled = this.disabled;
				if (this.valueNode && this.valueNode !== this) {
					this.valueNode.disabled = isDisabled; // prevent submit
				}
				tabStops.forEach(
					function (nodeName) {
						var node = self[nodeName];
						if (node !== self) {
							node.disabled = isDisabled;
						}
						// let JAWS know
						node.setAttribute("aria-disabled", "" + isDisabled);
					},
					this
				);
				if (!isDisabled) {
					this.removeAttribute("disabled");
				}
			}
			if (props.tabStops || props.tabIndex || props.disabled) {
				tabStops.forEach(
					function (nodeName) {
						var node = self[nodeName];
						if (node !== self) {
							if (self.disabled) {
								node.removeAttribute("tabindex");
							} else {
								node.tabIndex = self._get("tabIndex");
							}
						}
					},
					this
				);
			}
			return props; // for after advice
		}),

		_onFocus: dcl.before(function () {
			if (this.scrollOnFocus) {
				this.defer(function () {
					winUtils.scrollIntoView(this);
				}); // without defer, the input caret position can change on mouse click
			}
		}),

		isFocusable: function () {
			// summary:
			//		Tells if this widget is focusable or not.  Used internally by delite.
			// tags:
			//		protected
			return !this.disabled && this.focusNode && (domStyle.get(this, "display") !== "none");
		},

		focus: function () {
			// summary:
			//		Put focus on this widget
			if (!this.disabled && this.focusNode.focus) {
				try {
					this.focusNode.focus();
				} catch (e) {
					// squelch errors from hidden nodes
				}
			}
		}
	});
});
<commit_msg>remove scrollOnFocus flag, refs #177<commit_after>define([
	"dcl/dcl",
	"dojo/window", // winUtils.scrollIntoView
	"dojo/dom-style", // domStyle
	"./Widget",
	"./Invalidating"
], function (dcl, winUtils, domStyle, Widget, Invalidating) {

	// module:
	//		delite/FormWidget

	return dcl([Widget, Invalidating], {
		// summary:
		//		Mixin for widgets that extend HTMLElement, but conceptually correspond
		//		to native HTML elements such as `<checkbox>` or `<button>`,
		//		which can be children of a `<form>` node or a `deliteful/Form` widget.
		//
		// description:
		//		Represents a single HTML element.
		//		All these widgets should have these attributes just like native HTML input elements.
		//		You can set them during widget construction or afterwards, via `delite/Widget.set()`.
		//
		//		They also share some common methods.

		// name: [const] String
		//		Name used when submitting form; same as "name" attribute or plain HTML elements
		name: "",

		// alt: String
		//		Corresponds to the native HTML `<input>` element's attribute.
		alt: "",

		// value: String
		//		Corresponds to the native HTML `<input>` element's attribute.
		value: "",

		// tabIndex: Number
		//        The order in which fields are traversed when user hits the tab key
		tabIndex: 0,

		// tabStops: [const] String
		//        Concatenated list of node names that can receive focus during tab operations
		tabStops: "focusNode", // should be "" if the widget's only tab stop is the outer root node

		// disabled: Boolean
		//		Should this widget respond to user input?
		//		In markup, this is specified as "disabled='disabled'", or just "disabled".
		disabled: false,

		preCreate: function () {
			this.addInvalidatingProperties(
				"disabled",
				"tabStops",
				"tabIndex"
			);
		},

		refreshRendering: dcl.after(function (args) {
			// summary:
			//		Handle disabled and tabIndex, across the tabStops and root node.
			//		No special processing is needed for tabStops other than just to refresh disable and tabIndex.
			var props = args[0];
			var self = this;
			var tabStops = this.tabStops.split(/[ ,]/);
			if (props.tabStops || props.disabled) {
				var isDisabled = this.disabled;
				if (this.valueNode && this.valueNode !== this) {
					this.valueNode.disabled = isDisabled; // prevent submit
				}
				tabStops.forEach(
					function (nodeName) {
						var node = self[nodeName];
						if (node !== self) {
							node.disabled = isDisabled;
						}
						// let JAWS know
						node.setAttribute("aria-disabled", "" + isDisabled);
					},
					this
				);
				if (!isDisabled) {
					this.removeAttribute("disabled");
				}
			}
			if (props.tabStops || props.tabIndex || props.disabled) {
				tabStops.forEach(
					function (nodeName) {
						var node = self[nodeName];
						if (node !== self) {
							if (self.disabled) {
								node.removeAttribute("tabindex");
							} else {
								node.tabIndex = self._get("tabIndex");
							}
						}
					},
					this
				);
			}
			return props; // for after advice
		}),

		isFocusable: function () {
			// summary:
			//		Tells if this widget is focusable or not.  Used internally by delite.
			// tags:
			//		protected
			return !this.disabled && this.focusNode && (domStyle.get(this, "display") !== "none");
		},

		focus: function () {
			// summary:
			//		Put focus on this widget
			if (!this.disabled && this.focusNode.focus) {
				try {
					this.focusNode.focus();
				} catch (e) {
					// squelch errors from hidden nodes
				}
			}
		}
	});
});
<|endoftext|>


<commit_before>/*
	By Osvaldas Valutis, www.osvaldas.info
	Available for use under the MIT License
*/
;( function( $, window, document, undefined )
{
    'use strict';

    var cssTransitionSupport = function()
        {
            var s = document.body || document.documentElement;
            s = s.style;
            if( s.WebkitTransition === '' ) { return '-webkit-'; }
            if( s.MozTransition === '' ) { return '-moz-'; }
            if( s.OTransition === '' ) { return '-o-'; }
            if( s.transition === '' ) { return ''; }
            return false;
        },

        isCssTransitionSupport = cssTransitionSupport() !== false,

        cssTransitionTranslateX = function( element, positionX, speed )
        {
            var options = {}, prefix = cssTransitionSupport();
            options[ prefix + 'transform' ]	 = 'translateX(' + positionX + ')';
            options[ prefix + 'transition' ] = prefix + 'transform ' + speed + 's linear';
            element.css( options );
        },

        hasTouch	= ( 'ontouchstart' in window ),
        hasPointers = window.navigator.pointerEnabled || window.navigator.msPointerEnabled,
        wasTouched	= function( event )
        {
            if( hasTouch ) { return true; }

            if( !hasPointers || typeof event === 'undefined' || typeof event.pointerType === 'undefined' ) { return false; }

            if( typeof event.MSPOINTER_TYPE_MOUSE !== 'undefined' )
            {
                if( event.MSPOINTER_TYPE_MOUSE !== event.pointerType ) { return true; }
            }
            else if( event.pointerType !== 'mouse' ) {
                return true;
            }

            return false;
        };

    $.fn.imageLightbox = function( opts )
    {
        var options = $.extend(
            {
                selector:		'id="imagelightbox"',
                allowedTypes:	'png|jpg|jpeg||gif', // add support for generated images without an extension
                animationSpeed:	250,
                preloadNext:	true,
                enableKeyboard:	true,
                quitOnEnd:		false,
                quitOnImgClick: false,
                quitOnDocClick: true,
                quitOnEscKey:   true,               // quit when Esc key is pressed
                onStart:		false,
                onEnd:			false,
                onLoadStart:	false,
                onLoadEnd:		false,

                previousTarget : function () {
                    return this.previousTargetDefault();
                },

                previousTargetDefault : function () {
                    var targetIndex = targets.index( target ) - 1;
                    if( targetIndex < 0 ) {
                        if(options.quitOnEnd === true)
                        {
                            quitLightbox();
                            return false;
                        }
                        else
                        {
                            targetIndex = targets.length - 1;
                        }
                    }
                    target = targets.eq( targetIndex );
                },

                nextTarget : function () {
                    return this.nextTargetDefault();
                },

                nextTargetDefault : function () {
                    var targetIndex = targets.index(target) + 1;
                    if (targetIndex >= targets.length)
                    {
                        if(options.quitOnEnd === true)
                        {
                            quitLightbox();
                            return false;
                        }
                        else
                        {
                            targetIndex = 0;
                        }
                    }
                    target = targets.eq(targetIndex);
                }
            },
            opts ),

            targets		= $([]),
            target		= $(),
            image		= $(),
            imageWidth	= 0,
            imageHeight = 0,
            swipeDiff	= 0,
            inProgress	= false,

            isTargetValid = function( element )
            {
                var classic =  $( element ).prop( 'tagName' ).toLowerCase() === 'a' && ( new RegExp( '.(' + options.allowedTypes + ')$', 'i' ) ).test( $( element ).attr( 'href' ) );
                var html5 = $( element ).attr( 'data-lightbox' ) !== undefined;
                return classic || html5;
            },

            setImage = function()
            {
                if( !image.length ) { return true; }

                var screenWidth	 = $( window ).width() * 0.8,
                    screenHeight = $( window ).height() * 0.9,
                    tmpImage 	 = new Image();

                tmpImage.src	= image.attr( 'src' );
                tmpImage.onload = function()
                {
                    imageWidth	 = tmpImage.width;
                    imageHeight	 = tmpImage.height;

                    if( imageWidth > screenWidth || imageHeight > screenHeight )
                    {
                        var ratio	 = imageWidth / imageHeight > screenWidth / screenHeight ? imageWidth / screenWidth : imageHeight / screenHeight;
                        imageWidth	/= ratio;
                        imageHeight	/= ratio;
                    }

                    image.css(
                        {
                            'width':  imageWidth + 'px',
                            'height': imageHeight + 'px',
                            'top':    ( $( window ).height() - imageHeight ) / 2 + 'px',
                            'left':   ( $( window ).width() - imageWidth ) / 2 + 'px'
                        });
                };
            },

            loadImage = function( direction )
            {
                if( inProgress ) { return false; }

                direction = typeof direction === 'undefined' ? false : direction === 'left' ? 1 : -1;

                if( image.length )
                {
                    var params = { 'opacity': 0 };
                    if( isCssTransitionSupport ) { cssTransitionTranslateX( image, ( 100 * direction ) - swipeDiff + 'px', options.animationSpeed / 1000 ); }
                    else { params.left = parseInt( image.css( 'left' ) ) + 100 * direction + 'px'; }
                    image.animate( params, options.animationSpeed, function(){ removeImage(); });
                    swipeDiff = 0;
                }

                inProgress = true;
                if( options.onLoadStart !== false ) { options.onLoadStart(); }

                setTimeout( function()
                {
                    var imgPath = target.attr( 'href' );
                    if ( imgPath === undefined ) {
                        imgPath = target.attr( 'data-lightbox' );
                    }
                    image = $( '<img ' + options.selector + ' />' )
                        .attr( 'src', imgPath )
                        .load( function()
                        {
                            image.appendTo( 'body' );
                            setImage();

                            var params = { 'opacity': 1 };

                            image.css( 'opacity', 0 );
                            if( isCssTransitionSupport )
                            {
                                cssTransitionTranslateX( image, -100 * direction + 'px', 0 );
                                setTimeout( function(){ cssTransitionTranslateX( image, 0 + 'px', options.animationSpeed / 1000 ); }, 50 );
                            }
                            else
                            {
                                var imagePosLeft = parseInt( image.css( 'left' ) );
                                params.left = imagePosLeft + 'px';
                                image.css( 'left', imagePosLeft - 100 * direction + 'px' );
                            }

                            image.animate( params, options.animationSpeed, function()
                            {
                                inProgress = false;
                                if( options.onLoadEnd !== false ) { options.onLoadEnd(); }
                            });
                            if( options.preloadNext )
                            {
                                var nextTarget = targets.eq( targets.index( target ) + 1 );
                                if( !nextTarget.length ) { nextTarget = targets.eq( 0 ); }
                                $( '<img />' ).attr( 'src', nextTarget.attr( 'href' ) ).load();
                            }
                        })
                        .error( function()
                        {
                            if( options.onLoadEnd !== false ) { options.onLoadEnd(); }
                        });

                    var swipeStart	 = 0,
                        swipeEnd	 = 0,
                        imagePosLeft = 0;

                    image.on( hasPointers ? 'pointerup MSPointerUp' : 'click', function( e )
                    {
                        e.preventDefault();
                        if( options.quitOnImgClick )
                        {
                            quitLightbox();
                            return false;
                        }
                        if( wasTouched( e.originalEvent ) ) { return true; }
                        var posX = ( e.pageX || e.originalEvent.pageX ) - e.target.offsetLeft;
                        if (imageWidth / 2 > posX)
                        {
                            loadPreviousImage();
                        }
                        else
                        {
                            loadNextImage();
                        }
                    })
                        .on( 'touchstart pointerdown MSPointerDown', function( e )
                        {
                            if( !wasTouched( e.originalEvent ) || options.quitOnImgClick ) { return true; }
                            if( isCssTransitionSupport ) { imagePosLeft = parseInt( image.css( 'left' ) ); }
                            swipeStart = e.originalEvent.pageX || e.originalEvent.touches[ 0 ].pageX;
                        })
                        .on( 'touchmove pointermove MSPointerMove', function( e )
                        {
                            if( !wasTouched( e.originalEvent ) || options.quitOnImgClick ) { return true; }
                            e.preventDefault();
                            swipeEnd = e.originalEvent.pageX || e.originalEvent.touches[ 0 ].pageX;
                            swipeDiff = swipeStart - swipeEnd;
                            if( isCssTransitionSupport ) { cssTransitionTranslateX( image, -swipeDiff + 'px', 0 ); }
                            else { image.css( 'left', imagePosLeft - swipeDiff + 'px' ); }
                        })
                        .on( 'touchend touchcancel pointerup pointercancel MSPointerUp MSPointerCancel', function( e )
                        {
                            if( !wasTouched( e.originalEvent ) || options.quitOnImgClick ) { return true; }
                            if( Math.abs( swipeDiff ) > 50 )
                            {
                                if (swipeDiff < 0)
                                {
                                    loadPreviousImage();
                                }
                                else
                                {
                                    loadNextImage();
                                }
                            }
                            else
                            {
                                if( isCssTransitionSupport ) { cssTransitionTranslateX( image, 0 + 'px', options.animationSpeed / 1000 ); }
                                else { image.animate({ 'left': imagePosLeft + 'px' }, options.animationSpeed / 2 ); }
                            }
                        });

                }, options.animationSpeed + 100 );
            },

            loadPreviousImage = function () {
                if (options.previousTarget() !== false) {
                    loadImage('left');
                }
            },

            loadNextImage = function () {
                if (options.nextTarget() !== false) {
                    loadImage('right');
                }
            },

            removeImage = function()
            {
                if( !image.length ) { return false; }
                image.remove();
                image = $();
            },

            quitLightbox = function()
            {
                if( !image.length ) { return false; }
                image.animate({ 'opacity': 0 }, options.animationSpeed, function()
                {
                    removeImage();
                    inProgress = false;
                    if( options.onEnd !== false ) { options.onEnd(); }
                });
            };

        $( window ).on( 'resize', setImage );

        if( options.quitOnDocClick )
        {
            $( document ).on( hasTouch ? 'touchend' : 'click', function( e )
            {
                if( image.length && !$( e.target ).is( image ) ) { quitLightbox(); }
            });
        }

        if( options.enableKeyboard )
        {
            $( document ).on( 'keyup', function( e )
            {
                if( !image.length ) { return true; }
                e.preventDefault();
                if( e.keyCode === 27 && options.quitOnEscKey === true ) { quitLightbox(); }
                if( e.keyCode === 37)
                {
                    loadPreviousImage();
                } else if (e.keyCode === 39) {
                    loadNextImage();
                }
            });
        }

        this.startImageLightbox = function( e )
        {
            if( !isTargetValid( this ) ) { return true; }
            if (e !== undefined) { e.preventDefault(); }
            if( inProgress ) { return false; }
            inProgress = false;
            if( options.onStart !== false ) { options.onStart(); }
            target = $( this );
            loadImage();
        };

        $( document ).off( 'click', this.selector);
        $( document ).on( 'click', this.selector, this.startImageLightbox);

        this.each( function()
        {
            if( !isTargetValid( this ) ) { return true; }
            targets = targets.add( $( this ) );
        });

        this.switchImageLightbox = function( index )
        {
            var tmpTarget = targets.eq( index );
            if( tmpTarget.length )
            {
                var currentIndex = targets.index( target );
                target = tmpTarget;
                loadImage( index < currentIndex ? 'left' : 'right' );
            }
            return this;
        };

        this.quitImageLightbox = function()
        {
            quitLightbox();
            return this;
        };

        return this;
    };
})( jQuery, window, document );
<commit_msg>Make it possible to add other images using the ajax<commit_after>/*
	By Osvaldas Valutis, www.osvaldas.info
	Available for use under the MIT License
*/
;( function( $, window, document, undefined )
{
    'use strict';

    var cssTransitionSupport = function()
        {
            var s = document.body || document.documentElement;
            s = s.style;
            if( s.WebkitTransition === '' ) { return '-webkit-'; }
            if( s.MozTransition === '' ) { return '-moz-'; }
            if( s.OTransition === '' ) { return '-o-'; }
            if( s.transition === '' ) { return ''; }
            return false;
        },

        isCssTransitionSupport = cssTransitionSupport() !== false,

        cssTransitionTranslateX = function( element, positionX, speed )
        {
            var options = {}, prefix = cssTransitionSupport();
            options[ prefix + 'transform' ]	 = 'translateX(' + positionX + ')';
            options[ prefix + 'transition' ] = prefix + 'transform ' + speed + 's linear';
            element.css( options );
        },

        hasTouch	= ( 'ontouchstart' in window ),
        hasPointers = window.navigator.pointerEnabled || window.navigator.msPointerEnabled,
        wasTouched	= function( event )
        {
            if( hasTouch ) { return true; }

            if( !hasPointers || typeof event === 'undefined' || typeof event.pointerType === 'undefined' ) { return false; }

            if( typeof event.MSPOINTER_TYPE_MOUSE !== 'undefined' )
            {
                if( event.MSPOINTER_TYPE_MOUSE !== event.pointerType ) { return true; }
            }
            else if( event.pointerType !== 'mouse' ) {
                return true;
            }

            return false;
        };

    $.fn.imageLightbox = function( opts )
    {
        var options = $.extend(
            {
                selector:		'id="imagelightbox"',
                allowedTypes:	'png|jpg|jpeg||gif', // add support for generated images without an extension
                animationSpeed:	250,
                preloadNext:	true,
                enableKeyboard:	true,
                quitOnEnd:		false,
                quitOnImgClick: false,
                quitOnDocClick: true,
                quitOnEscKey:   true,               // quit when Esc key is pressed
                onStart:		false,
                onEnd:			false,
                onLoadStart:	false,
                onLoadEnd:		false,

                previousTarget : function () {
                    return this.previousTargetDefault();
                },

                previousTargetDefault : function () {
                    var targetIndex = targets.index( target ) - 1;
                    if( targetIndex < 0 ) {
                        if(options.quitOnEnd === true)
                        {
                            quitLightbox();
                            return false;
                        }
                        else
                        {
                            targetIndex = targets.length - 1;
                        }
                    }
                    target = targets.eq( targetIndex );
                },

                nextTarget : function () {
                    return this.nextTargetDefault();
                },

                nextTargetDefault : function () {
                    var targetIndex = targets.index(target) + 1;
                    if (targetIndex >= targets.length)
                    {
                        if(options.quitOnEnd === true)
                        {
                            quitLightbox();
                            return false;
                        }
                        else
                        {
                            targetIndex = 0;
                        }
                    }
                    target = targets.eq(targetIndex);
                }
            },
            opts ),

            targets		= $([]),
            target		= $(),
            image		= $(),
            imageWidth	= 0,
            imageHeight = 0,
            swipeDiff	= 0,
            inProgress	= false,

            isTargetValid = function( element )
            {
                var classic =  $( element ).prop( 'tagName' ).toLowerCase() === 'a' && ( new RegExp( '.(' + options.allowedTypes + ')$', 'i' ) ).test( $( element ).attr( 'href' ) );
                var html5 = $( element ).attr( 'data-lightbox' ) !== undefined;
                return classic || html5;
            },

            setImage = function()
            {
                if( !image.length ) { return true; }

                var screenWidth	 = $( window ).width() * 0.8,
                    screenHeight = $( window ).height() * 0.9,
                    tmpImage 	 = new Image();

                tmpImage.src	= image.attr( 'src' );
                tmpImage.onload = function()
                {
                    imageWidth	 = tmpImage.width;
                    imageHeight	 = tmpImage.height;

                    if( imageWidth > screenWidth || imageHeight > screenHeight )
                    {
                        var ratio	 = imageWidth / imageHeight > screenWidth / screenHeight ? imageWidth / screenWidth : imageHeight / screenHeight;
                        imageWidth	/= ratio;
                        imageHeight	/= ratio;
                    }

                    image.css(
                        {
                            'width':  imageWidth + 'px',
                            'height': imageHeight + 'px',
                            'top':    ( $( window ).height() - imageHeight ) / 2 + 'px',
                            'left':   ( $( window ).width() - imageWidth ) / 2 + 'px'
                        });
                };
            },

            loadImage = function( direction )
            {
                if( inProgress ) { return false; }

                direction = typeof direction === 'undefined' ? false : direction === 'left' ? 1 : -1;

                if( image.length )
                {
                    var params = { 'opacity': 0 };
                    if( isCssTransitionSupport ) { cssTransitionTranslateX( image, ( 100 * direction ) - swipeDiff + 'px', options.animationSpeed / 1000 ); }
                    else { params.left = parseInt( image.css( 'left' ) ) + 100 * direction + 'px'; }
                    image.animate( params, options.animationSpeed, function(){ removeImage(); });
                    swipeDiff = 0;
                }

                inProgress = true;
                if( options.onLoadStart !== false ) { options.onLoadStart(); }

                setTimeout( function()
                {
                    var imgPath = target.attr( 'href' );
                    if ( imgPath === undefined ) {
                        imgPath = target.attr( 'data-lightbox' );
                    }
                    image = $( '<img ' + options.selector + ' />' )
                        .attr( 'src', imgPath )
                        .load( function()
                        {
                            image.appendTo( 'body' );
                            setImage();

                            var params = { 'opacity': 1 };

                            image.css( 'opacity', 0 );
                            if( isCssTransitionSupport )
                            {
                                cssTransitionTranslateX( image, -100 * direction + 'px', 0 );
                                setTimeout( function(){ cssTransitionTranslateX( image, 0 + 'px', options.animationSpeed / 1000 ); }, 50 );
                            }
                            else
                            {
                                var imagePosLeft = parseInt( image.css( 'left' ) );
                                params.left = imagePosLeft + 'px';
                                image.css( 'left', imagePosLeft - 100 * direction + 'px' );
                            }

                            image.animate( params, options.animationSpeed, function()
                            {
                                inProgress = false;
                                if( options.onLoadEnd !== false ) { options.onLoadEnd(); }
                            });
                            if( options.preloadNext )
                            {
                                var nextTarget = targets.eq( targets.index( target ) + 1 );
                                if( !nextTarget.length ) { nextTarget = targets.eq( 0 ); }
                                $( '<img />' ).attr( 'src', nextTarget.attr( 'href' ) ).load();
                            }
                        })
                        .error( function()
                        {
                            if( options.onLoadEnd !== false ) { options.onLoadEnd(); }
                        });

                    var swipeStart	 = 0,
                        swipeEnd	 = 0,
                        imagePosLeft = 0;

                    image.on( hasPointers ? 'pointerup MSPointerUp' : 'click', function( e )
                    {
                        e.preventDefault();
                        if( options.quitOnImgClick )
                        {
                            quitLightbox();
                            return false;
                        }
                        if( wasTouched( e.originalEvent ) ) { return true; }
                        var posX = ( e.pageX || e.originalEvent.pageX ) - e.target.offsetLeft;
                        if (imageWidth / 2 > posX)
                        {
                            loadPreviousImage();
                        }
                        else
                        {
                            loadNextImage();
                        }
                    })
                        .on( 'touchstart pointerdown MSPointerDown', function( e )
                        {
                            if( !wasTouched( e.originalEvent ) || options.quitOnImgClick ) { return true; }
                            if( isCssTransitionSupport ) { imagePosLeft = parseInt( image.css( 'left' ) ); }
                            swipeStart = e.originalEvent.pageX || e.originalEvent.touches[ 0 ].pageX;
                        })
                        .on( 'touchmove pointermove MSPointerMove', function( e )
                        {
                            if( !wasTouched( e.originalEvent ) || options.quitOnImgClick ) { return true; }
                            e.preventDefault();
                            swipeEnd = e.originalEvent.pageX || e.originalEvent.touches[ 0 ].pageX;
                            swipeDiff = swipeStart - swipeEnd;
                            if( isCssTransitionSupport ) { cssTransitionTranslateX( image, -swipeDiff + 'px', 0 ); }
                            else { image.css( 'left', imagePosLeft - swipeDiff + 'px' ); }
                        })
                        .on( 'touchend touchcancel pointerup pointercancel MSPointerUp MSPointerCancel', function( e )
                        {
                            if( !wasTouched( e.originalEvent ) || options.quitOnImgClick ) { return true; }
                            if( Math.abs( swipeDiff ) > 50 )
                            {
                                if (swipeDiff < 0)
                                {
                                    loadPreviousImage();
                                }
                                else
                                {
                                    loadNextImage();
                                }
                            }
                            else
                            {
                                if( isCssTransitionSupport ) { cssTransitionTranslateX( image, 0 + 'px', options.animationSpeed / 1000 ); }
                                else { image.animate({ 'left': imagePosLeft + 'px' }, options.animationSpeed / 2 ); }
                            }
                        });

                }, options.animationSpeed + 100 );
            },

            loadPreviousImage = function () {
                if (options.previousTarget() !== false) {
                    loadImage('left');
                }
            },

            loadNextImage = function () {
                if (options.nextTarget() !== false) {
                    loadImage('right');
                }
            },

            removeImage = function()
            {
                if( !image.length ) { return false; }
                image.remove();
                image = $();
            },

            quitLightbox = function()
            {
                if( !image.length ) { return false; }
                image.animate({ 'opacity': 0 }, options.animationSpeed, function()
                {
                    removeImage();
                    inProgress = false;
                    if( options.onEnd !== false ) { options.onEnd(); }
                });
            };

        $( window ).on( 'resize', setImage );

        if( options.quitOnDocClick )
        {
            $( document ).on( hasTouch ? 'touchend' : 'click', function( e )
            {
                if( image.length && !$( e.target ).is( image ) ) { quitLightbox(); }
            });
        }

        if( options.enableKeyboard )
        {
            $( document ).on( 'keyup', function( e )
            {
                if( !image.length ) { return true; }
                e.preventDefault();
                if( e.keyCode === 27 && options.quitOnEscKey === true ) { quitLightbox(); }
                if( e.keyCode === 37)
                {
                    loadPreviousImage();
                } else if (e.keyCode === 39) {
                    loadNextImage();
                }
            });
        }

        this.startImageLightbox = function( e )
        {
            if( !isTargetValid( this ) ) { return true; }
            if (e !== undefined) { e.preventDefault(); }
            if( inProgress ) { return false; }
            inProgress = false;
            if( options.onStart !== false ) { options.onStart(); }
            target = $( this );
            loadImage();
        };

        $( document ).off( 'click', this.selector);
        $( document ).on( 'click', this.selector, this.startImageLightbox);

        this.each( function()
        {
            if( !isTargetValid( this ) ) { return true; }
            targets = targets.add( $( this ) );
        });

        this.switchImageLightbox = function( index )
        {
            var tmpTarget = targets.eq( index );
            if( tmpTarget.length )
            {
                var currentIndex = targets.index( target );
                target = tmpTarget;
                loadImage( index < currentIndex ? 'left' : 'right' );
            }
            return this;
        };

        this.quitImageLightbox = function()
        {
            quitLightbox();
            return this;
        };
        // 对于ajax操作后增加到targets队列中
        // You can add the other targets to the image queue.
        this.addImageLightbox = function(elements)
        {
            elements.each(function(){
                if( !isTargetValid( this )) { return true; }
                targets = targets.add( $( this ) );
            });
            elements.click(this.startImageLightbox);
        };
        return this;
    };
})( jQuery, window, document );
<|endoftext|>


<commit_before>loadActivity = function() {
	$.ajax({
		dataType: 'json',
		url: 'https://api.github.com/repos'+getProjectPath()+'/stats/commit_activity',
		statusCode: {
			200: function(data) {
				var activity = computeActivity(data);
				var activityLabel = getActivityLabel(activity);
				console.log(getProjectPath() + ": "+activity);
				displayActivity(activityLabel, activity);
			},
			202: function() {
				// github processing the data
				setTimeout(loadActivity, 250)
			},
			403: function() {
				console.warn('GitHub API limit reached!');
			},
			404: function() {
				console.log('Private repository, wont get activity.');
			}

		}
	});
}

displayActivity = function(label, number) {
	var hint = number + ' commits in the past year';

	if(!$('.activity').size()) {
		var html = '
';
		html += '<svg aria-hidden="true" class="octicon octicon-pulse" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M11.5 8L8.8 5.4 6.6 8.5 5.5 1.6 2.38 8H0V10h3.6L4.5 8.2l0.9 5.4L9 8.5l1.6 1.5H14V8H11.5z"></path></svg>';
		html += ' ' + label + ' activity
';

		$('.numbers-summary').append(html);

	} else {
		$('.activity .num').text(label);
		$('.activity a').attr('title', hint)
	}
}

computeActivity = function(data) {
	return _(data)
		.mapValues(function(week) {return week.total})
		.reduce(function(a, b) {return a + b}, 0);
}

getActivityLabel = function(total) {
	if(total === 0)		return 'no';
	if(total < 10) 		return 'low';
	if(total < 100) 	return 'medium';
	if(total < 1000) 	return 'high';
	return 'incredible';
}

getProjectPath = function() {
	return window.location.pathname;
}

displayActivity('-', 0);
loadActivity();<commit_msg>Made time window configurable<commit_after>ACTIVITY_WINDOW_WEEKS = 4;
TIME_RETRY_STATS_MS = 250;

loadActivity = function() {
	$.ajax({
		dataType: 'json',
		url: 'https://api.github.com/repos'+getProjectPath()+'/stats/commit_activity',
		statusCode: {
			200: function(data) {
				var activity = computeActivity(data);
				var activityLabel = getActivityLabel(activity);
				displayActivity(activityLabel, activity);
			},
			202: function() {
				// github processing the data
				setTimeout(loadActivity, TIME_RETRY_STATS_MS)
			},
			403: function() {
				console.warn('GitHub API limit reached!');
			},
			404: function() {
				console.log('Private repository, wont get activity.');
			}

		}
	});
}

displayActivity = function(label, number) {
	var hint = number + ' commits in the past ' + ACTIVITY_WINDOW_WEEKS + ' weeks';

	if(!$('.activity').size()) {
		var html = '
';
		html += '<svg aria-hidden="true" class="octicon octicon-pulse" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M11.5 8L8.8 5.4 6.6 8.5 5.5 1.6 2.38 8H0V10h3.6L4.5 8.2l0.9 5.4L9 8.5l1.6 1.5H14V8H11.5z"></path></svg>';
		html += ' ' + label + ' activity
';

		$('.numbers-summary').append(html);

	} else {
		$('.activity .num').text(label);
		$('.activity a').attr('title', hint)
	}
}

computeActivity = function(data) {
	return _(data)
		.mapValues(function(week) {return week.total})
		.values()
		.slice(52 - ACTIVITY_WINDOW_WEEKS)
		.reduce(function(a, b) {return a + b}, 0);
}

getActivityLabel = function(total) {
	if(total === 0)		return 'no';
	if(total < 10) 		return 'low';
	if(total < 100) 	return 'medium';
	if(total < 1000) 	return 'high';
	return 'incredible';
}

getProjectPath = function() {
	return window.location.pathname;
}

displayActivity('-', 0);
loadActivity();<|endoftext|>


<commit_before>/* Copyright (c) 2017 Adam Browne
 * Inject.js is the content script which analyzes twitch. */

/* To observe the new messages, we need to use a MutationObserver
* and then apply a queryselector to get the new message added:
we're looking for:
 */
	var toggle_filter = false;
    chrome.runtime.onMessage.addListener(function(response){
        if(response == "tf")
            toggleFilter();
    });
    var port = chrome.runtime.connect({name: "handler"});
    port.onMessage.addListener(function(message){
        var data = message.split("|");
        var target_msg = document.getElementById(data[0]);
        if(data[3] == "1") {//TODO: Non-relevant display CONFIGURE BY POPup
            try {
                target_msg.setAttribute("style", "display:block;visibility:visible;");
            } catch(err){

            }
        } else {
            try {
                target_msg.setAttribute("style", "display:none;visibility:hidden;");
            } catch(err) {}
        }
    });

    var handleTwitchMsg = function(msg) {
        var timestamp = msg.querySelector("span.timestamp").textContent
        var user = msg.querySelector("span.from").textContent
        var message = msg.querySelector("span.message").textContent.trim()
        var identifier = msg.id;
        port.postMessage({id: identifier, time: timestamp, usr: user, data: message});
    }

    var getChatBoxElement = function(main) {
        if(document.querySelector(main) != null){
            setupMessageListener(document.querySelector(main));
        } else {
            setTimeout(function() {
                getChatBoxElement(main);
            }, 500);
        }
    }

    var need = [];
    var handled = [];
    /* to setup the message listener, we have to first get the "ul.chat-lines" element.
     * this is added dynamically, so we'll use a mutation observer to get it, disconnect that observer,
     * and then begin listening to chat messages. */
    var setupMessageListener = function(chat_box) {
        var observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                for(var i = 0; i < mutation.addedNodes.length; ++i){
                	if(toggle_filter == false) {
	                    var newTwitchMsg = mutation.addedNodes[i], identifier;
	                    var isValid = true;
	                    if(newTwitchMsg.id != "undefined" && newTwitchMsg.id != ""){ //native twitch
	                        identifier = newTwitchMsg.id;
	                    } else {
	                        identifier = newTwitchMsg.getAttribute("data-id"); //better twitch tv
	                        isValid = false;
	                    }
	                    if(handled.includes(identifier) == false){
	                        handled.push(identifier);
	                        if(isValid == false) //better twitch tv compatibility
	                            newTwitchMsg.setAttribute("id", identifier);
	                        handleTwitchMsg(newTwitchMsg);
	                    }
	                } else {
                        try {
                            var curMsg = mutation.addedNodes[i];
                            curMsg.setAttribute("style", "display:block;visibility:visible;");
                            if(curMsg.id == "" || curMsg.id == "undefined")
                                curMsg.setAttribute("id", curMsg.getAttribute("data-id"));
                            need.push(curMsg.id);
                        } catch(err) { }
	                }
                }
            })
        });
        //listen for new chat messages. We've incorporated BTTV compatibility.
        observer.observe(chat_box, {childList: true});
    }

    function toggleFilter() {
    	toggle_filter = toggle_filter == false? true: false;
        if(toggle_filter == false){
            while(need.length > 0){
                try {
                    handleTwitchMsg(document.getElementById(need.shift()));
                } catch(err){

                }
            }
        }
    }

    var oldStream = "";
    var curStream = window.location.href;
    function checkChangedStream(curStream){
        if(curStream != oldStream){
            oldStream = curStream;
            getChatBoxElement("ul.chat-lines");
        }
        oldStream = window.location.href;
        setTimeout(function() {
            checkChangedStream(window.location.href);
        }, 2000);
    }

    checkChangedStream();

// var tester = false, pie;
// ready("div.mg-b-2", function(element){
//     if(tester == false){
//         pie = new d3pie(element, {
//             header: {
//                 title: {
//                     text: "A very simple example pie"
//                 }
//             },
//             data: {
//                 content: [
//                     { label: "JavaScript", value: 264131 },
//                     { label: "Ruby", value: 218812 },
//                     { label: "Java", value: 157618},
//                 ]
//             }
//         });
//         tester = true;
//         pie.redraw();
//     }
// });

<commit_msg>Fix brace.<commit_after>/* Copyright (c) 2017 Adam Browne
 * Inject.js is the content script which analyzes twitch. */

/* To observe the new messages, we need to use a MutationObserver
* and then apply a queryselector to get the new message added:
we're looking for:
 */
	var toggle_filter = false;
    chrome.runtime.onMessage.addListener(function(response){
        if(response == "tf")
            toggleFilter();
    });
    var port = chrome.runtime.connect({name: "handler"});
    port.onMessage.addListener(function(message){
        var data = message.split("|");
        var target_msg = document.getElementById(data[0]);
        if(data[3] == "1") {//TODO: Non-relevant display CONFIGURE BY POPup
            try {
                target_msg.setAttribute("style", "display:block;visibility:visible;");
            } catch(err){

            }
        } else {
            try {
                target_msg.setAttribute("style", "display:none;visibility:hidden;");
            } catch(err) {}
        }
    });

    var handleTwitchMsg = function(msg) {
        var timestamp = msg.querySelector("span.timestamp").textContent
        var user = msg.querySelector("span.from").textContent
        var message = msg.querySelector("span.message").textContent.trim()
        var identifier = msg.id;
        port.postMessage({id: identifier, time: timestamp, usr: user, data: message});
    }

    var getChatBoxElement = function(main) {
        if(document.querySelector(main) != null){
            setupMessageListener(document.querySelector(main));
        } else {
            setTimeout(function() {
                getChatBoxElement(main);
            }, 500);
        }
    }

    var need = [];
    var handled = [];
    /* to setup the message listener, we have to first get the "ul.chat-lines" element.
     * this is added dynamically, so we'll use a mutation observer to get it, disconnect that observer,
     * and then begin listening to chat messages. */
    var setupMessageListener = function(chat_box) {
        var observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                for(var i = 0; i < mutation.addedNodes.length; ++i){
                	if(toggle_filter == false) {
	                    var newTwitchMsg = mutation.addedNodes[i], identifier;
	                    var isValid = true;
	                    if(newTwitchMsg.id != "undefined" && newTwitchMsg.id != ""){ //native twitch
	                        identifier = newTwitchMsg.id;
	                    } else {
	                        identifier = newTwitchMsg.getAttribute("data-id"); //better twitch tv
	                        isValid = false;
	                    }
	                    if(handled.includes(identifier) == false){
	                        handled.push(identifier);
	                        if(isValid == false) //better twitch tv compatibility
	                            newTwitchMsg.setAttribute("id", identifier);
	                        handleTwitchMsg(newTwitchMsg);
	                    }
	                } else {
                        try {
                            var curMsg = mutation.addedNodes[i];
                            curMsg.setAttribute("style", "display:block;visibility:visible;");
                            if(curMsg.id == "" || curMsg.id == "undefined")
                                curMsg.setAttribute("id", curMsg.getAttribute("data-id"));
                            need.push(curMsg.id);
                        } catch(err) { }
                    }
                }
            })
        });
        //listen for new chat messages. We've incorporated BTTV compatibility.
        observer.observe(chat_box, {childList: true});
    }

    function toggleFilter() {
    	toggle_filter = toggle_filter == false? true: false;
        if(toggle_filter == false){
            while(need.length > 0){
                try {
                    handleTwitchMsg(document.getElementById(need.shift()));
                } catch(err){

                }
            }
        }
    }

    var oldStream = "";
    var curStream = window.location.href;
    function checkChangedStream(curStream){
        if(curStream != oldStream){
            oldStream = curStream;
            getChatBoxElement("ul.chat-lines");
        }
        oldStream = window.location.href;
        setTimeout(function() {
            checkChangedStream(window.location.href);
        }, 2000);
    }

    checkChangedStream();

// var tester = false, pie;
// ready("div.mg-b-2", function(element){
//     if(tester == false){
//         pie = new d3pie(element, {
//             header: {
//                 title: {
//                     text: "A very simple example pie"
//                 }
//             },
//             data: {
//                 content: [
//                     { label: "JavaScript", value: 264131 },
//                     { label: "Ruby", value: 218812 },
//                     { label: "Java", value: 157618},
//                 ]
//             }
//         });
//         tester = true;
//         pie.redraw();
//     }
// });

<|endoftext|>


<commit_before>var GameboyJS;
(function (GameboyJS) {
"use strict";

var xboxMapping = {
    0: 'UP',
    1: 'DOWN',
    2: 'LEFT',
    3: 'RIGHT',
    4: 'START',
    5: 'SELECT',
    11: 'A',
    12: 'B'
};

// Gamepad listener
// Does the mapping between the Gamepad API and the Input class
var Gamepad = function(mapping) {
    this.gamepad = null;
    this.state = {A:0,B:0,START:0,SELECT:0,LEFT:0,RIGHT:0,UP:0,DOWN:0};
    this.pullInterval = null;
    this.buttonMapping = mapping || xboxMapping;
};

// Initialize the keyboard listeners and set up the callbacks
// for button press / release
Gamepad.prototype.init = function(onPress, onRelease) {
    this.onPress = onPress;
    this.onRelease = onRelease;

    var self = this;
    window.addEventListener('gamepadconnected', function(e) {
        self.gamepad = e.gamepad;
        self.activatePull();
    });
    window.addEventListener('gamepaddisconnected', function(e) {
        self.gamepad = null;
        self.deactivatePull();
    });
};

Gamepad.prototype.activatePull = function() {
    this.pullInterval = setInterval(this.pullState.bind(this), 100);
};

Gamepad.prototype.deactivatePull = function() {
    clearInterval(this.pullInterval);
};

Gamepad.prototype.pullState = function() {
    for (var index in this.buttonMapping) {
        var button = this.buttonMapping[index];
        var oldState = this.state[button];
        this.state[button] = this.gamepad.buttons[index].pressed;

        if (this.state[button] == 1 && oldState == 0) {
            this.managePress(button);
        } else if (this.state[button] == 0 && oldState == 1) {
            this.manageRelease(button);
        }
    }
};

Gamepad.prototype.managePress = function(key) {
    this.onPress(key);
};

Gamepad.prototype.manageRelease = function(key) {
    this.onRelease(key);
};

GameboyJS.Gamepad = Gamepad;
}(GameboyJS || (GameboyJS = {})));
<commit_msg>document the gamepad class<commit_after>var GameboyJS;
(function (GameboyJS) {
"use strict";

// This is the default buttons mapping for the Gamepad
// It's optimized for the XBOX pad
//
// Any other mapping can be provided as a constructor argument of the Gamepad object
// An alternative mapping should be an object with keys being the indexes
// of the gamepad buttons and values the normalized gameboy button names
var xboxMapping = {
    0: 'UP',
    1: 'DOWN',
    2: 'LEFT',
    3: 'RIGHT',
    4: 'START',
    5: 'SELECT',
    11: 'A',
    12: 'B'
};

// Gamepad listener
// Communication layer between the Gamepad API and the Input class
// Any physical controller can be used but the mapping should be provided
// in order to get an optimal layout of the buttons (see above)
var Gamepad = function(mapping) {
    this.gamepad = null;
    this.state = {A:0,B:0,START:0,SELECT:0,LEFT:0,RIGHT:0,UP:0,DOWN:0};
    this.pullInterval = null;
    this.buttonMapping = mapping || xboxMapping;
};

// Initialize the keyboard listeners and set up the callbacks
// for button press / release
Gamepad.prototype.init = function(onPress, onRelease) {
    this.onPress = onPress;
    this.onRelease = onRelease;

    var self = this;
    window.addEventListener('gamepadconnected', function(e) {
        self.gamepad = e.gamepad;
        self.activatePull();
    });
    window.addEventListener('gamepaddisconnected', function(e) {
        self.gamepad = null;
        self.deactivatePull();
    });
};

Gamepad.prototype.activatePull = function() {
    this.deactivatePull();
    this.pullInterval = setInterval(this.pullState.bind(this), 100);
};

Gamepad.prototype.deactivatePull = function() {
    clearInterval(this.pullInterval);
};

// Check the state of the current gamepad in order to detect any press/release action
Gamepad.prototype.pullState = function() {
    for (var index in this.buttonMapping) {
        var button = this.buttonMapping[index];
        var oldState = this.state[button];
        this.state[button] = this.gamepad.buttons[index].pressed;

        if (this.state[button] == 1 && oldState == 0) {
            this.managePress(button);
        } else if (this.state[button] == 0 && oldState == 1) {
            this.manageRelease(button);
        }
    }
};

Gamepad.prototype.managePress = function(key) {
    this.onPress(key);
};

Gamepad.prototype.manageRelease = function(key) {
    this.onRelease(key);
};

GameboyJS.Gamepad = Gamepad;
}(GameboyJS || (GameboyJS = {})));
<|endoftext|>


<commit_before>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2018 Olivier Biot
 * http://www.melonjs.org
 *
 */
(function () {

    /**
     * cache value for the offset of the canvas position within the page
     * @ignore
     */
    var viewportOffset = new me.Vector2d();

    /**
     * a pointer object, representing a single finger on a touch enabled device.
     * @class
     * @extends me.Rect
     * @memberOf me
     * @constructor
     */
    me.Pointer = me.Rect.extend(
    /** @scope me.Pointer.prototype */
    {
        /**
         * @ignore
         */
        init : function (x, y, w, h) {

          /**
            * the originating Event Object
            * @public
            * @type {PointerEvent|TouchEvent|MouseEvent}
            * @name event
            * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent
            * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent
            * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
            * @memberOf me.Pointer
            */
            this.event = undefined;

           /**
            * a string containing the event's type.
            * @public
            * @type {String}
            * @name type
            * @see https://developer.mozilla.org/en-US/docs/Web/API/Event/type
            * @memberOf me.Pointer
            */
            this.type = undefined;

           /**
            * the button property indicates which button was pressed on the mouse to trigger the event.
            * @public
            * @type {Number}
            * @name width
            * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
            * @memberOf me.Pointer
            */
            this.button = 0;

           /**
            * indicates whether or not the pointer device that created the event is the primary pointe.
            * @public
            * @type {Boolean}
            * @name width
            * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary
            * @memberOf me.Pointer
            */
            this.isPrimary = false;

           /**
            * the horizontal coordinate within the application's client area at which the event occurred
            * @public
            * @type {Number}
            * @name clientX
            * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX
            * @memberOf me.Pointer
            */
            this.clientX = undefined;

           /**
            * the vertical coordinate within the application's client area at which the event occurred
            * @public
            * @type {Number}
            * @name clientY
            * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientY
            * @memberOf me.Pointer
            */
            this.clientY = undefined;

          /**
            * Event normalized X coordinate within the game canvas itself

            *
            * @public
            * @type {Number}
            * @name gameX
            * @memberOf me.Pointer
            */
            this.gameX = undefined;

           /**
            * Event normalized Y coordinate within the game canvas itself

            *
            * @public
            * @type {Number}
            * @name gameY
            * @memberOf me.Pointer
            */
            this.gameY = undefined;

           /**
            * Event X coordinate relative to the viewport
            * @public
            * @type {Number}
            * @name gameScreenX
            * @memberOf me.Pointer
            */
            this.gameScreenX = undefined;

           /**
            * Event Y coordinate relative to the viewport
            * @public
            * @type {Number}
            * @name gameScreenY
            * @memberOf me.Pointer
            */
            this.gameScreenY = undefined;

           /**
            * Event X coordinate relative to the map
            * @public
            * @type {Number}
            * @name gameWorldX
            * @memberOf me.Pointer
            */
            this.gameWorldX = undefined;

           /**
            * Event Y coordinate relative to the map
            * @public
            * @type {Number}
            * @name gameWorldY
            * @memberOf me.Pointer
            */
            this.gameWorldY = undefined;

           /**
            * Event X coordinate relative to the holding container
            * @public
            * @type {Number}
            * @name gameLocalX
            * @memberOf me.Pointer
            */
            this.gameLocalX = undefined;

           /**
            * Event Y coordinate relative to the holding container
            * @public
            * @type {Number}
            * @name gameLocalY
            * @memberOf me.Pointer
            */
            this.gameLocalY = undefined;

           /**
            * The unique identifier of the contact for a touch, mouse or pen
            * @public
            * @type {Number}
            * @name pointerId
            * @memberOf me.Pointer
            * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId
            */
            this.pointerId = undefined;

            // parent constructor
            this._super(me.Rect, "init", [x || 0, y || 0, w || 1, h || 1]);
        },

        /**
         * initialize the Pointer object using the given Event Object
         * @name me.Pointer#set
         * @private
         * @function
         * @param {Event} event the original Event object
         * @param {Number} clientX the horizontal coordinate within the application's client area at which the event occurred
         * @param {Number} clientX the vertical coordinate within the application's client area at which the event occurred
         * @param {Number} pointedId the Pointer, Touch or Mouse event Id
         */
        setEvent : function (event, clientX, clientY, pointerId) {
            var width = 1;
            var height = 1;

            // the original event object
            this.event = event;

            this.clientX = clientX || 0;
            this.clientY = clientY || 0;

            // translate to local coordinates
            me.input.globalToLocal(clientX, clientY, this.pos);

            // true if not originally a pointer event
            this.isNormalized = !me.device.PointerEvent || (me.device.PointerEvent && !(event instanceof window.PointerEvent));

            if (event.type === "wheel") {
                this.deltaMode = 1;
                this.deltaX = event.deltaX;
                this.deltaY = - 1 / 40 * event.wheelDelta;
                event.wheelDeltaX && (this.deltaX = - 1 / 40 * event.wheelDeltaX);
            }

            // could be 0, so test if defined
            this.pointerId = (typeof pointerId !== "undefined") ? pointerId : 1;

            this.isPrimary = (typeof event.isPrimary !== "undefined") ? event.isPrimary : true;

            // in case of touch events, button is not defined
            this.button = event.button || 0;

            this.type = event.type;

            this.gameScreenX = this.pos.x;
            this.gameScreenY = this.pos.y;

            // get the current screen to world offset
            me.game.viewport.localToWorld(this.gameScreenX, this.gameScreenY, viewportOffset);

            /* Initialize the two coordinate space properties. */
            this.gameWorldX = viewportOffset.x;
            this.gameWorldY = viewportOffset.y;

            // get the pointer size
            if (this.isNormalized === false) {
                // native PointerEvent
                width = event.width;
                height = event.height;
            } else if (typeof(event.radiusX) === "number")  {
                // TouchEvent
                width = event.radiusX * 2;
                height = event.radiusY * 2;
            }
            // resize the pointer object accordingly
            this.resize(width, height);
        }
    });
})();
<commit_msg>Small attribute documentation fix<commit_after>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2018 Olivier Biot
 * http://www.melonjs.org
 *
 */
(function () {

    /**
     * cache value for the offset of the canvas position within the page
     * @ignore
     */
    var viewportOffset = new me.Vector2d();

    /**
     * a pointer object, representing a single finger on a touch enabled device.
     * @class
     * @extends me.Rect
     * @memberOf me
     * @constructor
     */
    me.Pointer = me.Rect.extend(
    /** @scope me.Pointer.prototype */
    {
        /**
         * @ignore
         */
        init : function (x, y, w, h) {

          /**
            * the originating Event Object
            * @public
            * @type {PointerEvent|TouchEvent|MouseEvent}
            * @name event
            * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent
            * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent
            * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
            * @memberOf me.Pointer
            */
            this.event = undefined;

           /**
            * a string containing the event's type.
            * @public
            * @type {String}
            * @name type
            * @see https://developer.mozilla.org/en-US/docs/Web/API/Event/type
            * @memberOf me.Pointer
            */
            this.type = undefined;

           /**
            * the button property indicates which button was pressed on the mouse to trigger the event.
            * @public
            * @type {Number}
            * @name button
            * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
            * @memberOf me.Pointer
            */
            this.button = 0;

           /**
            * indicates whether or not the pointer device that created the event is the primary pointer.
            * @public
            * @type {Boolean}
            * @name isPrimary
            * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary
            * @memberOf me.Pointer
            */
            this.isPrimary = false;

           /**
            * the horizontal coordinate within the application's client area at which the event occurred
            * @public
            * @type {Number}
            * @name clientX
            * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX
            * @memberOf me.Pointer
            */
            this.clientX = undefined;

           /**
            * the vertical coordinate within the application's client area at which the event occurred
            * @public
            * @type {Number}
            * @name clientY
            * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientY
            * @memberOf me.Pointer
            */
            this.clientY = undefined;

          /**
            * Event normalized X coordinate within the game canvas itself

            *
            * @public
            * @type {Number}
            * @name gameX
            * @memberOf me.Pointer
            */
            this.gameX = undefined;

           /**
            * Event normalized Y coordinate within the game canvas itself

            *
            * @public
            * @type {Number}
            * @name gameY
            * @memberOf me.Pointer
            */
            this.gameY = undefined;

           /**
            * Event X coordinate relative to the viewport
            * @public
            * @type {Number}
            * @name gameScreenX
            * @memberOf me.Pointer
            */
            this.gameScreenX = undefined;

           /**
            * Event Y coordinate relative to the viewport
            * @public
            * @type {Number}
            * @name gameScreenY
            * @memberOf me.Pointer
            */
            this.gameScreenY = undefined;

           /**
            * Event X coordinate relative to the map
            * @public
            * @type {Number}
            * @name gameWorldX
            * @memberOf me.Pointer
            */
            this.gameWorldX = undefined;

           /**
            * Event Y coordinate relative to the map
            * @public
            * @type {Number}
            * @name gameWorldY
            * @memberOf me.Pointer
            */
            this.gameWorldY = undefined;

           /**
            * Event X coordinate relative to the holding container
            * @public
            * @type {Number}
            * @name gameLocalX
            * @memberOf me.Pointer
            */
            this.gameLocalX = undefined;

           /**
            * Event Y coordinate relative to the holding container
            * @public
            * @type {Number}
            * @name gameLocalY
            * @memberOf me.Pointer
            */
            this.gameLocalY = undefined;

           /**
            * The unique identifier of the contact for a touch, mouse or pen
            * @public
            * @type {Number}
            * @name pointerId
            * @memberOf me.Pointer
            * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId
            */
            this.pointerId = undefined;

            // parent constructor
            this._super(me.Rect, "init", [x || 0, y || 0, w || 1, h || 1]);
        },

        /**
         * initialize the Pointer object using the given Event Object
         * @name me.Pointer#set
         * @private
         * @function
         * @param {Event} event the original Event object
         * @param {Number} clientX the horizontal coordinate within the application's client area at which the event occurred
         * @param {Number} clientX the vertical coordinate within the application's client area at which the event occurred
         * @param {Number} pointedId the Pointer, Touch or Mouse event Id
         */
        setEvent : function (event, clientX, clientY, pointerId) {
            var width = 1;
            var height = 1;

            // the original event object
            this.event = event;

            this.clientX = clientX || 0;
            this.clientY = clientY || 0;

            // translate to local coordinates
            me.input.globalToLocal(clientX, clientY, this.pos);

            // true if not originally a pointer event
            this.isNormalized = !me.device.PointerEvent || (me.device.PointerEvent && !(event instanceof window.PointerEvent));

            if (event.type === "wheel") {
                this.deltaMode = 1;
                this.deltaX = event.deltaX;
                this.deltaY = - 1 / 40 * event.wheelDelta;
                event.wheelDeltaX && (this.deltaX = - 1 / 40 * event.wheelDeltaX);
            }

            // could be 0, so test if defined
            this.pointerId = (typeof pointerId !== "undefined") ? pointerId : 1;

            this.isPrimary = (typeof event.isPrimary !== "undefined") ? event.isPrimary : true;

            // in case of touch events, button is not defined
            this.button = event.button || 0;

            this.type = event.type;

            this.gameScreenX = this.pos.x;
            this.gameScreenY = this.pos.y;

            // get the current screen to world offset
            me.game.viewport.localToWorld(this.gameScreenX, this.gameScreenY, viewportOffset);

            /* Initialize the two coordinate space properties. */
            this.gameWorldX = viewportOffset.x;
            this.gameWorldY = viewportOffset.y;

            // get the pointer size
            if (this.isNormalized === false) {
                // native PointerEvent
                width = event.width;
                height = event.height;
            } else if (typeof(event.radiusX) === "number")  {
                // TouchEvent
                width = event.radiusX * 2;
                height = event.radiusY * 2;
            }
            // resize the pointer object accordingly
            this.resize(width, height);
        }
    });
})();
<|endoftext|>