<commit_before>var Board = require('./board')
var Tuple = require('tuple-w')

var sgf = require('./sgf')
var helper = require('./helper')
var setting = require('electron').remote.require('./modules/setting')

exports.new = function() {
    return {
        id: helper.getId(),
        nodes: [],
        subtrees: [],
        current: null,
        parent: null,
        collapsed: false
    }
}

exports.getRoot = function(tree) {
    while (tree.parent != null) tree = tree.parent
    return tree
}

exports.navigate = function(tree, index, step) {
    if (index + step >= 0 && index + step < tree.nodes.length) {
        return new Tuple(tree, index + step)
    } else if (index + step < 0 && tree.parent) {
        var prev = tree.parent
        var newstep = index + step + 1

        return exports.navigate(prev, prev.nodes.length - 1, newstep)
    } else if (index + step >= tree.nodes.length && tree.current != null) {
        var next = tree.subtrees[tree.current]
        var newstep = index + step - tree.nodes.length

        return exports.navigate(next, 0, newstep)
    }

    return new Tuple(null, 0)
}

exports.makeNodeIterator = function(tree, index) {
    var root = exports.getRoot(tree)
    var level = exports.getLevel(tree, index, root)
    var sections = exports.getSections(root, level)
    var j = sections.map(function(x) { return x[0] }).indexOf(tree)

    return {
        navigate: function(step) {
            if (j + step >= 0 && j + step < sections.length) {
                j = j + step
            } else if (j + step >= sections.length) {
                step = j + step - sections.length
                sections = exports.getSections(root, ++level)
                j = 0
                if (sections.length != 0) this.navigate(step)
            } else if (j + step < 0) {
                step = j + step + 1
                sections = exports.getSections(root, --level)
                j = sections.length - 1
                if (sections.length != 0) this.navigate(step)
            }
        },
        value: function() {
            return j < sections.length && j >= 0 ? sections[j] : null
        },
        next: function() {
            this.navigate(1)
            return this.value()
        },
        prev: function() {
            this.navigate(-1)
            return this.value()
        }
    }
}

exports.splitTree = function(tree, index) {
    if (index < 0 || index >= tree.nodes.length - 1) return tree

    var newnodes = tree.nodes.slice(0, index + 1)
    tree.nodes = tree.nodes.slice(index + 1)

    var newtree = exports.new()
    newtree.nodes = newnodes
    newtree.subtrees = [tree]
    newtree.parent = tree.parent
    newtree.current = 0
    tree.parent = newtree

    if (newtree.parent)
        newtree.parent.subtrees[newtree.parent.subtrees.indexOf(tree)] = newtree

    return newtree
}

exports.reduceTree = function(tree) {
    if (tree.subtrees.length != 1) return tree

    tree.nodes = tree.nodes.concat(tree.subtrees[0].nodes)
    tree.current = tree.subtrees[0].current
    tree.subtrees = tree.subtrees[0].subtrees

    tree.subtrees.forEach(function(subtree) {
        subtree.parent = tree
    })

    return tree
}

exports.getHeight = function(tree) {
    var height = 0

    tree.subtrees.forEach(function(subtree) {
        height = Math.max(exports.getHeight(subtree), height)
    })

    return height + tree.nodes.length
}

exports.getCurrentHeight = function(tree) {
    var height = tree.nodes.length

    if (tree.current != null)
        height += exports.getCurrentHeight(tree.subtrees[tree.current])

    return height
}

exports.getLevel = function(tree, index) {
    return index + (tree.parent ? exports.getLevel(tree.parent, tree.parent.nodes.length) : 0)
}

exports.getSections = function(tree, level) {
    if (level < 0) return []
    if (level < tree.nodes.length) return [new Tuple(tree, level)]

    var sections = []

    tree.subtrees.forEach(function(subtree) {
        sections = sections.concat(exports.getSections(subtree, level - tree.nodes.length))
    })

    return sections
}

exports.getWidth = function(y, matrix) {
    var keys = Object.keys(new Int8Array(10)).map(function(i) {
        return parseFloat(i) + y - 4
    }).filter(function(i) { return i >= 0 && i < matrix.length })

    var padding = Math.min.apply(null, keys.map(function(i) {
        return matrix[i].indexOf(matrix[i].pick())
    }))
    var width = Math.max.apply(null, keys.map(function(i) {
        return matrix[i].length
    })) - padding

    return new Tuple(width, padding)
}

exports.tree2matrixdict = function(tree, matrix, dict, xshift, yshift) {
    if (!matrix) matrix = Array.apply(null, new Array(exports.getHeight(tree))).map(function() { return [] });
    if (!dict) dict = {}
    if (!xshift) xshift = 0
    if (!yshift) yshift = 0

    var hasCollisions = true
    while (hasCollisions) {
        hasCollisions = false

        for (var y = 0; y < Math.min(tree.nodes.length + 1, matrix.length - yshift); y++) {
            if (xshift >= matrix[yshift + y].length) continue

            hasCollisions = true
            xshift++
            break
        }
    }

    for (var y = 0; y < tree.nodes.length; y++) {
        while (xshift >= matrix[yshift + y].length) {
            matrix[yshift + y].push(null)
        }

        matrix[yshift + y][xshift] = new Tuple(tree, y)
        dict[tree.id + '-' + y] = new Tuple(xshift, yshift + y)
    }

    if (!tree.collapsed) {
        for (var k = 0; k < tree.subtrees.length; k++) {
            var subtree = tree.subtrees[k]
            exports.tree2matrixdict(subtree, matrix, dict, xshift, yshift + tree.nodes.length)
        }
    }

    return new Tuple(matrix, dict)
}

exports.onCurrentTrack = function(tree) {
    return !tree.parent || tree.parent.subtrees[tree.parent.current] == tree && exports.onCurrentTrack(tree.parent)
}

exports.matrixdict2graph = function(matrixdict) {
    var matrix = matrixdict[0]
    var dict = matrixdict[1]
    var graph = { nodes: [], edges: [] }
    var currentTrack = []
    var notCurrentTrack = []
    var width = Math.max.apply(null, matrix.map(function(x) { return x.length }))
    var gridSize = setting.get('graph.grid_size')

    for (y = 0; y < matrix.length; y++) {
        for (x = 0; x < width; x++) {
            if (!matrix[y][x]) continue

            var tree = matrix[y][x][0]
            var index = matrix[y][x][1]
            var id = tree.id + '-' + index
            var node = {
                id: id,
                x: x * gridSize,
                y: y * gridSize,
                size: setting.get('graph.node_size'),
                data: matrix[y][x],
                originalColor: setting.get('graph.node_color')
            }

            if ('C' in tree.nodes[index])
                node.originalColor = setting.get('graph.node_comment_color')

            if (currentTrack.indexOf(tree.id) != -1) {
                node.color = node.originalColor
            } else if (notCurrentTrack.indexOf(tree.id) == -1) {
                if (exports.onCurrentTrack(tree)) {
                    currentTrack.push(tree.id)
                    node.color = node.originalColor
                } else {
                    notCurrentTrack.push(tree.id)
                }
            }

            if (tree.collapsed && tree.subtrees.length > 0 && index == tree.nodes.length - 1)
                node.color = node.originalColor = setting.get('graph.node_collapsed_color')

            graph.nodes.push(node)

            var prev = exports.navigate(tree, index, -1)
            if (!prev[0]) continue
            var prevId = prev[0].id + '-' + prev[1]
            var prevPos = dict[prevId]

            if (prevPos[0] != x) {
                graph.nodes.push({
                    'id': id + '-h',
                    'x': (x - 1) * gridSize,
                    'y': (y - 1) * gridSize,
                    'size': 0
                })

                graph.edges.push({
                    'id': id + '-e1',
                    'source': id,
                    'target': id + '-h'
                })

                graph.edges.push({
                    'id': id + '-e2',
                    'source': id + '-h',
                    'target': prevId
                })
            } else {
                graph.edges.push({
                    'id': id + '-e1',
                    'source': id,
                    'target': prevId
                })
            }
        }
    }

    return graph
}

exports.getHash = function(tree) {
    return helper.md5(sgf.tree2string(tree))
}
<commit_msg>Add gametree.clone()<commit_after>var Board = require('./board')
var Tuple = require('tuple-w')

var sgf = require('./sgf')
var helper = require('./helper')
var setting = require('electron').remote.require('./modules/setting')

exports.new = function() {
    return {
        id: helper.getId(),
        nodes: [],
        subtrees: [],
        current: null,
        parent: null,
        collapsed: false
    }
}

exports.clone = function(tree, parent) {
    if (!parent) parent = null

    var c = {
        id: tree.id,
        nodes: [],
        subtrees: [],
        current: tree.current,
        parent: parent,
        collapsed: tree.collapsed
    }

    tree.nodes.forEach(function(node) {
        c.nodes.push(Object.clone(node))
    })

    tree.subtrees.forEach(function(subtree) {
        c.subtrees.push(exports.clone(subtree), c)
    })

    return c
}

exports.getRoot = function(tree) {
    while (tree.parent != null) tree = tree.parent
    return tree
}

exports.navigate = function(tree, index, step) {
    if (index + step >= 0 && index + step < tree.nodes.length) {
        return new Tuple(tree, index + step)
    } else if (index + step < 0 && tree.parent) {
        var prev = tree.parent
        var newstep = index + step + 1

        return exports.navigate(prev, prev.nodes.length - 1, newstep)
    } else if (index + step >= tree.nodes.length && tree.current != null) {
        var next = tree.subtrees[tree.current]
        var newstep = index + step - tree.nodes.length

        return exports.navigate(next, 0, newstep)
    }

    return new Tuple(null, 0)
}

exports.makeNodeIterator = function(tree, index) {
    var root = exports.getRoot(tree)
    var level = exports.getLevel(tree, index, root)
    var sections = exports.getSections(root, level)
    var j = sections.map(function(x) { return x[0] }).indexOf(tree)

    return {
        navigate: function(step) {
            if (j + step >= 0 && j + step < sections.length) {
                j = j + step
            } else if (j + step >= sections.length) {
                step = j + step - sections.length
                sections = exports.getSections(root, ++level)
                j = 0
                if (sections.length != 0) this.navigate(step)
            } else if (j + step < 0) {
                step = j + step + 1
                sections = exports.getSections(root, --level)
                j = sections.length - 1
                if (sections.length != 0) this.navigate(step)
            }
        },
        value: function() {
            return j < sections.length && j >= 0 ? sections[j] : null
        },
        next: function() {
            this.navigate(1)
            return this.value()
        },
        prev: function() {
            this.navigate(-1)
            return this.value()
        }
    }
}

exports.splitTree = function(tree, index) {
    if (index < 0 || index >= tree.nodes.length - 1) return tree

    var newnodes = tree.nodes.slice(0, index + 1)
    tree.nodes = tree.nodes.slice(index + 1)

    var newtree = exports.new()
    newtree.nodes = newnodes
    newtree.subtrees = [tree]
    newtree.parent = tree.parent
    newtree.current = 0
    tree.parent = newtree

    if (newtree.parent)
        newtree.parent.subtrees[newtree.parent.subtrees.indexOf(tree)] = newtree

    return newtree
}

exports.reduceTree = function(tree) {
    if (tree.subtrees.length != 1) return tree

    tree.nodes = tree.nodes.concat(tree.subtrees[0].nodes)
    tree.current = tree.subtrees[0].current
    tree.subtrees = tree.subtrees[0].subtrees

    tree.subtrees.forEach(function(subtree) {
        subtree.parent = tree
    })

    return tree
}

exports.getHeight = function(tree) {
    var height = 0

    tree.subtrees.forEach(function(subtree) {
        height = Math.max(exports.getHeight(subtree), height)
    })

    return height + tree.nodes.length
}

exports.getCurrentHeight = function(tree) {
    var height = tree.nodes.length

    if (tree.current != null)
        height += exports.getCurrentHeight(tree.subtrees[tree.current])

    return height
}

exports.getLevel = function(tree, index) {
    return index + (tree.parent ? exports.getLevel(tree.parent, tree.parent.nodes.length) : 0)
}

exports.getSections = function(tree, level) {
    if (level < 0) return []
    if (level < tree.nodes.length) return [new Tuple(tree, level)]

    var sections = []

    tree.subtrees.forEach(function(subtree) {
        sections = sections.concat(exports.getSections(subtree, level - tree.nodes.length))
    })

    return sections
}

exports.getWidth = function(y, matrix) {
    var keys = Object.keys(new Int8Array(10)).map(function(i) {
        return parseFloat(i) + y - 4
    }).filter(function(i) { return i >= 0 && i < matrix.length })

    var padding = Math.min.apply(null, keys.map(function(i) {
        return matrix[i].indexOf(matrix[i].pick())
    }))
    var width = Math.max.apply(null, keys.map(function(i) {
        return matrix[i].length
    })) - padding

    return new Tuple(width, padding)
}

exports.tree2matrixdict = function(tree, matrix, dict, xshift, yshift) {
    if (!matrix) matrix = Array.apply(null, new Array(exports.getHeight(tree))).map(function() { return [] });
    if (!dict) dict = {}
    if (!xshift) xshift = 0
    if (!yshift) yshift = 0

    var hasCollisions = true
    while (hasCollisions) {
        hasCollisions = false

        for (var y = 0; y < Math.min(tree.nodes.length + 1, matrix.length - yshift); y++) {
            if (xshift >= matrix[yshift + y].length) continue

            hasCollisions = true
            xshift++
            break
        }
    }

    for (var y = 0; y < tree.nodes.length; y++) {
        while (xshift >= matrix[yshift + y].length) {
            matrix[yshift + y].push(null)
        }

        matrix[yshift + y][xshift] = new Tuple(tree, y)
        dict[tree.id + '-' + y] = new Tuple(xshift, yshift + y)
    }

    if (!tree.collapsed) {
        for (var k = 0; k < tree.subtrees.length; k++) {
            var subtree = tree.subtrees[k]
            exports.tree2matrixdict(subtree, matrix, dict, xshift, yshift + tree.nodes.length)
        }
    }

    return new Tuple(matrix, dict)
}

exports.onCurrentTrack = function(tree) {
    return !tree.parent || tree.parent.subtrees[tree.parent.current] == tree && exports.onCurrentTrack(tree.parent)
}

exports.matrixdict2graph = function(matrixdict) {
    var matrix = matrixdict[0]
    var dict = matrixdict[1]
    var graph = { nodes: [], edges: [] }
    var currentTrack = []
    var notCurrentTrack = []
    var width = Math.max.apply(null, matrix.map(function(x) { return x.length }))
    var gridSize = setting.get('graph.grid_size')

    for (y = 0; y < matrix.length; y++) {
        for (x = 0; x < width; x++) {
            if (!matrix[y][x]) continue

            var tree = matrix[y][x][0]
            var index = matrix[y][x][1]
            var id = tree.id + '-' + index
            var node = {
                id: id,
                x: x * gridSize,
                y: y * gridSize,
                size: setting.get('graph.node_size'),
                data: matrix[y][x],
                originalColor: setting.get('graph.node_color')
            }

            if ('C' in tree.nodes[index])
                node.originalColor = setting.get('graph.node_comment_color')

            if (currentTrack.indexOf(tree.id) != -1) {
                node.color = node.originalColor
            } else if (notCurrentTrack.indexOf(tree.id) == -1) {
                if (exports.onCurrentTrack(tree)) {
                    currentTrack.push(tree.id)
                    node.color = node.originalColor
                } else {
                    notCurrentTrack.push(tree.id)
                }
            }

            if (tree.collapsed && tree.subtrees.length > 0 && index == tree.nodes.length - 1)
                node.color = node.originalColor = setting.get('graph.node_collapsed_color')

            graph.nodes.push(node)

            var prev = exports.navigate(tree, index, -1)
            if (!prev[0]) continue
            var prevId = prev[0].id + '-' + prev[1]
            var prevPos = dict[prevId]

            if (prevPos[0] != x) {
                graph.nodes.push({
                    'id': id + '-h',
                    'x': (x - 1) * gridSize,
                    'y': (y - 1) * gridSize,
                    'size': 0
                })

                graph.edges.push({
                    'id': id + '-e1',
                    'source': id,
                    'target': id + '-h'
                })

                graph.edges.push({
                    'id': id + '-e2',
                    'source': id + '-h',
                    'target': prevId
                })
            } else {
                graph.edges.push({
                    'id': id + '-e1',
                    'source': id,
                    'target': prevId
                })
            }
        }
    }

    return graph
}

exports.getHash = function(tree) {
    return helper.md5(sgf.tree2string(tree))
}
<|endoftext|>


<commit_before>import clone from 'clone';
import equal from 'deep-equal';
import extend from 'extend';
import DeltaOp from 'quill-delta/lib/op';
import Parchment from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';

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

const SHORTKEY = /Mac/i.test(navigator.platform) ? 'metaKey' : 'ctrlKey';

class Keyboard extends Module {
  static match(evt, binding) {
    binding = normalize(binding);
    if (!!binding.shortKey !== evt[SHORTKEY] && binding.shortKey !== null) return false;
    if (['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].some(function(key) {
      return (key != SHORTKEY && !!binding[key] !== evt[key] && binding[key] !== null);
    })) {
      return false;
    }
    return binding.key === (evt.which || evt.keyCode);
  }

  constructor(quill, options) {
    super(quill, options);
    this.bindings = {};
    Object.keys(this.options.bindings).forEach((name) => {
      if (this.options.bindings[name]) {
        this.addBinding(this.options.bindings[name]);
      }
    });
    this.addBinding({ key: Keyboard.keys.ENTER, shiftKey: null }, handleEnter);
    this.addBinding({ key: Keyboard.keys.ENTER, metaKey: null, ctrlKey: null, altKey: null }, function() {});
    if (/Gecko/i.test(navigator.userAgent)) {
      // Need to handle delete and backspace for Firefox in the general case #1171
      this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: true }, handleBackspace);
      this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: true }, handleDelete);
    } else {
      this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: true, prefix: /^.?$/ }, handleBackspace);
      this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: true, suffix: /^.?$/ }, handleDelete);
    }
    this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: false }, handleDeleteRange);
    this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: false }, handleDeleteRange);
    this.listen();
  }

  addBinding(key, context = {}, handler = {}) {
    let binding = normalize(key);
    if (binding == null || binding.key == null) {
      return debug.warn('Attempted to add invalid keyboard binding', binding);
    }
    if (typeof context === 'function') {
      context = { handler: context };
    }
    if (typeof handler === 'function') {
      handler = { handler: handler };
    }
    binding = extend(binding, context, handler);
    this.bindings[binding.key] = this.bindings[binding.key] || [];
    this.bindings[binding.key].push(binding);
  }

  listen() {
    this.quill.root.addEventListener('keydown', (evt) => {
      if (evt.defaultPrevented) return;
      let which = evt.which || evt.keyCode;
      let bindings = (this.bindings[which] || []).filter(function(binding) {
        return Keyboard.match(evt, binding);
      });
      if (bindings.length === 0) return;
      let range = this.quill.getSelection();
      if (range == null || !this.quill.hasFocus()) return;
      let [line, offset] = this.quill.scroll.line(range.index);
      let [leafStart, offsetStart] = this.quill.scroll.leaf(range.index);
      let [leafEnd, offsetEnd] = range.length === 0 ? [leafStart, offsetStart] : this.quill.scroll.leaf(range.index + range.length);
      let prefixText = leafStart instanceof Parchment.Text ? leafStart.value().slice(0, offsetStart) : '';
      let suffixText = leafEnd instanceof Parchment.Text ? leafEnd.value().slice(offsetEnd) : '';
      let curContext = {
        collapsed: range.length === 0,
        empty: range.length === 0 && line.length() <= 1,
        format: this.quill.getFormat(range),
        offset: offset,
        prefix: prefixText,
        suffix: suffixText
      };
      let prevented = bindings.some((binding) => {
        if (binding.collapsed != null && binding.collapsed !== curContext.collapsed) return false;
        if (binding.empty != null && binding.empty !== curContext.empty) return false;
        if (binding.offset != null && binding.offset !== curContext.offset) return false;
        if (Array.isArray(binding.format)) {
          // any format is present
          if (binding.format.every(function(name) {
            return curContext.format[name] == null;
          })) {
            return false;
          }
        } else if (typeof binding.format === 'object') {
          // all formats must match
          if (!Object.keys(binding.format).every(function(name) {
            if (binding.format[name] === true) return curContext.format[name] != null;
            if (binding.format[name] === false) return curContext.format[name] == null;
            return equal(binding.format[name], curContext.format[name]);
          })) {
            return false;
          }
        }
        if (binding.prefix != null && !binding.prefix.test(curContext.prefix)) return false;
        if (binding.suffix != null && !binding.suffix.test(curContext.suffix)) return false;
        return binding.handler.call(this, range, curContext) !== true;
      });
      if (prevented) {
        evt.preventDefault();
      }
    });
  }
}

Keyboard.keys = {
  BACKSPACE: 8,
  TAB: 9,
  ENTER: 13,
  ESCAPE: 27,
  LEFT: 37,
  UP: 38,
  RIGHT: 39,
  DOWN: 40,
  DELETE: 46
};

Keyboard.DEFAULTS = {
  bindings: {
    'bold'      : makeFormatHandler('bold'),
    'italic'    : makeFormatHandler('italic'),
    'underline' : makeFormatHandler('underline'),
    'indent': {
      // highlight tab or tab at beginning of list, indent or blockquote
      key: Keyboard.keys.TAB,
      format: ['blockquote', 'indent', 'list'],
      handler: function(range, context) {
        if (context.collapsed && context.offset !== 0) return true;
        this.quill.format('indent', '+1', Quill.sources.USER);
      }
    },
    'outdent': {
      key: Keyboard.keys.TAB,
      shiftKey: true,
      format: ['blockquote', 'indent', 'list'],
      // highlight tab or tab at beginning of list, indent or blockquote
      handler: function(range, context) {
        if (context.collapsed && context.offset !== 0) return true;
        this.quill.format('indent', '-1', Quill.sources.USER);
      }
    },
    'outdent backspace': {
      key: Keyboard.keys.BACKSPACE,
      collapsed: true,
      format: ['blockquote', 'indent', 'list'],
      offset: 0,
      handler: function(range, context) {
        if (context.format.indent != null) {
          this.quill.format('indent', '-1', Quill.sources.USER);
        } else if (context.format.blockquote != null) {
          this.quill.format('blockquote', false, Quill.sources.USER);
        } else if (context.format.list != null) {
          this.quill.format('list', false, Quill.sources.USER);
        }
      }
    },
    'indent code-block': makeCodeBlockHandler(true),
    'outdent code-block': makeCodeBlockHandler(false),
    'remove tab': {
      key: Keyboard.keys.TAB,
      shiftKey: true,
      collapsed: true,
      prefix: /\t$/,
      handler: function(range) {
        this.quill.deleteText(range.index - 1, 1, Quill.sources.USER);
      }
    },
    'tab': {
      key: Keyboard.keys.TAB,
      handler: function(range, context) {
        if (!context.collapsed) {
          this.quill.scroll.deleteAt(range.index, range.length);
        }
        this.quill.insertText(range.index, '\t', Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
      }
    },
    'list empty enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: ['list'],
      empty: true,
      handler: function(range, context) {
        this.quill.format('list', false, Quill.sources.USER);
        if (context.format.indent) {
          this.quill.format('indent', false, Quill.sources.USER);
        }
      }
    },
    'checklist enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: { list: 'checked' },
      handler: function(range) {
        this.quill.scroll.insertAt(range.index, '\n');
        let [line, ] = this.quill.scroll.line(range.index + 1);
        line.format('list', 'unchecked');
        this.quill.update(Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
        this.quill.selection.scrollIntoView();
      }
    },
    'header enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: ['header'],
      suffix: /^$/,
      handler: function(range) {
        this.quill.scroll.insertAt(range.index, '\n');
        this.quill.formatText(range.index + 1, 1, 'header', false, Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
        this.quill.selection.scrollIntoView();
      }
    },
    'list autofill': {
      key: ' ',
      collapsed: true,
      format: { list: false },
      prefix: /^(1\.|-)$/,
      handler: function(range, context) {
        let length = context.prefix.length;
        this.quill.scroll.deleteAt(range.index - length, length);
        this.quill.formatLine(range.index - length, 1, 'list', length === 1 ? 'bullet' : 'ordered', Quill.sources.USER);
        this.quill.setSelection(range.index - length, Quill.sources.SILENT);
      }
    }
  }
};

function handleBackspace(range, context) {
  if (range.index === 0) return;
  let [line, ] = this.quill.scroll.line(range.index);
  let formats = {};
  if (context.offset === 0) {
    let curFormats = line.formats();
    let prevFormats = this.quill.getFormat(range.index-1, 1);
    formats = DeltaOp.attributes.diff(curFormats, prevFormats) || {};
  }
  this.quill.deleteText(range.index-1, 1, Quill.sources.USER);
  if (Object.keys(formats).length > 0) {
    this.quill.formatLine(range.index-1, 1, formats, Quill.sources.USER);
  }
  this.quill.selection.scrollIntoView();
}

function handleDelete(range) {
  if (range.index >= this.quill.getLength() - 1) return;
  this.quill.deleteText(range.index, 1, Quill.sources.USER);
}

function handleDeleteRange(range) {
  this.quill.deleteText(range, Quill.sources.USER);
  this.quill.setSelection(range.index, Quill.sources.SILENT);
  this.quill.selection.scrollIntoView();
}

function handleEnter(range, context) {
  if (range.length > 0) {
    this.quill.scroll.deleteAt(range.index, range.length);  // So we do not trigger text-change
  }
  let lineFormats = Object.keys(context.format).reduce(function(lineFormats, format) {
    if (Parchment.query(format, Parchment.Scope.BLOCK) && !Array.isArray(context.format[format])) {
      lineFormats[format] = context.format[format];
    }
    return lineFormats;
  }, {});
  this.quill.insertText(range.index, '\n', lineFormats, Quill.sources.USER);
  this.quill.selection.scrollIntoView();
  Object.keys(context.format).forEach((name) => {
    if (lineFormats[name] != null) return;
    if (Array.isArray(context.format[name])) return;
    if (name === 'link') return;
    this.quill.format(name, context.format[name], Quill.sources.USER);
  });
}

function makeCodeBlockHandler(indent) {
  return {
    key: Keyboard.keys.TAB,
    shiftKey: !indent,
    format: {'code-block': true },
    handler: function(range) {
      let CodeBlock = Parchment.query('code-block');
      let index = range.index, length = range.length;
      let [block, offset] = this.quill.scroll.descendant(CodeBlock, index);
      if (block == null) return;
      let scrollOffset = this.quill.scroll.offset(block);
      let start = block.newlineIndex(offset, true) + 1;
      let end = block.newlineIndex(scrollOffset + offset + length);
      let lines = block.domNode.textContent.slice(start, end).split('\n');
      offset = 0;
      lines.forEach((line, i) => {
        if (indent) {
          block.insertAt(start + offset, CodeBlock.TAB);
          offset += CodeBlock.TAB.length;
          if (i === 0) {
            index += CodeBlock.TAB.length;
          } else {
            length += CodeBlock.TAB.length;
          }
        } else if (line.startsWith(CodeBlock.TAB)) {
          block.deleteAt(start + offset, CodeBlock.TAB.length);
          offset -= CodeBlock.TAB.length;
          if (i === 0) {
            index -= CodeBlock.TAB.length;
          } else {
            length -= CodeBlock.TAB.length;
          }
        }
        offset += line.length + 1;
      });
      this.quill.update(Quill.sources.USER);
      this.quill.setSelection(index, length, Quill.sources.SILENT);
    }
  };
}

function makeFormatHandler(format) {
  return {
    key: format[0].toUpperCase(),
    shortKey: true,
    handler: function(range, context) {
      this.quill.format(format, !context.format[format], Quill.sources.USER);
    }
  };
}

function normalize(binding) {
  if (typeof binding === 'string' || typeof binding === 'number') {
    return normalize({ key: binding });
  }
  if (typeof binding === 'object') {
    binding = clone(binding, false);
  }
  if (typeof binding.key === 'string') {
    if (Keyboard.keys[binding.key.toUpperCase()] != null) {
      binding.key = Keyboard.keys[binding.key.toUpperCase()];
    } else if (binding.key.length === 1) {
      binding.key = binding.key.toUpperCase().charCodeAt(0);
    } else {
      return null;
    }
  }
  return binding;
}

export default Keyboard;
<commit_msg>fix UA test<commit_after>import clone from 'clone';
import equal from 'deep-equal';
import extend from 'extend';
import DeltaOp from 'quill-delta/lib/op';
import Parchment from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';

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

const SHORTKEY = /Mac/i.test(navigator.platform) ? 'metaKey' : 'ctrlKey';

class Keyboard extends Module {
  static match(evt, binding) {
    binding = normalize(binding);
    if (!!binding.shortKey !== evt[SHORTKEY] && binding.shortKey !== null) return false;
    if (['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].some(function(key) {
      return (key != SHORTKEY && !!binding[key] !== evt[key] && binding[key] !== null);
    })) {
      return false;
    }
    return binding.key === (evt.which || evt.keyCode);
  }

  constructor(quill, options) {
    super(quill, options);
    this.bindings = {};
    Object.keys(this.options.bindings).forEach((name) => {
      if (this.options.bindings[name]) {
        this.addBinding(this.options.bindings[name]);
      }
    });
    this.addBinding({ key: Keyboard.keys.ENTER, shiftKey: null }, handleEnter);
    this.addBinding({ key: Keyboard.keys.ENTER, metaKey: null, ctrlKey: null, altKey: null }, function() {});
    if (/Firefox/i.test(navigator.userAgent)) {
      // Need to handle delete and backspace for Firefox in the general case #1171
      this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: true }, handleBackspace);
      this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: true }, handleDelete);
    } else {
      this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: true, prefix: /^.?$/ }, handleBackspace);
      this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: true, suffix: /^.?$/ }, handleDelete);
    }
    this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: false }, handleDeleteRange);
    this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: false }, handleDeleteRange);
    this.listen();
  }

  addBinding(key, context = {}, handler = {}) {
    let binding = normalize(key);
    if (binding == null || binding.key == null) {
      return debug.warn('Attempted to add invalid keyboard binding', binding);
    }
    if (typeof context === 'function') {
      context = { handler: context };
    }
    if (typeof handler === 'function') {
      handler = { handler: handler };
    }
    binding = extend(binding, context, handler);
    this.bindings[binding.key] = this.bindings[binding.key] || [];
    this.bindings[binding.key].push(binding);
  }

  listen() {
    this.quill.root.addEventListener('keydown', (evt) => {
      if (evt.defaultPrevented) return;
      let which = evt.which || evt.keyCode;
      let bindings = (this.bindings[which] || []).filter(function(binding) {
        return Keyboard.match(evt, binding);
      });
      if (bindings.length === 0) return;
      let range = this.quill.getSelection();
      if (range == null || !this.quill.hasFocus()) return;
      let [line, offset] = this.quill.scroll.line(range.index);
      let [leafStart, offsetStart] = this.quill.scroll.leaf(range.index);
      let [leafEnd, offsetEnd] = range.length === 0 ? [leafStart, offsetStart] : this.quill.scroll.leaf(range.index + range.length);
      let prefixText = leafStart instanceof Parchment.Text ? leafStart.value().slice(0, offsetStart) : '';
      let suffixText = leafEnd instanceof Parchment.Text ? leafEnd.value().slice(offsetEnd) : '';
      let curContext = {
        collapsed: range.length === 0,
        empty: range.length === 0 && line.length() <= 1,
        format: this.quill.getFormat(range),
        offset: offset,
        prefix: prefixText,
        suffix: suffixText
      };
      let prevented = bindings.some((binding) => {
        if (binding.collapsed != null && binding.collapsed !== curContext.collapsed) return false;
        if (binding.empty != null && binding.empty !== curContext.empty) return false;
        if (binding.offset != null && binding.offset !== curContext.offset) return false;
        if (Array.isArray(binding.format)) {
          // any format is present
          if (binding.format.every(function(name) {
            return curContext.format[name] == null;
          })) {
            return false;
          }
        } else if (typeof binding.format === 'object') {
          // all formats must match
          if (!Object.keys(binding.format).every(function(name) {
            if (binding.format[name] === true) return curContext.format[name] != null;
            if (binding.format[name] === false) return curContext.format[name] == null;
            return equal(binding.format[name], curContext.format[name]);
          })) {
            return false;
          }
        }
        if (binding.prefix != null && !binding.prefix.test(curContext.prefix)) return false;
        if (binding.suffix != null && !binding.suffix.test(curContext.suffix)) return false;
        return binding.handler.call(this, range, curContext) !== true;
      });
      if (prevented) {
        evt.preventDefault();
      }
    });
  }
}

Keyboard.keys = {
  BACKSPACE: 8,
  TAB: 9,
  ENTER: 13,
  ESCAPE: 27,
  LEFT: 37,
  UP: 38,
  RIGHT: 39,
  DOWN: 40,
  DELETE: 46
};

Keyboard.DEFAULTS = {
  bindings: {
    'bold'      : makeFormatHandler('bold'),
    'italic'    : makeFormatHandler('italic'),
    'underline' : makeFormatHandler('underline'),
    'indent': {
      // highlight tab or tab at beginning of list, indent or blockquote
      key: Keyboard.keys.TAB,
      format: ['blockquote', 'indent', 'list'],
      handler: function(range, context) {
        if (context.collapsed && context.offset !== 0) return true;
        this.quill.format('indent', '+1', Quill.sources.USER);
      }
    },
    'outdent': {
      key: Keyboard.keys.TAB,
      shiftKey: true,
      format: ['blockquote', 'indent', 'list'],
      // highlight tab or tab at beginning of list, indent or blockquote
      handler: function(range, context) {
        if (context.collapsed && context.offset !== 0) return true;
        this.quill.format('indent', '-1', Quill.sources.USER);
      }
    },
    'outdent backspace': {
      key: Keyboard.keys.BACKSPACE,
      collapsed: true,
      format: ['blockquote', 'indent', 'list'],
      offset: 0,
      handler: function(range, context) {
        if (context.format.indent != null) {
          this.quill.format('indent', '-1', Quill.sources.USER);
        } else if (context.format.blockquote != null) {
          this.quill.format('blockquote', false, Quill.sources.USER);
        } else if (context.format.list != null) {
          this.quill.format('list', false, Quill.sources.USER);
        }
      }
    },
    'indent code-block': makeCodeBlockHandler(true),
    'outdent code-block': makeCodeBlockHandler(false),
    'remove tab': {
      key: Keyboard.keys.TAB,
      shiftKey: true,
      collapsed: true,
      prefix: /\t$/,
      handler: function(range) {
        this.quill.deleteText(range.index - 1, 1, Quill.sources.USER);
      }
    },
    'tab': {
      key: Keyboard.keys.TAB,
      handler: function(range, context) {
        if (!context.collapsed) {
          this.quill.scroll.deleteAt(range.index, range.length);
        }
        this.quill.insertText(range.index, '\t', Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
      }
    },
    'list empty enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: ['list'],
      empty: true,
      handler: function(range, context) {
        this.quill.format('list', false, Quill.sources.USER);
        if (context.format.indent) {
          this.quill.format('indent', false, Quill.sources.USER);
        }
      }
    },
    'checklist enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: { list: 'checked' },
      handler: function(range) {
        this.quill.scroll.insertAt(range.index, '\n');
        let [line, ] = this.quill.scroll.line(range.index + 1);
        line.format('list', 'unchecked');
        this.quill.update(Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
        this.quill.selection.scrollIntoView();
      }
    },
    'header enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: ['header'],
      suffix: /^$/,
      handler: function(range) {
        this.quill.scroll.insertAt(range.index, '\n');
        this.quill.formatText(range.index + 1, 1, 'header', false, Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
        this.quill.selection.scrollIntoView();
      }
    },
    'list autofill': {
      key: ' ',
      collapsed: true,
      format: { list: false },
      prefix: /^(1\.|-)$/,
      handler: function(range, context) {
        let length = context.prefix.length;
        this.quill.scroll.deleteAt(range.index - length, length);
        this.quill.formatLine(range.index - length, 1, 'list', length === 1 ? 'bullet' : 'ordered', Quill.sources.USER);
        this.quill.setSelection(range.index - length, Quill.sources.SILENT);
      }
    }
  }
};

function handleBackspace(range, context) {
  if (range.index === 0) return;
  let [line, ] = this.quill.scroll.line(range.index);
  let formats = {};
  if (context.offset === 0) {
    let curFormats = line.formats();
    let prevFormats = this.quill.getFormat(range.index-1, 1);
    formats = DeltaOp.attributes.diff(curFormats, prevFormats) || {};
  }
  this.quill.deleteText(range.index-1, 1, Quill.sources.USER);
  if (Object.keys(formats).length > 0) {
    this.quill.formatLine(range.index-1, 1, formats, Quill.sources.USER);
  }
  this.quill.selection.scrollIntoView();
}

function handleDelete(range) {
  if (range.index >= this.quill.getLength() - 1) return;
  this.quill.deleteText(range.index, 1, Quill.sources.USER);
}

function handleDeleteRange(range) {
  this.quill.deleteText(range, Quill.sources.USER);
  this.quill.setSelection(range.index, Quill.sources.SILENT);
  this.quill.selection.scrollIntoView();
}

function handleEnter(range, context) {
  if (range.length > 0) {
    this.quill.scroll.deleteAt(range.index, range.length);  // So we do not trigger text-change
  }
  let lineFormats = Object.keys(context.format).reduce(function(lineFormats, format) {
    if (Parchment.query(format, Parchment.Scope.BLOCK) && !Array.isArray(context.format[format])) {
      lineFormats[format] = context.format[format];
    }
    return lineFormats;
  }, {});
  this.quill.insertText(range.index, '\n', lineFormats, Quill.sources.USER);
  this.quill.selection.scrollIntoView();
  Object.keys(context.format).forEach((name) => {
    if (lineFormats[name] != null) return;
    if (Array.isArray(context.format[name])) return;
    if (name === 'link') return;
    this.quill.format(name, context.format[name], Quill.sources.USER);
  });
}

function makeCodeBlockHandler(indent) {
  return {
    key: Keyboard.keys.TAB,
    shiftKey: !indent,
    format: {'code-block': true },
    handler: function(range) {
      let CodeBlock = Parchment.query('code-block');
      let index = range.index, length = range.length;
      let [block, offset] = this.quill.scroll.descendant(CodeBlock, index);
      if (block == null) return;
      let scrollOffset = this.quill.scroll.offset(block);
      let start = block.newlineIndex(offset, true) + 1;
      let end = block.newlineIndex(scrollOffset + offset + length);
      let lines = block.domNode.textContent.slice(start, end).split('\n');
      offset = 0;
      lines.forEach((line, i) => {
        if (indent) {
          block.insertAt(start + offset, CodeBlock.TAB);
          offset += CodeBlock.TAB.length;
          if (i === 0) {
            index += CodeBlock.TAB.length;
          } else {
            length += CodeBlock.TAB.length;
          }
        } else if (line.startsWith(CodeBlock.TAB)) {
          block.deleteAt(start + offset, CodeBlock.TAB.length);
          offset -= CodeBlock.TAB.length;
          if (i === 0) {
            index -= CodeBlock.TAB.length;
          } else {
            length -= CodeBlock.TAB.length;
          }
        }
        offset += line.length + 1;
      });
      this.quill.update(Quill.sources.USER);
      this.quill.setSelection(index, length, Quill.sources.SILENT);
    }
  };
}

function makeFormatHandler(format) {
  return {
    key: format[0].toUpperCase(),
    shortKey: true,
    handler: function(range, context) {
      this.quill.format(format, !context.format[format], Quill.sources.USER);
    }
  };
}

function normalize(binding) {
  if (typeof binding === 'string' || typeof binding === 'number') {
    return normalize({ key: binding });
  }
  if (typeof binding === 'object') {
    binding = clone(binding, false);
  }
  if (typeof binding.key === 'string') {
    if (Keyboard.keys[binding.key.toUpperCase()] != null) {
      binding.key = Keyboard.keys[binding.key.toUpperCase()];
    } else if (binding.key.length === 1) {
      binding.key = binding.key.toUpperCase().charCodeAt(0);
    } else {
      return null;
    }
  }
  return binding;
}

export default Keyboard;
<|endoftext|>


<commit_before>/**
 * @fileOverview XMLHttpRequest wrapper module for restartless addons
 * @author       YUKI "Piro" Hiroshi
 * @version      6
 * @description
 *   // get as a text
 *   http.get('http://.....',
 *            { 'X-Response-Content-Type': 'text/plain; charset=EUC-JP' })
 *       .next(function(aResponse) {
 *         console.log(aResponse.responseText);
 *       });
 *
 *   // get as a JSON
 *   http.getAsJSON('http://.....')
 *       .next(function(aResponse) {
 *         var body = aResponse.response;
 *         console.log(function);
 *       });
 *
 *   // get as a binary
 *   http.getAsBinary('http://.....')
 *       .next(function(aResponse) {
 *         // arraybyffer = aResponse.response;
 *         var bodyBytesArray = aResponse.responseText;
 *         var bodyBase64 = btoa(bodyBytesArray);
 *         console.log(bodyBase64);
 *       });
 *   // See also: https://developer.mozilla.org/docs/XMLHttpRequest/Sending_and_Receiving_Binary_Data
 *
 *   http.postAsJSON('http://.....', { a: true, b: 29 })
 *       .next(function(aResponse) {
 *       });
 *
 * @license
 *   The MIT License, Copyright (c) 2014 YUKI "Piro" Hiroshi.
 *   https://github.com/piroor/restartless/blob/master/license.txt
 * @url http://github.com/piroor/restartless
 */

var EXPORTED_SYMBOLS = [
  'get',
  'getAsJSON',
  'getAsBinary',
  'post',
  'postAsJSON',
  'RESPONSE_TYPE',
  'RESPONSE_CONTENT_TYPE'
];

var RESPONSE_TYPE         = 'X-Response-Type';
var RESPONSE_CONTENT_TYPE = 'X-Response-Content-Type';

var PSEUDO_HEADERS = [
  RESPONSE_TYPE,
  RESPONSE_CONTENT_TYPE
];

var Deferred = require('jsdeferred').Deferred;

function clone(aObject) {
  aObject = aObject || {};
  var cloned = {};
  Object.keys(aObject).forEach(function(aKey) {
    cloned[aKey] = aObject[aKey];
  });
  return cloned;
}

function get(aURI, aHeaders) {
  return sendRequest({
    method:  'GET',
    uri:     aURI,
    headers: aHeaders
  });
}

function getAsJSON(aURI, aHeaders) {
  var headers = clone(aHeaders);
  headers[RESPONSE_TYPE] = 'json';
  return get(aURI, headers);
}

function getAsBinary(aURI, aHeaders) {
  var headers = clone(aHeaders);
  headers[RESPONSE_TYPE] = 'arraybuffer';
  return get(aURI, headers);
}

function post(aURI, aPostData, aHeaders) {
  return sendRequest({
    method:   'POST',
    uri:      aURI,
    headers:  aHeaders,
    postData: aPostData
  });
}

function postAsJSON(aURI, aPostData, aHeaders) {
  var headers = clone(aHeaders);
  var postData = JSON.stringify(aPostData);
  headers['Content-Type'] = 'application/json';
  return post(aURI, aPostData, headers);
}

function ArrayBufferRespone(aResponse) {
  this._raw = aResponse;
  this._init();
}
ArrayBufferRespone.prototype = {
  _init: function() {
    for (let key in this._raw) {
      if (key in this)
        continue;
      this._bind(key);
    }
  },
  _bind: function(aName) {
    if (typeof this._raw[aName] == 'function') {
      this[aName] = this._raw[aName].bind(this._raw);
    } else {
      this.__defineGetter__(aName, function() {
        return this._raw[aName];
      });
    }
  },
  get responseText() {
    var bytesArray = ''
    var bytes = new Uint8Array(this._raw.response);
    for (let i = 0, maxi = bytes.byteLength; i < maxi; i++) {
      bytesArray += String.fromCharCode(bytes[i]);
    }
    return bytesArrya;
  },
  get responseXML() {
    // accessing to the property raises InvalidStateError, so I have to define this staticly
    return this._raw.responseXML;
  }
};

function sendRequest(aParams) {
  var deferred = new Deferred();

  var method   = (aParams.method || 'GET').toUpperCase();
  var uri      = aParams.uri || null;
  var headers  = aParams.headers || {};
  var postData = aParams.postData || null;
  var responseType = aParams.responseType || '';
  var responseContentType = aParams.responseContentType || null;

  if (headers[RESPONSE_TYPE])
    responseType = headers[RESPONSE_TYPE];
  if (headers[RESPONSE_CONTENT_TYPE])
    responseContentType = headers[RESPONSE_CONTENT_TYPE];

  if (!uri)
    throw new Error('no URL');

  var request;
  var listener = function(aEvent) {
    switch (aEvent.type) {
      case 'load':
      case 'error':
        break;
      default:
        return;
    }
    request.removeEventListener('load', listener, false);
    request.removeEventListener('error', listener, false);

    if (request.status == 200) {
      if (responseType == 'arraybuffer')
        deferred.call(new ArrayBufferRespone(request));
      else
        deferred.call(request);
    } else {
      try {
        deferred.fail(new Error(JSON.stringify({
          statusCode: request.status,
          statusText: request.statusText,
          body:       request.response,
          event:      {
            type:   aEvent.type,
            detail: aEvent.detail
          }
        })));
      } catch(error) {
        deferred.fail(error);
      }
    }
  };

  Deferred.next(function() {
    request = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
                .createInstance(Ci.nsIXMLHttpRequest)
                .QueryInterface(Ci.nsIDOMEventTarget);
    request.open(method, uri, true);
    if (responseType)
      request.responseType = responseType;
    if (responseContentType)
      request.overrideMimeType(responseContentType);
    Object.keys(headers).forEach(function(aKey) {
      if (PSEUDO_HEADERS.indexOf(aKey) < 0)
        request.setRequestHeader(aKey, headers[aKey]);
    });
    request.addEventListener('load', listener, false);
    request.addEventListener('error', listener, false);
    request.send(postData);
  }).error(function(aError) {
    deferred.fail(aError);
  });

  return deferred;
}
<commit_msg>Handle HTTP errors as "success".<commit_after>/**
 * @fileOverview XMLHttpRequest wrapper module for restartless addons
 * @author       YUKI "Piro" Hiroshi
 * @version      7
 * @description
 *   // get as a text
 *   http.get('http://.....',
 *            { 'X-Response-Content-Type': 'text/plain; charset=EUC-JP' })
 *       .next(function(aResponse) {
 *         console.log(aResponse.responseText);
 *       });
 *
 *   // get as a JSON
 *   http.getAsJSON('http://.....')
 *       .next(function(aResponse) {
 *         var body = aResponse.response;
 *         console.log(function);
 *       });
 *
 *   // get as a binary
 *   http.getAsBinary('http://.....')
 *       .next(function(aResponse) {
 *         // arraybyffer = aResponse.response;
 *         var bodyBytesArray = aResponse.responseText;
 *         var bodyBase64 = btoa(bodyBytesArray);
 *         console.log(bodyBase64);
 *       });
 *   // See also: https://developer.mozilla.org/docs/XMLHttpRequest/Sending_and_Receiving_Binary_Data
 *
 *   http.postAsJSON('http://.....', { a: true, b: 29 })
 *       .next(function(aResponse) {
 *       });
 *
 * @license
 *   The MIT License, Copyright (c) 2014 YUKI "Piro" Hiroshi.
 *   https://github.com/piroor/restartless/blob/master/license.txt
 * @url http://github.com/piroor/restartless
 */

var EXPORTED_SYMBOLS = [
  'get',
  'getAsJSON',
  'getAsBinary',
  'post',
  'postAsJSON',
  'RESPONSE_TYPE',
  'RESPONSE_CONTENT_TYPE'
];

var RESPONSE_TYPE         = 'X-Response-Type';
var RESPONSE_CONTENT_TYPE = 'X-Response-Content-Type';

var PSEUDO_HEADERS = [
  RESPONSE_TYPE,
  RESPONSE_CONTENT_TYPE
];

var Deferred = require('jsdeferred').Deferred;

function clone(aObject) {
  aObject = aObject || {};
  var cloned = {};
  Object.keys(aObject).forEach(function(aKey) {
    cloned[aKey] = aObject[aKey];
  });
  return cloned;
}

function get(aURI, aHeaders) {
  return sendRequest({
    method:  'GET',
    uri:     aURI,
    headers: aHeaders
  });
}

function getAsJSON(aURI, aHeaders) {
  var headers = clone(aHeaders);
  headers[RESPONSE_TYPE] = 'json';
  return get(aURI, headers);
}

function getAsBinary(aURI, aHeaders) {
  var headers = clone(aHeaders);
  headers[RESPONSE_TYPE] = 'arraybuffer';
  return get(aURI, headers);
}

function post(aURI, aPostData, aHeaders) {
  return sendRequest({
    method:   'POST',
    uri:      aURI,
    headers:  aHeaders,
    postData: aPostData
  });
}

function postAsJSON(aURI, aPostData, aHeaders) {
  var headers = clone(aHeaders);
  var postData = JSON.stringify(aPostData);
  headers['Content-Type'] = 'application/json';
  return post(aURI, aPostData, headers);
}

function ArrayBufferRespone(aResponse) {
  this._raw = aResponse;
  this._init();
}
ArrayBufferRespone.prototype = {
  _init: function() {
    for (let key in this._raw) {
      if (key in this)
        continue;
      this._bind(key);
    }
  },
  _bind: function(aName) {
    if (typeof this._raw[aName] == 'function') {
      this[aName] = this._raw[aName].bind(this._raw);
    } else {
      this.__defineGetter__(aName, function() {
        return this._raw[aName];
      });
    }
  },
  get responseText() {
    var bytesArray = ''
    var bytes = new Uint8Array(this._raw.response);
    for (let i = 0, maxi = bytes.byteLength; i < maxi; i++) {
      bytesArray += String.fromCharCode(bytes[i]);
    }
    return bytesArrya;
  },
  get responseXML() {
    // accessing to the property raises InvalidStateError, so I have to define this staticly
    return this._raw.responseXML;
  }
};

function sendRequest(aParams) {
  var deferred = new Deferred();

  var method   = (aParams.method || 'GET').toUpperCase();
  var uri      = aParams.uri || null;
  var headers  = aParams.headers || {};
  var postData = aParams.postData || null;
  var responseType = aParams.responseType || '';
  var responseContentType = aParams.responseContentType || null;

  if (headers[RESPONSE_TYPE])
    responseType = headers[RESPONSE_TYPE];
  if (headers[RESPONSE_CONTENT_TYPE])
    responseContentType = headers[RESPONSE_CONTENT_TYPE];

  if (!uri)
    throw new Error('no URL');

  var request;
  var listener = function(aEvent) {
    switch (aEvent.type) {
      case 'load':
      case 'error':
        break;
      default:
        return;
    }
    request.removeEventListener('load', listener, false);
    request.removeEventListener('error', listener, false);

    var response = request;
    if (responseType == 'arraybuffer')
      response = new ArrayBufferRespone(response)

    deferred.call(response);
  };

  Deferred.next(function() {
    request = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
                .createInstance(Ci.nsIXMLHttpRequest)
                .QueryInterface(Ci.nsIDOMEventTarget);
    request.open(method, uri, true);
    if (responseType)
      request.responseType = responseType;
    if (responseContentType)
      request.overrideMimeType(responseContentType);
    Object.keys(headers).forEach(function(aKey) {
      if (PSEUDO_HEADERS.indexOf(aKey) < 0)
        request.setRequestHeader(aKey, headers[aKey]);
    });
    request.addEventListener('load', listener, false);
    request.addEventListener('error', listener, false);
    request.send(postData);
  }).error(function(aError) {
    deferred.fail(aError);
  });

  return deferred;
}
<|endoftext|>


<commit_before>import * as d3 from 'd3';
import _ from 'lodash';
import { geoExtent, geoPolygonIntersectsPolygon } from '../geo/index';
import { osmEntity, osmNode, osmRelation, osmWay } from '../osm/index';
import { utilDetect } from '../util/detect';
import toGeoJSON from 'togeojson';
import fromEsri from 'esri-to-geojson';

export function svgEsri(projection, context, dispatch) {
    var showLabels = true,
        detected = utilDetect(),
        layer;

    function init() {
        if (svgEsri.initialized) return;  // run once

        svgEsri.geojson = {};
        svgEsri.enabled = true;

        function over() {
            d3.event.stopPropagation();
            d3.event.preventDefault();
            d3.event.dataTransfer.dropEffect = 'copy';
        }

        d3.select('body')
            .attr('dropzone', 'copy')
            .on('drop.localesri', function() {
                d3.event.stopPropagation();
                d3.event.preventDefault();
                if (!detected.filedrop) return;
                drawEsri.files(d3.event.dataTransfer.files);
            })
            .on('dragenter.localesri', over)
            .on('dragexit.localesri', over)
            .on('dragover.localesri', over);

        svgEsri.initialized = true;
    }

    function drawEsri(selection) {
        var geojson = svgEsri.geojson,
            enabled = svgEsri.enabled;

        layer = selection.selectAll('.layer-esri')
            .data(enabled ? [0] : []);

        layer.exit()
            .remove();

        layer = layer.enter()
            .append('g')
            .attr('class', 'layer-esri')
            .merge(layer);

        var paths = layer
            .selectAll('path')
            .data(geojson.features || []);

        paths.exit()
            .remove();

        var entities = [];

        paths = paths.enter()
            .append('path')
            .attr('class', function(d) {
                // console.log(d);
                entities.push(new osmNode({
                    id: 'esri_' + d.properties.OBJECTID,
                    loc: d.geometry.coordinates,
                    version: 1, // attrs.version.value,
                    user: 'mapmeld', // attrs.user && attrs.user.value,
                    tags: d.properties, // getTags(obj),
                    visible: true // getVisible(attrs)
                }));
                /*
                var type = d.geometry.type.toLowerCase();
                return 'esri esri-' + type;
                */
            })
            .merge(paths);

        if (entities.length) {
            context.entitiesLoaded(null, {
                data: entities,
                extent: context.map().trimmedExtent()
            });
        }

        return this;
/*
        var path = d3.geoPath(projection);

        paths
            .attr('d', path);

        var labels = layer.selectAll('text')
            .data(showLabels && geojson.features ? geojson.features : []);

        labels.exit()
            .remove();

        labels = labels.enter()
            .append('text')
            .attr('class', 'esri')
            .merge(labels);

        labels
            .text(function(d) {
                return d.properties.desc || d.properties.name;
            })
            .attr('x', function(d) {
                var centroid = path.centroid(d);
                return centroid[0] + 7;
            })
            .attr('y', function(d) {
                var centroid = path.centroid(d);
                return centroid[1];
            });
            */

    }

    drawEsri.showLabels = function(_) {
        if (!arguments.length) return showLabels;
        showLabels = _;
        return this;
    };

    drawEsri.enabled = function(_) {
        if (!arguments.length) return svgEsri.enabled;
        svgEsri.enabled = _;
        dispatch.call('change');
        return this;
    };

    drawEsri.hasData = function() {
        var geojson = svgEsri.geojson;
        return (!(_.isEmpty(geojson) || _.isEmpty(geojson.features)));
    };

    drawEsri.geojson = function(gj) {
        if (!arguments.length) return svgEsri.geojson;
        if (_.isEmpty(gj) || _.isEmpty(gj.features)) return this;
        svgEsri.geojson = gj;
        dispatch.call('change');
        return this;
    };

    drawEsri.url = function(true_url) {
        var url = true_url;
        if (url.indexOf('outSR') === -1) {
            url += '&outSR=4326';
        }
        if (url.indexOf('&f=') === -1) {
            url += '&f=json';
        }

        var bounds = context.map().trimmedExtent().bbox();
        var bounds = JSON.stringify({
            xmin: bounds.minX.toFixed(6),
			ymin: bounds.minY.toFixed(6),
			xmax: bounds.maxX.toFixed(6),
			ymax: bounds.maxY.toFixed(6),
			spatialReference: {
			  wkid: 4326
		    }
		});
		if (this.lastBounds === bounds) {
		    // unchanged
		    return this;
		}
		this.lastBounds = bounds;

		if (url.indexOf('spatialRel') === -1) {
		    // don't overwrite a spatial query
            url += '&geometry=' + this.lastBounds;
            url += '&geometryType=esriGeometryEnvelope';
            url += '&spatialRel=esriSpatialRelIntersects';
            url += '&inSR=4326';
        }

        d3.text(url, function(err, data) {
            if (err) {
                console.log('Esri service URL did not load');
                console.error(err);
            } else {
                drawEsri.geojson(fromEsri.fromEsri(JSON.parse(data)));
            }
        });

        context.map().on('move', function() {
            if (this.timeout) {
               clearTimeout(this.timeout);
            }
            this.timeout = setTimeout(function() {
                this.url(true_url);
            }.bind(this), 500);
        }.bind(this));

        return this;
    };

    drawEsri.fitZoom = function() {
        if (!this.hasData()) return this;
        var geojson = svgEsri.geojson;

        var map = context.map(),
            viewport = map.trimmedExtent().polygon(),
            coords = _.reduce(geojson.features, function(coords, feature) {
                var c = feature.geometry.coordinates;
                return _.union(coords, feature.geometry.type === 'Point' ? [c] : c);
            }, []);

        if (!geoPolygonIntersectsPolygon(viewport, coords, true)) {
            var extent = geoExtent(d3.geoBounds(geojson));
            map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
        }

        return this;
    };

    init();
    return drawEsri;
}
<commit_msg>support linestring, polygon, rough support multi-shapes<commit_after>import * as d3 from 'd3';
import _ from 'lodash';
import { geoExtent, geoPolygonIntersectsPolygon } from '../geo/index';
import { osmEntity, osmNode, osmRelation, osmWay } from '../osm/index';
import { utilDetect } from '../util/detect';
import toGeoJSON from 'togeojson';
import fromEsri from 'esri-to-geojson';

export function svgEsri(projection, context, dispatch) {
    var showLabels = true,
        detected = utilDetect(),
        layer;

    function init() {
        if (svgEsri.initialized) return;  // run once

        svgEsri.geojson = {};
        svgEsri.enabled = true;

        function over() {
            d3.event.stopPropagation();
            d3.event.preventDefault();
            d3.event.dataTransfer.dropEffect = 'copy';
        }

        d3.select('body')
            .attr('dropzone', 'copy')
            .on('drop.localesri', function() {
                d3.event.stopPropagation();
                d3.event.preventDefault();
                if (!detected.filedrop) return;
                drawEsri.files(d3.event.dataTransfer.files);
            })
            .on('dragenter.localesri', over)
            .on('dragexit.localesri', over)
            .on('dragover.localesri', over);

        svgEsri.initialized = true;
    }

    function drawEsri(selection) {
        var geojson = svgEsri.geojson,
            enabled = svgEsri.enabled;

        layer = selection.selectAll('.layer-esri')
            .data(enabled ? [0] : []);

        layer.exit()
            .remove();

        layer = layer.enter()
            .append('g')
            .attr('class', 'layer-esri')
            .merge(layer);

        var paths = layer
            .selectAll('path')
            .data(geojson.features || []);

        paths.exit()
            .remove();

        var entities = [];

        paths = paths.enter()
            .append('path')
            .attr('class', function(d) {
                // console.log(d);
                if (d.geometry.type === 'Point') {
                    entities.push(new osmNode({
                         id: 'esri_node_' + d.properties.OBJECTID,
                         loc: d.geometry.coordinates,
                         version: 1, // attrs.version.value,
                         user: 'mapmeld', // attrs.user && attrs.user.value,
                         tags: d.properties, // getTags(obj),
                         visible: true // getVisible(attrs)
                     }));
                } else if (d.geometry.type === 'LineString') {
                    var nodes = [];
                    var pts = d.geometry.coordinates;
                    for (var p = 0; p < pts.length; p++) {
                        var mininode_id = 'n' + ln + '' + p + '000' + d.properties.OBJECTID;
                        nodes.push(mininode_id);
                        entities.push(new osmNode({
                            id: mininode_id,
                            loc: pts[p],
                            version: 1,
                            user: 'mapmeld',
                            tags: {},
                            visible: true
                        }));
                    }
                    entities.push(new osmWay({
                        id: 'esri_way_' + d.properties.OBJECTID,
			    		version: 1,
					    user: 'mapmeld',
					    tags: d.properties,
					    nodes: nodes,
					    visible: true
					}));
				} else if (d.geometry.type === 'MultiLineString') {
				    for (var ln = 0; ln < d.geometry.coordinates.length; ln++) {
                        var nodes = [];
                        var pts = d.geometry.coordinates[ln];
                        for (var p = 0; p < pts.length; p++) {
                            var mininode_id = 'n' + ln + '' + p + '000' + d.properties.OBJECTID;
                            nodes.push(mininode_id);
                            entities.push(new osmNode({
                                id: mininode_id,
                                loc: pts[p],
                                version: 1,
                                user: 'mapmeld',
                                tags: {},
                                visible: true
                            }));
                        }
                        entities.push(new osmWay({
                            id: 'esri_way_' + ln + '_' + d.properties.OBJECTID,
			        		version: 1,
			    		    user: 'mapmeld',
				    	    tags: d.properties,
			    		    nodes: nodes,
					        visible: true
					    }));
					}
				} else if (d.geometry.type === 'Polygon') {
				    d.properties.area = d.properties.area || 'yes';
                    var nodes = [];
                    var pts = d.geometry.coordinates[0];
                    for (var p = 0; p < pts.length; p++) {
                        var mininode_id = 'n' + p + '000' + d.properties.OBJECTID;
                        nodes.push(mininode_id);
                        entities.push(new osmNode({
                            id: mininode_id,
                            loc: pts[p],
                            version: 1,
                            user: 'mapmeld',
                            tags: {},
                            visible: true
                        }));
                    }
                    entities.push(new osmWay({
                        id: 'esri_way_' + d.properties.OBJECTID,
		    			version: 1,
			    		user: 'mapmeld',
				    	tags: d.properties,
					    nodes: nodes,
					    visible: true
					}));
                } else if (d.geometry.type === 'MultiPolygon') {
                    d.properties.area = d.properties.area || 'yes';
				    for (var ln = 0; ln < d.geometry.coordinates.length; ln++) {
                        var nodes = [];
                        var pts = d.geometry.coordinates[ln][0];
                        for (var p = 0; p < pts.length; p++) {
                            var mininode_id = 'n' + ln + '' + p + '000' + d.properties.OBJECTID;
                            nodes.push(mininode_id);
                            entities.push(new osmNode({
                                id: mininode_id,
                                loc: pts[p],
                                version: 1,
                                user: 'mapmeld',
                                tags: {},
                                visible: true
                            }));
                        }
                        entities.push(new osmWay({
                            id: 'esri_way_' + ln + '_' + d.properties.OBJECTID,
			        		version: 1,
			    		    user: 'mapmeld',
				    	    tags: d.properties,
			    		    nodes: nodes,
					        visible: true
					    }));
					}
				}
            })
            .merge(paths);

        if (entities.length) {
            context.entitiesLoaded(null, {
                data: entities,
                extent: context.map().trimmedExtent()
            });
        }

        return this;
    }

    drawEsri.showLabels = function(_) {
        if (!arguments.length) return showLabels;
        showLabels = _;
        return this;
    };

    drawEsri.enabled = function(_) {
        if (!arguments.length) return svgEsri.enabled;
        svgEsri.enabled = _;
        dispatch.call('change');
        return this;
    };

    drawEsri.hasData = function() {
        var geojson = svgEsri.geojson;
        return (!(_.isEmpty(geojson) || _.isEmpty(geojson.features)));
    };

    drawEsri.geojson = function(gj) {
        if (!arguments.length) return svgEsri.geojson;
        if (_.isEmpty(gj) || _.isEmpty(gj.features)) return this;
        svgEsri.geojson = gj;
        dispatch.call('change');
        return this;
    };

    drawEsri.url = function(true_url) {
        var url = true_url;
        if (url.indexOf('outSR') === -1) {
            url += '&outSR=4326';
        }
        if (url.indexOf('&f=') === -1) {
            url += '&f=json';
        }

        var bounds = context.map().trimmedExtent().bbox();
        var bounds = JSON.stringify({
            xmin: bounds.minX.toFixed(6),
			ymin: bounds.minY.toFixed(6),
			xmax: bounds.maxX.toFixed(6),
			ymax: bounds.maxY.toFixed(6),
			spatialReference: {
			  wkid: 4326
		    }
		});
		if (this.lastBounds === bounds) {
		    // unchanged
		    return this;
		}
		this.lastBounds = bounds;

		if (url.indexOf('spatialRel') === -1) {
		    // don't overwrite a spatial query
            url += '&geometry=' + this.lastBounds;
            url += '&geometryType=esriGeometryEnvelope';
            url += '&spatialRel=esriSpatialRelIntersects';
            url += '&inSR=4326';
        }

        d3.text(url, function(err, data) {
            if (err) {
                console.log('Esri service URL did not load');
                console.error(err);
            } else {
                drawEsri.geojson(fromEsri.fromEsri(JSON.parse(data)));
            }
        });

        context.map().on('move', function() {
            if (this.timeout) {
               clearTimeout(this.timeout);
            }
            this.timeout = setTimeout(function() {
                this.url(true_url);
            }.bind(this), 500);
        }.bind(this));

        return this;
    };

    drawEsri.fitZoom = function() {
        if (!this.hasData()) return this;
        var geojson = svgEsri.geojson;

        var map = context.map(),
            viewport = map.trimmedExtent().polygon(),
            coords = _.reduce(geojson.features, function(coords, feature) {
                var c = feature.geometry.coordinates;
                return _.union(coords, feature.geometry.type === 'Point' ? [c] : c);
            }, []);

        if (!geoPolygonIntersectsPolygon(viewport, coords, true)) {
            var extent = geoExtent(d3.geoBounds(geojson));
            map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
        }

        return this;
    };

    init();
    return drawEsri;
}
<|endoftext|>


<commit_before>/*!
 * boily v3.2.5
 * (c) 2016 KFlash
 * Released under the MIT License.
 */
(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
	typeof define === 'function' && define.amd ? define(factory) :
	(global.Boily = factory());
}(this, function () { 'use strict';

	var boily = { foo: 123 };

	// Correct version will be set with the 'rollup-replace plugin'
	boily.version = 'VERSION';

	// Flow example
	function foo(one, two, three) {}

	boily.flow = foo;

	return boily;

}));

//# sourceMappingURL=boily.js.map
<commit_msg>v3.2.7<commit_after>/*!
 * boily v3.2.7
 * (c) 2016 KFlash
 * Released under the MIT License.
 */
(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
	typeof define === 'function' && define.amd ? define(factory) :
	(global.Boily = factory());
}(this, function () { 'use strict';

	var boily = { foo: 123 };

	// Correct version will be set with the 'rollup-replace plugin'
	boily.version = 'VERSION';

	// Flow example
	function foo(one, two, three) {}

	boily.flow = foo;

	return boily;

}));

//# sourceMappingURL=boily.js.map
<|endoftext|>


<commit_before>import Ember from 'ember';
import layout from '../templates/components/ember-remodal';

const {
  observer,
  inject,
  computed: { oneWay },
  RSVP: { Promise },
  Component
} = Ember;

export default Component.extend({
  remodal: inject.service(),

  layout,
  tagName: 'span',
  name: 'modal',
  modifier: '',
  modal: null,
  options: null,
  closeOnEscape: true,
  closeOnCancel: true,
  closeOnConfirm: true,
  hashTracking: false,
  closeOnOutsideClick: true,
  forService: false,
  isApplicationModal: false,
  disableForeground: false,
  disableNativeClose: oneWay('disableForeground'),

  didInitAttrs() {
    const opts = this.get('options');
    const modal = `[data-remodal-id=${this.get('elementId')}]`;
    const isApplicationModal = this.get('isApplicationModal');

    if (opts) {
      this.setProperties(opts);
    }

    $(document).on('closed', modal, () => {
      this.sendAction('onClose');
    });

    $(document).on('opened', modal, () => {
      this.sendAction('onOpen');
    });

    if (this.get('forService') || isApplicationModal) {

      if (isApplicationModal) {
        Ember.deprecate('ember-remodal\'s "isApplicationModal" is deprecated and will be removed in ember-remodal 1.0.0. Use "forService" instead.');
      }

      this.get('remodal').set(this.get('name'), this);
    }

    if (this.get('linkButton')) {
      Ember.deprecate('ember-remodal\'s "linkButton" is deprecated and will be removed in ember-remodal 1.0.0. It was a stupid name. You should use "openLink" instead.')
    }
  },

  open() {
    const modal = `[data-remodal-id=${this.get('elementId')}]`;

    return new Promise((resolve) => {
      $(document).on('opened', modal, () => {
        resolve();
      });

      this.send('open');
    });
  },

  close() {
    const modal = `[data-remodal-id=${this.get('elementId')}]`;

    return new Promise((resolve) => {
      $(document).on('closed', modal, () => {
        resolve();
      });

      this.send('close');
    });
  },

  actions: {
    confirm() {
      this.sendAction('onConfirm');

      if (this.get('closeOnConfirm')) {
        this.send('close');
      }
    },

    cancel() {
      this.sendAction('onCancel');

      if (this.get('closeOnCancel')) {
        this.send('close');
      }
    },

    open() {
      const modal = $(`[data-remodal-id=${this.get('elementId')}]`);
      const opts = {
        hashTracking: this.get('hashTracking'),
        closeOnOutsideClick: this.get('closeOnOutsideClick'),
        closeOnEscape: this.get('closeOnEscape'),
        modifier: this.get('modifier')
      };

      this.set('modal', modal.remodal(opts));
      this.get('modal').open();
    },

    close() {
      this.get('modal').close();
    }
  }
});
<commit_msg>missing semicolon<commit_after>import Ember from 'ember';
import layout from '../templates/components/ember-remodal';

const {
  observer,
  inject,
  computed: { oneWay },
  RSVP: { Promise },
  Component
} = Ember;

export default Component.extend({
  remodal: inject.service(),

  layout,
  tagName: 'span',
  name: 'modal',
  modifier: '',
  modal: null,
  options: null,
  closeOnEscape: true,
  closeOnCancel: true,
  closeOnConfirm: true,
  hashTracking: false,
  closeOnOutsideClick: true,
  forService: false,
  isApplicationModal: false,
  disableForeground: false,
  disableNativeClose: oneWay('disableForeground'),

  didInitAttrs() {
    const opts = this.get('options');
    const modal = `[data-remodal-id=${this.get('elementId')}]`;
    const isApplicationModal = this.get('isApplicationModal');

    if (opts) {
      this.setProperties(opts);
    }

    $(document).on('closed', modal, () => {
      this.sendAction('onClose');
    });

    $(document).on('opened', modal, () => {
      this.sendAction('onOpen');
    });

    if (this.get('forService') || isApplicationModal) {

      if (isApplicationModal) {
        Ember.deprecate('ember-remodal\'s "isApplicationModal" is deprecated and will be removed in ember-remodal 1.0.0. Use "forService" instead.');
      }

      this.get('remodal').set(this.get('name'), this);
    }

    if (this.get('linkButton')) {
      Ember.deprecate('ember-remodal\'s "linkButton" is deprecated and will be removed in ember-remodal 1.0.0. It was a stupid name. You should use "openLink" instead.');
    }
  },

  open() {
    const modal = `[data-remodal-id=${this.get('elementId')}]`;

    return new Promise((resolve) => {
      $(document).on('opened', modal, () => {
        resolve();
      });

      this.send('open');
    });
  },

  close() {
    const modal = `[data-remodal-id=${this.get('elementId')}]`;

    return new Promise((resolve) => {
      $(document).on('closed', modal, () => {
        resolve();
      });

      this.send('close');
    });
  },

  actions: {
    confirm() {
      this.sendAction('onConfirm');

      if (this.get('closeOnConfirm')) {
        this.send('close');
      }
    },

    cancel() {
      this.sendAction('onCancel');

      if (this.get('closeOnCancel')) {
        this.send('close');
      }
    },

    open() {
      const modal = $(`[data-remodal-id=${this.get('elementId')}]`);
      const opts = {
        hashTracking: this.get('hashTracking'),
        closeOnOutsideClick: this.get('closeOnOutsideClick'),
        closeOnEscape: this.get('closeOnEscape'),
        modifier: this.get('modifier')
      };

      this.set('modal', modal.remodal(opts));
      this.get('modal').open();
    },

    close() {
      this.get('modal').close();
    }
  }
});
<|endoftext|>