+ var parentsMoving = parents.every(function (way) {
+ return cache.moving[way.id];
+ });
+ if (!parentsMoving) delete cache.moving[nodeID];
+ return parentsMoving;
+ }
+
+ function cacheEntities(ids) {
+ for (var i = 0; i < ids.length; i++) {
+ var id = ids[i];
+ if (cache.moving[id]) continue;
+ cache.moving[id] = true;
+ var entity = graph.hasEntity(id);
+ if (!entity) continue;
+
+ if (entity.type === 'node') {
+ cache.nodes.push(id);
+ cache.startLoc[id] = entity.loc;
+ } else if (entity.type === 'way') {
+ cache.ways.push(id);
+ cacheEntities(entity.nodes);
+ } else {
+ cacheEntities(entity.members.map(function (member) {
+ return member.id;
+ }));
+ }
+ }
+ }
+
+ function cacheIntersections(ids) {
+ function isEndpoint(way, id) {
+ return !way.isClosed() && !!way.affix(id);
+ }
+
+ for (var i = 0; i < ids.length; i++) {
+ var id = ids[i]; // consider only intersections with 1 moved and 1 unmoved way.
+
+ var childNodes = graph.childNodes(graph.entity(id));
+
+ for (var j = 0; j < childNodes.length; j++) {
+ var node = childNodes[j];
+ var parents = graph.parentWays(node);
+ if (parents.length !== 2) continue;
+ var moved = graph.entity(id);
+ var unmoved = null;
+
+ for (var k = 0; k < parents.length; k++) {
+ var way = parents[k];
+
+ if (!cache.moving[way.id]) {
+ unmoved = way;
+ break;
+ }
+ }
+
+ if (!unmoved) continue; // exclude ways that are overly connected..
+
+ if (utilArrayIntersection(moved.nodes, unmoved.nodes).length > 2) continue;
+ if (moved.isArea() || unmoved.isArea()) continue;
+ cache.intersections.push({
+ nodeId: node.id,
+ movedId: moved.id,
+ unmovedId: unmoved.id,
+ movedIsEP: isEndpoint(moved, node.id),
+ unmovedIsEP: isEndpoint(unmoved, node.id)
+ });
+ }
+ }
+ }
+
+ if (!cache) {
+ cache = {};
+ }
+
+ if (!cache.ok) {
+ cache.moving = {};
+ cache.intersections = [];
+ cache.replacedVertex = {};
+ cache.startLoc = {};
+ cache.nodes = [];
+ cache.ways = [];
+ cacheEntities(moveIDs);
+ cacheIntersections(cache.ways);
+ cache.nodes = cache.nodes.filter(canMove);
+ cache.ok = true;
+ }
+ } // Place a vertex where the moved vertex used to be, to preserve way shape..
+ //
+ // Start:
+ // b ---- e
+ // / \
+ // / \
+ // / \
+ // a c
+ //
+ // * node '*' added to preserve shape
+ // / \
+ // / b ---- e way `b,e` moved here:
+ // / \
+ // a c
+ //
+ //
+
+
+ function replaceMovedVertex(nodeId, wayId, graph, delta) {
+ var way = graph.entity(wayId);
+ var moved = graph.entity(nodeId);
+ var movedIndex = way.nodes.indexOf(nodeId);
+ var len, prevIndex, nextIndex;
+
+ if (way.isClosed()) {
+ len = way.nodes.length - 1;
+ prevIndex = (movedIndex + len - 1) % len;
+ nextIndex = (movedIndex + len + 1) % len;
+ } else {
+ len = way.nodes.length;
+ prevIndex = movedIndex - 1;
+ nextIndex = movedIndex + 1;
+ }
+
+ var prev = graph.hasEntity(way.nodes[prevIndex]);
+ var next = graph.hasEntity(way.nodes[nextIndex]); // Don't add orig vertex at endpoint..
+
+ if (!prev || !next) return graph;
+ var key = wayId + '_' + nodeId;
+ var orig = cache.replacedVertex[key];
+
+ if (!orig) {
+ orig = osmNode();
+ cache.replacedVertex[key] = orig;
+ cache.startLoc[orig.id] = cache.startLoc[nodeId];
+ }
+
+ var start, end;
+
+ if (delta) {
+ start = projection(cache.startLoc[nodeId]);
+ end = projection.invert(geoVecAdd(start, delta));
+ } else {
+ end = cache.startLoc[nodeId];
+ }
+
+ orig = orig.move(end);
+ var angle = Math.abs(geoAngle(orig, prev, projection) - geoAngle(orig, next, projection)) * 180 / Math.PI; // Don't add orig vertex if it would just make a straight line..
+
+ if (angle > 175 && angle < 185) return graph; // moving forward or backward along way?
+
+ var p1 = [prev.loc, orig.loc, moved.loc, next.loc].map(projection);
+ var p2 = [prev.loc, moved.loc, orig.loc, next.loc].map(projection);
+ var d1 = geoPathLength(p1);
+ var d2 = geoPathLength(p2);
+ var insertAt = d1 <= d2 ? movedIndex : nextIndex; // moving around closed loop?
+
+ if (way.isClosed() && insertAt === 0) insertAt = len;
+ way = way.addNode(orig.id, insertAt);
+ return graph.replace(orig).replace(way);
+ } // Remove duplicate vertex that might have been added by
+ // replaceMovedVertex. This is done after the unzorro checks.
+
+
+ function removeDuplicateVertices(wayId, graph) {
+ var way = graph.entity(wayId);
+ var epsilon = 1e-6;
+ var prev, curr;
+
+ function isInteresting(node, graph) {
+ return graph.parentWays(node).length > 1 || graph.parentRelations(node).length || node.hasInterestingTags();
+ }
+
+ for (var i = 0; i < way.nodes.length; i++) {
+ curr = graph.entity(way.nodes[i]);
+
+ if (prev && curr && geoVecEqual(prev.loc, curr.loc, epsilon)) {
+ if (!isInteresting(prev, graph)) {
+ way = way.removeNode(prev.id);
+ graph = graph.replace(way).remove(prev);
+ } else if (!isInteresting(curr, graph)) {
+ way = way.removeNode(curr.id);
+ graph = graph.replace(way).remove(curr);
+ }
+ }
+
+ prev = curr;
+ }
+
+ return graph;
+ } // Reorder nodes around intersections that have moved..
+ //
+ // Start: way1.nodes: b,e (moving)
+ // a - b - c ----- d way2.nodes: a,b,c,d (static)
+ // | vertex: b
+ // e isEP1: true, isEP2, false
+ //
+ // way1 `b,e` moved here:
+ // a ----- c = b - d
+ // |
+ // e
+ //
+ // reorder nodes way1.nodes: b,e
+ // a ----- c - b - d way2.nodes: a,c,b,d
+ // |
+ // e
+ //
+
+
+ function unZorroIntersection(intersection, graph) {
+ var vertex = graph.entity(intersection.nodeId);
+ var way1 = graph.entity(intersection.movedId);
+ var way2 = graph.entity(intersection.unmovedId);
+ var isEP1 = intersection.movedIsEP;
+ var isEP2 = intersection.unmovedIsEP; // don't move the vertex if it is the endpoint of both ways.
+
+ if (isEP1 && isEP2) return graph;
+ var nodes1 = graph.childNodes(way1).filter(function (n) {
+ return n !== vertex;
+ });
+ var nodes2 = graph.childNodes(way2).filter(function (n) {
+ return n !== vertex;
+ });
+ if (way1.isClosed() && way1.first() === vertex.id) nodes1.push(nodes1[0]);
+ if (way2.isClosed() && way2.first() === vertex.id) nodes2.push(nodes2[0]);
+ var edge1 = !isEP1 && geoChooseEdge(nodes1, projection(vertex.loc), projection);
+ var edge2 = !isEP2 && geoChooseEdge(nodes2, projection(vertex.loc), projection);
+ var loc; // snap vertex to nearest edge (or some point between them)..
+
+ if (!isEP1 && !isEP2) {
+ var epsilon = 1e-6,
+ maxIter = 10;
+
+ for (var i = 0; i < maxIter; i++) {
+ loc = geoVecInterp(edge1.loc, edge2.loc, 0.5);
+ edge1 = geoChooseEdge(nodes1, projection(loc), projection);
+ edge2 = geoChooseEdge(nodes2, projection(loc), projection);
+ if (Math.abs(edge1.distance - edge2.distance) < epsilon) break;
+ }
+ } else if (!isEP1) {
+ loc = edge1.loc;
+ } else {
+ loc = edge2.loc;
+ }
+
+ graph = graph.replace(vertex.move(loc)); // if zorro happened, reorder nodes..
+
+ if (!isEP1 && edge1.index !== way1.nodes.indexOf(vertex.id)) {
+ way1 = way1.removeNode(vertex.id).addNode(vertex.id, edge1.index);
+ graph = graph.replace(way1);
+ }
+
+ if (!isEP2 && edge2.index !== way2.nodes.indexOf(vertex.id)) {
+ way2 = way2.removeNode(vertex.id).addNode(vertex.id, edge2.index);
+ graph = graph.replace(way2);
+ }
+
+ return graph;
+ }
+
+ function cleanupIntersections(graph) {
+ for (var i = 0; i < cache.intersections.length; i++) {
+ var obj = cache.intersections[i];
+ graph = replaceMovedVertex(obj.nodeId, obj.movedId, graph, _delta);
+ graph = replaceMovedVertex(obj.nodeId, obj.unmovedId, graph, null);
+ graph = unZorroIntersection(obj, graph);
+ graph = removeDuplicateVertices(obj.movedId, graph);
+ graph = removeDuplicateVertices(obj.unmovedId, graph);
+ }
+
+ return graph;
+ } // check if moving way endpoint can cross an unmoved way, if so limit delta..
+
+
+ function limitDelta(graph) {
+ function moveNode(loc) {
+ return geoVecAdd(projection(loc), _delta);
+ }
+
+ for (var i = 0; i < cache.intersections.length; i++) {
+ var obj = cache.intersections[i]; // Don't limit movement if this is vertex joins 2 endpoints..
+
+ if (obj.movedIsEP && obj.unmovedIsEP) continue; // Don't limit movement if this vertex is not an endpoint anyway..
+
+ if (!obj.movedIsEP) continue;
+ var node = graph.entity(obj.nodeId);
+ var start = projection(node.loc);
+ var end = geoVecAdd(start, _delta);
+ var movedNodes = graph.childNodes(graph.entity(obj.movedId));
+ var movedPath = movedNodes.map(function (n) {
+ return moveNode(n.loc);
+ });
+ var unmovedNodes = graph.childNodes(graph.entity(obj.unmovedId));
+ var unmovedPath = unmovedNodes.map(function (n) {
+ return projection(n.loc);
+ });
+ var hits = geoPathIntersections(movedPath, unmovedPath);
+
+ for (var j = 0; i < hits.length; i++) {
+ if (geoVecEqual(hits[j], end)) continue;
+ var edge = geoChooseEdge(unmovedNodes, end, projection);
+ _delta = geoVecSubtract(projection(edge.loc), start);
+ }
+ }
+ }
+
+ var action = function action(graph) {
+ if (_delta[0] === 0 && _delta[1] === 0) return graph;
+ setupCache(graph);
+
+ if (cache.intersections.length) {
+ limitDelta(graph);
+ }
+
+ for (var i = 0; i < cache.nodes.length; i++) {
+ var node = graph.entity(cache.nodes[i]);
+ var start = projection(node.loc);
+ var end = geoVecAdd(start, _delta);
+ graph = graph.replace(node.move(projection.invert(end)));
+ }
+
+ if (cache.intersections.length) {
+ graph = cleanupIntersections(graph);
+ }
+
+ return graph;
+ };
+
+ action.delta = function () {
+ return _delta;
+ };
+
+ return action;
+ }
+
+ function actionMoveMember(relationId, fromIndex, toIndex) {
+ return function (graph) {
+ return graph.replace(graph.entity(relationId).moveMember(fromIndex, toIndex));
+ };
+ }
+
+ function actionMoveNode(nodeID, toLoc) {
+ var action = function action(graph, t) {
+ if (t === null || !isFinite(t)) t = 1;
+ t = Math.min(Math.max(+t, 0), 1);
+ var node = graph.entity(nodeID);
+ return graph.replace(node.move(geoVecInterp(node.loc, toLoc, t)));
+ };
+
+ action.transitionable = true;
+ return action;
+ }
+
+ function actionNoop() {
+ return function (graph) {
+ return graph;
+ };
+ }
+
+ function actionOrthogonalize(wayID, projection, vertexID, degThresh, ep) {
+ var epsilon = ep || 1e-4;
+ var threshold = degThresh || 13; // degrees within right or straight to alter
+ // We test normalized dot products so we can compare as cos(angle)
+
+ var lowerThreshold = Math.cos((90 - threshold) * Math.PI / 180);
+ var upperThreshold = Math.cos(threshold * Math.PI / 180);
+
+ var action = function action(graph, t) {
+ if (t === null || !isFinite(t)) t = 1;
+ t = Math.min(Math.max(+t, 0), 1);
+ var way = graph.entity(wayID);
+ way = way.removeNode(''); // sanity check - remove any consecutive duplicates
+
+ if (way.tags.nonsquare) {
+ var tags = Object.assign({}, way.tags); // since we're squaring, remove indication that this is physically unsquare
+
+ delete tags.nonsquare;
+ way = way.update({
+ tags: tags
+ });
+ }
+
+ graph = graph.replace(way);
+ var isClosed = way.isClosed();
+ var nodes = graph.childNodes(way).slice(); // shallow copy
+
+ if (isClosed) nodes.pop();
+
+ if (vertexID !== undefined) {
+ nodes = nodeSubset(nodes, vertexID, isClosed);
+ if (nodes.length !== 3) return graph;
+ } // note: all geometry functions here use the unclosed node/point/coord list
+
+
+ var nodeCount = {};
+ var points = [];
+ var corner = {
+ i: 0,
+ dotp: 1
+ };
+ var node, point, loc, score, motions, i, j;
+
+ for (i = 0; i < nodes.length; i++) {
+ node = nodes[i];
+ nodeCount[node.id] = (nodeCount[node.id] || 0) + 1;
+ points.push({
+ id: node.id,
+ coord: projection(node.loc)
+ });
+ }
+
+ if (points.length === 3) {
+ // move only one vertex for right triangle
+ for (i = 0; i < 1000; i++) {
+ motions = points.map(calcMotion);
+ points[corner.i].coord = geoVecAdd(points[corner.i].coord, motions[corner.i]);
+ score = corner.dotp;
+
+ if (score < epsilon) {
+ break;
+ }
+ }
+
+ node = graph.entity(nodes[corner.i].id);
+ loc = projection.invert(points[corner.i].coord);
+ graph = graph.replace(node.move(geoVecInterp(node.loc, loc, t)));
+ } else {
+ var straights = [];
+ var simplified = []; // Remove points from nearly straight sections..
+ // This produces a simplified shape to orthogonalize
+
+ for (i = 0; i < points.length; i++) {
+ point = points[i];
+ var dotp = 0;
+
+ if (isClosed || i > 0 && i < points.length - 1) {
+ var a = points[(i - 1 + points.length) % points.length];
+ var b = points[(i + 1) % points.length];
+ dotp = Math.abs(geoOrthoNormalizedDotProduct(a.coord, b.coord, point.coord));
+ }
+
+ if (dotp > upperThreshold) {
+ straights.push(point);
+ } else {
+ simplified.push(point);
+ }
+ } // Orthogonalize the simplified shape
+
+
+ var bestPoints = clonePoints(simplified);
+ var originalPoints = clonePoints(simplified);
+ score = Infinity;
+
+ for (i = 0; i < 1000; i++) {
+ motions = simplified.map(calcMotion);
+
+ for (j = 0; j < motions.length; j++) {
+ simplified[j].coord = geoVecAdd(simplified[j].coord, motions[j]);
+ }
+
+ var newScore = geoOrthoCalcScore(simplified, isClosed, epsilon, threshold);
+
+ if (newScore < score) {
+ bestPoints = clonePoints(simplified);
+ score = newScore;
+ }
+
+ if (score < epsilon) {
+ break;
+ }
+ }
+
+ var bestCoords = bestPoints.map(function (p) {
+ return p.coord;
+ });
+ if (isClosed) bestCoords.push(bestCoords[0]); // move the nodes that should move
+
+ for (i = 0; i < bestPoints.length; i++) {
+ point = bestPoints[i];
+
+ if (!geoVecEqual(originalPoints[i].coord, point.coord)) {
+ node = graph.entity(point.id);
+ loc = projection.invert(point.coord);
+ graph = graph.replace(node.move(geoVecInterp(node.loc, loc, t)));
+ }
+ } // move the nodes along straight segments
+
+
+ for (i = 0; i < straights.length; i++) {
+ point = straights[i];
+ if (nodeCount[point.id] > 1) continue; // skip self-intersections
+
+ node = graph.entity(point.id);
+
+ if (t === 1 && graph.parentWays(node).length === 1 && graph.parentRelations(node).length === 0 && !node.hasInterestingTags()) {
+ // remove uninteresting points..
+ graph = actionDeleteNode(node.id)(graph);
+ } else {
+ // move interesting points to the nearest edge..
+ var choice = geoVecProject(point.coord, bestCoords);
+
+ if (choice) {
+ loc = projection.invert(choice.target);
+ graph = graph.replace(node.move(geoVecInterp(node.loc, loc, t)));
+ }
+ }
+ }
+ }
+
+ return graph;
+
+ function clonePoints(array) {
+ return array.map(function (p) {
+ return {
+ id: p.id,
+ coord: [p.coord[0], p.coord[1]]
+ };
+ });
+ }
+
+ function calcMotion(point, i, array) {
+ // don't try to move the endpoints of a non-closed way.
+ if (!isClosed && (i === 0 || i === array.length - 1)) return [0, 0]; // don't try to move a node that appears more than once (self intersection)
+
+ if (nodeCount[array[i].id] > 1) return [0, 0];
+ var a = array[(i - 1 + array.length) % array.length].coord;
+ var origin = point.coord;
+ var b = array[(i + 1) % array.length].coord;
+ var p = geoVecSubtract(a, origin);
+ var q = geoVecSubtract(b, origin);
+ var scale = 2 * Math.min(geoVecLength(p), geoVecLength(q));
+ p = geoVecNormalize(p);
+ q = geoVecNormalize(q);
+ var dotp = p[0] * q[0] + p[1] * q[1];
+ var val = Math.abs(dotp);
+
+ if (val < lowerThreshold) {
+ // nearly orthogonal
+ corner.i = i;
+ corner.dotp = val;
+ var vec = geoVecNormalize(geoVecAdd(p, q));
+ return geoVecScale(vec, 0.1 * dotp * scale);
+ }
+
+ return [0, 0]; // do nothing
+ }
+ }; // if we are only orthogonalizing one vertex,
+ // get that vertex and the previous and next
+
+
+ function nodeSubset(nodes, vertexID, isClosed) {
+ var first = isClosed ? 0 : 1;
+ var last = isClosed ? nodes.length : nodes.length - 1;
+
+ for (var i = first; i < last; i++) {
+ if (nodes[i].id === vertexID) {
+ return [nodes[(i - 1 + nodes.length) % nodes.length], nodes[i], nodes[(i + 1) % nodes.length]];
+ }
+ }
+
+ return [];
+ }
+
+ action.disabled = function (graph) {
+ var way = graph.entity(wayID);
+ way = way.removeNode(''); // sanity check - remove any consecutive duplicates
+
+ graph = graph.replace(way);
+ var isClosed = way.isClosed();
+ var nodes = graph.childNodes(way).slice(); // shallow copy
+
+ if (isClosed) nodes.pop();
+ var allowStraightAngles = false;
+
+ if (vertexID !== undefined) {
+ allowStraightAngles = true;
+ nodes = nodeSubset(nodes, vertexID, isClosed);
+ if (nodes.length !== 3) return 'end_vertex';
+ }
+
+ var coords = nodes.map(function (n) {
+ return projection(n.loc);
+ });
+ var score = geoOrthoCanOrthogonalize(coords, isClosed, epsilon, threshold, allowStraightAngles);
+
+ if (score === null) {
+ return 'not_squarish';
+ } else if (score === 0) {
+ return 'square_enough';
+ } else {
+ return false;
+ }
+ };
+
+ action.transitionable = true;
+ return action;
+ }
+
+ //
+ // `turn` must be an `osmTurn` object
+ // see osm/intersection.js, pathToTurn()
+ //
+ // This specifies a restriction of type `restriction` when traveling from
+ // `turn.from.way` toward `turn.to.way` via `turn.via.node` OR `turn.via.ways`.
+ // (The action does not check that these entities form a valid intersection.)
+ //
+ // From, to, and via ways should be split before calling this action.
+ // (old versions of the code would split the ways here, but we no longer do it)
+ //
+ // For testing convenience, accepts a restrictionID to assign to the new
+ // relation. Normally, this will be undefined and the relation will
+ // automatically be assigned a new ID.
+ //
+
+ function actionRestrictTurn(turn, restrictionType, restrictionID) {
+ return function (graph) {
+ var fromWay = graph.entity(turn.from.way);
+ var toWay = graph.entity(turn.to.way);
+ var viaNode = turn.via.node && graph.entity(turn.via.node);
+ var viaWays = turn.via.ways && turn.via.ways.map(function (id) {
+ return graph.entity(id);
+ });
+ var members = [];
+ members.push({
+ id: fromWay.id,
+ type: 'way',
+ role: 'from'
+ });
+
+ if (viaNode) {
+ members.push({
+ id: viaNode.id,
+ type: 'node',
+ role: 'via'
+ });
+ } else if (viaWays) {
+ viaWays.forEach(function (viaWay) {
+ members.push({
+ id: viaWay.id,
+ type: 'way',
+ role: 'via'
+ });
+ });
+ }
+
+ members.push({
+ id: toWay.id,
+ type: 'way',
+ role: 'to'
+ });
+ return graph.replace(osmRelation({
+ id: restrictionID,
+ tags: {
+ type: 'restriction',
+ restriction: restrictionType
+ },
+ members: members
+ }));
+ };
+ }
+
+ function actionRevert(id) {
+ var action = function action(graph) {
+ var entity = graph.hasEntity(id),
+ base = graph.base().entities[id];
+
+ if (entity && !base) {
+ // entity will be removed..
+ if (entity.type === 'node') {
+ graph.parentWays(entity).forEach(function (parent) {
+ parent = parent.removeNode(id);
+ graph = graph.replace(parent);
+
+ if (parent.isDegenerate()) {
+ graph = actionDeleteWay(parent.id)(graph);
+ }
+ });
+ }
+
+ graph.parentRelations(entity).forEach(function (parent) {
+ parent = parent.removeMembersWithID(id);
+ graph = graph.replace(parent);
+
+ if (parent.isDegenerate()) {
+ graph = actionDeleteRelation(parent.id)(graph);
+ }
+ });
+ }
+
+ return graph.revert(id);
+ };
+
+ return action;
+ }
+
+ function actionRotate(rotateIds, pivot, angle, projection) {
+ var action = function action(graph) {
+ return graph.update(function (graph) {
+ utilGetAllNodes(rotateIds, graph).forEach(function (node) {
+ var point = geoRotate([projection(node.loc)], angle, pivot)[0];
+ graph = graph.replace(node.move(projection.invert(point)));
+ });
+ });
+ };
+
+ return action;
+ }
+
+ function actionScale(ids, pivotLoc, scaleFactor, projection) {
+ return function (graph) {
+ return graph.update(function (graph) {
+ var point, radial;
+ utilGetAllNodes(ids, graph).forEach(function (node) {
+ point = projection(node.loc);
+ radial = [point[0] - pivotLoc[0], point[1] - pivotLoc[1]];
+ point = [pivotLoc[0] + scaleFactor * radial[0], pivotLoc[1] + scaleFactor * radial[1]];
+ graph = graph.replace(node.move(projection.invert(point)));
+ });
+ });
+ };
+ }
+
+ /* Align nodes along their common axis */
+
+ function actionStraightenNodes(nodeIDs, projection) {
+ function positionAlongWay(a, o, b) {
+ return geoVecDot(a, b, o) / geoVecDot(b, b, o);
+ } // returns the endpoints of the long axis of symmetry of the `points` bounding rect
+
+
+ function getEndpoints(points) {
+ var ssr = geoGetSmallestSurroundingRectangle(points); // Choose line pq = axis of symmetry.
+ // The shape's surrounding rectangle has 2 axes of symmetry.
+ // Snap points to the long axis
+
+ var p1 = [(ssr.poly[0][0] + ssr.poly[1][0]) / 2, (ssr.poly[0][1] + ssr.poly[1][1]) / 2];
+ var q1 = [(ssr.poly[2][0] + ssr.poly[3][0]) / 2, (ssr.poly[2][1] + ssr.poly[3][1]) / 2];
+ var p2 = [(ssr.poly[3][0] + ssr.poly[4][0]) / 2, (ssr.poly[3][1] + ssr.poly[4][1]) / 2];
+ var q2 = [(ssr.poly[1][0] + ssr.poly[2][0]) / 2, (ssr.poly[1][1] + ssr.poly[2][1]) / 2];
+ var isLong = geoVecLength(p1, q1) > geoVecLength(p2, q2);
+
+ if (isLong) {
+ return [p1, q1];
+ }
+
+ return [p2, q2];
+ }
+
+ var action = function action(graph, t) {
+ if (t === null || !isFinite(t)) t = 1;
+ t = Math.min(Math.max(+t, 0), 1);
+ var nodes = nodeIDs.map(function (id) {
+ return graph.entity(id);
+ });
+ var points = nodes.map(function (n) {
+ return projection(n.loc);
+ });
+ var endpoints = getEndpoints(points);
+ var startPoint = endpoints[0];
+ var endPoint = endpoints[1]; // Move points onto the line connecting the endpoints
+
+ for (var i = 0; i < points.length; i++) {
+ var node = nodes[i];
+ var point = points[i];
+ var u = positionAlongWay(point, startPoint, endPoint);
+ var point2 = geoVecInterp(startPoint, endPoint, u);
+ var loc2 = projection.invert(point2);
+ graph = graph.replace(node.move(geoVecInterp(node.loc, loc2, t)));
+ }
+
+ return graph;
+ };
+
+ action.disabled = function (graph) {
+ var nodes = nodeIDs.map(function (id) {
+ return graph.entity(id);
+ });
+ var points = nodes.map(function (n) {
+ return projection(n.loc);
+ });
+ var endpoints = getEndpoints(points);
+ var startPoint = endpoints[0];
+ var endPoint = endpoints[1];
+ var maxDistance = 0;
+
+ for (var i = 0; i < points.length; i++) {
+ var point = points[i];
+ var u = positionAlongWay(point, startPoint, endPoint);
+ var p = geoVecInterp(startPoint, endPoint, u);
+ var dist = geoVecLength(p, point);
+
+ if (!isNaN(dist) && dist > maxDistance) {
+ maxDistance = dist;
+ }
+ }
+
+ if (maxDistance < 0.0001) {
+ return 'straight_enough';
+ }
+ };
+
+ action.transitionable = true;
+ return action;
+ }
+
+ /*
+ * Based on https://github.com/openstreetmap/potlatch2/net/systemeD/potlatch2/tools/Straighten.as
+ */
+
+ function actionStraightenWay(selectedIDs, projection) {
+ function positionAlongWay(a, o, b) {
+ return geoVecDot(a, b, o) / geoVecDot(b, b, o);
+ } // Return all selected ways as a continuous, ordered array of nodes
+
+
+ function allNodes(graph) {
+ var nodes = [];
+ var startNodes = [];
+ var endNodes = [];
+ var remainingWays = [];
+ var selectedWays = selectedIDs.filter(function (w) {
+ return graph.entity(w).type === 'way';
+ });
+ var selectedNodes = selectedIDs.filter(function (n) {
+ return graph.entity(n).type === 'node';
+ });
+
+ for (var i = 0; i < selectedWays.length; i++) {
+ var way = graph.entity(selectedWays[i]);
+ nodes = way.nodes.slice(0);
+ remainingWays.push(nodes);
+ startNodes.push(nodes[0]);
+ endNodes.push(nodes[nodes.length - 1]);
+ } // Remove duplicate end/startNodes (duplicate nodes cannot be at the line end,
+ // and need to be removed so currNode difference calculation below works)
+ // i.e. ["n-1", "n-1", "n-2"] => ["n-2"]
+
+
+ startNodes = startNodes.filter(function (n) {
+ return startNodes.indexOf(n) === startNodes.lastIndexOf(n);
+ });
+ endNodes = endNodes.filter(function (n) {
+ return endNodes.indexOf(n) === endNodes.lastIndexOf(n);
+ }); // Choose the initial endpoint to start from
+
+ var currNode = utilArrayDifference(startNodes, endNodes).concat(utilArrayDifference(endNodes, startNodes))[0];
+ var nextWay = [];
+ nodes = []; // Create nested function outside of loop to avoid "function in loop" lint error
+
+ var getNextWay = function getNextWay(currNode, remainingWays) {
+ return remainingWays.filter(function (way) {
+ return way[0] === currNode || way[way.length - 1] === currNode;
+ })[0];
+ }; // Add nodes to end of nodes array, until all ways are added
+
+
+ while (remainingWays.length) {
+ nextWay = getNextWay(currNode, remainingWays);
+ remainingWays = utilArrayDifference(remainingWays, [nextWay]);
+
+ if (nextWay[0] !== currNode) {
+ nextWay.reverse();
+ }
+
+ nodes = nodes.concat(nextWay);
+ currNode = nodes[nodes.length - 1];
+ } // If user selected 2 nodes to straighten between, then slice nodes array to those nodes
+
+
+ if (selectedNodes.length === 2) {
+ var startNodeIdx = nodes.indexOf(selectedNodes[0]);
+ var endNodeIdx = nodes.indexOf(selectedNodes[1]);
+ var sortedStartEnd = [startNodeIdx, endNodeIdx];
+ sortedStartEnd.sort(function (a, b) {
+ return a - b;
+ });
+ nodes = nodes.slice(sortedStartEnd[0], sortedStartEnd[1] + 1);
+ }
+
+ return nodes.map(function (n) {
+ return graph.entity(n);
+ });
+ }
+
+ function shouldKeepNode(node, graph) {
+ return graph.parentWays(node).length > 1 || graph.parentRelations(node).length || node.hasInterestingTags();
+ }
+
+ var action = function action(graph, t) {
+ if (t === null || !isFinite(t)) t = 1;
+ t = Math.min(Math.max(+t, 0), 1);
+ var nodes = allNodes(graph);
+ var points = nodes.map(function (n) {
+ return projection(n.loc);
+ });
+ var startPoint = points[0];
+ var endPoint = points[points.length - 1];
+ var toDelete = [];
+ var i;
+
+ for (i = 1; i < points.length - 1; i++) {
+ var node = nodes[i];
+ var point = points[i];
+
+ if (t < 1 || shouldKeepNode(node, graph)) {
+ var u = positionAlongWay(point, startPoint, endPoint);
+ var p = geoVecInterp(startPoint, endPoint, u);
+ var loc2 = projection.invert(p);
+ graph = graph.replace(node.move(geoVecInterp(node.loc, loc2, t)));
+ } else {
+ // safe to delete
+ if (toDelete.indexOf(node) === -1) {
+ toDelete.push(node);
+ }
+ }
+ }
+
+ for (i = 0; i < toDelete.length; i++) {
+ graph = actionDeleteNode(toDelete[i].id)(graph);
+ }
+
+ return graph;
+ };
+
+ action.disabled = function (graph) {
+ // check way isn't too bendy
+ var nodes = allNodes(graph);
+ var points = nodes.map(function (n) {
+ return projection(n.loc);
+ });
+ var startPoint = points[0];
+ var endPoint = points[points.length - 1];
+ var threshold = 0.2 * geoVecLength(startPoint, endPoint);
+ var i;
+
+ if (threshold === 0) {
+ return 'too_bendy';
+ }
+
+ var maxDistance = 0;
+
+ for (i = 1; i < points.length - 1; i++) {
+ var point = points[i];
+ var u = positionAlongWay(point, startPoint, endPoint);
+ var p = geoVecInterp(startPoint, endPoint, u);
+ var dist = geoVecLength(p, point); // to bendy if point is off by 20% of total start/end distance in projected space
+
+ if (isNaN(dist) || dist > threshold) {
+ return 'too_bendy';
+ } else if (dist > maxDistance) {
+ maxDistance = dist;
+ }
+ }
+
+ var keepingAllNodes = nodes.every(function (node, i) {
+ return i === 0 || i === nodes.length - 1 || shouldKeepNode(node, graph);
+ });
+
+ if (maxDistance < 0.0001 && // Allow straightening even if already straight in order to remove extraneous nodes
+ keepingAllNodes) {
+ return 'straight_enough';
+ }
+ };
+
+ action.transitionable = true;
+ return action;
+ }
+
+ //
+ // `turn` must be an `osmTurn` object with a `restrictionID` property.
+ // see osm/intersection.js, pathToTurn()
+ //
+
+ function actionUnrestrictTurn(turn) {
+ return function (graph) {
+ return actionDeleteRelation(turn.restrictionID)(graph);
+ };
+ }
+
+ /* Reflect the given area around its axis of symmetry */
+
+ function actionReflect(reflectIds, projection) {
+ var _useLongAxis = true;
+
+ var action = function action(graph, t) {
+ if (t === null || !isFinite(t)) t = 1;
+ t = Math.min(Math.max(+t, 0), 1);
+ var nodes = utilGetAllNodes(reflectIds, graph);
+ var points = nodes.map(function (n) {
+ return projection(n.loc);
+ });
+ var ssr = geoGetSmallestSurroundingRectangle(points); // Choose line pq = axis of symmetry.
+ // The shape's surrounding rectangle has 2 axes of symmetry.
+ // Reflect across the longer axis by default.
+
+ var p1 = [(ssr.poly[0][0] + ssr.poly[1][0]) / 2, (ssr.poly[0][1] + ssr.poly[1][1]) / 2];
+ var q1 = [(ssr.poly[2][0] + ssr.poly[3][0]) / 2, (ssr.poly[2][1] + ssr.poly[3][1]) / 2];
+ var p2 = [(ssr.poly[3][0] + ssr.poly[4][0]) / 2, (ssr.poly[3][1] + ssr.poly[4][1]) / 2];
+ var q2 = [(ssr.poly[1][0] + ssr.poly[2][0]) / 2, (ssr.poly[1][1] + ssr.poly[2][1]) / 2];
+ var p, q;
+ var isLong = geoVecLength(p1, q1) > geoVecLength(p2, q2);
+
+ if (_useLongAxis && isLong || !_useLongAxis && !isLong) {
+ p = p1;
+ q = q1;
+ } else {
+ p = p2;
+ q = q2;
+ } // reflect c across pq
+ // http://math.stackexchange.com/questions/65503/point-reflection-over-a-line
+
+
+ var dx = q[0] - p[0];
+ var dy = q[1] - p[1];
+ var a = (dx * dx - dy * dy) / (dx * dx + dy * dy);
+ var b = 2 * dx * dy / (dx * dx + dy * dy);
+
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ var c = projection(node.loc);
+ var c2 = [a * (c[0] - p[0]) + b * (c[1] - p[1]) + p[0], b * (c[0] - p[0]) - a * (c[1] - p[1]) + p[1]];
+ var loc2 = projection.invert(c2);
+ node = node.move(geoVecInterp(node.loc, loc2, t));
+ graph = graph.replace(node);
+ }
+
+ return graph;
+ };
+
+ action.useLongAxis = function (val) {
+ if (!arguments.length) return _useLongAxis;
+ _useLongAxis = val;
+ return action;
+ };
+
+ action.transitionable = true;
+ return action;
+ }
+
+ function actionUpgradeTags(entityId, oldTags, replaceTags) {
+ return function (graph) {
+ var entity = graph.entity(entityId);
+ var tags = Object.assign({}, entity.tags); // shallow copy
+
+ var transferValue;
+ var semiIndex;
+
+ for (var oldTagKey in oldTags) {
+ if (!(oldTagKey in tags)) continue; // wildcard match
+
+ if (oldTags[oldTagKey] === '*') {
+ // note the value since we might need to transfer it
+ transferValue = tags[oldTagKey];
+ delete tags[oldTagKey]; // exact match
+ } else if (oldTags[oldTagKey] === tags[oldTagKey]) {
+ delete tags[oldTagKey]; // match is within semicolon-delimited values
+ } else {
+ var vals = tags[oldTagKey].split(';').filter(Boolean);
+ var oldIndex = vals.indexOf(oldTags[oldTagKey]);
+
+ if (vals.length === 1 || oldIndex === -1) {
+ delete tags[oldTagKey];
+ } else {
+ if (replaceTags && replaceTags[oldTagKey]) {
+ // replacing a value within a semicolon-delimited value, note the index
+ semiIndex = oldIndex;
+ }
+
+ vals.splice(oldIndex, 1);
+ tags[oldTagKey] = vals.join(';');
+ }
+ }
+ }
+
+ if (replaceTags) {
+ for (var replaceKey in replaceTags) {
+ var replaceValue = replaceTags[replaceKey];
+
+ if (replaceValue === '*') {
+ if (tags[replaceKey] && tags[replaceKey] !== 'no') {
+ // allow any pre-existing value except `no` (troll tag)
+ continue;
+ } else {
+ // otherwise assume `yes` is okay
+ tags[replaceKey] = 'yes';
+ }
+ } else if (replaceValue === '$1') {
+ tags[replaceKey] = transferValue;
+ } else {
+ if (tags[replaceKey] && oldTags[replaceKey] && semiIndex !== undefined) {
+ // don't override preexisting values
+ var existingVals = tags[replaceKey].split(';').filter(Boolean);
+
+ if (existingVals.indexOf(replaceValue) === -1) {
+ existingVals.splice(semiIndex, 0, replaceValue);
+ tags[replaceKey] = existingVals.join(';');
+ }
+ } else {
+ tags[replaceKey] = replaceValue;
+ }
+ }
+ }
+ }
+
+ return graph.replace(entity.update({
+ tags: tags
+ }));
+ };
+ }
+
+ function behaviorEdit(context) {
+ function behavior() {
+ context.map().minzoom(context.minEditableZoom());
+ }
+
+ behavior.off = function () {
+ context.map().minzoom(0);
+ };
+
+ return behavior;
+ }
+
+ /*
+ The hover behavior adds the `.hover` class on pointerover to all elements to which
+ the identical datum is bound, and removes it on pointerout.
+
+ The :hover pseudo-class is insufficient for iD's purposes because a datum's visual
+ representation may consist of several elements scattered throughout the DOM hierarchy.
+ Only one of these elements can have the :hover pseudo-class, but all of them will
+ have the .hover class.
+ */
+
+ function behaviorHover(context) {
+ var dispatch = dispatch$8('hover');
+
+ var _selection = select(null);
+
+ var _newNodeId = null;
+ var _initialNodeID = null;
+
+ var _altDisables;
+
+ var _ignoreVertex;
+
+ var _targets = []; // use pointer events on supported platforms; fallback to mouse events
+
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+
+ function keydown(d3_event) {
+ if (_altDisables && d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
+ _selection.selectAll('.hover').classed('hover-suppressed', true).classed('hover', false);
+
+ _selection.classed('hover-disabled', true);
+
+ dispatch.call('hover', this, null);
+ }
+ }
+
+ function keyup(d3_event) {
+ if (_altDisables && d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
+ _selection.selectAll('.hover-suppressed').classed('hover-suppressed', false).classed('hover', true);
+
+ _selection.classed('hover-disabled', false);
+
+ dispatch.call('hover', this, _targets);
+ }
+ }
+
+ function behavior(selection) {
+ _selection = selection;
+ _targets = [];
+
+ if (_initialNodeID) {
+ _newNodeId = _initialNodeID;
+ _initialNodeID = null;
+ } else {
+ _newNodeId = null;
+ }
+
+ _selection.on(_pointerPrefix + 'over.hover', pointerover).on(_pointerPrefix + 'out.hover', pointerout) // treat pointerdown as pointerover for touch devices
+ .on(_pointerPrefix + 'down.hover', pointerover);
+
+ select(window).on(_pointerPrefix + 'up.hover pointercancel.hover', pointerout, true).on('keydown.hover', keydown).on('keyup.hover', keyup);
+
+ function eventTarget(d3_event) {
+ var datum = d3_event.target && d3_event.target.__data__;
+ if (_typeof(datum) !== 'object') return null;
+
+ if (!(datum instanceof osmEntity) && datum.properties && datum.properties.entity instanceof osmEntity) {
+ return datum.properties.entity;
+ }
+
+ return datum;
+ }
+
+ function pointerover(d3_event) {
+ // ignore mouse hovers with buttons pressed unless dragging
+ if (context.mode().id.indexOf('drag') === -1 && (!d3_event.pointerType || d3_event.pointerType === 'mouse') && d3_event.buttons) return;
+ var target = eventTarget(d3_event);
+
+ if (target && _targets.indexOf(target) === -1) {
+ _targets.push(target);
+
+ updateHover(d3_event, _targets);
+ }
+ }
+
+ function pointerout(d3_event) {
+ var target = eventTarget(d3_event);
+
+ var index = _targets.indexOf(target);
+
+ if (index !== -1) {
+ _targets.splice(index);
+
+ updateHover(d3_event, _targets);
+ }
+ }
+
+ function allowsVertex(d) {
+ return d.geometry(context.graph()) === 'vertex' || _mainPresetIndex.allowsVertex(d, context.graph());
+ }
+
+ function modeAllowsHover(target) {
+ var mode = context.mode();
+
+ if (mode.id === 'add-point') {
+ return mode.preset.matchGeometry('vertex') || target.type !== 'way' && target.geometry(context.graph()) !== 'vertex';
+ }
+
+ return true;
+ }
+
+ function updateHover(d3_event, targets) {
+ _selection.selectAll('.hover').classed('hover', false);
+
+ _selection.selectAll('.hover-suppressed').classed('hover-suppressed', false);
+
+ var mode = context.mode();
+
+ if (!_newNodeId && (mode.id === 'draw-line' || mode.id === 'draw-area')) {
+ var node = targets.find(function (target) {
+ return target instanceof osmEntity && target.type === 'node';
+ });
+ _newNodeId = node && node.id;
+ }
+
+ targets = targets.filter(function (datum) {
+ if (datum instanceof osmEntity) {
+ // If drawing a way, don't hover on a node that was just placed. #3974
+ return datum.id !== _newNodeId && (datum.type !== 'node' || !_ignoreVertex || allowsVertex(datum)) && modeAllowsHover(datum);
+ }
+
+ return true;
+ });
+ var selector = '';
+
+ for (var i in targets) {
+ var datum = targets[i]; // What are we hovering over?
+
+ if (datum.__featurehash__) {
+ // hovering custom data
+ selector += ', .data' + datum.__featurehash__;
+ } else if (datum instanceof QAItem) {
+ selector += ', .' + datum.service + '.itemId-' + datum.id;
+ } else if (datum instanceof osmNote) {
+ selector += ', .note-' + datum.id;
+ } else if (datum instanceof osmEntity) {
+ selector += ', .' + datum.id;
+
+ if (datum.type === 'relation') {
+ for (var j in datum.members) {
+ selector += ', .' + datum.members[j].id;
+ }
+ }
+ }
+ }
+
+ var suppressed = _altDisables && d3_event && d3_event.altKey;
+
+ if (selector.trim().length) {
+ // remove the first comma
+ selector = selector.slice(1);
+
+ _selection.selectAll(selector).classed(suppressed ? 'hover-suppressed' : 'hover', true);
+ }
+
+ dispatch.call('hover', this, !suppressed && targets);
+ }
+ }
+
+ behavior.off = function (selection) {
+ selection.selectAll('.hover').classed('hover', false);
+ selection.selectAll('.hover-suppressed').classed('hover-suppressed', false);
+ selection.classed('hover-disabled', false);
+ selection.on(_pointerPrefix + 'over.hover', null).on(_pointerPrefix + 'out.hover', null).on(_pointerPrefix + 'down.hover', null);
+ select(window).on(_pointerPrefix + 'up.hover pointercancel.hover', null, true).on('keydown.hover', null).on('keyup.hover', null);
+ };
+
+ behavior.altDisables = function (val) {
+ if (!arguments.length) return _altDisables;
+ _altDisables = val;
+ return behavior;
+ };
+
+ behavior.ignoreVertex = function (val) {
+ if (!arguments.length) return _ignoreVertex;
+ _ignoreVertex = val;
+ return behavior;
+ };
+
+ behavior.initialNodeID = function (nodeId) {
+ _initialNodeID = nodeId;
+ return behavior;
+ };
+
+ return utilRebind(behavior, dispatch, 'on');
+ }
+
+ var _disableSpace = false;
+ var _lastSpace = null;
+ function behaviorDraw(context) {
+ var dispatch = dispatch$8('move', 'down', 'downcancel', 'click', 'clickWay', 'clickNode', 'undo', 'cancel', 'finish');
+ var keybinding = utilKeybinding('draw');
+
+ var _hover = behaviorHover(context).altDisables(true).ignoreVertex(true).on('hover', context.ui().sidebar.hover);
+
+ var _edit = behaviorEdit(context);
+
+ var _closeTolerance = 4;
+ var _tolerance = 12;
+ var _mouseLeave = false;
+ var _lastMouse = null;
+
+ var _lastPointerUpEvent;
+
+ var _downPointer; // use pointer events on supported platforms; fallback to mouse events
+
+
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; // related code
+ // - `mode/drag_node.js` `datum()`
+
+
+ function datum(d3_event) {
+ var mode = context.mode();
+ var isNote = mode && mode.id.indexOf('note') !== -1;
+ if (d3_event.altKey || isNote) return {};
+ var element;
+
+ if (d3_event.type === 'keydown') {
+ element = _lastMouse && _lastMouse.target;
+ } else {
+ element = d3_event.target;
+ } // When drawing, snap only to touch targets..
+ // (this excludes area fills and active drawing elements)
+
+
+ var d = element.__data__;
+ return d && d.properties && d.properties.target ? d : {};
+ }
+
+ function pointerdown(d3_event) {
+ if (_downPointer) return;
+ var pointerLocGetter = utilFastMouse(this);
+ _downPointer = {
+ id: d3_event.pointerId || 'mouse',
+ pointerLocGetter: pointerLocGetter,
+ downTime: +new Date(),
+ downLoc: pointerLocGetter(d3_event)
+ };
+ dispatch.call('down', this, d3_event, datum(d3_event));
+ }
+
+ function pointerup(d3_event) {
+ if (!_downPointer || _downPointer.id !== (d3_event.pointerId || 'mouse')) return;
+ var downPointer = _downPointer;
+ _downPointer = null;
+ _lastPointerUpEvent = d3_event;
+ if (downPointer.isCancelled) return;
+ var t2 = +new Date();
+ var p2 = downPointer.pointerLocGetter(d3_event);
+ var dist = geoVecLength(downPointer.downLoc, p2);
+
+ if (dist < _closeTolerance || dist < _tolerance && t2 - downPointer.downTime < 500) {
+ // Prevent a quick second click
+ select(window).on('click.draw-block', function () {
+ d3_event.stopPropagation();
+ }, true);
+ context.map().dblclickZoomEnable(false);
+ window.setTimeout(function () {
+ context.map().dblclickZoomEnable(true);
+ select(window).on('click.draw-block', null);
+ }, 500);
+ click(d3_event, p2);
+ }
+ }
+
+ function pointermove(d3_event) {
+ if (_downPointer && _downPointer.id === (d3_event.pointerId || 'mouse') && !_downPointer.isCancelled) {
+ var p2 = _downPointer.pointerLocGetter(d3_event);
+
+ var dist = geoVecLength(_downPointer.downLoc, p2);
+
+ if (dist >= _closeTolerance) {
+ _downPointer.isCancelled = true;
+ dispatch.call('downcancel', this);
+ }
+ }
+
+ if (d3_event.pointerType && d3_event.pointerType !== 'mouse' || d3_event.buttons || _downPointer) return; // HACK: Mobile Safari likes to send one or more `mouse` type pointermove
+ // events immediately after non-mouse pointerup events; detect and ignore them.
+
+ if (_lastPointerUpEvent && _lastPointerUpEvent.pointerType !== 'mouse' && d3_event.timeStamp - _lastPointerUpEvent.timeStamp < 100) return;
+ _lastMouse = d3_event;
+ dispatch.call('move', this, d3_event, datum(d3_event));
+ }
+
+ function pointercancel(d3_event) {
+ if (_downPointer && _downPointer.id === (d3_event.pointerId || 'mouse')) {
+ if (!_downPointer.isCancelled) {
+ dispatch.call('downcancel', this);
+ }
+
+ _downPointer = null;
+ }
+ }
+
+ function mouseenter() {
+ _mouseLeave = false;
+ }
+
+ function mouseleave() {
+ _mouseLeave = true;
+ }
+
+ function allowsVertex(d) {
+ return d.geometry(context.graph()) === 'vertex' || _mainPresetIndex.allowsVertex(d, context.graph());
+ } // related code
+ // - `mode/drag_node.js` `doMove()`
+ // - `behavior/draw.js` `click()`
+ // - `behavior/draw_way.js` `move()`
+
+
+ function click(d3_event, loc) {
+ var d = datum(d3_event);
+ var target = d && d.properties && d.properties.entity;
+ var mode = context.mode();
+
+ if (target && target.type === 'node' && allowsVertex(target)) {
+ // Snap to a node
+ dispatch.call('clickNode', this, target, d);
+ return;
+ } else if (target && target.type === 'way' && (mode.id !== 'add-point' || mode.preset.matchGeometry('vertex'))) {
+ // Snap to a way
+ var choice = geoChooseEdge(context.graph().childNodes(target), loc, context.projection, context.activeID());
+
+ if (choice) {
+ var edge = [target.nodes[choice.index - 1], target.nodes[choice.index]];
+ dispatch.call('clickWay', this, choice.loc, edge, d);
+ return;
+ }
+ } else if (mode.id !== 'add-point' || mode.preset.matchGeometry('point')) {
+ var locLatLng = context.projection.invert(loc);
+ dispatch.call('click', this, locLatLng, d);
+ }
+ } // treat a spacebar press like a click
+
+
+ function space(d3_event) {
+ d3_event.preventDefault();
+ d3_event.stopPropagation();
+ var currSpace = context.map().mouse();
+
+ if (_disableSpace && _lastSpace) {
+ var dist = geoVecLength(_lastSpace, currSpace);
+
+ if (dist > _tolerance) {
+ _disableSpace = false;
+ }
+ }
+
+ if (_disableSpace || _mouseLeave || !_lastMouse) return; // user must move mouse or release space bar to allow another click
+
+ _lastSpace = currSpace;
+ _disableSpace = true;
+ select(window).on('keyup.space-block', function () {
+ d3_event.preventDefault();
+ d3_event.stopPropagation();
+ _disableSpace = false;
+ select(window).on('keyup.space-block', null);
+ }); // get the current mouse position
+
+ var loc = context.map().mouse() || // or the map center if the mouse has never entered the map
+ context.projection(context.map().center());
+ click(d3_event, loc);
+ }
+
+ function backspace(d3_event) {
+ d3_event.preventDefault();
+ dispatch.call('undo');
+ }
+
+ function del(d3_event) {
+ d3_event.preventDefault();
+ dispatch.call('cancel');
+ }
+
+ function ret(d3_event) {
+ d3_event.preventDefault();
+ dispatch.call('finish');
+ }
+
+ function behavior(selection) {
+ context.install(_hover);
+ context.install(_edit);
+ _downPointer = null;
+ keybinding.on('⌫', backspace).on('⌦', del).on('⎋', ret).on('↩', ret).on('space', space).on('⌥space', space);
+ selection.on('mouseenter.draw', mouseenter).on('mouseleave.draw', mouseleave).on(_pointerPrefix + 'down.draw', pointerdown).on(_pointerPrefix + 'move.draw', pointermove);
+ select(window).on(_pointerPrefix + 'up.draw', pointerup, true).on('pointercancel.draw', pointercancel, true);
+ select(document).call(keybinding);
+ return behavior;
+ }
+
+ behavior.off = function (selection) {
+ context.ui().sidebar.hover.cancel();
+ context.uninstall(_hover);
+ context.uninstall(_edit);
+ selection.on('mouseenter.draw', null).on('mouseleave.draw', null).on(_pointerPrefix + 'down.draw', null).on(_pointerPrefix + 'move.draw', null);
+ select(window).on(_pointerPrefix + 'up.draw', null).on('pointercancel.draw', null); // note: keyup.space-block, click.draw-block should remain
+
+ select(document).call(keybinding.unbind);
+ };
+
+ behavior.hover = function () {
+ return _hover;
+ };
+
+ return utilRebind(behavior, dispatch, 'on');
+ }
+
+ function initRange(domain, range) {
+ switch (arguments.length) {
+ case 0:
+ break;
+
+ case 1:
+ this.range(domain);
+ break;
+
+ default:
+ this.range(range).domain(domain);
+ break;
+ }
+
+ return this;
+ }
+
+ function constants(x) {
+ return function () {
+ return x;
+ };
+ }
+
+ function number(x) {
+ return +x;
+ }
+
+ var unit = [0, 1];
+ function identity$1(x) {
+ return x;
+ }
+
+ function normalize(a, b) {
+ return (b -= a = +a) ? function (x) {
+ return (x - a) / b;
+ } : constants(isNaN(b) ? NaN : 0.5);
+ }
+
+ function clamper(a, b) {
+ var t;
+ if (a > b) t = a, a = b, b = t;
+ return function (x) {
+ return Math.max(a, Math.min(b, x));
+ };
+ } // normalize(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1].
+ // interpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding range value x in [a,b].
+
+
+ function bimap(domain, range, interpolate) {
+ var d0 = domain[0],
+ d1 = domain[1],
+ r0 = range[0],
+ r1 = range[1];
+ if (d1 < d0) d0 = normalize(d1, d0), r0 = interpolate(r1, r0);else d0 = normalize(d0, d1), r0 = interpolate(r0, r1);
+ return function (x) {
+ return r0(d0(x));
+ };
+ }
+
+ function polymap(domain, range, interpolate) {
+ var j = Math.min(domain.length, range.length) - 1,
+ d = new Array(j),
+ r = new Array(j),
+ i = -1; // Reverse descending domains.
+
+ if (domain[j] < domain[0]) {
+ domain = domain.slice().reverse();
+ range = range.slice().reverse();
+ }
+
+ while (++i < j) {
+ d[i] = normalize(domain[i], domain[i + 1]);
+ r[i] = interpolate(range[i], range[i + 1]);
+ }
+
+ return function (x) {
+ var i = bisectRight(domain, x, 1, j) - 1;
+ return r[i](d[i](x));
+ };
+ }
+
+ function copy(source, target) {
+ return target.domain(source.domain()).range(source.range()).interpolate(source.interpolate()).clamp(source.clamp()).unknown(source.unknown());
+ }
+ function transformer() {
+ var domain = unit,
+ range = unit,
+ interpolate = interpolate$1,
+ transform,
+ untransform,
+ unknown,
+ clamp = identity$1,
+ piecewise,
+ output,
+ input;
+
+ function rescale() {
+ var n = Math.min(domain.length, range.length);
+ if (clamp !== identity$1) clamp = clamper(domain[0], domain[n - 1]);
+ piecewise = n > 2 ? polymap : bimap;
+ output = input = null;
+ return scale;
+ }
+
+ function scale(x) {
+ return x == null || isNaN(x = +x) ? unknown : (output || (output = piecewise(domain.map(transform), range, interpolate)))(transform(clamp(x)));
+ }
+
+ scale.invert = function (y) {
+ return clamp(untransform((input || (input = piecewise(range, domain.map(transform), d3_interpolateNumber)))(y)));
+ };
+
+ scale.domain = function (_) {
+ return arguments.length ? (domain = Array.from(_, number), rescale()) : domain.slice();
+ };
+
+ scale.range = function (_) {
+ return arguments.length ? (range = Array.from(_), rescale()) : range.slice();
+ };
+
+ scale.rangeRound = function (_) {
+ return range = Array.from(_), interpolate = interpolateRound, rescale();
+ };
+
+ scale.clamp = function (_) {
+ return arguments.length ? (clamp = _ ? true : identity$1, rescale()) : clamp !== identity$1;
+ };
+
+ scale.interpolate = function (_) {
+ return arguments.length ? (interpolate = _, rescale()) : interpolate;
+ };
+
+ scale.unknown = function (_) {
+ return arguments.length ? (unknown = _, scale) : unknown;
+ };
+
+ return function (t, u) {
+ transform = t, untransform = u;
+ return rescale();
+ };
+ }
+ function continuous() {
+ return transformer()(identity$1, identity$1);
+ }
+
+ function formatDecimal (x) {
+ return Math.abs(x = Math.round(x)) >= 1e21 ? x.toLocaleString("en").replace(/,/g, "") : x.toString(10);
+ } // Computes the decimal coefficient and exponent of the specified number x with
+ // significant digits p, where x is positive and p is in [1, 21] or undefined.
+ // For example, formatDecimalParts(1.23) returns ["123", 0].
+
+ function formatDecimalParts(x, p) {
+ if ((i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e")) < 0) return null; // NaN, ±Infinity
+
+ var i,
+ coefficient = x.slice(0, i); // The string returned by toExponential either has the form \d\.\d+e[-+]\d+
+ // (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3).
+
+ return [coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, +x.slice(i + 1)];
+ }
+
+ function exponent (x) {
+ return x = formatDecimalParts(Math.abs(x)), x ? x[1] : NaN;
+ }
+
+ function formatGroup (grouping, thousands) {
+ return function (value, width) {
+ var i = value.length,
+ t = [],
+ j = 0,
+ g = grouping[0],
+ length = 0;
+
+ while (i > 0 && g > 0) {
+ if (length + g + 1 > width) g = Math.max(1, width - length);
+ t.push(value.substring(i -= g, i + g));
+ if ((length += g + 1) > width) break;
+ g = grouping[j = (j + 1) % grouping.length];
+ }
+
+ return t.reverse().join(thousands);
+ };
+ }
+
+ function formatNumerals (numerals) {
+ return function (value) {
+ return value.replace(/[0-9]/g, function (i) {
+ return numerals[+i];
+ });
+ };
+ }
+
+ // [[fill]align][sign][symbol][0][width][,][.precision][~][type]
+ var re = /^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;
+ function formatSpecifier(specifier) {
+ if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier);
+ var match;
+ return new FormatSpecifier({
+ fill: match[1],
+ align: match[2],
+ sign: match[3],
+ symbol: match[4],
+ zero: match[5],
+ width: match[6],
+ comma: match[7],
+ precision: match[8] && match[8].slice(1),
+ trim: match[9],
+ type: match[10]
+ });
+ }
+ formatSpecifier.prototype = FormatSpecifier.prototype; // instanceof
+
+ function FormatSpecifier(specifier) {
+ this.fill = specifier.fill === undefined ? " " : specifier.fill + "";
+ this.align = specifier.align === undefined ? ">" : specifier.align + "";
+ this.sign = specifier.sign === undefined ? "-" : specifier.sign + "";
+ this.symbol = specifier.symbol === undefined ? "" : specifier.symbol + "";
+ this.zero = !!specifier.zero;
+ this.width = specifier.width === undefined ? undefined : +specifier.width;
+ this.comma = !!specifier.comma;
+ this.precision = specifier.precision === undefined ? undefined : +specifier.precision;
+ this.trim = !!specifier.trim;
+ this.type = specifier.type === undefined ? "" : specifier.type + "";
+ }
+
+ FormatSpecifier.prototype.toString = function () {
+ return this.fill + this.align + this.sign + this.symbol + (this.zero ? "0" : "") + (this.width === undefined ? "" : Math.max(1, this.width | 0)) + (this.comma ? "," : "") + (this.precision === undefined ? "" : "." + Math.max(0, this.precision | 0)) + (this.trim ? "~" : "") + this.type;
+ };
+
+ // Trims insignificant zeros, e.g., replaces 1.2000k with 1.2k.
+ function formatTrim (s) {
+ out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) {
+ switch (s[i]) {
+ case ".":
+ i0 = i1 = i;
+ break;
+
+ case "0":
+ if (i0 === 0) i0 = i;
+ i1 = i;
+ break;
+
+ default:
+ if (!+s[i]) break out;
+ if (i0 > 0) i0 = 0;
+ break;
+ }
+ }
+
+ return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s;
+ }
+
+ var $$5 = _export;
+ var uncurryThis$3 = functionUncurryThis;
+ var fails$3 = fails$V;
+ var thisNumberValue = thisNumberValue$3;
+
+ var un$ToPrecision = uncurryThis$3(1.0.toPrecision);
+
+ var FORCED$1 = fails$3(function () {
+ // IE7-
+ return un$ToPrecision(1, undefined) !== '1';
+ }) || !fails$3(function () {
+ // V8 ~ Android 4.3-
+ un$ToPrecision({});
+ });
+
+ // `Number.prototype.toPrecision` method
+ // https://tc39.es/ecma262/#sec-number.prototype.toprecision
+ $$5({ target: 'Number', proto: true, forced: FORCED$1 }, {
+ toPrecision: function toPrecision(precision) {
+ return precision === undefined
+ ? un$ToPrecision(thisNumberValue(this))
+ : un$ToPrecision(thisNumberValue(this), precision);
+ }
+ });
+
+ var prefixExponent;
+ function formatPrefixAuto (x, p) {
+ var d = formatDecimalParts(x, p);
+ if (!d) return x + "";
+ var coefficient = d[0],
+ exponent = d[1],
+ i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1,
+ n = coefficient.length;
+ return i === n ? coefficient : i > n ? coefficient + new Array(i - n + 1).join("0") : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i) : "0." + new Array(1 - i).join("0") + formatDecimalParts(x, Math.max(0, p + i - 1))[0]; // less than 1y!
+ }
+
+ function formatRounded (x, p) {
+ var d = formatDecimalParts(x, p);
+ if (!d) return x + "";
+ var coefficient = d[0],
+ exponent = d[1];
+ return exponent < 0 ? "0." + new Array(-exponent).join("0") + coefficient : coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1) : coefficient + new Array(exponent - coefficient.length + 2).join("0");
+ }
+
+ var formatTypes = {
+ "%": function _(x, p) {
+ return (x * 100).toFixed(p);
+ },
+ "b": function b(x) {
+ return Math.round(x).toString(2);
+ },
+ "c": function c(x) {
+ return x + "";
+ },
+ "d": formatDecimal,
+ "e": function e(x, p) {
+ return x.toExponential(p);
+ },
+ "f": function f(x, p) {
+ return x.toFixed(p);
+ },
+ "g": function g(x, p) {
+ return x.toPrecision(p);
+ },
+ "o": function o(x) {
+ return Math.round(x).toString(8);
+ },
+ "p": function p(x, _p) {
+ return formatRounded(x * 100, _p);
+ },
+ "r": formatRounded,
+ "s": formatPrefixAuto,
+ "X": function X(x) {
+ return Math.round(x).toString(16).toUpperCase();
+ },
+ "x": function x(_x) {
+ return Math.round(_x).toString(16);
+ }
+ };
+
+ function identity (x) {
+ return x;
+ }
+
+ var map$1 = Array.prototype.map,
+ prefixes = ["y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y"];
+ function formatLocale (locale) {
+ var group = locale.grouping === undefined || locale.thousands === undefined ? identity : formatGroup(map$1.call(locale.grouping, Number), locale.thousands + ""),
+ currencyPrefix = locale.currency === undefined ? "" : locale.currency[0] + "",
+ currencySuffix = locale.currency === undefined ? "" : locale.currency[1] + "",
+ decimal = locale.decimal === undefined ? "." : locale.decimal + "",
+ numerals = locale.numerals === undefined ? identity : formatNumerals(map$1.call(locale.numerals, String)),
+ percent = locale.percent === undefined ? "%" : locale.percent + "",
+ minus = locale.minus === undefined ? "−" : locale.minus + "",
+ nan = locale.nan === undefined ? "NaN" : locale.nan + "";
+
+ function newFormat(specifier) {
+ specifier = formatSpecifier(specifier);
+ var fill = specifier.fill,
+ align = specifier.align,
+ sign = specifier.sign,
+ symbol = specifier.symbol,
+ zero = specifier.zero,
+ width = specifier.width,
+ comma = specifier.comma,
+ precision = specifier.precision,
+ trim = specifier.trim,
+ type = specifier.type; // The "n" type is an alias for ",g".
+
+ if (type === "n") comma = true, type = "g"; // The "" type, and any invalid type, is an alias for ".12~g".
+ else if (!formatTypes[type]) precision === undefined && (precision = 12), trim = true, type = "g"; // If zero fill is specified, padding goes after sign and before digits.
+
+ if (zero || fill === "0" && align === "=") zero = true, fill = "0", align = "="; // Compute the prefix and suffix.
+ // For SI-prefix, the suffix is lazily computed.
+
+ var prefix = symbol === "$" ? currencyPrefix : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : "",
+ suffix = symbol === "$" ? currencySuffix : /[%p]/.test(type) ? percent : ""; // What format function should we use?
+ // Is this an integer type?
+ // Can this type generate exponential notation?
+
+ var formatType = formatTypes[type],
+ maybeSuffix = /[defgprs%]/.test(type); // Set the default precision if not specified,
+ // or clamp the specified precision to the supported range.
+ // For significant precision, it must be in [1, 21].
+ // For fixed precision, it must be in [0, 20].
+
+ precision = precision === undefined ? 6 : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) : Math.max(0, Math.min(20, precision));
+
+ function format(value) {
+ var valuePrefix = prefix,
+ valueSuffix = suffix,
+ i,
+ n,
+ c;
+
+ if (type === "c") {
+ valueSuffix = formatType(value) + valueSuffix;
+ value = "";
+ } else {
+ value = +value; // Determine the sign. -0 is not less than 0, but 1 / -0 is!
+
+ var valueNegative = value < 0 || 1 / value < 0; // Perform the initial formatting.
+
+ value = isNaN(value) ? nan : formatType(Math.abs(value), precision); // Trim insignificant zeros.
+
+ if (trim) value = formatTrim(value); // If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign.
+
+ if (valueNegative && +value === 0 && sign !== "+") valueNegative = false; // Compute the prefix and suffix.
+
+ valuePrefix = (valueNegative ? sign === "(" ? sign : minus : sign === "-" || sign === "(" ? "" : sign) + valuePrefix;
+ valueSuffix = (type === "s" ? prefixes[8 + prefixExponent / 3] : "") + valueSuffix + (valueNegative && sign === "(" ? ")" : ""); // Break the formatted value into the integer “value” part that can be
+ // grouped, and fractional or exponential “suffix” part that is not.
+
+ if (maybeSuffix) {
+ i = -1, n = value.length;
+
+ while (++i < n) {
+ if (c = value.charCodeAt(i), 48 > c || c > 57) {
+ valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix;
+ value = value.slice(0, i);
+ break;
+ }
+ }
+ }
+ } // If the fill character is not "0", grouping is applied before padding.
+
+
+ if (comma && !zero) value = group(value, Infinity); // Compute the padding.
+
+ var length = valuePrefix.length + value.length + valueSuffix.length,
+ padding = length < width ? new Array(width - length + 1).join(fill) : ""; // If the fill character is "0", grouping is applied after padding.
+
+ if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = ""; // Reconstruct the final output based on the desired alignment.
+
+ switch (align) {
+ case "<":
+ value = valuePrefix + value + valueSuffix + padding;
+ break;
+
+ case "=":
+ value = valuePrefix + padding + value + valueSuffix;
+ break;
+
+ case "^":
+ value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length);
+ break;
+
+ default:
+ value = padding + valuePrefix + value + valueSuffix;
+ break;
+ }
+
+ return numerals(value);
+ }
+
+ format.toString = function () {
+ return specifier + "";
+ };
+
+ return format;
+ }
+
+ function formatPrefix(specifier, value) {
+ var f = newFormat((specifier = formatSpecifier(specifier), specifier.type = "f", specifier)),
+ e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3,
+ k = Math.pow(10, -e),
+ prefix = prefixes[8 + e / 3];
+ return function (value) {
+ return f(k * value) + prefix;
+ };
+ }
+
+ return {
+ format: newFormat,
+ formatPrefix: formatPrefix
+ };
+ }
+
+ var locale;
+ var format$1;
+ var formatPrefix;
+ defaultLocale({
+ thousands: ",",
+ grouping: [3],
+ currency: ["$", ""]
+ });
+ function defaultLocale(definition) {
+ locale = formatLocale(definition);
+ format$1 = locale.format;
+ formatPrefix = locale.formatPrefix;
+ return locale;
+ }
+
+ function precisionFixed (step) {
+ return Math.max(0, -exponent(Math.abs(step)));
+ }
+
+ function precisionPrefix (step, value) {
+ return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step)));
+ }
+
+ function precisionRound (step, max) {
+ step = Math.abs(step), max = Math.abs(max) - step;
+ return Math.max(0, exponent(max) - exponent(step)) + 1;
+ }
+
+ function tickFormat(start, stop, count, specifier) {
+ var step = tickStep(start, stop, count),
+ precision;
+ specifier = formatSpecifier(specifier == null ? ",f" : specifier);
+
+ switch (specifier.type) {
+ case "s":
+ {
+ var value = Math.max(Math.abs(start), Math.abs(stop));
+ if (specifier.precision == null && !isNaN(precision = precisionPrefix(step, value))) specifier.precision = precision;
+ return formatPrefix(specifier, value);
+ }
+
+ case "":
+ case "e":
+ case "g":
+ case "p":
+ case "r":
+ {
+ if (specifier.precision == null && !isNaN(precision = precisionRound(step, Math.max(Math.abs(start), Math.abs(stop))))) specifier.precision = precision - (specifier.type === "e");
+ break;
+ }
+
+ case "f":
+ case "%":
+ {
+ if (specifier.precision == null && !isNaN(precision = precisionFixed(step))) specifier.precision = precision - (specifier.type === "%") * 2;
+ break;
+ }
+ }
+
+ return format$1(specifier);
+ }
+
+ function linearish(scale) {
+ var domain = scale.domain;
+
+ scale.ticks = function (count) {
+ var d = domain();
+ return ticks(d[0], d[d.length - 1], count == null ? 10 : count);
+ };
+
+ scale.tickFormat = function (count, specifier) {
+ var d = domain();
+ return tickFormat(d[0], d[d.length - 1], count == null ? 10 : count, specifier);
+ };
+
+ scale.nice = function (count) {
+ if (count == null) count = 10;
+ var d = domain();
+ var i0 = 0;
+ var i1 = d.length - 1;
+ var start = d[i0];
+ var stop = d[i1];
+ var prestep;
+ var step;
+ var maxIter = 10;
+
+ if (stop < start) {
+ step = start, start = stop, stop = step;
+ step = i0, i0 = i1, i1 = step;
+ }
+
+ while (maxIter-- > 0) {
+ step = tickIncrement(start, stop, count);
+
+ if (step === prestep) {
+ d[i0] = start;
+ d[i1] = stop;
+ return domain(d);
+ } else if (step > 0) {
+ start = Math.floor(start / step) * step;
+ stop = Math.ceil(stop / step) * step;
+ } else if (step < 0) {
+ start = Math.ceil(start * step) / step;
+ stop = Math.floor(stop * step) / step;
+ } else {
+ break;
+ }
+
+ prestep = step;
+ }
+
+ return scale;
+ };
+
+ return scale;
+ }
+ function linear() {
+ var scale = continuous();
+
+ scale.copy = function () {
+ return copy(scale, linear());
+ };
+
+ initRange.apply(scale, arguments);
+ return linearish(scale);
+ }
+
+ // eslint-disable-next-line es/no-math-expm1 -- safe
+ var $expm1 = Math.expm1;
+ var exp$1 = Math.exp;
+
+ // `Math.expm1` method implementation
+ // https://tc39.es/ecma262/#sec-math.expm1
+ var mathExpm1 = (!$expm1
+ // Old FF bug
+ || $expm1(10) > 22025.465794806719 || $expm1(10) < 22025.4657948067165168
+ // Tor Browser bug
+ || $expm1(-2e-17) != -2e-17
+ ) ? function expm1(x) {
+ return (x = +x) == 0 ? x : x > -1e-6 && x < 1e-6 ? x + x * x / 2 : exp$1(x) - 1;
+ } : $expm1;
+
+ function quantize() {
+ var x0 = 0,
+ x1 = 1,
+ n = 1,
+ domain = [0.5],
+ range = [0, 1],
+ unknown;
+
+ function scale(x) {
+ return x != null && x <= x ? range[bisectRight(domain, x, 0, n)] : unknown;
+ }
+
+ function rescale() {
+ var i = -1;
+ domain = new Array(n);
+
+ while (++i < n) {
+ domain[i] = ((i + 1) * x1 - (i - n) * x0) / (n + 1);
+ }
+
+ return scale;
+ }
+
+ scale.domain = function (_) {
+ var _ref, _ref2;
+
+ return arguments.length ? ((_ref = _, _ref2 = _slicedToArray(_ref, 2), x0 = _ref2[0], x1 = _ref2[1], _ref), x0 = +x0, x1 = +x1, rescale()) : [x0, x1];
+ };
+
+ scale.range = function (_) {
+ return arguments.length ? (n = (range = Array.from(_)).length - 1, rescale()) : range.slice();
+ };
+
+ scale.invertExtent = function (y) {
+ var i = range.indexOf(y);
+ return i < 0 ? [NaN, NaN] : i < 1 ? [x0, domain[0]] : i >= n ? [domain[n - 1], x1] : [domain[i - 1], domain[i]];
+ };
+
+ scale.unknown = function (_) {
+ return arguments.length ? (unknown = _, scale) : scale;
+ };
+
+ scale.thresholds = function () {
+ return domain.slice();
+ };
+
+ scale.copy = function () {
+ return quantize().domain([x0, x1]).range(range).unknown(unknown);
+ };
+
+ return initRange.apply(linearish(scale), arguments);
+ }
+
+ var global$3 = global$1o;
+ var uncurryThis$2 = functionUncurryThis;
+ var fails$2 = fails$V;
+ var padStart = stringPad.start;
+
+ var RangeError$2 = global$3.RangeError;
+ var abs$1 = Math.abs;
+ var DatePrototype = Date.prototype;
+ var n$DateToISOString = DatePrototype.toISOString;
+ var getTime = uncurryThis$2(DatePrototype.getTime);
+ var getUTCDate = uncurryThis$2(DatePrototype.getUTCDate);
+ var getUTCFullYear = uncurryThis$2(DatePrototype.getUTCFullYear);
+ var getUTCHours = uncurryThis$2(DatePrototype.getUTCHours);
+ var getUTCMilliseconds = uncurryThis$2(DatePrototype.getUTCMilliseconds);
+ var getUTCMinutes = uncurryThis$2(DatePrototype.getUTCMinutes);
+ var getUTCMonth = uncurryThis$2(DatePrototype.getUTCMonth);
+ var getUTCSeconds = uncurryThis$2(DatePrototype.getUTCSeconds);
+
+ // `Date.prototype.toISOString` method implementation
+ // https://tc39.es/ecma262/#sec-date.prototype.toisostring
+ // PhantomJS / old WebKit fails here:
+ var dateToIsoString = (fails$2(function () {
+ return n$DateToISOString.call(new Date(-5e13 - 1)) != '0385-07-25T07:06:39.999Z';
+ }) || !fails$2(function () {
+ n$DateToISOString.call(new Date(NaN));
+ })) ? function toISOString() {
+ if (!isFinite(getTime(this))) throw RangeError$2('Invalid time value');
+ var date = this;
+ var year = getUTCFullYear(date);
+ var milliseconds = getUTCMilliseconds(date);
+ var sign = year < 0 ? '-' : year > 9999 ? '+' : '';
+ return sign + padStart(abs$1(year), sign ? 6 : 4, 0) +
+ '-' + padStart(getUTCMonth(date) + 1, 2, 0) +
+ '-' + padStart(getUTCDate(date), 2, 0) +
+ 'T' + padStart(getUTCHours(date), 2, 0) +
+ ':' + padStart(getUTCMinutes(date), 2, 0) +
+ ':' + padStart(getUTCSeconds(date), 2, 0) +
+ '.' + padStart(milliseconds, 3, 0) +
+ 'Z';
+ } : n$DateToISOString;
+
+ var $$4 = _export;
+ var toISOString = dateToIsoString;
+
+ // `Date.prototype.toISOString` method
+ // https://tc39.es/ecma262/#sec-date.prototype.toisostring
+ // PhantomJS / old WebKit has a broken implementations
+ $$4({ target: 'Date', proto: true, forced: Date.prototype.toISOString !== toISOString }, {
+ toISOString: toISOString
+ });
+
+ function behaviorBreathe() {
+ var duration = 800;
+ var steps = 4;
+ var selector = '.selected.shadow, .selected .shadow';
+
+ var _selected = select(null);
+
+ var _classed = '';
+ var _params = {};
+ var _done = false;
+
+ var _timer;
+
+ function ratchetyInterpolator(a, b, steps, units) {
+ a = parseFloat(a);
+ b = parseFloat(b);
+ var sample = quantize().domain([0, 1]).range(d3_quantize(d3_interpolateNumber(a, b), steps));
+ return function (t) {
+ return String(sample(t)) + (units || '');
+ };
+ }
+
+ function reset(selection) {
+ selection.style('stroke-opacity', null).style('stroke-width', null).style('fill-opacity', null).style('r', null);
+ }
+
+ function setAnimationParams(transition, fromTo) {
+ var toFrom = fromTo === 'from' ? 'to' : 'from';
+ transition.styleTween('stroke-opacity', function (d) {
+ return ratchetyInterpolator(_params[d.id][toFrom].opacity, _params[d.id][fromTo].opacity, steps);
+ }).styleTween('stroke-width', function (d) {
+ return ratchetyInterpolator(_params[d.id][toFrom].width, _params[d.id][fromTo].width, steps, 'px');
+ }).styleTween('fill-opacity', function (d) {
+ return ratchetyInterpolator(_params[d.id][toFrom].opacity, _params[d.id][fromTo].opacity, steps);
+ }).styleTween('r', function (d) {
+ return ratchetyInterpolator(_params[d.id][toFrom].width, _params[d.id][fromTo].width, steps, 'px');
+ });
+ }
+
+ function calcAnimationParams(selection) {
+ selection.call(reset).each(function (d) {
+ var s = select(this);
+ var tag = s.node().tagName;
+ var p = {
+ 'from': {},
+ 'to': {}
+ };
+ var opacity;
+ var width; // determine base opacity and width
+
+ if (tag === 'circle') {
+ opacity = parseFloat(s.style('fill-opacity') || 0.5);
+ width = parseFloat(s.style('r') || 15.5);
+ } else {
+ opacity = parseFloat(s.style('stroke-opacity') || 0.7);
+ width = parseFloat(s.style('stroke-width') || 10);
+ } // calculate from/to interpolation params..
+
+
+ p.tag = tag;
+ p.from.opacity = opacity * 0.6;
+ p.to.opacity = opacity * 1.25;
+ p.from.width = width * 0.7;
+ p.to.width = width * (tag === 'circle' ? 1.5 : 1);
+ _params[d.id] = p;
+ });
+ }
+
+ function run(surface, fromTo) {
+ var toFrom = fromTo === 'from' ? 'to' : 'from';
+ var currSelected = surface.selectAll(selector);
+ var currClassed = surface.attr('class');
+
+ if (_done || currSelected.empty()) {
+ _selected.call(reset);
+
+ _selected = select(null);
+ return;
+ }
+
+ if (!fastDeepEqual(currSelected.data(), _selected.data()) || currClassed !== _classed) {
+ _selected.call(reset);
+
+ _classed = currClassed;
+ _selected = currSelected.call(calcAnimationParams);
+ }
+
+ var didCallNextRun = false;
+
+ _selected.transition().duration(duration).call(setAnimationParams, fromTo).on('end', function () {
+ // `end` event is called for each selected element, but we want
+ // it to run only once
+ if (!didCallNextRun) {
+ surface.call(run, toFrom);
+ didCallNextRun = true;
+ } // if entity was deselected, remove breathe styling
+
+
+ if (!select(this).classed('selected')) {
+ reset(select(this));
+ }
+ });
+ }
+
+ function behavior(surface) {
+ _done = false;
+ _timer = timer(function () {
+ // wait for elements to actually become selected
+ if (surface.selectAll(selector).empty()) {
+ return false;
+ }
+
+ surface.call(run, 'from');
+
+ _timer.stop();
+
+ return true;
+ }, 20);
+ }
+
+ behavior.restartIfNeeded = function (surface) {
+ if (_selected.empty()) {
+ surface.call(run, 'from');
+
+ if (_timer) {
+ _timer.stop();
+ }
+ }
+ };
+
+ behavior.off = function () {
+ _done = true;
+
+ if (_timer) {
+ _timer.stop();
+ }
+
+ _selected.interrupt().call(reset);
+ };
+
+ return behavior;
+ }
+
+ /* Creates a keybinding behavior for an operation */
+ function behaviorOperation(context) {
+ var _operation;
+
+ function keypress(d3_event) {
+ // prevent operations during low zoom selection
+ if (!context.map().withinEditableZoom()) return;
+ if (_operation.availableForKeypress && !_operation.availableForKeypress()) return;
+ d3_event.preventDefault();
+
+ var disabled = _operation.disabled();
+
+ if (disabled) {
+ context.ui().flash.duration(4000).iconName('#iD-operation-' + _operation.id).iconClass('operation disabled').label(_operation.tooltip)();
+ } else {
+ context.ui().flash.duration(2000).iconName('#iD-operation-' + _operation.id).iconClass('operation').label(_operation.annotation() || _operation.title)();
+ if (_operation.point) _operation.point(null);
+
+ _operation();
+ }
+ }
+
+ function behavior() {
+ if (_operation && _operation.available()) {
+ context.keybinding().on(_operation.keys, keypress);
+ }
+
+ return behavior;
+ }
+
+ behavior.off = function () {
+ context.keybinding().off(_operation.keys);
+ };
+
+ behavior.which = function (_) {
+ if (!arguments.length) return _operation;
+ _operation = _;
+ return behavior;
+ };
+
+ return behavior;
+ }
+
+ function operationCircularize(context, selectedIDs) {
+ var _extent;
+
+ var _actions = selectedIDs.map(getAction).filter(Boolean);
+
+ var _amount = _actions.length === 1 ? 'single' : 'multiple';
+
+ var _coords = utilGetAllNodes(selectedIDs, context.graph()).map(function (n) {
+ return n.loc;
+ });
+
+ function getAction(entityID) {
+ var entity = context.entity(entityID);
+ if (entity.type !== 'way' || new Set(entity.nodes).size <= 1) return null;
+
+ if (!_extent) {
+ _extent = entity.extent(context.graph());
+ } else {
+ _extent = _extent.extend(entity.extent(context.graph()));
+ }
+
+ return actionCircularize(entityID, context.projection);
+ }
+
+ var operation = function operation() {
+ if (!_actions.length) return;
+
+ var combinedAction = function combinedAction(graph, t) {
+ _actions.forEach(function (action) {
+ if (!action.disabled(graph)) {
+ graph = action(graph, t);
+ }
+ });
+
+ return graph;
+ };
+
+ combinedAction.transitionable = true;
+ context.perform(combinedAction, operation.annotation());
+ window.setTimeout(function () {
+ context.validator().validate();
+ }, 300); // after any transition
+ };
+
+ operation.available = function () {
+ return _actions.length && selectedIDs.length === _actions.length;
+ }; // don't cache this because the visible extent could change
+
+
+ operation.disabled = function () {
+ if (!_actions.length) return '';
+
+ var actionDisableds = _actions.map(function (action) {
+ return action.disabled(context.graph());
+ }).filter(Boolean);
+
+ if (actionDisableds.length === _actions.length) {
+ // none of the features can be circularized
+ if (new Set(actionDisableds).size > 1) {
+ return 'multiple_blockers';
+ }
+
+ return actionDisableds[0];
+ } else if (_extent.percentContainedIn(context.map().extent()) < 0.8) {
+ return 'too_large';
+ } else if (someMissing()) {
+ return 'not_downloaded';
+ } else if (selectedIDs.some(context.hasHiddenConnections)) {
+ return 'connected_to_hidden';
+ }
+
+ return false;
+
+ function someMissing() {
+ if (context.inIntro()) return false;
+ var osm = context.connection();
+
+ if (osm) {
+ var missing = _coords.filter(function (loc) {
+ return !osm.isDataLoaded(loc);
+ });
+
+ if (missing.length) {
+ missing.forEach(function (loc) {
+ context.loadTileAtLoc(loc);
+ });
+ return true;
+ }
+ }
+
+ return false;
+ }
+ };
+
+ operation.tooltip = function () {
+ var disable = operation.disabled();
+ return disable ? _t('operations.circularize.' + disable + '.' + _amount) : _t('operations.circularize.description.' + _amount);
+ };
+
+ operation.annotation = function () {
+ return _t('operations.circularize.annotation.feature', {
+ n: _actions.length
+ });
+ };
+
+ operation.id = 'circularize';
+ operation.keys = [_t('operations.circularize.key')];
+ operation.title = _t('operations.circularize.title');
+ operation.behavior = behaviorOperation(context).which(operation);
+ return operation;
+ }
+
+ // For example, ⌘Z -> Ctrl+Z
+
+ var uiCmd = function uiCmd(code) {
+ var detected = utilDetect();
+
+ if (detected.os === 'mac') {
+ return code;
+ }
+
+ if (detected.os === 'win') {
+ if (code === '⌘⇧Z') return 'Ctrl+Y';
+ }
+
+ var result = '',
+ replacements = {
+ '⌘': 'Ctrl',
+ '⇧': 'Shift',
+ '⌥': 'Alt',
+ '⌫': 'Backspace',
+ '⌦': 'Delete'
+ };
+
+ for (var i = 0; i < code.length; i++) {
+ if (code[i] in replacements) {
+ result += replacements[code[i]] + (i < code.length - 1 ? '+' : '');
+ } else {
+ result += code[i];
+ }
+ }
+
+ return result;
+ }; // return a display-focused string for a given keyboard code
+
+ uiCmd.display = function (code) {
+ if (code.length !== 1) return code;
+ var detected = utilDetect();
+ var mac = detected.os === 'mac';
+ var replacements = {
+ '⌘': mac ? '⌘ ' + _t('shortcuts.key.cmd') : _t('shortcuts.key.ctrl'),
+ '⇧': mac ? '⇧ ' + _t('shortcuts.key.shift') : _t('shortcuts.key.shift'),
+ '⌥': mac ? '⌥ ' + _t('shortcuts.key.option') : _t('shortcuts.key.alt'),
+ '⌃': mac ? '⌃ ' + _t('shortcuts.key.ctrl') : _t('shortcuts.key.ctrl'),
+ '⌫': mac ? '⌫ ' + _t('shortcuts.key.delete') : _t('shortcuts.key.backspace'),
+ '⌦': mac ? '⌦ ' + _t('shortcuts.key.del') : _t('shortcuts.key.del'),
+ '↖': mac ? '↖ ' + _t('shortcuts.key.pgup') : _t('shortcuts.key.pgup'),
+ '↘': mac ? '↘ ' + _t('shortcuts.key.pgdn') : _t('shortcuts.key.pgdn'),
+ '⇞': mac ? '⇞ ' + _t('shortcuts.key.home') : _t('shortcuts.key.home'),
+ '⇟': mac ? '⇟ ' + _t('shortcuts.key.end') : _t('shortcuts.key.end'),
+ '↵': mac ? '⏎ ' + _t('shortcuts.key.return') : _t('shortcuts.key.enter'),
+ '⎋': mac ? '⎋ ' + _t('shortcuts.key.esc') : _t('shortcuts.key.esc'),
+ '☰': mac ? '☰ ' + _t('shortcuts.key.menu') : _t('shortcuts.key.menu')
+ };
+ return replacements[code] || code;
+ };
+
+ function operationDelete(context, selectedIDs) {
+ var multi = selectedIDs.length === 1 ? 'single' : 'multiple';
+ var action = actionDeleteMultiple(selectedIDs);
+ var nodes = utilGetAllNodes(selectedIDs, context.graph());
+ var coords = nodes.map(function (n) {
+ return n.loc;
+ });
+ var extent = utilTotalExtent(selectedIDs, context.graph());
+
+ var operation = function operation() {
+ var nextSelectedID;
+ var nextSelectedLoc;
+
+ if (selectedIDs.length === 1) {
+ var id = selectedIDs[0];
+ var entity = context.entity(id);
+ var geometry = entity.geometry(context.graph());
+ var parents = context.graph().parentWays(entity);
+ var parent = parents[0]; // Select the next closest node in the way.
+
+ if (geometry === 'vertex') {
+ var nodes = parent.nodes;
+ var i = nodes.indexOf(id);
+
+ if (i === 0) {
+ i++;
+ } else if (i === nodes.length - 1) {
+ i--;
+ } else {
+ var a = geoSphericalDistance(entity.loc, context.entity(nodes[i - 1]).loc);
+ var b = geoSphericalDistance(entity.loc, context.entity(nodes[i + 1]).loc);
+ i = a < b ? i - 1 : i + 1;
+ }
+
+ nextSelectedID = nodes[i];
+ nextSelectedLoc = context.entity(nextSelectedID).loc;
+ }
+ }
+
+ context.perform(action, operation.annotation());
+ context.validator().validate();
+
+ if (nextSelectedID && nextSelectedLoc) {
+ if (context.hasEntity(nextSelectedID)) {
+ context.enter(modeSelect(context, [nextSelectedID]).follow(true));
+ } else {
+ context.map().centerEase(nextSelectedLoc);
+ context.enter(modeBrowse(context));
+ }
+ } else {
+ context.enter(modeBrowse(context));
+ }
+ };
+
+ operation.available = function () {
+ return true;
+ };
+
+ operation.disabled = function () {
+ if (extent.percentContainedIn(context.map().extent()) < 0.8) {
+ return 'too_large';
+ } else if (someMissing()) {
+ return 'not_downloaded';
+ } else if (selectedIDs.some(context.hasHiddenConnections)) {
+ return 'connected_to_hidden';
+ } else if (selectedIDs.some(protectedMember)) {
+ return 'part_of_relation';
+ } else if (selectedIDs.some(incompleteRelation)) {
+ return 'incomplete_relation';
+ } else if (selectedIDs.some(hasWikidataTag)) {
+ return 'has_wikidata_tag';
+ }
+
+ return false;
+
+ function someMissing() {
+ if (context.inIntro()) return false;
+ var osm = context.connection();
+
+ if (osm) {
+ var missing = coords.filter(function (loc) {
+ return !osm.isDataLoaded(loc);
+ });
+
+ if (missing.length) {
+ missing.forEach(function (loc) {
+ context.loadTileAtLoc(loc);
+ });
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function hasWikidataTag(id) {
+ var entity = context.entity(id);
+ return entity.tags.wikidata && entity.tags.wikidata.trim().length > 0;
+ }
+
+ function incompleteRelation(id) {
+ var entity = context.entity(id);
+ return entity.type === 'relation' && !entity.isComplete(context.graph());
+ }
+
+ function protectedMember(id) {
+ var entity = context.entity(id);
+ if (entity.type !== 'way') return false;
+ var parents = context.graph().parentRelations(entity);
+
+ for (var i = 0; i < parents.length; i++) {
+ var parent = parents[i];
+ var type = parent.tags.type;
+ var role = parent.memberById(id).role || 'outer';
+
+ if (type === 'route' || type === 'boundary' || type === 'multipolygon' && role === 'outer') {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ };
+
+ operation.tooltip = function () {
+ var disable = operation.disabled();
+ return disable ? _t('operations.delete.' + disable + '.' + multi) : _t('operations.delete.description.' + multi);
+ };
+
+ operation.annotation = function () {
+ return selectedIDs.length === 1 ? _t('operations.delete.annotation.' + context.graph().geometry(selectedIDs[0])) : _t('operations.delete.annotation.feature', {
+ n: selectedIDs.length
+ });
+ };
+
+ operation.id = 'delete';
+ operation.keys = [uiCmd('⌘⌫'), uiCmd('⌘⌦'), uiCmd('⌦')];
+ operation.title = _t('operations.delete.title');
+ operation.behavior = behaviorOperation(context).which(operation);
+ return operation;
+ }
+
+ function operationOrthogonalize(context, selectedIDs) {
+ var _extent;
+
+ var _type;
+
+ var _actions = selectedIDs.map(chooseAction).filter(Boolean);
+
+ var _amount = _actions.length === 1 ? 'single' : 'multiple';
+
+ var _coords = utilGetAllNodes(selectedIDs, context.graph()).map(function (n) {
+ return n.loc;
+ });
+
+ function chooseAction(entityID) {
+ var entity = context.entity(entityID);
+ var geometry = entity.geometry(context.graph());
+
+ if (!_extent) {
+ _extent = entity.extent(context.graph());
+ } else {
+ _extent = _extent.extend(entity.extent(context.graph()));
+ } // square a line/area
+
+
+ if (entity.type === 'way' && new Set(entity.nodes).size > 2) {
+ if (_type && _type !== 'feature') return null;
+ _type = 'feature';
+ return actionOrthogonalize(entityID, context.projection); // square a single vertex
+ } else if (geometry === 'vertex') {
+ if (_type && _type !== 'corner') return null;
+ _type = 'corner';
+ var graph = context.graph();
+ var parents = graph.parentWays(entity);
+
+ if (parents.length === 1) {
+ var way = parents[0];
+
+ if (way.nodes.indexOf(entityID) !== -1) {
+ return actionOrthogonalize(way.id, context.projection, entityID);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ var operation = function operation() {
+ if (!_actions.length) return;
+
+ var combinedAction = function combinedAction(graph, t) {
+ _actions.forEach(function (action) {
+ if (!action.disabled(graph)) {
+ graph = action(graph, t);
+ }
+ });
+
+ return graph;
+ };
+
+ combinedAction.transitionable = true;
+ context.perform(combinedAction, operation.annotation());
+ window.setTimeout(function () {
+ context.validator().validate();
+ }, 300); // after any transition
+ };
+
+ operation.available = function () {
+ return _actions.length && selectedIDs.length === _actions.length;
+ }; // don't cache this because the visible extent could change
+
+
+ operation.disabled = function () {
+ if (!_actions.length) return '';
+
+ var actionDisableds = _actions.map(function (action) {
+ return action.disabled(context.graph());
+ }).filter(Boolean);
+
+ if (actionDisableds.length === _actions.length) {
+ // none of the features can be squared
+ if (new Set(actionDisableds).size > 1) {
+ return 'multiple_blockers';
+ }
+
+ return actionDisableds[0];
+ } else if (_extent && _extent.percentContainedIn(context.map().extent()) < 0.8) {
+ return 'too_large';
+ } else if (someMissing()) {
+ return 'not_downloaded';
+ } else if (selectedIDs.some(context.hasHiddenConnections)) {
+ return 'connected_to_hidden';
+ }
+
+ return false;
+
+ function someMissing() {
+ if (context.inIntro()) return false;
+ var osm = context.connection();
+
+ if (osm) {
+ var missing = _coords.filter(function (loc) {
+ return !osm.isDataLoaded(loc);
+ });
+
+ if (missing.length) {
+ missing.forEach(function (loc) {
+ context.loadTileAtLoc(loc);
+ });
+ return true;
+ }
+ }
+
+ return false;
+ }
+ };
+
+ operation.tooltip = function () {
+ var disable = operation.disabled();
+ return disable ? _t('operations.orthogonalize.' + disable + '.' + _amount) : _t('operations.orthogonalize.description.' + _type + '.' + _amount);
+ };
+
+ operation.annotation = function () {
+ return _t('operations.orthogonalize.annotation.' + _type, {
+ n: _actions.length
+ });
+ };
+
+ operation.id = 'orthogonalize';
+ operation.keys = [_t('operations.orthogonalize.key')];
+ operation.title = _t('operations.orthogonalize.title');
+ operation.behavior = behaviorOperation(context).which(operation);
+ return operation;
+ }
+
+ function operationReflectShort(context, selectedIDs) {
+ return operationReflect(context, selectedIDs, 'short');
+ }
+ function operationReflectLong(context, selectedIDs) {
+ return operationReflect(context, selectedIDs, 'long');
+ }
+ function operationReflect(context, selectedIDs, axis) {
+ axis = axis || 'long';
+ var multi = selectedIDs.length === 1 ? 'single' : 'multiple';
+ var nodes = utilGetAllNodes(selectedIDs, context.graph());
+ var coords = nodes.map(function (n) {
+ return n.loc;
+ });
+ var extent = utilTotalExtent(selectedIDs, context.graph());
+
+ var operation = function operation() {
+ var action = actionReflect(selectedIDs, context.projection).useLongAxis(Boolean(axis === 'long'));
+ context.perform(action, operation.annotation());
+ window.setTimeout(function () {
+ context.validator().validate();
+ }, 300); // after any transition
+ };
+
+ operation.available = function () {
+ return nodes.length >= 3;
+ }; // don't cache this because the visible extent could change
+
+
+ operation.disabled = function () {
+ if (extent.percentContainedIn(context.map().extent()) < 0.8) {
+ return 'too_large';
+ } else if (someMissing()) {
+ return 'not_downloaded';
+ } else if (selectedIDs.some(context.hasHiddenConnections)) {
+ return 'connected_to_hidden';
+ } else if (selectedIDs.some(incompleteRelation)) {
+ return 'incomplete_relation';
+ }
+
+ return false;
+
+ function someMissing() {
+ if (context.inIntro()) return false;
+ var osm = context.connection();
+
+ if (osm) {
+ var missing = coords.filter(function (loc) {
+ return !osm.isDataLoaded(loc);
+ });
+
+ if (missing.length) {
+ missing.forEach(function (loc) {
+ context.loadTileAtLoc(loc);
+ });
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function incompleteRelation(id) {
+ var entity = context.entity(id);
+ return entity.type === 'relation' && !entity.isComplete(context.graph());
+ }
+ };
+
+ operation.tooltip = function () {
+ var disable = operation.disabled();
+ return disable ? _t('operations.reflect.' + disable + '.' + multi) : _t('operations.reflect.description.' + axis + '.' + multi);
+ };
+
+ operation.annotation = function () {
+ return _t('operations.reflect.annotation.' + axis + '.feature', {
+ n: selectedIDs.length
+ });
+ };
+
+ operation.id = 'reflect-' + axis;
+ operation.keys = [_t('operations.reflect.key.' + axis)];
+ operation.title = _t('operations.reflect.title.' + axis);
+ operation.behavior = behaviorOperation(context).which(operation);
+ return operation;
+ }
+
+ function operationMove(context, selectedIDs) {
+ var multi = selectedIDs.length === 1 ? 'single' : 'multiple';
+ var nodes = utilGetAllNodes(selectedIDs, context.graph());
+ var coords = nodes.map(function (n) {
+ return n.loc;
+ });
+ var extent = utilTotalExtent(selectedIDs, context.graph());
+
+ var operation = function operation() {
+ context.enter(modeMove(context, selectedIDs));
+ };
+
+ operation.available = function () {
+ return selectedIDs.length > 0;
+ };
+
+ operation.disabled = function () {
+ if (extent.percentContainedIn(context.map().extent()) < 0.8) {
+ return 'too_large';
+ } else if (someMissing()) {
+ return 'not_downloaded';
+ } else if (selectedIDs.some(context.hasHiddenConnections)) {
+ return 'connected_to_hidden';
+ } else if (selectedIDs.some(incompleteRelation)) {
+ return 'incomplete_relation';
+ }
+
+ return false;
+
+ function someMissing() {
+ if (context.inIntro()) return false;
+ var osm = context.connection();
+
+ if (osm) {
+ var missing = coords.filter(function (loc) {
+ return !osm.isDataLoaded(loc);
+ });
+
+ if (missing.length) {
+ missing.forEach(function (loc) {
+ context.loadTileAtLoc(loc);
+ });
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function incompleteRelation(id) {
+ var entity = context.entity(id);
+ return entity.type === 'relation' && !entity.isComplete(context.graph());
+ }
+ };
+
+ operation.tooltip = function () {
+ var disable = operation.disabled();
+ return disable ? _t('operations.move.' + disable + '.' + multi) : _t('operations.move.description.' + multi);
+ };
+
+ operation.annotation = function () {
+ return selectedIDs.length === 1 ? _t('operations.move.annotation.' + context.graph().geometry(selectedIDs[0])) : _t('operations.move.annotation.feature', {
+ n: selectedIDs.length
+ });
+ };
+
+ operation.id = 'move';
+ operation.keys = [_t('operations.move.key')];
+ operation.title = _t('operations.move.title');
+ operation.behavior = behaviorOperation(context).which(operation);
+ operation.mouseOnly = true;
+ return operation;
+ }
+
+ function modeRotate(context, entityIDs) {
+ var _tolerancePx = 4; // see also behaviorDrag, behaviorSelect, modeMove
+
+ var mode = {
+ id: 'rotate',
+ button: 'browse'
+ };
+ var keybinding = utilKeybinding('rotate');
+ var behaviors = [behaviorEdit(context), operationCircularize(context, entityIDs).behavior, operationDelete(context, entityIDs).behavior, operationMove(context, entityIDs).behavior, operationOrthogonalize(context, entityIDs).behavior, operationReflectLong(context, entityIDs).behavior, operationReflectShort(context, entityIDs).behavior];
+ var annotation = entityIDs.length === 1 ? _t('operations.rotate.annotation.' + context.graph().geometry(entityIDs[0])) : _t('operations.rotate.annotation.feature', {
+ n: entityIDs.length
+ });
+
+ var _prevGraph;
+
+ var _prevAngle;
+
+ var _prevTransform;
+
+ var _pivot; // use pointer events on supported platforms; fallback to mouse events
+
+
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+
+ function doRotate(d3_event) {
+ var fn;
+
+ if (context.graph() !== _prevGraph) {
+ fn = context.perform;
+ } else {
+ fn = context.replace;
+ } // projection changed, recalculate _pivot
+
+
+ var projection = context.projection;
+ var currTransform = projection.transform();
+
+ if (!_prevTransform || currTransform.k !== _prevTransform.k || currTransform.x !== _prevTransform.x || currTransform.y !== _prevTransform.y) {
+ var nodes = utilGetAllNodes(entityIDs, context.graph());
+ var points = nodes.map(function (n) {
+ return projection(n.loc);
+ });
+ _pivot = getPivot(points);
+ _prevAngle = undefined;
+ }
+
+ var currMouse = context.map().mouse(d3_event);
+ var currAngle = Math.atan2(currMouse[1] - _pivot[1], currMouse[0] - _pivot[0]);
+ if (typeof _prevAngle === 'undefined') _prevAngle = currAngle;
+ var delta = currAngle - _prevAngle;
+ fn(actionRotate(entityIDs, _pivot, delta, projection));
+ _prevTransform = currTransform;
+ _prevAngle = currAngle;
+ _prevGraph = context.graph();
+ }
+
+ function getPivot(points) {
+ var _pivot;
+
+ if (points.length === 1) {
+ _pivot = points[0];
+ } else if (points.length === 2) {
+ _pivot = geoVecInterp(points[0], points[1], 0.5);
+ } else {
+ var polygonHull = d3_polygonHull(points);
+
+ if (polygonHull.length === 2) {
+ _pivot = geoVecInterp(points[0], points[1], 0.5);
+ } else {
+ _pivot = d3_polygonCentroid(d3_polygonHull(points));
+ }
+ }
+
+ return _pivot;
+ }
+
+ function finish(d3_event) {
+ d3_event.stopPropagation();
+ context.replace(actionNoop(), annotation);
+ context.enter(modeSelect(context, entityIDs));
+ }
+
+ function cancel() {
+ if (_prevGraph) context.pop(); // remove the rotate
+
+ context.enter(modeSelect(context, entityIDs));
+ }
+
+ function undone() {
+ context.enter(modeBrowse(context));
+ }
+
+ mode.enter = function () {
+ _prevGraph = null;
+ context.features().forceVisible(entityIDs);
+ behaviors.forEach(context.install);
+ var downEvent;
+ context.surface().on(_pointerPrefix + 'down.modeRotate', function (d3_event) {
+ downEvent = d3_event;
+ });
+ select(window).on(_pointerPrefix + 'move.modeRotate', doRotate, true).on(_pointerPrefix + 'up.modeRotate', function (d3_event) {
+ if (!downEvent) return;
+ var mapNode = context.container().select('.main-map').node();
+ var pointGetter = utilFastMouse(mapNode);
+ var p1 = pointGetter(downEvent);
+ var p2 = pointGetter(d3_event);
+ var dist = geoVecLength(p1, p2);
+ if (dist <= _tolerancePx) finish(d3_event);
+ downEvent = null;
+ }, true);
+ context.history().on('undone.modeRotate', undone);
+ keybinding.on('⎋', cancel).on('↩', finish);
+ select(document).call(keybinding);
+ };
+
+ mode.exit = function () {
+ behaviors.forEach(context.uninstall);
+ context.surface().on(_pointerPrefix + 'down.modeRotate', null);
+ select(window).on(_pointerPrefix + 'move.modeRotate', null, true).on(_pointerPrefix + 'up.modeRotate', null, true);
+ context.history().on('undone.modeRotate', null);
+ select(document).call(keybinding.unbind);
+ context.features().forceVisible([]);
+ };
+
+ mode.selectedIDs = function () {
+ if (!arguments.length) return entityIDs; // no assign
+
+ return mode;
+ };
+
+ return mode;
+ }
+
+ function operationRotate(context, selectedIDs) {
+ var multi = selectedIDs.length === 1 ? 'single' : 'multiple';
+ var nodes = utilGetAllNodes(selectedIDs, context.graph());
+ var coords = nodes.map(function (n) {
+ return n.loc;
+ });
+ var extent = utilTotalExtent(selectedIDs, context.graph());
+
+ var operation = function operation() {
+ context.enter(modeRotate(context, selectedIDs));
+ };
+
+ operation.available = function () {
+ return nodes.length >= 2;
+ };
+
+ operation.disabled = function () {
+ if (extent.percentContainedIn(context.map().extent()) < 0.8) {
+ return 'too_large';
+ } else if (someMissing()) {
+ return 'not_downloaded';
+ } else if (selectedIDs.some(context.hasHiddenConnections)) {
+ return 'connected_to_hidden';
+ } else if (selectedIDs.some(incompleteRelation)) {
+ return 'incomplete_relation';
+ }
+
+ return false;
+
+ function someMissing() {
+ if (context.inIntro()) return false;
+ var osm = context.connection();
+
+ if (osm) {
+ var missing = coords.filter(function (loc) {
+ return !osm.isDataLoaded(loc);
+ });
+
+ if (missing.length) {
+ missing.forEach(function (loc) {
+ context.loadTileAtLoc(loc);
+ });
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function incompleteRelation(id) {
+ var entity = context.entity(id);
+ return entity.type === 'relation' && !entity.isComplete(context.graph());
+ }
+ };
+
+ operation.tooltip = function () {
+ var disable = operation.disabled();
+ return disable ? _t('operations.rotate.' + disable + '.' + multi) : _t('operations.rotate.description.' + multi);
+ };
+
+ operation.annotation = function () {
+ return selectedIDs.length === 1 ? _t('operations.rotate.annotation.' + context.graph().geometry(selectedIDs[0])) : _t('operations.rotate.annotation.feature', {
+ n: selectedIDs.length
+ });
+ };
+
+ operation.id = 'rotate';
+ operation.keys = [_t('operations.rotate.key')];
+ operation.title = _t('operations.rotate.title');
+ operation.behavior = behaviorOperation(context).which(operation);
+ operation.mouseOnly = true;
+ return operation;
+ }
+
+ function modeMove(context, entityIDs, baseGraph) {
+ var _tolerancePx = 4; // see also behaviorDrag, behaviorSelect, modeRotate
+
+ var mode = {
+ id: 'move',
+ button: 'browse'
+ };
+ var keybinding = utilKeybinding('move');
+ var behaviors = [behaviorEdit(context), operationCircularize(context, entityIDs).behavior, operationDelete(context, entityIDs).behavior, operationOrthogonalize(context, entityIDs).behavior, operationReflectLong(context, entityIDs).behavior, operationReflectShort(context, entityIDs).behavior, operationRotate(context, entityIDs).behavior];
+ var annotation = entityIDs.length === 1 ? _t('operations.move.annotation.' + context.graph().geometry(entityIDs[0])) : _t('operations.move.annotation.feature', {
+ n: entityIDs.length
+ });
+
+ var _prevGraph;
+
+ var _cache;
+
+ var _origin;
+
+ var _nudgeInterval; // use pointer events on supported platforms; fallback to mouse events
+
+
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+
+ function doMove(nudge) {
+ nudge = nudge || [0, 0];
+ var fn;
+
+ if (_prevGraph !== context.graph()) {
+ _cache = {};
+ _origin = context.map().mouseCoordinates();
+ fn = context.perform;
+ } else {
+ fn = context.overwrite;
+ }
+
+ var currMouse = context.map().mouse();
+ var origMouse = context.projection(_origin);
+ var delta = geoVecSubtract(geoVecSubtract(currMouse, origMouse), nudge);
+ fn(actionMove(entityIDs, delta, context.projection, _cache));
+ _prevGraph = context.graph();
+ }
+
+ function startNudge(nudge) {
+ if (_nudgeInterval) window.clearInterval(_nudgeInterval);
+ _nudgeInterval = window.setInterval(function () {
+ context.map().pan(nudge);
+ doMove(nudge);
+ }, 50);
+ }
+
+ function stopNudge() {
+ if (_nudgeInterval) {
+ window.clearInterval(_nudgeInterval);
+ _nudgeInterval = null;
+ }
+ }
+
+ function move() {
+ doMove();
+ var nudge = geoViewportEdge(context.map().mouse(), context.map().dimensions());
+
+ if (nudge) {
+ startNudge(nudge);
+ } else {
+ stopNudge();
+ }
+ }
+
+ function finish(d3_event) {
+ d3_event.stopPropagation();
+ context.replace(actionNoop(), annotation);
+ context.enter(modeSelect(context, entityIDs));
+ stopNudge();
+ }
+
+ function cancel() {
+ if (baseGraph) {
+ while (context.graph() !== baseGraph) {
+ context.pop();
+ } // reset to baseGraph
+
+
+ context.enter(modeBrowse(context));
+ } else {
+ if (_prevGraph) context.pop(); // remove the move
+
+ context.enter(modeSelect(context, entityIDs));
+ }
+
+ stopNudge();
+ }
+
+ function undone() {
+ context.enter(modeBrowse(context));
+ }
+
+ mode.enter = function () {
+ _origin = context.map().mouseCoordinates();
+ _prevGraph = null;
+ _cache = {};
+ context.features().forceVisible(entityIDs);
+ behaviors.forEach(context.install);
+ var downEvent;
+ context.surface().on(_pointerPrefix + 'down.modeMove', function (d3_event) {
+ downEvent = d3_event;
+ });
+ select(window).on(_pointerPrefix + 'move.modeMove', move, true).on(_pointerPrefix + 'up.modeMove', function (d3_event) {
+ if (!downEvent) return;
+ var mapNode = context.container().select('.main-map').node();
+ var pointGetter = utilFastMouse(mapNode);
+ var p1 = pointGetter(downEvent);
+ var p2 = pointGetter(d3_event);
+ var dist = geoVecLength(p1, p2);
+ if (dist <= _tolerancePx) finish(d3_event);
+ downEvent = null;
+ }, true);
+ context.history().on('undone.modeMove', undone);
+ keybinding.on('⎋', cancel).on('↩', finish);
+ select(document).call(keybinding);
+ };
+
+ mode.exit = function () {
+ stopNudge();
+ behaviors.forEach(function (behavior) {
+ context.uninstall(behavior);
+ });
+ context.surface().on(_pointerPrefix + 'down.modeMove', null);
+ select(window).on(_pointerPrefix + 'move.modeMove', null, true).on(_pointerPrefix + 'up.modeMove', null, true);
+ context.history().on('undone.modeMove', null);
+ select(document).call(keybinding.unbind);
+ context.features().forceVisible([]);
+ };
+
+ mode.selectedIDs = function () {
+ if (!arguments.length) return entityIDs; // no assign
+
+ return mode;
+ };
+
+ return mode;
+ }
+
+ function behaviorPaste(context) {
+ function doPaste(d3_event) {
+ // prevent paste during low zoom selection
+ if (!context.map().withinEditableZoom()) return;
+ d3_event.preventDefault();
+ var baseGraph = context.graph();
+ var mouse = context.map().mouse();
+ var projection = context.projection;
+ var viewport = geoExtent(projection.clipExtent()).polygon();
+ if (!geoPointInPolygon(mouse, viewport)) return;
+ var oldIDs = context.copyIDs();
+ if (!oldIDs.length) return;
+ var extent = geoExtent();
+ var oldGraph = context.copyGraph();
+ var newIDs = [];
+ var action = actionCopyEntities(oldIDs, oldGraph);
+ context.perform(action);
+ var copies = action.copies();
+ var originals = new Set();
+ Object.values(copies).forEach(function (entity) {
+ originals.add(entity.id);
+ });
+
+ for (var id in copies) {
+ var oldEntity = oldGraph.entity(id);
+ var newEntity = copies[id];
+
+ extent._extend(oldEntity.extent(oldGraph)); // Exclude child nodes from newIDs if their parent way was also copied.
+
+
+ var parents = context.graph().parentWays(newEntity);
+ var parentCopied = parents.some(function (parent) {
+ return originals.has(parent.id);
+ });
+
+ if (!parentCopied) {
+ newIDs.push(newEntity.id);
+ }
+ } // Put pasted objects where mouse pointer is..
+
+
+ var copyPoint = context.copyLonLat() && projection(context.copyLonLat()) || projection(extent.center());
+ var delta = geoVecSubtract(mouse, copyPoint);
+ context.perform(actionMove(newIDs, delta, projection));
+ context.enter(modeMove(context, newIDs, baseGraph));
+ }
+
+ function behavior() {
+ context.keybinding().on(uiCmd('⌘V'), doPaste);
+ return behavior;
+ }
+
+ behavior.off = function () {
+ context.keybinding().off(uiCmd('⌘V'));
+ };
+
+ return behavior;
+ }
+
+ /*
+ `behaviorDrag` is like `d3_behavior.drag`, with the following differences:
+
+ * The `origin` function is expected to return an [x, y] tuple rather than an
+ {x, y} object.
+ * The events are `start`, `move`, and `end`.
+ (https://github.com/mbostock/d3/issues/563)
+ * The `start` event is not dispatched until the first cursor movement occurs.
+ (https://github.com/mbostock/d3/pull/368)
+ * The `move` event has a `point` and `delta` [x, y] tuple properties rather
+ than `x`, `y`, `dx`, and `dy` properties.
+ * The `end` event is not dispatched if no movement occurs.
+ * An `off` function is available that unbinds the drag's internal event handlers.
+ */
+
+ function behaviorDrag() {
+ var dispatch = dispatch$8('start', 'move', 'end'); // see also behaviorSelect
+
+ var _tolerancePx = 1; // keep this low to facilitate pixel-perfect micromapping
+
+ var _penTolerancePx = 4; // styluses can be touchy so require greater movement - #1981
+
+ var _origin = null;
+ var _selector = '';
+
+ var _targetNode;
+
+ var _targetEntity;
+
+ var _surface;
+
+ var _pointerId; // use pointer events on supported platforms; fallback to mouse events
+
+
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+
+ var d3_event_userSelectProperty = utilPrefixCSSProperty('UserSelect');
+
+ var d3_event_userSelectSuppress = function d3_event_userSelectSuppress() {
+ var selection$1 = selection();
+ var select = selection$1.style(d3_event_userSelectProperty);
+ selection$1.style(d3_event_userSelectProperty, 'none');
+ return function () {
+ selection$1.style(d3_event_userSelectProperty, select);
+ };
+ };
+
+ function pointerdown(d3_event) {
+ if (_pointerId) return;
+ _pointerId = d3_event.pointerId || 'mouse';
+ _targetNode = this; // only force reflow once per drag
+
+ var pointerLocGetter = utilFastMouse(_surface || _targetNode.parentNode);
+ var offset;
+ var startOrigin = pointerLocGetter(d3_event);
+ var started = false;
+ var selectEnable = d3_event_userSelectSuppress();
+ select(window).on(_pointerPrefix + 'move.drag', pointermove).on(_pointerPrefix + 'up.drag pointercancel.drag', pointerup, true);
+
+ if (_origin) {
+ offset = _origin.call(_targetNode, _targetEntity);
+ offset = [offset[0] - startOrigin[0], offset[1] - startOrigin[1]];
+ } else {
+ offset = [0, 0];
+ }
+
+ d3_event.stopPropagation();
+
+ function pointermove(d3_event) {
+ if (_pointerId !== (d3_event.pointerId || 'mouse')) return;
+ var p = pointerLocGetter(d3_event);
+
+ if (!started) {
+ var dist = geoVecLength(startOrigin, p);
+ var tolerance = d3_event.pointerType === 'pen' ? _penTolerancePx : _tolerancePx; // don't start until the drag has actually moved somewhat
+
+ if (dist < tolerance) return;
+ started = true;
+ dispatch.call('start', this, d3_event, _targetEntity); // Don't send a `move` event in the same cycle as `start` since dragging
+ // a midpoint will convert the target to a node.
+ } else {
+ startOrigin = p;
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ var dx = p[0] - startOrigin[0];
+ var dy = p[1] - startOrigin[1];
+ dispatch.call('move', this, d3_event, _targetEntity, [p[0] + offset[0], p[1] + offset[1]], [dx, dy]);
+ }
+ }
+
+ function pointerup(d3_event) {
+ if (_pointerId !== (d3_event.pointerId || 'mouse')) return;
+ _pointerId = null;
+
+ if (started) {
+ dispatch.call('end', this, d3_event, _targetEntity);
+ d3_event.preventDefault();
+ }
+
+ select(window).on(_pointerPrefix + 'move.drag', null).on(_pointerPrefix + 'up.drag pointercancel.drag', null);
+ selectEnable();
+ }
+ }
+
+ function behavior(selection) {
+ var matchesSelector = utilPrefixDOMProperty('matchesSelector');
+ var delegate = pointerdown;
+
+ if (_selector) {
+ delegate = function delegate(d3_event) {
+ var root = this;
+ var target = d3_event.target;
+
+ for (; target && target !== root; target = target.parentNode) {
+ var datum = target.__data__;
+ _targetEntity = datum instanceof osmNote ? datum : datum && datum.properties && datum.properties.entity;
+
+ if (_targetEntity && target[matchesSelector](_selector)) {
+ return pointerdown.call(target, d3_event);
+ }
+ }
+ };
+ }
+
+ selection.on(_pointerPrefix + 'down.drag' + _selector, delegate);
+ }
+
+ behavior.off = function (selection) {
+ selection.on(_pointerPrefix + 'down.drag' + _selector, null);
+ };
+
+ behavior.selector = function (_) {
+ if (!arguments.length) return _selector;
+ _selector = _;
+ return behavior;
+ };
+
+ behavior.origin = function (_) {
+ if (!arguments.length) return _origin;
+ _origin = _;
+ return behavior;
+ };
+
+ behavior.cancel = function () {
+ select(window).on(_pointerPrefix + 'move.drag', null).on(_pointerPrefix + 'up.drag pointercancel.drag', null);
+ return behavior;
+ };
+
+ behavior.targetNode = function (_) {
+ if (!arguments.length) return _targetNode;
+ _targetNode = _;
+ return behavior;
+ };
+
+ behavior.targetEntity = function (_) {
+ if (!arguments.length) return _targetEntity;
+ _targetEntity = _;
+ return behavior;
+ };
+
+ behavior.surface = function (_) {
+ if (!arguments.length) return _surface;
+ _surface = _;
+ return behavior;
+ };
+
+ return utilRebind(behavior, dispatch, 'on');
+ }
+
+ function modeDragNode(context) {
+ var mode = {
+ id: 'drag-node',
+ button: 'browse'
+ };
+ var hover = behaviorHover(context).altDisables(true).on('hover', context.ui().sidebar.hover);
+ var edit = behaviorEdit(context);
+
+ var _nudgeInterval;
+
+ var _restoreSelectedIDs = [];
+ var _wasMidpoint = false;
+ var _isCancelled = false;
+
+ var _activeEntity;
+
+ var _startLoc;
+
+ var _lastLoc;
+
+ function startNudge(d3_event, entity, nudge) {
+ if (_nudgeInterval) window.clearInterval(_nudgeInterval);
+ _nudgeInterval = window.setInterval(function () {
+ context.map().pan(nudge);
+ doMove(d3_event, entity, nudge);
+ }, 50);
+ }
+
+ function stopNudge() {
+ if (_nudgeInterval) {
+ window.clearInterval(_nudgeInterval);
+ _nudgeInterval = null;
+ }
+ }
+
+ function moveAnnotation(entity) {
+ return _t('operations.move.annotation.' + entity.geometry(context.graph()));
+ }
+
+ function connectAnnotation(nodeEntity, targetEntity) {
+ var nodeGeometry = nodeEntity.geometry(context.graph());
+ var targetGeometry = targetEntity.geometry(context.graph());
+
+ if (nodeGeometry === 'vertex' && targetGeometry === 'vertex') {
+ var nodeParentWayIDs = context.graph().parentWays(nodeEntity);
+ var targetParentWayIDs = context.graph().parentWays(targetEntity);
+ var sharedParentWays = utilArrayIntersection(nodeParentWayIDs, targetParentWayIDs); // if both vertices are part of the same way
+
+ if (sharedParentWays.length !== 0) {
+ // if the nodes are next to each other, they are merged
+ if (sharedParentWays[0].areAdjacent(nodeEntity.id, targetEntity.id)) {
+ return _t('operations.connect.annotation.from_vertex.to_adjacent_vertex');
+ }
+
+ return _t('operations.connect.annotation.from_vertex.to_sibling_vertex');
+ }
+ }
+
+ return _t('operations.connect.annotation.from_' + nodeGeometry + '.to_' + targetGeometry);
+ }
+
+ function shouldSnapToNode(target) {
+ if (!_activeEntity) return false;
+ return _activeEntity.geometry(context.graph()) !== 'vertex' || target.geometry(context.graph()) === 'vertex' || _mainPresetIndex.allowsVertex(target, context.graph());
+ }
+
+ function origin(entity) {
+ return context.projection(entity.loc);
+ }
+
+ function keydown(d3_event) {
+ if (d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
+ if (context.surface().classed('nope')) {
+ context.surface().classed('nope-suppressed', true);
+ }
+
+ context.surface().classed('nope', false).classed('nope-disabled', true);
+ }
+ }
+
+ function keyup(d3_event) {
+ if (d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
+ if (context.surface().classed('nope-suppressed')) {
+ context.surface().classed('nope', true);
+ }
+
+ context.surface().classed('nope-suppressed', false).classed('nope-disabled', false);
+ }
+ }
+
+ function start(d3_event, entity) {
+ _wasMidpoint = entity.type === 'midpoint';
+ var hasHidden = context.features().hasHiddenConnections(entity, context.graph());
+ _isCancelled = !context.editable() || d3_event.shiftKey || hasHidden;
+
+ if (_isCancelled) {
+ if (hasHidden) {
+ context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t('modes.drag_node.connected_to_hidden'))();
+ }
+
+ return drag.cancel();
+ }
+
+ if (_wasMidpoint) {
+ var midpoint = entity;
+ entity = osmNode();
+ context.perform(actionAddMidpoint(midpoint, entity));
+ entity = context.entity(entity.id); // get post-action entity
+
+ var vertex = context.surface().selectAll('.' + entity.id);
+ drag.targetNode(vertex.node()).targetEntity(entity);
+ } else {
+ context.perform(actionNoop());
+ }
+
+ _activeEntity = entity;
+ _startLoc = entity.loc;
+ hover.ignoreVertex(entity.geometry(context.graph()) === 'vertex');
+ context.surface().selectAll('.' + _activeEntity.id).classed('active', true);
+ context.enter(mode);
+ } // related code
+ // - `behavior/draw.js` `datum()`
+
+
+ function datum(d3_event) {
+ if (!d3_event || d3_event.altKey) {
+ return {};
+ } else {
+ // When dragging, snap only to touch targets..
+ // (this excludes area fills and active drawing elements)
+ var d = d3_event.target.__data__;
+ return d && d.properties && d.properties.target ? d : {};
+ }
+ }
+
+ function doMove(d3_event, entity, nudge) {
+ nudge = nudge || [0, 0];
+ var currPoint = d3_event && d3_event.point || context.projection(_lastLoc);
+ var currMouse = geoVecSubtract(currPoint, nudge);
+ var loc = context.projection.invert(currMouse);
+ var target, edge;
+
+ if (!_nudgeInterval) {
+ // If not nudging at the edge of the viewport, try to snap..
+ // related code
+ // - `mode/drag_node.js` `doMove()`
+ // - `behavior/draw.js` `click()`
+ // - `behavior/draw_way.js` `move()`
+ var d = datum(d3_event);
+ target = d && d.properties && d.properties.entity;
+ var targetLoc = target && target.loc;
+ var targetNodes = d && d.properties && d.properties.nodes;
+
+ if (targetLoc) {
+ // snap to node/vertex - a point target with `.loc`
+ if (shouldSnapToNode(target)) {
+ loc = targetLoc;
+ }
+ } else if (targetNodes) {
+ // snap to way - a line target with `.nodes`
+ edge = geoChooseEdge(targetNodes, context.map().mouse(), context.projection, end.id);
+
+ if (edge) {
+ loc = edge.loc;
+ }
+ }
+ }
+
+ context.replace(actionMoveNode(entity.id, loc)); // Below here: validations
+
+ var isInvalid = false; // Check if this connection to `target` could cause relations to break..
+
+ if (target) {
+ isInvalid = hasRelationConflict(entity, target, edge, context.graph());
+ } // Check if this drag causes the geometry to break..
+
+
+ if (!isInvalid) {
+ isInvalid = hasInvalidGeometry(entity, context.graph());
+ }
+
+ var nope = context.surface().classed('nope');
+
+ if (isInvalid === 'relation' || isInvalid === 'restriction') {
+ if (!nope) {
+ // about to nope - show hint
+ context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t.html('operations.connect.' + isInvalid, {
+ relation: _mainPresetIndex.item('type/restriction').name()
+ }))();
+ }
+ } else if (isInvalid) {
+ var errorID = isInvalid === 'line' ? 'lines' : 'areas';
+ context.ui().flash.duration(3000).iconName('#iD-icon-no').label(_t.html('self_intersection.error.' + errorID))();
+ } else {
+ if (nope) {
+ // about to un-nope, remove hint
+ context.ui().flash.duration(1).label('')();
+ }
+ }
+
+ var nopeDisabled = context.surface().classed('nope-disabled');
+
+ if (nopeDisabled) {
+ context.surface().classed('nope', false).classed('nope-suppressed', isInvalid);
+ } else {
+ context.surface().classed('nope', isInvalid).classed('nope-suppressed', false);
+ }
+
+ _lastLoc = loc;
+ } // Uses `actionConnect.disabled()` to know whether this connection is ok..
+
+
+ function hasRelationConflict(entity, target, edge, graph) {
+ var testGraph = graph.update(); // copy
+ // if snapping to way - add midpoint there and consider that the target..
+
+ if (edge) {
+ var midpoint = osmNode();
+ var action = actionAddMidpoint({
+ loc: edge.loc,
+ edge: [target.nodes[edge.index - 1], target.nodes[edge.index]]
+ }, midpoint);
+ testGraph = action(testGraph);
+ target = midpoint;
+ } // can we connect to it?
+
+
+ var ids = [entity.id, target.id];
+ return actionConnect(ids).disabled(testGraph);
+ }
+
+ function hasInvalidGeometry(entity, graph) {
+ var parents = graph.parentWays(entity);
+ var i, j, k;
+
+ for (i = 0; i < parents.length; i++) {
+ var parent = parents[i];
+ var nodes = [];
+ var activeIndex = null; // which multipolygon ring contains node being dragged
+ // test any parent multipolygons for valid geometry
+
+ var relations = graph.parentRelations(parent);
+
+ for (j = 0; j < relations.length; j++) {
+ if (!relations[j].isMultipolygon()) continue;
+ var rings = osmJoinWays(relations[j].members, graph); // find active ring and test it for self intersections
+
+ for (k = 0; k < rings.length; k++) {
+ nodes = rings[k].nodes;
+
+ if (nodes.find(function (n) {
+ return n.id === entity.id;
+ })) {
+ activeIndex = k;
+
+ if (geoHasSelfIntersections(nodes, entity.id)) {
+ return 'multipolygonMember';
+ }
+ }
+
+ rings[k].coords = nodes.map(function (n) {
+ return n.loc;
+ });
+ } // test active ring for intersections with other rings in the multipolygon
+
+
+ for (k = 0; k < rings.length; k++) {
+ if (k === activeIndex) continue; // make sure active ring doesn't cross passive rings
+
+ if (geoHasLineIntersections(rings[activeIndex].nodes, rings[k].nodes, entity.id)) {
+ return 'multipolygonRing';
+ }
+ }
+ } // If we still haven't tested this node's parent way for self-intersections.
+ // (because it's not a member of a multipolygon), test it now.
+
+
+ if (activeIndex === null) {
+ nodes = parent.nodes.map(function (nodeID) {
+ return graph.entity(nodeID);
+ });
+
+ if (nodes.length && geoHasSelfIntersections(nodes, entity.id)) {
+ return parent.geometry(graph);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ function move(d3_event, entity, point) {
+ if (_isCancelled) return;
+ d3_event.stopPropagation();
+ context.surface().classed('nope-disabled', d3_event.altKey);
+ _lastLoc = context.projection.invert(point);
+ doMove(d3_event, entity);
+ var nudge = geoViewportEdge(point, context.map().dimensions());
+
+ if (nudge) {
+ startNudge(d3_event, entity, nudge);
+ } else {
+ stopNudge();
+ }
+ }
+
+ function end(d3_event, entity) {
+ if (_isCancelled) return;
+ var wasPoint = entity.geometry(context.graph()) === 'point';
+ var d = datum(d3_event);
+ var nope = d && d.properties && d.properties.nope || context.surface().classed('nope');
+ var target = d && d.properties && d.properties.entity; // entity to snap to
+
+ if (nope) {
+ // bounce back
+ context.perform(_actionBounceBack(entity.id, _startLoc));
+ } else if (target && target.type === 'way') {
+ var choice = geoChooseEdge(context.graph().childNodes(target), context.map().mouse(), context.projection, entity.id);
+ context.replace(actionAddMidpoint({
+ loc: choice.loc,
+ edge: [target.nodes[choice.index - 1], target.nodes[choice.index]]
+ }, entity), connectAnnotation(entity, target));
+ } else if (target && target.type === 'node' && shouldSnapToNode(target)) {
+ context.replace(actionConnect([target.id, entity.id]), connectAnnotation(entity, target));
+ } else if (_wasMidpoint) {
+ context.replace(actionNoop(), _t('operations.add.annotation.vertex'));
+ } else {
+ context.replace(actionNoop(), moveAnnotation(entity));
+ }
+
+ if (wasPoint) {
+ context.enter(modeSelect(context, [entity.id]));
+ } else {
+ var reselection = _restoreSelectedIDs.filter(function (id) {
+ return context.graph().hasEntity(id);
+ });
+
+ if (reselection.length) {
+ context.enter(modeSelect(context, reselection));
+ } else {
+ context.enter(modeBrowse(context));
+ }
+ }
+ }
+
+ function _actionBounceBack(nodeID, toLoc) {
+ var moveNode = actionMoveNode(nodeID, toLoc);
+
+ var action = function action(graph, t) {
+ // last time through, pop off the bounceback perform.
+ // it will then overwrite the initial perform with a moveNode that does nothing
+ if (t === 1) context.pop();
+ return moveNode(graph, t);
+ };
+
+ action.transitionable = true;
+ return action;
+ }
+
+ function cancel() {
+ drag.cancel();
+ context.enter(modeBrowse(context));
+ }
+
+ var drag = behaviorDrag().selector('.layer-touch.points .target').surface(context.container().select('.main-map').node()).origin(origin).on('start', start).on('move', move).on('end', end);
+
+ mode.enter = function () {
+ context.install(hover);
+ context.install(edit);
+ select(window).on('keydown.dragNode', keydown).on('keyup.dragNode', keyup);
+ context.history().on('undone.drag-node', cancel);
+ };
+
+ mode.exit = function () {
+ context.ui().sidebar.hover.cancel();
+ context.uninstall(hover);
+ context.uninstall(edit);
+ select(window).on('keydown.dragNode', null).on('keyup.dragNode', null);
+ context.history().on('undone.drag-node', null);
+ _activeEntity = null;
+ context.surface().classed('nope', false).classed('nope-suppressed', false).classed('nope-disabled', false).selectAll('.active').classed('active', false);
+ stopNudge();
+ };
+
+ mode.selectedIDs = function () {
+ if (!arguments.length) return _activeEntity ? [_activeEntity.id] : []; // no assign
+
+ return mode;
+ };
+
+ mode.activeID = function () {
+ if (!arguments.length) return _activeEntity && _activeEntity.id; // no assign
+
+ return mode;
+ };
+
+ mode.restoreSelectedIDs = function (_) {
+ if (!arguments.length) return _restoreSelectedIDs;
+ _restoreSelectedIDs = _;
+ return mode;
+ };
+
+ mode.behavior = drag;
+ return mode;
+ }
+
+ var $$3 = _export;
+ var NativePromise = nativePromiseConstructor;
+ var fails$1 = fails$V;
+ var getBuiltIn = getBuiltIn$b;
+ var isCallable = isCallable$r;
+ var speciesConstructor = speciesConstructor$5;
+ var promiseResolve = promiseResolve$2;
+ var redefine$1 = redefine$h.exports;
+
+ // Safari bug https://bugs.webkit.org/show_bug.cgi?id=200829
+ var NON_GENERIC = !!NativePromise && fails$1(function () {
+ // eslint-disable-next-line unicorn/no-thenable -- required for testing
+ NativePromise.prototype['finally'].call({ then: function () { /* empty */ } }, function () { /* empty */ });
+ });
+
+ // `Promise.prototype.finally` method
+ // https://tc39.es/ecma262/#sec-promise.prototype.finally
+ $$3({ target: 'Promise', proto: true, real: true, forced: NON_GENERIC }, {
+ 'finally': function (onFinally) {
+ var C = speciesConstructor(this, getBuiltIn('Promise'));
+ var isFunction = isCallable(onFinally);
+ return this.then(
+ isFunction ? function (x) {
+ return promiseResolve(C, onFinally()).then(function () { return x; });
+ } : onFinally,
+ isFunction ? function (e) {
+ return promiseResolve(C, onFinally()).then(function () { throw e; });
+ } : onFinally
+ );
+ }
+ });
+
+ // makes sure that native promise-based APIs `Promise#finally` properly works with patched `Promise#then`
+ if (isCallable(NativePromise)) {
+ var method = getBuiltIn('Promise').prototype['finally'];
+ if (NativePromise.prototype['finally'] !== method) {
+ redefine$1(NativePromise.prototype, 'finally', method, { unsafe: true });
+ }
+ }
+
+ function quickselect(arr, k, left, right, compare) {
+ quickselectStep(arr, k, left || 0, right || arr.length - 1, compare || defaultCompare);
+ }
+
+ function quickselectStep(arr, k, left, right, compare) {
+ while (right > left) {
+ if (right - left > 600) {
+ var n = right - left + 1;
+ var m = k - left + 1;
+ var z = Math.log(n);
+ var s = 0.5 * Math.exp(2 * z / 3);
+ var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
+ var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
+ var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
+ quickselectStep(arr, k, newLeft, newRight, compare);
+ }
+
+ var t = arr[k];
+ var i = left;
+ var j = right;
+ swap(arr, left, k);
+ if (compare(arr[right], t) > 0) swap(arr, left, right);
+
+ while (i < j) {
+ swap(arr, i, j);
+ i++;
+ j--;
+
+ while (compare(arr[i], t) < 0) {
+ i++;
+ }
+
+ while (compare(arr[j], t) > 0) {
+ j--;
+ }
+ }
+
+ if (compare(arr[left], t) === 0) swap(arr, left, j);else {
+ j++;
+ swap(arr, j, right);
+ }
+ if (j <= k) left = j + 1;
+ if (k <= j) right = j - 1;
+ }
+ }
+
+ function swap(arr, i, j) {
+ var tmp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = tmp;
+ }
+
+ function defaultCompare(a, b) {
+ return a < b ? -1 : a > b ? 1 : 0;
+ }
+
+ var RBush = /*#__PURE__*/function () {
+ function RBush() {
+ var maxEntries = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 9;
+
+ _classCallCheck$1(this, RBush);
+
+ // max entries in a node is 9 by default; min node fill is 40% for best performance
+ this._maxEntries = Math.max(4, maxEntries);
+ this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
+ this.clear();
+ }
+
+ _createClass$1(RBush, [{
+ key: "all",
+ value: function all() {
+ return this._all(this.data, []);
+ }
+ }, {
+ key: "search",
+ value: function search(bbox) {
+ var node = this.data;
+ var result = [];
+ if (!intersects(bbox, node)) return result;
+ var toBBox = this.toBBox;
+ var nodesToSearch = [];
+
+ while (node) {
+ for (var i = 0; i < node.children.length; i++) {
+ var child = node.children[i];
+ var childBBox = node.leaf ? toBBox(child) : child;
+
+ if (intersects(bbox, childBBox)) {
+ if (node.leaf) result.push(child);else if (contains(bbox, childBBox)) this._all(child, result);else nodesToSearch.push(child);
+ }
+ }
+
+ node = nodesToSearch.pop();
+ }
+
+ return result;
+ }
+ }, {
+ key: "collides",
+ value: function collides(bbox) {
+ var node = this.data;
+ if (!intersects(bbox, node)) return false;
+ var nodesToSearch = [];
+
+ while (node) {
+ for (var i = 0; i < node.children.length; i++) {
+ var child = node.children[i];
+ var childBBox = node.leaf ? this.toBBox(child) : child;
+
+ if (intersects(bbox, childBBox)) {
+ if (node.leaf || contains(bbox, childBBox)) return true;
+ nodesToSearch.push(child);
+ }
+ }
+
+ node = nodesToSearch.pop();
+ }
+
+ return false;
+ }
+ }, {
+ key: "load",
+ value: function load(data) {
+ if (!(data && data.length)) return this;
+
+ if (data.length < this._minEntries) {
+ for (var i = 0; i < data.length; i++) {
+ this.insert(data[i]);
+ }
+
+ return this;
+ } // recursively build the tree with the given data from scratch using OMT algorithm
+
+
+ var node = this._build(data.slice(), 0, data.length - 1, 0);
+
+ if (!this.data.children.length) {
+ // save as is if tree is empty
+ this.data = node;
+ } else if (this.data.height === node.height) {
+ // split root if trees have the same height
+ this._splitRoot(this.data, node);
+ } else {
+ if (this.data.height < node.height) {
+ // swap trees if inserted one is bigger
+ var tmpNode = this.data;
+ this.data = node;
+ node = tmpNode;
+ } // insert the small tree into the large tree at appropriate level
+
+
+ this._insert(node, this.data.height - node.height - 1, true);
+ }
+
+ return this;
+ }
+ }, {
+ key: "insert",
+ value: function insert(item) {
+ if (item) this._insert(item, this.data.height - 1);
+ return this;
+ }
+ }, {
+ key: "clear",
+ value: function clear() {
+ this.data = createNode([]);
+ return this;
+ }
+ }, {
+ key: "remove",
+ value: function remove(item, equalsFn) {
+ if (!item) return this;
+ var node = this.data;
+ var bbox = this.toBBox(item);
+ var path = [];
+ var indexes = [];
+ var i, parent, goingUp; // depth-first iterative tree traversal
+
+ while (node || path.length) {
+ if (!node) {
+ // go up
+ node = path.pop();
+ parent = path[path.length - 1];
+ i = indexes.pop();
+ goingUp = true;
+ }
+
+ if (node.leaf) {
+ // check current node
+ var index = findItem(item, node.children, equalsFn);
+
+ if (index !== -1) {
+ // item found, remove the item and condense tree upwards
+ node.children.splice(index, 1);
+ path.push(node);
+
+ this._condense(path);
+
+ return this;
+ }
+ }
+
+ if (!goingUp && !node.leaf && contains(node, bbox)) {
+ // go down
+ path.push(node);
+ indexes.push(i);
+ i = 0;
+ parent = node;
+ node = node.children[0];
+ } else if (parent) {
+ // go right
+ i++;
+ node = parent.children[i];
+ goingUp = false;
+ } else node = null; // nothing found
+
+ }
+
+ return this;
+ }
+ }, {
+ key: "toBBox",
+ value: function toBBox(item) {
+ return item;
+ }
+ }, {
+ key: "compareMinX",
+ value: function compareMinX(a, b) {
+ return a.minX - b.minX;
+ }
+ }, {
+ key: "compareMinY",
+ value: function compareMinY(a, b) {
+ return a.minY - b.minY;
+ }
+ }, {
+ key: "toJSON",
+ value: function toJSON() {
+ return this.data;
+ }
+ }, {
+ key: "fromJSON",
+ value: function fromJSON(data) {
+ this.data = data;
+ return this;
+ }
+ }, {
+ key: "_all",
+ value: function _all(node, result) {
+ var nodesToSearch = [];
+
+ while (node) {
+ if (node.leaf) result.push.apply(result, _toConsumableArray(node.children));else nodesToSearch.push.apply(nodesToSearch, _toConsumableArray(node.children));
+ node = nodesToSearch.pop();
+ }
+
+ return result;
+ }
+ }, {
+ key: "_build",
+ value: function _build(items, left, right, height) {
+ var N = right - left + 1;
+ var M = this._maxEntries;
+ var node;
+
+ if (N <= M) {
+ // reached leaf level; return leaf
+ node = createNode(items.slice(left, right + 1));
+ calcBBox(node, this.toBBox);
+ return node;
+ }
+
+ if (!height) {
+ // target height of the bulk-loaded tree
+ height = Math.ceil(Math.log(N) / Math.log(M)); // target number of root entries to maximize storage utilization
+
+ M = Math.ceil(N / Math.pow(M, height - 1));
+ }
+
+ node = createNode([]);
+ node.leaf = false;
+ node.height = height; // split the items into M mostly square tiles
+
+ var N2 = Math.ceil(N / M);
+ var N1 = N2 * Math.ceil(Math.sqrt(M));
+ multiSelect(items, left, right, N1, this.compareMinX);
+
+ for (var i = left; i <= right; i += N1) {
+ var right2 = Math.min(i + N1 - 1, right);
+ multiSelect(items, i, right2, N2, this.compareMinY);
+
+ for (var j = i; j <= right2; j += N2) {
+ var right3 = Math.min(j + N2 - 1, right2); // pack each entry recursively
+
+ node.children.push(this._build(items, j, right3, height - 1));
+ }
+ }
+
+ calcBBox(node, this.toBBox);
+ return node;
+ }
+ }, {
+ key: "_chooseSubtree",
+ value: function _chooseSubtree(bbox, node, level, path) {
+ while (true) {
+ path.push(node);
+ if (node.leaf || path.length - 1 === level) break;
+ var minArea = Infinity;
+ var minEnlargement = Infinity;
+ var targetNode = void 0;
+
+ for (var i = 0; i < node.children.length; i++) {
+ var child = node.children[i];
+ var area = bboxArea(child);
+ var enlargement = enlargedArea(bbox, child) - area; // choose entry with the least area enlargement
+
+ if (enlargement < minEnlargement) {
+ minEnlargement = enlargement;
+ minArea = area < minArea ? area : minArea;
+ targetNode = child;
+ } else if (enlargement === minEnlargement) {
+ // otherwise choose one with the smallest area
+ if (area < minArea) {
+ minArea = area;
+ targetNode = child;
+ }
+ }
+ }
+
+ node = targetNode || node.children[0];
+ }
+
+ return node;
+ }
+ }, {
+ key: "_insert",
+ value: function _insert(item, level, isNode) {
+ var bbox = isNode ? item : this.toBBox(item);
+ var insertPath = []; // find the best node for accommodating the item, saving all nodes along the path too
+
+ var node = this._chooseSubtree(bbox, this.data, level, insertPath); // put the item into the node
+
+
+ node.children.push(item);
+ extend$1(node, bbox); // split on node overflow; propagate upwards if necessary
+
+ while (level >= 0) {
+ if (insertPath[level].children.length > this._maxEntries) {
+ this._split(insertPath, level);
+
+ level--;
+ } else break;
+ } // adjust bboxes along the insertion path
+
+
+ this._adjustParentBBoxes(bbox, insertPath, level);
+ } // split overflowed node into two
+
+ }, {
+ key: "_split",
+ value: function _split(insertPath, level) {
+ var node = insertPath[level];
+ var M = node.children.length;
+ var m = this._minEntries;
+
+ this._chooseSplitAxis(node, m, M);
+
+ var splitIndex = this._chooseSplitIndex(node, m, M);
+
+ var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
+ newNode.height = node.height;
+ newNode.leaf = node.leaf;
+ calcBBox(node, this.toBBox);
+ calcBBox(newNode, this.toBBox);
+ if (level) insertPath[level - 1].children.push(newNode);else this._splitRoot(node, newNode);
+ }
+ }, {
+ key: "_splitRoot",
+ value: function _splitRoot(node, newNode) {
+ // split root node
+ this.data = createNode([node, newNode]);
+ this.data.height = node.height + 1;
+ this.data.leaf = false;
+ calcBBox(this.data, this.toBBox);
+ }
+ }, {
+ key: "_chooseSplitIndex",
+ value: function _chooseSplitIndex(node, m, M) {
+ var index;
+ var minOverlap = Infinity;
+ var minArea = Infinity;
+
+ for (var i = m; i <= M - m; i++) {
+ var bbox1 = distBBox(node, 0, i, this.toBBox);
+ var bbox2 = distBBox(node, i, M, this.toBBox);
+ var overlap = intersectionArea(bbox1, bbox2);
+ var area = bboxArea(bbox1) + bboxArea(bbox2); // choose distribution with minimum overlap
+
+ if (overlap < minOverlap) {
+ minOverlap = overlap;
+ index = i;
+ minArea = area < minArea ? area : minArea;
+ } else if (overlap === minOverlap) {
+ // otherwise choose distribution with minimum area
+ if (area < minArea) {
+ minArea = area;
+ index = i;
+ }
+ }
+ }
+
+ return index || M - m;
+ } // sorts node children by the best axis for split
+
+ }, {
+ key: "_chooseSplitAxis",
+ value: function _chooseSplitAxis(node, m, M) {
+ var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
+ var compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
+
+ var xMargin = this._allDistMargin(node, m, M, compareMinX);
+
+ var yMargin = this._allDistMargin(node, m, M, compareMinY); // if total distributions margin value is minimal for x, sort by minX,
+ // otherwise it's already sorted by minY
+
+
+ if (xMargin < yMargin) node.children.sort(compareMinX);
+ } // total margin of all possible split distributions where each node is at least m full
+
+ }, {
+ key: "_allDistMargin",
+ value: function _allDistMargin(node, m, M, compare) {
+ node.children.sort(compare);
+ var toBBox = this.toBBox;
+ var leftBBox = distBBox(node, 0, m, toBBox);
+ var rightBBox = distBBox(node, M - m, M, toBBox);
+ var margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
+
+ for (var i = m; i < M - m; i++) {
+ var child = node.children[i];
+ extend$1(leftBBox, node.leaf ? toBBox(child) : child);
+ margin += bboxMargin(leftBBox);
+ }
+
+ for (var _i = M - m - 1; _i >= m; _i--) {
+ var _child = node.children[_i];
+ extend$1(rightBBox, node.leaf ? toBBox(_child) : _child);
+ margin += bboxMargin(rightBBox);
+ }
+
+ return margin;
+ }
+ }, {
+ key: "_adjustParentBBoxes",
+ value: function _adjustParentBBoxes(bbox, path, level) {
+ // adjust bboxes along the given tree path
+ for (var i = level; i >= 0; i--) {
+ extend$1(path[i], bbox);
+ }
+ }
+ }, {
+ key: "_condense",
+ value: function _condense(path) {
+ // go through the path, removing empty nodes and updating bboxes
+ for (var i = path.length - 1, siblings; i >= 0; i--) {
+ if (path[i].children.length === 0) {
+ if (i > 0) {
+ siblings = path[i - 1].children;
+ siblings.splice(siblings.indexOf(path[i]), 1);
+ } else this.clear();
+ } else calcBBox(path[i], this.toBBox);
+ }
+ }
+ }]);
+
+ return RBush;
+ }();
+
+ function findItem(item, items, equalsFn) {
+ if (!equalsFn) return items.indexOf(item);
+
+ for (var i = 0; i < items.length; i++) {
+ if (equalsFn(item, items[i])) return i;
+ }
+
+ return -1;
+ } // calculate node's bbox from bboxes of its children
+
+
+ function calcBBox(node, toBBox) {
+ distBBox(node, 0, node.children.length, toBBox, node);
+ } // min bounding rectangle of node children from k to p-1
+
+
+ function distBBox(node, k, p, toBBox, destNode) {
+ if (!destNode) destNode = createNode(null);
+ destNode.minX = Infinity;
+ destNode.minY = Infinity;
+ destNode.maxX = -Infinity;
+ destNode.maxY = -Infinity;
+
+ for (var i = k; i < p; i++) {
+ var child = node.children[i];
+ extend$1(destNode, node.leaf ? toBBox(child) : child);
+ }
+
+ return destNode;
+ }
+
+ function extend$1(a, b) {
+ a.minX = Math.min(a.minX, b.minX);
+ a.minY = Math.min(a.minY, b.minY);
+ a.maxX = Math.max(a.maxX, b.maxX);
+ a.maxY = Math.max(a.maxY, b.maxY);
+ return a;
+ }
+
+ function compareNodeMinX(a, b) {
+ return a.minX - b.minX;
+ }
+
+ function compareNodeMinY(a, b) {
+ return a.minY - b.minY;
+ }
+
+ function bboxArea(a) {
+ return (a.maxX - a.minX) * (a.maxY - a.minY);
+ }
+
+ function bboxMargin(a) {
+ return a.maxX - a.minX + (a.maxY - a.minY);
+ }
+
+ function enlargedArea(a, b) {
+ return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
+ }
+
+ function intersectionArea(a, b) {
+ var minX = Math.max(a.minX, b.minX);
+ var minY = Math.max(a.minY, b.minY);
+ var maxX = Math.min(a.maxX, b.maxX);
+ var maxY = Math.min(a.maxY, b.maxY);
+ return Math.max(0, maxX - minX) * Math.max(0, maxY - minY);
+ }
+
+ function contains(a, b) {
+ return a.minX <= b.minX && a.minY <= b.minY && b.maxX <= a.maxX && b.maxY <= a.maxY;
+ }
+
+ function intersects(a, b) {
+ return b.minX <= a.maxX && b.minY <= a.maxY && b.maxX >= a.minX && b.maxY >= a.minY;
+ }
+
+ function createNode(children) {
+ return {
+ children: children,
+ height: 1,
+ leaf: true,
+ minX: Infinity,
+ minY: Infinity,
+ maxX: -Infinity,
+ maxY: -Infinity
+ };
+ } // sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
+ // combines selection algorithm with binary divide & conquer approach
+
+
+ function multiSelect(arr, left, right, n, compare) {
+ var stack = [left, right];
+
+ while (stack.length) {
+ right = stack.pop();
+ left = stack.pop();
+ if (right - left <= n) continue;
+ var mid = left + Math.ceil((right - left) / n / 2) * n;
+ quickselect(arr, mid, left, right, compare);
+ stack.push(left, mid, mid, right);
+ }
+ }
+
+ function responseText(response) {
+ if (!response.ok) throw new Error(response.status + " " + response.statusText);
+ return response.text();
+ }
+
+ function d3_text (input, init) {
+ return fetch(input, init).then(responseText);
+ }
+
+ function responseJson(response) {
+ if (!response.ok) throw new Error(response.status + " " + response.statusText);
+ if (response.status === 204 || response.status === 205) return;
+ return response.json();
+ }
+
+ function d3_json (input, init) {
+ return fetch(input, init).then(responseJson);
+ }
+
+ function parser(type) {
+ return function (input, init) {
+ return d3_text(input, init).then(function (text) {
+ return new DOMParser().parseFromString(text, type);
+ });
+ };
+ }
+
+ var d3_xml = parser("application/xml");
+ var svg = parser("image/svg+xml");
+
+ var tiler$6 = utilTiler();
+ var dispatch$7 = dispatch$8('loaded');
+ var _tileZoom$3 = 14;
+ var _krUrlRoot = 'https://www.keepright.at';
+ var _krData = {
+ errorTypes: {},
+ localizeStrings: {}
+ }; // This gets reassigned if reset
+
+ var _cache$2;
+
+ var _krRuleset = [// no 20 - multiple node on same spot - these are mostly boundaries overlapping roads
+ 30, 40, 50, 60, 70, 90, 100, 110, 120, 130, 150, 160, 170, 180, 190, 191, 192, 193, 194, 195, 196, 197, 198, 200, 201, 202, 203, 204, 205, 206, 207, 208, 210, 220, 230, 231, 232, 270, 280, 281, 282, 283, 284, 285, 290, 291, 292, 293, 294, 295, 296, 297, 298, 300, 310, 311, 312, 313, 320, 350, 360, 370, 380, 390, 400, 401, 402, 410, 411, 412, 413];
+
+ function abortRequest$6(controller) {
+ if (controller) {
+ controller.abort();
+ }
+ }
+
+ function abortUnwantedRequests$3(cache, tiles) {
+ Object.keys(cache.inflightTile).forEach(function (k) {
+ var wanted = tiles.find(function (tile) {
+ return k === tile.id;
+ });
+
+ if (!wanted) {
+ abortRequest$6(cache.inflightTile[k]);
+ delete cache.inflightTile[k];
+ }
+ });
+ }
+
+ function encodeIssueRtree$2(d) {
+ return {
+ minX: d.loc[0],
+ minY: d.loc[1],
+ maxX: d.loc[0],
+ maxY: d.loc[1],
+ data: d
+ };
+ } // Replace or remove QAItem from rtree
+
+
+ function updateRtree$3(item, replace) {
+ _cache$2.rtree.remove(item, function (a, b) {
+ return a.data.id === b.data.id;
+ });
+
+ if (replace) {
+ _cache$2.rtree.insert(item);
+ }
+ }
+
+ function tokenReplacements(d) {
+ if (!(d instanceof QAItem)) return;
+ var replacements = {};
+ var issueTemplate = _krData.errorTypes[d.whichType];
+
+ if (!issueTemplate) {
+ /* eslint-disable no-console */
+ console.log('No Template: ', d.whichType);
+ console.log(' ', d.description);
+ /* eslint-enable no-console */
+
+ return;
+ } // some descriptions are just fixed text
+
+
+ if (!issueTemplate.regex) return; // regex pattern should match description with variable details captured
+
+ var errorRegex = new RegExp(issueTemplate.regex, 'i');
+ var errorMatch = errorRegex.exec(d.description);
+
+ if (!errorMatch) {
+ /* eslint-disable no-console */
+ console.log('Unmatched: ', d.whichType);
+ console.log(' ', d.description);
+ console.log(' ', errorRegex);
+ /* eslint-enable no-console */
+
+ return;
+ }
+
+ for (var i = 1; i < errorMatch.length; i++) {
+ // skip first
+ var capture = errorMatch[i];
+ var idType = void 0;
+ idType = 'IDs' in issueTemplate ? issueTemplate.IDs[i - 1] : '';
+
+ if (idType && capture) {
+ // link IDs if present in the capture
+ capture = parseError(capture, idType);
+ } else {
+ var compare = capture.toLowerCase();
+
+ if (_krData.localizeStrings[compare]) {
+ // some replacement strings can be localized
+ capture = _t('QA.keepRight.error_parts.' + _krData.localizeStrings[compare]);
+ } else {
+ capture = unescape$3(capture);
+ }
+ }
+
+ replacements['var' + i] = capture;
+ }
+
+ return replacements;
+ }
+
+ function parseError(capture, idType) {
+ var compare = capture.toLowerCase();
+
+ if (_krData.localizeStrings[compare]) {
+ // some replacement strings can be localized
+ capture = _t('QA.keepRight.error_parts.' + _krData.localizeStrings[compare]);
+ }
+
+ switch (idType) {
+ // link a string like "this node"
+ case 'this':
+ capture = linkErrorObject(capture);
+ break;
+
+ case 'url':
+ capture = linkURL(capture);
+ break;
+ // link an entity ID
+
+ case 'n':
+ case 'w':
+ case 'r':
+ capture = linkEntity(idType + capture);
+ break;
+ // some errors have more complex ID lists/variance
+
+ case '20':
+ capture = parse20(capture);
+ break;
+
+ case '211':
+ capture = parse211(capture);
+ break;
+
+ case '231':
+ capture = parse231(capture);
+ break;
+
+ case '294':
+ capture = parse294(capture);
+ break;
+
+ case '370':
+ capture = parse370(capture);
+ break;
+ }
+
+ return capture;
+
+ function linkErrorObject(d) {
+ return {
+ html: "<a class=\"error_object_link\">".concat(d, "</a>")
+ };
+ }
+
+ function linkEntity(d) {
+ return {
+ html: "<a class=\"error_entity_link\">".concat(d, "</a>")
+ };
+ }
+
+ function linkURL(d) {
+ return {
+ html: "<a class=\"kr_external_link\" target=\"_blank\" href=\"".concat(d, "\">").concat(d, "</a>")
+ };
+ } // arbitrary node list of form: #ID, #ID, #ID...
+
+
+ function parse211(capture) {
+ var newList = [];
+ var items = capture.split(', ');
+ items.forEach(function (item) {
+ // ID has # at the front
+ var id = linkEntity('n' + item.slice(1));
+ newList.push(id);
+ });
+ return newList.join(', ');
+ } // arbitrary way list of form: #ID(layer),#ID(layer),#ID(layer)...
+
+
+ function parse231(capture) {
+ var newList = []; // unfortunately 'layer' can itself contain commas, so we split on '),'
+
+ var items = capture.split('),');
+ items.forEach(function (item) {
+ var match = item.match(/\#(\d+)\((.+)\)?/);
+
+ if (match !== null && match.length > 2) {
+ newList.push(linkEntity('w' + match[1]) + ' ' + _t('QA.keepRight.errorTypes.231.layer', {
+ layer: match[2]
+ }));
+ }
+ });
+ return newList.join(', ');
+ } // arbitrary node/relation list of form: from node #ID,to relation #ID,to node #ID...
+
+
+ function parse294(capture) {
+ var newList = [];
+ var items = capture.split(',');
+ items.forEach(function (item) {
+ // item of form "from/to node/relation #ID"
+ item = item.split(' '); // to/from role is more clear in quotes
+
+ var role = "\"".concat(item[0], "\""); // first letter of node/relation provides the type
+
+ var idType = item[1].slice(0, 1); // ID has # at the front
+
+ var id = item[2].slice(1);
+ id = linkEntity(idType + id);
+ newList.push("".concat(role, " ").concat(item[1], " ").concat(id));
+ });
+ return newList.join(', ');
+ } // may or may not include the string "(including the name 'name')"
+
+
+ function parse370(capture) {
+ if (!capture) return '';
+ var match = capture.match(/\(including the name (\'.+\')\)/);
+
+ if (match && match.length) {
+ return _t('QA.keepRight.errorTypes.370.including_the_name', {
+ name: match[1]
+ });
+ }
+
+ return '';
+ } // arbitrary node list of form: #ID,#ID,#ID...
+
+
+ function parse20(capture) {
+ var newList = [];
+ var items = capture.split(',');
+ items.forEach(function (item) {
+ // ID has # at the front
+ var id = linkEntity('n' + item.slice(1));
+ newList.push(id);
+ });
+ return newList.join(', ');
+ }
+ }
+
+ var serviceKeepRight = {
+ title: 'keepRight',
+ init: function init() {
+ _mainFileFetcher.get('keepRight').then(function (d) {
+ return _krData = d;
+ });
+
+ if (!_cache$2) {
+ this.reset();
+ }
+
+ this.event = utilRebind(this, dispatch$7, 'on');
+ },
+ reset: function reset() {
+ if (_cache$2) {
+ Object.values(_cache$2.inflightTile).forEach(abortRequest$6);
+ }
+
+ _cache$2 = {
+ data: {},
+ loadedTile: {},
+ inflightTile: {},
+ inflightPost: {},
+ closed: {},
+ rtree: new RBush()
+ };
+ },
+ // KeepRight API: http://osm.mueschelsoft.de/keepright/interfacing.php
+ loadIssues: function loadIssues(projection) {
+ var _this = this;
+
+ var options = {
+ format: 'geojson',
+ ch: _krRuleset
+ }; // determine the needed tiles to cover the view
+
+ var tiles = tiler$6.zoomExtent([_tileZoom$3, _tileZoom$3]).getTiles(projection); // abort inflight requests that are no longer needed
+
+ abortUnwantedRequests$3(_cache$2, tiles); // issue new requests..
+
+ tiles.forEach(function (tile) {
+ if (_cache$2.loadedTile[tile.id] || _cache$2.inflightTile[tile.id]) return;
+
+ var _tile$extent$rectangl = tile.extent.rectangle(),
+ _tile$extent$rectangl2 = _slicedToArray(_tile$extent$rectangl, 4),
+ left = _tile$extent$rectangl2[0],
+ top = _tile$extent$rectangl2[1],
+ right = _tile$extent$rectangl2[2],
+ bottom = _tile$extent$rectangl2[3];
+
+ var params = Object.assign({}, options, {
+ left: left,
+ bottom: bottom,
+ right: right,
+ top: top
+ });
+ var url = "".concat(_krUrlRoot, "/export.php?") + utilQsString(params);
+ var controller = new AbortController();
+ _cache$2.inflightTile[tile.id] = controller;
+ d3_json(url, {
+ signal: controller.signal
+ }).then(function (data) {
+ delete _cache$2.inflightTile[tile.id];
+ _cache$2.loadedTile[tile.id] = true;
+
+ if (!data || !data.features || !data.features.length) {
+ throw new Error('No Data');
+ }
+
+ data.features.forEach(function (feature) {
+ var _feature$properties = feature.properties,
+ itemType = _feature$properties.error_type,
+ id = _feature$properties.error_id,
+ _feature$properties$c = _feature$properties.comment,
+ comment = _feature$properties$c === void 0 ? null : _feature$properties$c,
+ objectId = _feature$properties.object_id,
+ objectType = _feature$properties.object_type,
+ schema = _feature$properties.schema,
+ title = _feature$properties.title;
+ var loc = feature.geometry.coordinates,
+ _feature$properties$d = feature.properties.description,
+ description = _feature$properties$d === void 0 ? '' : _feature$properties$d; // if there is a parent, save its error type e.g.:
+ // Error 191 = "highway-highway"
+ // Error 190 = "intersections without junctions" (parent)
+
+ var issueTemplate = _krData.errorTypes[itemType];
+ var parentIssueType = (Math.floor(itemType / 10) * 10).toString(); // try to handle error type directly, fallback to parent error type.
+
+ var whichType = issueTemplate ? itemType : parentIssueType;
+ var whichTemplate = _krData.errorTypes[whichType]; // Rewrite a few of the errors at this point..
+ // This is done to make them easier to linkify and translate.
+
+ switch (whichType) {
+ case '170':
+ description = "This feature has a FIXME tag: ".concat(description);
+ break;
+
+ case '292':
+ case '293':
+ description = description.replace('A turn-', 'This turn-');
+ break;
+
+ case '294':
+ case '295':
+ case '296':
+ case '297':
+ case '298':
+ description = "This turn-restriction~".concat(description);
+ break;
+
+ case '300':
+ description = 'This highway is missing a maxspeed tag';
+ break;
+
+ case '411':
+ case '412':
+ case '413':
+ description = "This feature~".concat(description);
+ break;
+ } // move markers slightly so it doesn't obscure the geometry,
+ // then move markers away from other coincident markers
+
+
+ var coincident = false;
+
+ do {
+ // first time, move marker up. after that, move marker right.
+ var delta = coincident ? [0.00001, 0] : [0, 0.00001];
+ loc = geoVecAdd(loc, delta);
+ var bbox = geoExtent(loc).bbox();
+ coincident = _cache$2.rtree.search(bbox).length;
+ } while (coincident);
+
+ var d = new QAItem(loc, _this, itemType, id, {
+ comment: comment,
+ description: description,
+ whichType: whichType,
+ parentIssueType: parentIssueType,
+ severity: whichTemplate.severity || 'error',
+ objectId: objectId,
+ objectType: objectType,
+ schema: schema,
+ title: title
+ });
+ d.replacements = tokenReplacements(d);
+ _cache$2.data[id] = d;
+
+ _cache$2.rtree.insert(encodeIssueRtree$2(d));
+ });
+ dispatch$7.call('loaded');
+ })["catch"](function () {
+ delete _cache$2.inflightTile[tile.id];
+ _cache$2.loadedTile[tile.id] = true;
+ });
+ });
+ },
+ postUpdate: function postUpdate(d, callback) {
+ var _this2 = this;
+
+ if (_cache$2.inflightPost[d.id]) {
+ return callback({
+ message: 'Error update already inflight',
+ status: -2
+ }, d);
+ }
+
+ var params = {
+ schema: d.schema,
+ id: d.id
+ };
+
+ if (d.newStatus) {
+ params.st = d.newStatus;
+ }
+
+ if (d.newComment !== undefined) {
+ params.co = d.newComment;
+ } // NOTE: This throws a CORS err, but it seems successful.
+ // We don't care too much about the response, so this is fine.
+
+
+ var url = "".concat(_krUrlRoot, "/comment.php?") + utilQsString(params);
+ var controller = new AbortController();
+ _cache$2.inflightPost[d.id] = controller; // Since this is expected to throw an error just continue as if it worked
+ // (worst case scenario the request truly fails and issue will show up if iD restarts)
+
+ d3_json(url, {
+ signal: controller.signal
+ })["finally"](function () {
+ delete _cache$2.inflightPost[d.id];
+
+ if (d.newStatus === 'ignore') {
+ // ignore permanently (false positive)
+ _this2.removeItem(d);
+ } else if (d.newStatus === 'ignore_t') {
+ // ignore temporarily (error fixed)
+ _this2.removeItem(d);
+
+ _cache$2.closed["".concat(d.schema, ":").concat(d.id)] = true;
+ } else {
+ d = _this2.replaceItem(d.update({
+ comment: d.newComment,
+ newComment: undefined,
+ newState: undefined
+ }));
+ }
+
+ if (callback) callback(null, d);
+ });
+ },
+ // Get all cached QAItems covering the viewport
+ getItems: function getItems(projection) {
+ var viewport = projection.clipExtent();
+ var min = [viewport[0][0], viewport[1][1]];
+ var max = [viewport[1][0], viewport[0][1]];
+ var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
+ return _cache$2.rtree.search(bbox).map(function (d) {
+ return d.data;
+ });
+ },
+ // Get a QAItem from cache
+ // NOTE: Don't change method name until UI v3 is merged
+ getError: function getError(id) {
+ return _cache$2.data[id];
+ },
+ // Replace a single QAItem in the cache
+ replaceItem: function replaceItem(item) {
+ if (!(item instanceof QAItem) || !item.id) return;
+ _cache$2.data[item.id] = item;
+ updateRtree$3(encodeIssueRtree$2(item), true); // true = replace
+
+ return item;
+ },
+ // Remove a single QAItem from the cache
+ removeItem: function removeItem(item) {
+ if (!(item instanceof QAItem) || !item.id) return;
+ delete _cache$2.data[item.id];
+ updateRtree$3(encodeIssueRtree$2(item), false); // false = remove
+ },
+ issueURL: function issueURL(item) {
+ return "".concat(_krUrlRoot, "/report_map.php?schema=").concat(item.schema, "&error=").concat(item.id);
+ },
+ // Get an array of issues closed during this session.
+ // Used to populate `closed:keepright` changeset tag
+ getClosedIDs: function getClosedIDs() {
+ return Object.keys(_cache$2.closed).sort();
+ }
+ };
+
+ var tiler$5 = utilTiler();
+ var dispatch$6 = dispatch$8('loaded');
+ var _tileZoom$2 = 14;
+ var _impOsmUrls = {
+ ow: 'https://grab.community.improve-osm.org/directionOfFlowService',
+ mr: 'https://grab.community.improve-osm.org/missingGeoService',
+ tr: 'https://grab.community.improve-osm.org/turnRestrictionService'
+ };
+ var _impOsmData = {
+ icons: {}
+ }; // This gets reassigned if reset
+
+ var _cache$1;
+
+ function abortRequest$5(i) {
+ Object.values(i).forEach(function (controller) {
+ if (controller) {
+ controller.abort();
+ }
+ });
+ }
+
+ function abortUnwantedRequests$2(cache, tiles) {
+ Object.keys(cache.inflightTile).forEach(function (k) {
+ var wanted = tiles.find(function (tile) {
+ return k === tile.id;
+ });
+
+ if (!wanted) {
+ abortRequest$5(cache.inflightTile[k]);
+ delete cache.inflightTile[k];
+ }
+ });
+ }
+
+ function encodeIssueRtree$1(d) {
+ return {
+ minX: d.loc[0],
+ minY: d.loc[1],
+ maxX: d.loc[0],
+ maxY: d.loc[1],
+ data: d
+ };
+ } // Replace or remove QAItem from rtree
+
+
+ function updateRtree$2(item, replace) {
+ _cache$1.rtree.remove(item, function (a, b) {
+ return a.data.id === b.data.id;
+ });
+
+ if (replace) {
+ _cache$1.rtree.insert(item);
+ }
+ }
+
+ function linkErrorObject(d) {
+ return {
+ html: "<a class=\"error_object_link\">".concat(d, "</a>")
+ };
+ }
+
+ function linkEntity(d) {
+ return {
+ html: "<a class=\"error_entity_link\">".concat(d, "</a>")
+ };
+ }
+
+ function pointAverage(points) {
+ if (points.length) {
+ var sum = points.reduce(function (acc, point) {
+ return geoVecAdd(acc, [point.lon, point.lat]);
+ }, [0, 0]);
+ return geoVecScale(sum, 1 / points.length);
+ } else {
+ return [0, 0];
+ }
+ }
+
+ function relativeBearing(p1, p2) {
+ var angle = Math.atan2(p2.lon - p1.lon, p2.lat - p1.lat);
+
+ if (angle < 0) {
+ angle += 2 * Math.PI;
+ } // Return degrees
+
+
+ return angle * 180 / Math.PI;
+ } // Assuming range [0,360)
+
+
+ function cardinalDirection(bearing) {
+ var dir = 45 * Math.round(bearing / 45);
+ var compass = {
+ 0: 'north',
+ 45: 'northeast',
+ 90: 'east',
+ 135: 'southeast',
+ 180: 'south',
+ 225: 'southwest',
+ 270: 'west',
+ 315: 'northwest',
+ 360: 'north'
+ };
+ return _t("QA.improveOSM.directions.".concat(compass[dir]));
+ } // Errors shouldn't obscure each other
+
+
+ function preventCoincident$1(loc, bumpUp) {
+ var coincident = false;
+
+ do {
+ // first time, move marker up. after that, move marker right.
+ var delta = coincident ? [0.00001, 0] : bumpUp ? [0, 0.00001] : [0, 0];
+ loc = geoVecAdd(loc, delta);
+ var bbox = geoExtent(loc).bbox();
+ coincident = _cache$1.rtree.search(bbox).length;
+ } while (coincident);
+
+ return loc;
+ }
+
+ var serviceImproveOSM = {
+ title: 'improveOSM',
+ init: function init() {
+ _mainFileFetcher.get('qa_data').then(function (d) {
+ return _impOsmData = d.improveOSM;
+ });
+
+ if (!_cache$1) {
+ this.reset();
+ }
+
+ this.event = utilRebind(this, dispatch$6, 'on');
+ },
+ reset: function reset() {
+ if (_cache$1) {
+ Object.values(_cache$1.inflightTile).forEach(abortRequest$5);
+ }
+
+ _cache$1 = {
+ data: {},
+ loadedTile: {},
+ inflightTile: {},
+ inflightPost: {},
+ closed: {},
+ rtree: new RBush()
+ };
+ },
+ loadIssues: function loadIssues(projection) {
+ var _this = this;
+
+ var options = {
+ client: 'iD',
+ status: 'OPEN',
+ zoom: '19' // Use a high zoom so that clusters aren't returned
+
+ }; // determine the needed tiles to cover the view
+
+ var tiles = tiler$5.zoomExtent([_tileZoom$2, _tileZoom$2]).getTiles(projection); // abort inflight requests that are no longer needed
+
+ abortUnwantedRequests$2(_cache$1, tiles); // issue new requests..
+
+ tiles.forEach(function (tile) {
+ if (_cache$1.loadedTile[tile.id] || _cache$1.inflightTile[tile.id]) return;
+
+ var _tile$extent$rectangl = tile.extent.rectangle(),
+ _tile$extent$rectangl2 = _slicedToArray(_tile$extent$rectangl, 4),
+ east = _tile$extent$rectangl2[0],
+ north = _tile$extent$rectangl2[1],
+ west = _tile$extent$rectangl2[2],
+ south = _tile$extent$rectangl2[3];
+
+ var params = Object.assign({}, options, {
+ east: east,
+ south: south,
+ west: west,
+ north: north
+ }); // 3 separate requests to store for each tile
+
+ var requests = {};
+ Object.keys(_impOsmUrls).forEach(function (k) {
+ // We exclude WATER from missing geometry as it doesn't seem useful
+ // We use most confident one-way and turn restrictions only, still have false positives
+ var kParams = Object.assign({}, params, k === 'mr' ? {
+ type: 'PARKING,ROAD,BOTH,PATH'
+ } : {
+ confidenceLevel: 'C1'
+ });
+ var url = "".concat(_impOsmUrls[k], "/search?") + utilQsString(kParams);
+ var controller = new AbortController();
+ requests[k] = controller;
+ d3_json(url, {
+ signal: controller.signal
+ }).then(function (data) {
+ delete _cache$1.inflightTile[tile.id][k];
+
+ if (!Object.keys(_cache$1.inflightTile[tile.id]).length) {
+ delete _cache$1.inflightTile[tile.id];
+ _cache$1.loadedTile[tile.id] = true;
+ } // Road segments at high zoom == oneways
+
+
+ if (data.roadSegments) {
+ data.roadSegments.forEach(function (feature) {
+ // Position error at the approximate middle of the segment
+ var points = feature.points,
+ wayId = feature.wayId,
+ fromNodeId = feature.fromNodeId,
+ toNodeId = feature.toNodeId;
+ var itemId = "".concat(wayId).concat(fromNodeId).concat(toNodeId);
+ var mid = points.length / 2;
+ var loc; // Even number of points, find midpoint of the middle two
+ // Odd number of points, use position of very middle point
+
+ if (mid % 1 === 0) {
+ loc = pointAverage([points[mid - 1], points[mid]]);
+ } else {
+ mid = points[Math.floor(mid)];
+ loc = [mid.lon, mid.lat];
+ } // One-ways can land on same segment in opposite direction
+
+
+ loc = preventCoincident$1(loc, false);
+ var d = new QAItem(loc, _this, k, itemId, {
+ issueKey: k,
+ // used as a category
+ identifier: {
+ // used to post changes
+ wayId: wayId,
+ fromNodeId: fromNodeId,
+ toNodeId: toNodeId
+ },
+ objectId: wayId,
+ objectType: 'way'
+ }); // Variables used in the description
+
+ d.replacements = {
+ percentage: feature.percentOfTrips,
+ num_trips: feature.numberOfTrips,
+ highway: linkErrorObject(_t('QA.keepRight.error_parts.highway')),
+ from_node: linkEntity('n' + feature.fromNodeId),
+ to_node: linkEntity('n' + feature.toNodeId)
+ };
+ _cache$1.data[d.id] = d;
+
+ _cache$1.rtree.insert(encodeIssueRtree$1(d));
+ });
+ } // Tiles at high zoom == missing roads
+
+
+ if (data.tiles) {
+ data.tiles.forEach(function (feature) {
+ var type = feature.type,
+ x = feature.x,
+ y = feature.y,
+ numberOfTrips = feature.numberOfTrips;
+ var geoType = type.toLowerCase();
+ var itemId = "".concat(geoType).concat(x).concat(y).concat(numberOfTrips); // Average of recorded points should land on the missing geometry
+ // Missing geometry could happen to land on another error
+
+ var loc = pointAverage(feature.points);
+ loc = preventCoincident$1(loc, false);
+ var d = new QAItem(loc, _this, "".concat(k, "-").concat(geoType), itemId, {
+ issueKey: k,
+ identifier: {
+ x: x,
+ y: y
+ }
+ });
+ d.replacements = {
+ num_trips: numberOfTrips,
+ geometry_type: _t("QA.improveOSM.geometry_types.".concat(geoType))
+ }; // -1 trips indicates data came from a 3rd party
+
+ if (numberOfTrips === -1) {
+ d.desc = _t('QA.improveOSM.error_types.mr.description_alt', d.replacements);
+ }
+
+ _cache$1.data[d.id] = d;
+
+ _cache$1.rtree.insert(encodeIssueRtree$1(d));
+ });
+ } // Entities at high zoom == turn restrictions
+
+
+ if (data.entities) {
+ data.entities.forEach(function (feature) {
+ var point = feature.point,
+ id = feature.id,
+ segments = feature.segments,
+ numberOfPasses = feature.numberOfPasses,
+ turnType = feature.turnType;
+ var itemId = "".concat(id.replace(/[,:+#]/g, '_')); // Turn restrictions could be missing at same junction
+ // We also want to bump the error up so node is accessible
+
+ var loc = preventCoincident$1([point.lon, point.lat], true); // Elements are presented in a strange way
+
+ var ids = id.split(',');
+ var from_way = ids[0];
+ var via_node = ids[3];
+ var to_way = ids[2].split(':')[1];
+ var d = new QAItem(loc, _this, k, itemId, {
+ issueKey: k,
+ identifier: id,
+ objectId: via_node,
+ objectType: 'node'
+ }); // Travel direction along from_way clarifies the turn restriction
+
+ var _segments$0$points = _slicedToArray(segments[0].points, 2),
+ p1 = _segments$0$points[0],
+ p2 = _segments$0$points[1];
+
+ var dir_of_travel = cardinalDirection(relativeBearing(p1, p2)); // Variables used in the description
+
+ d.replacements = {
+ num_passed: numberOfPasses,
+ num_trips: segments[0].numberOfTrips,
+ turn_restriction: turnType.toLowerCase(),
+ from_way: linkEntity('w' + from_way),
+ to_way: linkEntity('w' + to_way),
+ travel_direction: dir_of_travel,
+ junction: linkErrorObject(_t('QA.keepRight.error_parts.this_node'))
+ };
+ _cache$1.data[d.id] = d;
+
+ _cache$1.rtree.insert(encodeIssueRtree$1(d));
+
+ dispatch$6.call('loaded');
+ });
+ }
+ })["catch"](function () {
+ delete _cache$1.inflightTile[tile.id][k];
+
+ if (!Object.keys(_cache$1.inflightTile[tile.id]).length) {
+ delete _cache$1.inflightTile[tile.id];
+ _cache$1.loadedTile[tile.id] = true;
+ }
+ });
+ });
+ _cache$1.inflightTile[tile.id] = requests;
+ });
+ },
+ getComments: function getComments(item) {
+ var _this2 = this;
+
+ // If comments already retrieved no need to do so again
+ if (item.comments) {
+ return Promise.resolve(item);
+ }
+
+ var key = item.issueKey;
+ var qParams = {};
+
+ if (key === 'ow') {
+ qParams = item.identifier;
+ } else if (key === 'mr') {
+ qParams.tileX = item.identifier.x;
+ qParams.tileY = item.identifier.y;
+ } else if (key === 'tr') {
+ qParams.targetId = item.identifier;
+ }
+
+ var url = "".concat(_impOsmUrls[key], "/retrieveComments?") + utilQsString(qParams);
+
+ var cacheComments = function cacheComments(data) {
+ // Assign directly for immediate use afterwards
+ // comments are served newest to oldest
+ item.comments = data.comments ? data.comments.reverse() : [];
+
+ _this2.replaceItem(item);
+ };
+
+ return d3_json(url).then(cacheComments).then(function () {
+ return item;
+ });
+ },
+ postUpdate: function postUpdate(d, callback) {
+ if (!serviceOsm.authenticated()) {
+ // Username required in payload
+ return callback({
+ message: 'Not Authenticated',
+ status: -3
+ }, d);
+ }
+
+ if (_cache$1.inflightPost[d.id]) {
+ return callback({
+ message: 'Error update already inflight',
+ status: -2
+ }, d);
+ } // Payload can only be sent once username is established
+
+
+ serviceOsm.userDetails(sendPayload.bind(this));
+
+ function sendPayload(err, user) {
+ var _this3 = this;
+
+ if (err) {
+ return callback(err, d);
+ }
+
+ var key = d.issueKey;
+ var url = "".concat(_impOsmUrls[key], "/comment");
+ var payload = {
+ username: user.display_name,
+ targetIds: [d.identifier]
+ };
+
+ if (d.newStatus) {
+ payload.status = d.newStatus;
+ payload.text = 'status changed';
+ } // Comment take place of default text
+
+
+ if (d.newComment) {
+ payload.text = d.newComment;
+ }
+
+ var controller = new AbortController();
+ _cache$1.inflightPost[d.id] = controller;
+ var options = {
+ method: 'POST',
+ signal: controller.signal,
+ body: JSON.stringify(payload)
+ };
+ d3_json(url, options).then(function () {
+ delete _cache$1.inflightPost[d.id]; // Just a comment, update error in cache
+
+ if (!d.newStatus) {
+ var now = new Date();
+ var comments = d.comments ? d.comments : [];
+ comments.push({
+ username: payload.username,
+ text: payload.text,
+ timestamp: now.getTime() / 1000
+ });
+
+ _this3.replaceItem(d.update({
+ comments: comments,
+ newComment: undefined
+ }));
+ } else {
+ _this3.removeItem(d);
+
+ if (d.newStatus === 'SOLVED') {
+ // Keep track of the number of issues closed per type to tag the changeset
+ if (!(d.issueKey in _cache$1.closed)) {
+ _cache$1.closed[d.issueKey] = 0;
+ }
+
+ _cache$1.closed[d.issueKey] += 1;
+ }
+ }
+
+ if (callback) callback(null, d);
+ })["catch"](function (err) {
+ delete _cache$1.inflightPost[d.id];
+ if (callback) callback(err.message);
+ });
+ }
+ },
+ // Get all cached QAItems covering the viewport
+ getItems: function getItems(projection) {
+ var viewport = projection.clipExtent();
+ var min = [viewport[0][0], viewport[1][1]];
+ var max = [viewport[1][0], viewport[0][1]];
+ var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
+ return _cache$1.rtree.search(bbox).map(function (d) {
+ return d.data;
+ });
+ },
+ // Get a QAItem from cache
+ // NOTE: Don't change method name until UI v3 is merged
+ getError: function getError(id) {
+ return _cache$1.data[id];
+ },
+ // get the name of the icon to display for this item
+ getIcon: function getIcon(itemType) {
+ return _impOsmData.icons[itemType];
+ },
+ // Replace a single QAItem in the cache
+ replaceItem: function replaceItem(issue) {
+ if (!(issue instanceof QAItem) || !issue.id) return;
+ _cache$1.data[issue.id] = issue;
+ updateRtree$2(encodeIssueRtree$1(issue), true); // true = replace
+
+ return issue;
+ },
+ // Remove a single QAItem from the cache
+ removeItem: function removeItem(issue) {
+ if (!(issue instanceof QAItem) || !issue.id) return;
+ delete _cache$1.data[issue.id];
+ updateRtree$2(encodeIssueRtree$1(issue), false); // false = remove
+ },
+ // Used to populate `closed:improveosm:*` changeset tags
+ getClosedCounts: function getClosedCounts() {
+ return _cache$1.closed;
+ }
+ };
+
+ var defaults$5 = {exports: {}};
+
+ function getDefaults$1() {
+ return {
+ baseUrl: null,
+ breaks: false,
+ gfm: true,
+ headerIds: true,
+ headerPrefix: '',
+ highlight: null,
+ langPrefix: 'language-',
+ mangle: true,
+ pedantic: false,
+ renderer: null,
+ sanitize: false,
+ sanitizer: null,
+ silent: false,
+ smartLists: false,
+ smartypants: false,
+ tokenizer: null,
+ walkTokens: null,
+ xhtml: false
+ };
+ }
+
+ function changeDefaults$1(newDefaults) {
+ defaults$5.exports.defaults = newDefaults;
+ }
+
+ defaults$5.exports = {
+ defaults: getDefaults$1(),
+ getDefaults: getDefaults$1,
+ changeDefaults: changeDefaults$1
+ };
+
+ var escapeTest = /[&<>"']/;
+ var escapeReplace = /[&<>"']/g;
+ var escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
+ var escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
+ var escapeReplacements = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ };
+
+ var getEscapeReplacement = function getEscapeReplacement(ch) {
+ return escapeReplacements[ch];
+ };
+
+ function escape$3(html, encode) {
+ if (encode) {
+ if (escapeTest.test(html)) {
+ return html.replace(escapeReplace, getEscapeReplacement);
+ }
+ } else {
+ if (escapeTestNoEncode.test(html)) {
+ return html.replace(escapeReplaceNoEncode, getEscapeReplacement);
+ }
+ }
+
+ return html;
+ }
+
+ var unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;
+
+ function unescape$2(html) {
+ // explicitly match decimal, hex, and named HTML entities
+ return html.replace(unescapeTest, function (_, n) {
+ n = n.toLowerCase();
+ if (n === 'colon') return ':';
+
+ if (n.charAt(0) === '#') {
+ return n.charAt(1) === 'x' ? String.fromCharCode(parseInt(n.substring(2), 16)) : String.fromCharCode(+n.substring(1));
+ }
+
+ return '';
+ });
+ }
+
+ var caret = /(^|[^\[])\^/g;
+
+ function edit$1(regex, opt) {
+ regex = regex.source || regex;
+ opt = opt || '';
+ var obj = {
+ replace: function replace(name, val) {
+ val = val.source || val;
+ val = val.replace(caret, '$1');
+ regex = regex.replace(name, val);
+ return obj;
+ },
+ getRegex: function getRegex() {
+ return new RegExp(regex, opt);
+ }
+ };
+ return obj;
+ }
+
+ var nonWordAndColonTest = /[^\w:]/g;
+ var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
+
+ function cleanUrl$1(sanitize, base, href) {
+ if (sanitize) {
+ var prot;
+
+ try {
+ prot = decodeURIComponent(unescape$2(href)).replace(nonWordAndColonTest, '').toLowerCase();
+ } catch (e) {
+ return null;
+ }
+
+ if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
+ return null;
+ }
+ }
+
+ if (base && !originIndependentUrl.test(href)) {
+ href = resolveUrl$2(base, href);
+ }
+
+ try {
+ href = encodeURI(href).replace(/%25/g, '%');
+ } catch (e) {
+ return null;
+ }
+
+ return href;
+ }
+
+ var baseUrls = {};
+ var justDomain = /^[^:]+:\/*[^/]*$/;
+ var protocol = /^([^:]+:)[\s\S]*$/;
+ var domain = /^([^:]+:\/*[^/]*)[\s\S]*$/;
+
+ function resolveUrl$2(base, href) {
+ if (!baseUrls[' ' + base]) {
+ // we can ignore everything in base after the last slash of its path component,
+ // but we might need to add _that_
+ // https://tools.ietf.org/html/rfc3986#section-3
+ if (justDomain.test(base)) {
+ baseUrls[' ' + base] = base + '/';
+ } else {
+ baseUrls[' ' + base] = rtrim$1(base, '/', true);
+ }
+ }
+
+ base = baseUrls[' ' + base];
+ var relativeBase = base.indexOf(':') === -1;
+
+ if (href.substring(0, 2) === '//') {
+ if (relativeBase) {
+ return href;
+ }
+
+ return base.replace(protocol, '$1') + href;
+ } else if (href.charAt(0) === '/') {
+ if (relativeBase) {
+ return href;
+ }
+
+ return base.replace(domain, '$1') + href;
+ } else {
+ return base + href;
+ }
+ }
+
+ var noopTest$1 = {
+ exec: function noopTest() {}
+ };
+
+ function merge$2(obj) {
+ var i = 1,
+ target,
+ key;
+
+ for (; i < arguments.length; i++) {
+ target = arguments[i];
+
+ for (key in target) {
+ if (Object.prototype.hasOwnProperty.call(target, key)) {
+ obj[key] = target[key];
+ }
+ }
+ }
+
+ return obj;
+ }
+
+ function splitCells$1(tableRow, count) {
+ // ensure that every cell-delimiting pipe has a space
+ // before it to distinguish it from an escaped pipe
+ var row = tableRow.replace(/\|/g, function (match, offset, str) {
+ var escaped = false,
+ curr = offset;
+
+ while (--curr >= 0 && str[curr] === '\\') {
+ escaped = !escaped;
+ }
+
+ if (escaped) {
+ // odd number of slashes means | is escaped
+ // so we leave it alone
+ return '|';
+ } else {
+ // add space before unescaped |
+ return ' |';
+ }
+ }),
+ cells = row.split(/ \|/);
+ var i = 0;
+
+ if (cells.length > count) {
+ cells.splice(count);
+ } else {
+ while (cells.length < count) {
+ cells.push('');
+ }
+ }
+
+ for (; i < cells.length; i++) {
+ // leading or trailing whitespace is ignored per the gfm spec
+ cells[i] = cells[i].trim().replace(/\\\|/g, '|');
+ }
+
+ return cells;
+ } // Remove trailing 'c's. Equivalent to str.replace(/c*$/, '').
+ // /c*$/ is vulnerable to REDOS.
+ // invert: Remove suffix of non-c chars instead. Default falsey.
+
+
+ function rtrim$1(str, c, invert) {
+ var l = str.length;
+
+ if (l === 0) {
+ return '';
+ } // Length of suffix matching the invert condition.
+
+
+ var suffLen = 0; // Step left until we fail to match the invert condition.
+
+ while (suffLen < l) {
+ var currChar = str.charAt(l - suffLen - 1);
+
+ if (currChar === c && !invert) {
+ suffLen++;
+ } else if (currChar !== c && invert) {
+ suffLen++;
+ } else {
+ break;
+ }
+ }
+
+ return str.substr(0, l - suffLen);
+ }
+
+ function findClosingBracket$1(str, b) {
+ if (str.indexOf(b[1]) === -1) {
+ return -1;
+ }
+
+ var l = str.length;
+ var level = 0,
+ i = 0;
+
+ for (; i < l; i++) {
+ if (str[i] === '\\') {
+ i++;
+ } else if (str[i] === b[0]) {
+ level++;
+ } else if (str[i] === b[1]) {
+ level--;
+
+ if (level < 0) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ function checkSanitizeDeprecation$1(opt) {
+ if (opt && opt.sanitize && !opt.silent) {
+ console.warn('marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options');
+ }
+ } // copied from https://stackoverflow.com/a/5450113/806777
+
+
+ function repeatString$1(pattern, count) {
+ if (count < 1) {
+ return '';
+ }
+
+ var result = '';
+
+ while (count > 1) {
+ if (count & 1) {
+ result += pattern;
+ }
+
+ count >>= 1;
+ pattern += pattern;
+ }
+
+ return result + pattern;
+ }
+
+ var helpers = {
+ escape: escape$3,
+ unescape: unescape$2,
+ edit: edit$1,
+ cleanUrl: cleanUrl$1,
+ resolveUrl: resolveUrl$2,
+ noopTest: noopTest$1,
+ merge: merge$2,
+ splitCells: splitCells$1,
+ rtrim: rtrim$1,
+ findClosingBracket: findClosingBracket$1,
+ checkSanitizeDeprecation: checkSanitizeDeprecation$1,
+ repeatString: repeatString$1
+ };
+
+ var defaults$4 = defaults$5.exports.defaults;
+ var rtrim = helpers.rtrim,
+ splitCells = helpers.splitCells,
+ _escape = helpers.escape,
+ findClosingBracket = helpers.findClosingBracket;
+
+ function outputLink(cap, link, raw) {
+ var href = link.href;
+ var title = link.title ? _escape(link.title) : null;
+ var text = cap[1].replace(/\\([\[\]])/g, '$1');
+
+ if (cap[0].charAt(0) !== '!') {
+ return {
+ type: 'link',
+ raw: raw,
+ href: href,
+ title: title,
+ text: text
+ };
+ } else {
+ return {
+ type: 'image',
+ raw: raw,
+ href: href,
+ title: title,
+ text: _escape(text)
+ };
+ }
+ }
+
+ function indentCodeCompensation(raw, text) {
+ var matchIndentToCode = raw.match(/^(\s+)(?:```)/);
+
+ if (matchIndentToCode === null) {
+ return text;
+ }
+
+ var indentToCode = matchIndentToCode[1];
+ return text.split('\n').map(function (node) {
+ var matchIndentInNode = node.match(/^\s+/);
+
+ if (matchIndentInNode === null) {
+ return node;
+ }
+
+ var _matchIndentInNode = _slicedToArray(matchIndentInNode, 1),
+ indentInNode = _matchIndentInNode[0];
+
+ if (indentInNode.length >= indentToCode.length) {
+ return node.slice(indentToCode.length);
+ }
+
+ return node;
+ }).join('\n');
+ }
+ /**
+ * Tokenizer
+ */
+
+
+ var Tokenizer_1 = /*#__PURE__*/function () {
+ function Tokenizer(options) {
+ _classCallCheck$1(this, Tokenizer);
+
+ this.options = options || defaults$4;
+ }
+
+ _createClass$1(Tokenizer, [{
+ key: "space",
+ value: function space(src) {
+ var cap = this.rules.block.newline.exec(src);
+
+ if (cap) {
+ if (cap[0].length > 1) {
+ return {
+ type: 'space',
+ raw: cap[0]
+ };
+ }
+
+ return {
+ raw: '\n'
+ };
+ }
+ }
+ }, {
+ key: "code",
+ value: function code(src) {
+ var cap = this.rules.block.code.exec(src);
+
+ if (cap) {
+ var text = cap[0].replace(/^ {1,4}/gm, '');
+ return {
+ type: 'code',
+ raw: cap[0],
+ codeBlockStyle: 'indented',
+ text: !this.options.pedantic ? rtrim(text, '\n') : text
+ };
+ }
+ }
+ }, {
+ key: "fences",
+ value: function fences(src) {
+ var cap = this.rules.block.fences.exec(src);
+
+ if (cap) {
+ var raw = cap[0];
+ var text = indentCodeCompensation(raw, cap[3] || '');
+ return {
+ type: 'code',
+ raw: raw,
+ lang: cap[2] ? cap[2].trim() : cap[2],
+ text: text
+ };
+ }
+ }
+ }, {
+ key: "heading",
+ value: function heading(src) {
+ var cap = this.rules.block.heading.exec(src);
+
+ if (cap) {
+ var text = cap[2].trim(); // remove trailing #s
+
+ if (/#$/.test(text)) {
+ var trimmed = rtrim(text, '#');
+
+ if (this.options.pedantic) {
+ text = trimmed.trim();
+ } else if (!trimmed || / $/.test(trimmed)) {
+ // CommonMark requires space before trailing #s
+ text = trimmed.trim();
+ }
+ }
+
+ return {
+ type: 'heading',
+ raw: cap[0],
+ depth: cap[1].length,
+ text: text
+ };
+ }
+ }
+ }, {
+ key: "nptable",
+ value: function nptable(src) {
+ var cap = this.rules.block.nptable.exec(src);
+
+ if (cap) {
+ var item = {
+ type: 'table',
+ header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+ cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [],
+ raw: cap[0]
+ };
+
+ if (item.header.length === item.align.length) {
+ var l = item.align.length;
+ var i;
+
+ for (i = 0; i < l; i++) {
+ if (/^ *-+: *$/.test(item.align[i])) {
+ item.align[i] = 'right';
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
+ item.align[i] = 'center';
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
+ item.align[i] = 'left';
+ } else {
+ item.align[i] = null;
+ }
+ }
+
+ l = item.cells.length;
+
+ for (i = 0; i < l; i++) {
+ item.cells[i] = splitCells(item.cells[i], item.header.length);
+ }
+
+ return item;
+ }
+ }
+ }
+ }, {
+ key: "hr",
+ value: function hr(src) {
+ var cap = this.rules.block.hr.exec(src);
+
+ if (cap) {
+ return {
+ type: 'hr',
+ raw: cap[0]
+ };
+ }
+ }
+ }, {
+ key: "blockquote",
+ value: function blockquote(src) {
+ var cap = this.rules.block.blockquote.exec(src);
+
+ if (cap) {
+ var text = cap[0].replace(/^ *> ?/gm, '');
+ return {
+ type: 'blockquote',
+ raw: cap[0],
+ text: text
+ };
+ }
+ }
+ }, {
+ key: "list",
+ value: function list(src) {
+ var cap = this.rules.block.list.exec(src);
+
+ if (cap) {
+ var raw = cap[0];
+ var bull = cap[2];
+ var isordered = bull.length > 1;
+ var list = {
+ type: 'list',
+ raw: raw,
+ ordered: isordered,
+ start: isordered ? +bull.slice(0, -1) : '',
+ loose: false,
+ items: []
+ }; // Get each top-level item.
+
+ var itemMatch = cap[0].match(this.rules.block.item);
+ var next = false,
+ item,
+ space,
+ bcurr,
+ bnext,
+ addBack,
+ loose,
+ istask,
+ ischecked,
+ endMatch;
+ var l = itemMatch.length;
+ bcurr = this.rules.block.listItemStart.exec(itemMatch[0]);
+
+ for (var i = 0; i < l; i++) {
+ item = itemMatch[i];
+ raw = item;
+
+ if (!this.options.pedantic) {
+ // Determine if current item contains the end of the list
+ endMatch = item.match(new RegExp('\\n\\s*\\n {0,' + (bcurr[0].length - 1) + '}\\S'));
+
+ if (endMatch) {
+ addBack = item.length - endMatch.index + itemMatch.slice(i + 1).join('\n').length;
+ list.raw = list.raw.substring(0, list.raw.length - addBack);
+ item = item.substring(0, endMatch.index);
+ raw = item;
+ l = i + 1;
+ }
+ } // Determine whether the next list item belongs here.
+ // Backpedal if it does not belong in this list.
+
+
+ if (i !== l - 1) {
+ bnext = this.rules.block.listItemStart.exec(itemMatch[i + 1]);
+
+ if (!this.options.pedantic ? bnext[1].length >= bcurr[0].length || bnext[1].length > 3 : bnext[1].length > bcurr[1].length) {
+ // nested list or continuation
+ itemMatch.splice(i, 2, itemMatch[i] + (!this.options.pedantic && bnext[1].length < bcurr[0].length && !itemMatch[i].match(/\n$/) ? '' : '\n') + itemMatch[i + 1]);
+ i--;
+ l--;
+ continue;
+ } else if ( // different bullet style
+ !this.options.pedantic || this.options.smartLists ? bnext[2][bnext[2].length - 1] !== bull[bull.length - 1] : isordered === (bnext[2].length === 1)) {
+ addBack = itemMatch.slice(i + 1).join('\n').length;
+ list.raw = list.raw.substring(0, list.raw.length - addBack);
+ i = l - 1;
+ }
+
+ bcurr = bnext;
+ } // Remove the list item's bullet
+ // so it is seen as the next token.
+
+
+ space = item.length;
+ item = item.replace(/^ *([*+-]|\d+[.)]) ?/, ''); // Outdent whatever the
+ // list item contains. Hacky.
+
+ if (~item.indexOf('\n ')) {
+ space -= item.length;
+ item = !this.options.pedantic ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') : item.replace(/^ {1,4}/gm, '');
+ } // trim item newlines at end
+
+
+ item = rtrim(item, '\n');
+
+ if (i !== l - 1) {
+ raw = raw + '\n';
+ } // Determine whether item is loose or not.
+ // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
+ // for discount behavior.
+
+
+ loose = next || /\n\n(?!\s*$)/.test(raw);
+
+ if (i !== l - 1) {
+ next = raw.slice(-2) === '\n\n';
+ if (!loose) loose = next;
+ }
+
+ if (loose) {
+ list.loose = true;
+ } // Check for task list items
+
+
+ if (this.options.gfm) {
+ istask = /^\[[ xX]\] /.test(item);
+ ischecked = undefined;
+
+ if (istask) {
+ ischecked = item[1] !== ' ';
+ item = item.replace(/^\[[ xX]\] +/, '');
+ }
+ }
+
+ list.items.push({
+ type: 'list_item',
+ raw: raw,
+ task: istask,
+ checked: ischecked,
+ loose: loose,
+ text: item
+ });
+ }
+
+ return list;
+ }
+ }
+ }, {
+ key: "html",
+ value: function html(src) {
+ var cap = this.rules.block.html.exec(src);
+
+ if (cap) {
+ return {
+ type: this.options.sanitize ? 'paragraph' : 'html',
+ raw: cap[0],
+ pre: !this.options.sanitizer && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
+ text: this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : _escape(cap[0]) : cap[0]
+ };
+ }
+ }
+ }, {
+ key: "def",
+ value: function def(src) {
+ var cap = this.rules.block.def.exec(src);
+
+ if (cap) {
+ if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1);
+ var tag = cap[1].toLowerCase().replace(/\s+/g, ' ');
+ return {
+ type: 'def',
+ tag: tag,
+ raw: cap[0],
+ href: cap[2],
+ title: cap[3]
+ };
+ }
+ }
+ }, {
+ key: "table",
+ value: function table(src) {
+ var cap = this.rules.block.table.exec(src);
+
+ if (cap) {
+ var item = {
+ type: 'table',
+ header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+ cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : []
+ };
+
+ if (item.header.length === item.align.length) {
+ item.raw = cap[0];
+ var l = item.align.length;
+ var i;
+
+ for (i = 0; i < l; i++) {
+ if (/^ *-+: *$/.test(item.align[i])) {
+ item.align[i] = 'right';
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
+ item.align[i] = 'center';
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
+ item.align[i] = 'left';
+ } else {
+ item.align[i] = null;
+ }
+ }
+
+ l = item.cells.length;
+
+ for (i = 0; i < l; i++) {
+ item.cells[i] = splitCells(item.cells[i].replace(/^ *\| *| *\| *$/g, ''), item.header.length);
+ }
+
+ return item;
+ }
+ }
+ }
+ }, {
+ key: "lheading",
+ value: function lheading(src) {
+ var cap = this.rules.block.lheading.exec(src);
+
+ if (cap) {
+ return {
+ type: 'heading',
+ raw: cap[0],
+ depth: cap[2].charAt(0) === '=' ? 1 : 2,
+ text: cap[1]
+ };
+ }
+ }
+ }, {
+ key: "paragraph",
+ value: function paragraph(src) {
+ var cap = this.rules.block.paragraph.exec(src);
+
+ if (cap) {
+ return {
+ type: 'paragraph',
+ raw: cap[0],
+ text: cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1]
+ };
+ }
+ }
+ }, {
+ key: "text",
+ value: function text(src) {
+ var cap = this.rules.block.text.exec(src);
+
+ if (cap) {
+ return {
+ type: 'text',
+ raw: cap[0],
+ text: cap[0]
+ };
+ }
+ }
+ }, {
+ key: "escape",
+ value: function escape(src) {
+ var cap = this.rules.inline.escape.exec(src);
+
+ if (cap) {
+ return {
+ type: 'escape',
+ raw: cap[0],
+ text: _escape(cap[1])
+ };
+ }
+ }
+ }, {
+ key: "tag",
+ value: function tag(src, inLink, inRawBlock) {
+ var cap = this.rules.inline.tag.exec(src);
+
+ if (cap) {
+ if (!inLink && /^<a /i.test(cap[0])) {
+ inLink = true;
+ } else if (inLink && /^<\/a>/i.test(cap[0])) {
+ inLink = false;
+ }
+
+ if (!inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
+ inRawBlock = true;
+ } else if (inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
+ inRawBlock = false;
+ }
+
+ return {
+ type: this.options.sanitize ? 'text' : 'html',
+ raw: cap[0],
+ inLink: inLink,
+ inRawBlock: inRawBlock,
+ text: this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : _escape(cap[0]) : cap[0]
+ };
+ }
+ }
+ }, {
+ key: "link",
+ value: function link(src) {
+ var cap = this.rules.inline.link.exec(src);
+
+ if (cap) {
+ var trimmedUrl = cap[2].trim();
+
+ if (!this.options.pedantic && /^</.test(trimmedUrl)) {
+ // commonmark requires matching angle brackets
+ if (!/>$/.test(trimmedUrl)) {
+ return;
+ } // ending angle bracket cannot be escaped
+
+
+ var rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\');
+
+ if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {
+ return;
+ }
+ } else {
+ // find closing parenthesis
+ var lastParenIndex = findClosingBracket(cap[2], '()');
+
+ if (lastParenIndex > -1) {
+ var start = cap[0].indexOf('!') === 0 ? 5 : 4;
+ var linkLen = start + cap[1].length + lastParenIndex;
+ cap[2] = cap[2].substring(0, lastParenIndex);
+ cap[0] = cap[0].substring(0, linkLen).trim();
+ cap[3] = '';
+ }
+ }
+
+ var href = cap[2];
+ var title = '';
+
+ if (this.options.pedantic) {
+ // split pedantic href and title
+ var link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
+
+ if (link) {
+ href = link[1];
+ title = link[3];
+ }
+ } else {
+ title = cap[3] ? cap[3].slice(1, -1) : '';
+ }
+
+ href = href.trim();
+
+ if (/^</.test(href)) {
+ if (this.options.pedantic && !/>$/.test(trimmedUrl)) {
+ // pedantic allows starting angle bracket without ending angle bracket
+ href = href.slice(1);
+ } else {
+ href = href.slice(1, -1);
+ }
+ }
+
+ return outputLink(cap, {
+ href: href ? href.replace(this.rules.inline._escapes, '$1') : href,
+ title: title ? title.replace(this.rules.inline._escapes, '$1') : title
+ }, cap[0]);
+ }
+ }
+ }, {
+ key: "reflink",
+ value: function reflink(src, links) {
+ var cap;
+
+ if ((cap = this.rules.inline.reflink.exec(src)) || (cap = this.rules.inline.nolink.exec(src))) {
+ var link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
+ link = links[link.toLowerCase()];
+
+ if (!link || !link.href) {
+ var text = cap[0].charAt(0);
+ return {
+ type: 'text',
+ raw: text,
+ text: text
+ };
+ }
+
+ return outputLink(cap, link, cap[0]);
+ }
+ }
+ }, {
+ key: "emStrong",
+ value: function emStrong(src, maskedSrc) {
+ var prevChar = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
+ var match = this.rules.inline.emStrong.lDelim.exec(src);
+ if (!match) return; // _ can't be between two alphanumerics. \p{L}\p{N} includes non-english alphabet/numbers as well
+
+ if (match[3] && prevChar.match(/(?:[0-9A-Za-z\xAA\xB2\xB3\xB5\xB9\xBA\xBC-\xBE\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088E\u08A0-\u08C9\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09F9\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BF2\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C66-\u0C6F\u0C78-\u0C7E\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D58-\u0D61\u0D66-\u0D78\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F33\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1369-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4C\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2070\u2071\u2074-\u2079\u207F-\u2089\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2150-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2CFD\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3192-\u3195\u31A0-\u31BF\u31F0-\u31FF\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA830-\uA835\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDE80-\uDE9C\uDEA0-\uDED0\uDEE1-\uDEFB\uDF00-\uDF23\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC58-\uDC76\uDC79-\uDC9E\uDCA7-\uDCAF\uDCE0-\uDCF2\uDCF4\uDCF5\uDCFB-\uDD1B\uDD20-\uDD39\uDD80-\uDDB7\uDDBC-\uDDCF\uDDD2-\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE40-\uDE48\uDE60-\uDE7E\uDE80-\uDE9F\uDEC0-\uDEC7\uDEC9-\uDEE4\uDEEB-\uDEEF\uDF00-\uDF35\uDF40-\uDF55\uDF58-\uDF72\uDF78-\uDF91\uDFA9-\uDFAF]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDCFA-\uDD23\uDD30-\uDD39\uDE60-\uDE7E\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF27\uDF30-\uDF45\uDF51-\uDF54\uDF70-\uDF81\uDFB0-\uDFCB\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC52-\uDC6F\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDDE1-\uDDF4\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF30-\uDF3B\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCF2\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC6C\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF2\uDFB0\uDFC0-\uDFD4]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDE70-\uDEBE\uDEC0-\uDEC9\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF5B-\uDF61\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE96\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD834[\uDEE0-\uDEF3\uDF60-\uDF78]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD837[\uDF00-\uDF1E]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDCC7-\uDCCF\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83C[\uDD00-\uDD0C]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])/)) return;
+ var nextChar = match[1] || match[2] || '';
+
+ if (!nextChar || nextChar && (prevChar === '' || this.rules.inline.punctuation.exec(prevChar))) {
+ var lLength = match[0].length - 1;
+ var rDelim,
+ rLength,
+ delimTotal = lLength,
+ midDelimTotal = 0;
+ var endReg = match[0][0] === '*' ? this.rules.inline.emStrong.rDelimAst : this.rules.inline.emStrong.rDelimUnd;
+ endReg.lastIndex = 0; // Clip maskedSrc to same section of string as src (move to lexer?)
+
+ maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
+
+ while ((match = endReg.exec(maskedSrc)) != null) {
+ rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
+ if (!rDelim) continue; // skip single * in __abc*abc__
+
+ rLength = rDelim.length;
+
+ if (match[3] || match[4]) {
+ // found another Left Delim
+ delimTotal += rLength;
+ continue;
+ } else if (match[5] || match[6]) {
+ // either Left or Right Delim
+ if (lLength % 3 && !((lLength + rLength) % 3)) {
+ midDelimTotal += rLength;
+ continue; // CommonMark Emphasis Rules 9-10
+ }
+ }
+
+ delimTotal -= rLength;
+ if (delimTotal > 0) continue; // Haven't found enough closing delimiters
+ // Remove extra characters. *a*** -> *a*
+
+ rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal); // Create `em` if smallest delimiter has odd char count. *a***
+
+ if (Math.min(lLength, rLength) % 2) {
+ return {
+ type: 'em',
+ raw: src.slice(0, lLength + match.index + rLength + 1),
+ text: src.slice(1, lLength + match.index + rLength)
+ };
+ } // Create 'strong' if smallest delimiter has even char count. **a***
+
+
+ return {
+ type: 'strong',
+ raw: src.slice(0, lLength + match.index + rLength + 1),
+ text: src.slice(2, lLength + match.index + rLength - 1)
+ };
+ }
+ }
+ }
+ }, {
+ key: "codespan",
+ value: function codespan(src) {
+ var cap = this.rules.inline.code.exec(src);
+
+ if (cap) {
+ var text = cap[2].replace(/\n/g, ' ');
+ var hasNonSpaceChars = /[^ ]/.test(text);
+ var hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text);
+
+ if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
+ text = text.substring(1, text.length - 1);
+ }
+
+ text = _escape(text, true);
+ return {
+ type: 'codespan',
+ raw: cap[0],
+ text: text
+ };
+ }
+ }
+ }, {
+ key: "br",
+ value: function br(src) {
+ var cap = this.rules.inline.br.exec(src);
+
+ if (cap) {
+ return {
+ type: 'br',
+ raw: cap[0]
+ };
+ }
+ }
+ }, {
+ key: "del",
+ value: function del(src) {
+ var cap = this.rules.inline.del.exec(src);
+
+ if (cap) {
+ return {
+ type: 'del',
+ raw: cap[0],
+ text: cap[2]
+ };
+ }
+ }
+ }, {
+ key: "autolink",
+ value: function autolink(src, mangle) {
+ var cap = this.rules.inline.autolink.exec(src);
+
+ if (cap) {
+ var text, href;
+
+ if (cap[2] === '@') {
+ text = _escape(this.options.mangle ? mangle(cap[1]) : cap[1]);
+ href = 'mailto:' + text;
+ } else {
+ text = _escape(cap[1]);
+ href = text;
+ }
+
+ return {
+ type: 'link',
+ raw: cap[0],
+ text: text,
+ href: href,
+ tokens: [{
+ type: 'text',
+ raw: text,
+ text: text
+ }]
+ };
+ }
+ }
+ }, {
+ key: "url",
+ value: function url(src, mangle) {
+ var cap;
+
+ if (cap = this.rules.inline.url.exec(src)) {
+ var text, href;
+
+ if (cap[2] === '@') {
+ text = _escape(this.options.mangle ? mangle(cap[0]) : cap[0]);
+ href = 'mailto:' + text;
+ } else {
+ // do extended autolink path validation
+ var prevCapZero;
+
+ do {
+ prevCapZero = cap[0];
+ cap[0] = this.rules.inline._backpedal.exec(cap[0])[0];
+ } while (prevCapZero !== cap[0]);
+
+ text = _escape(cap[0]);
+
+ if (cap[1] === 'www.') {
+ href = 'http://' + text;
+ } else {
+ href = text;
+ }
+ }
+
+ return {
+ type: 'link',
+ raw: cap[0],
+ text: text,
+ href: href,
+ tokens: [{
+ type: 'text',
+ raw: text,
+ text: text
+ }]
+ };
+ }
+ }
+ }, {
+ key: "inlineText",
+ value: function inlineText(src, inRawBlock, smartypants) {
+ var cap = this.rules.inline.text.exec(src);
+
+ if (cap) {
+ var text;
+
+ if (inRawBlock) {
+ text = this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : _escape(cap[0]) : cap[0];
+ } else {
+ text = _escape(this.options.smartypants ? smartypants(cap[0]) : cap[0]);
+ }
+
+ return {
+ type: 'text',
+ raw: cap[0],
+ text: text
+ };
+ }
+ }
+ }]);
+
+ return Tokenizer;
+ }();
+
+ var noopTest = helpers.noopTest,
+ edit = helpers.edit,
+ merge$1 = helpers.merge;
+ /**
+ * Block-Level Grammar
+ */
+
+ var block$1 = {
+ newline: /^(?: *(?:\n|$))+/,
+ code: /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,
+ fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,
+ hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
+ heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,
+ blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,
+ list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/,
+ html: '^ {0,3}(?:' // optional indentation
+ + '<(script|pre|style)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1)
+ + '|comment[^\\n]*(\\n+|$)' // (2)
+ + '|<\\?[\\s\\S]*?(?:\\?>\\n*|$)' // (3)
+ + '|<![A-Z][\\s\\S]*?(?:>\\n*|$)' // (4)
+ + '|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)' // (5)
+ + '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (6)
+ + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) open tag
+ + '|</(?!script|pre|style)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) closing tag
+ + ')',
+ def: /^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,
+ nptable: noopTest,
+ table: noopTest,
+ lheading: /^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,
+ // regex template, placeholders will be replaced according to different paragraph
+ // interruption rules of commonmark and the original markdown spec:
+ _paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html| +\n)[^\n]+)*)/,
+ text: /^[^\n]+/
+ };
+ block$1._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/;
+ block$1._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/;
+ block$1.def = edit(block$1.def).replace('label', block$1._label).replace('title', block$1._title).getRegex();
+ block$1.bullet = /(?:[*+-]|\d{1,9}[.)])/;
+ block$1.item = /^( *)(bull) ?[^\n]*(?:\n(?! *bull ?)[^\n]*)*/;
+ block$1.item = edit(block$1.item, 'gm').replace(/bull/g, block$1.bullet).getRegex();
+ block$1.listItemStart = edit(/^( *)(bull) */).replace('bull', block$1.bullet).getRegex();
+ block$1.list = edit(block$1.list).replace(/bull/g, block$1.bullet).replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))').replace('def', '\\n+(?=' + block$1.def.source + ')').getRegex();
+ block$1._tag = 'address|article|aside|base|basefont|blockquote|body|caption' + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' + '|track|ul';
+ block$1._comment = /<!--(?!-?>)[\s\S]*?(?:-->|$)/;
+ block$1.html = edit(block$1.html, 'i').replace('comment', block$1._comment).replace('tag', block$1._tag).replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
+ block$1.paragraph = edit(block$1._paragraph).replace('hr', block$1.hr).replace('heading', ' {0,3}#{1,6} ').replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs
+ .replace('blockquote', ' {0,3}>').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
+ .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)').replace('tag', block$1._tag) // pars can be interrupted by type (6) html blocks
+ .getRegex();
+ block$1.blockquote = edit(block$1.blockquote).replace('paragraph', block$1.paragraph).getRegex();
+ /**
+ * Normal Block Grammar
+ */
+
+ block$1.normal = merge$1({}, block$1);
+ /**
+ * GFM Block Grammar
+ */
+
+ block$1.gfm = merge$1({}, block$1.normal, {
+ nptable: '^ *([^|\\n ].*\\|.*)\\n' // Header
+ + ' {0,3}([-:]+ *\\|[-| :]*)' // Align
+ + '(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)',
+ // Cells
+ table: '^ *\\|(.+)\\n' // Header
+ + ' {0,3}\\|?( *[-:]+[-| :]*)' // Align
+ + '(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)' // Cells
+
+ });
+ block$1.gfm.nptable = edit(block$1.gfm.nptable).replace('hr', block$1.hr).replace('heading', ' {0,3}#{1,6} ').replace('blockquote', ' {0,3}>').replace('code', ' {4}[^\\n]').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
+ .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)').replace('tag', block$1._tag) // tables can be interrupted by type (6) html blocks
+ .getRegex();
+ block$1.gfm.table = edit(block$1.gfm.table).replace('hr', block$1.hr).replace('heading', ' {0,3}#{1,6} ').replace('blockquote', ' {0,3}>').replace('code', ' {4}[^\\n]').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
+ .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)').replace('tag', block$1._tag) // tables can be interrupted by type (6) html blocks
+ .getRegex();
+ /**
+ * Pedantic grammar (original John Gruber's loose markdown specification)
+ */
+
+ block$1.pedantic = merge$1({}, block$1.normal, {
+ html: edit('^ *(?:comment *(?:\\n|\\s*$)' + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag
+ + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))').replace('comment', block$1._comment).replace(/tag/g, '(?!(?:' + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b').getRegex(),
+ def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
+ heading: /^(#{1,6})(.*)(?:\n+|$)/,
+ fences: noopTest,
+ // fences not supported
+ paragraph: edit(block$1.normal._paragraph).replace('hr', block$1.hr).replace('heading', ' *#{1,6} *[^\n]').replace('lheading', block$1.lheading).replace('blockquote', ' {0,3}>').replace('|fences', '').replace('|list', '').replace('|html', '').getRegex()
+ });
+ /**
+ * Inline-Level Grammar
+ */
+
+ var inline$1 = {
+ escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,
+ autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/,
+ url: noopTest,
+ tag: '^comment' + '|^</[a-zA-Z][\\w:-]*\\s*>' // self-closing tag
+ + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag
+ + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?>
+ + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html>
+ + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>',
+ // CDATA section
+ link: /^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,
+ reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,
+ nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,
+ reflinkSearch: 'reflink|nolink(?!\\()',
+ emStrong: {
+ lDelim: /^(?:\*+(?:([punct_])|[^\s*]))|^_+(?:([punct*])|([^\s_]))/,
+ // (1) and (2) can only be a Right Delimiter. (3) and (4) can only be Left. (5) and (6) can be either Left or Right.
+ // () Skip other delimiter (1) #*** (2) a***#, a*** (3) #***a, ***a (4) ***# (5) #***# (6) a***a
+ rDelimAst: /\_\_[^_*]*?\*[^_*]*?\_\_|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/,
+ rDelimUnd: /\*\*[^_*]*?\_[^_*]*?\*\*|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/ // ^- Not allowed for _
+
+ },
+ code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,
+ br: /^( {2,}|\\)\n(?!\s*$)/,
+ del: noopTest,
+ text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,
+ punctuation: /^([\spunctuation])/
+ }; // list of punctuation marks from CommonMark spec
+ // without * and _ to handle the different emphasis markers * and _
+
+ inline$1._punctuation = '!"#$%&\'()+\\-.,/:;<=>?@\\[\\]`^{|}~';
+ inline$1.punctuation = edit(inline$1.punctuation).replace(/punctuation/g, inline$1._punctuation).getRegex(); // sequences em should skip over [title](link), `code`, <html>
+
+ inline$1.blockSkip = /\[[^\]]*?\]\([^\)]*?\)|`[^`]*?`|<[^>]*?>/g;
+ inline$1.escapedEmSt = /\\\*|\\_/g;
+ inline$1._comment = edit(block$1._comment).replace('(?:-->|$)', '-->').getRegex();
+ inline$1.emStrong.lDelim = edit(inline$1.emStrong.lDelim).replace(/punct/g, inline$1._punctuation).getRegex();
+ inline$1.emStrong.rDelimAst = edit(inline$1.emStrong.rDelimAst, 'g').replace(/punct/g, inline$1._punctuation).getRegex();
+ inline$1.emStrong.rDelimUnd = edit(inline$1.emStrong.rDelimUnd, 'g').replace(/punct/g, inline$1._punctuation).getRegex();
+ inline$1._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g;
+ inline$1._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/;
+ inline$1._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/;
+ inline$1.autolink = edit(inline$1.autolink).replace('scheme', inline$1._scheme).replace('email', inline$1._email).getRegex();
+ inline$1._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/;
+ inline$1.tag = edit(inline$1.tag).replace('comment', inline$1._comment).replace('attribute', inline$1._attribute).getRegex();
+ inline$1._label = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/;
+ inline$1._href = /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/;
+ inline$1._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/;
+ inline$1.link = edit(inline$1.link).replace('label', inline$1._label).replace('href', inline$1._href).replace('title', inline$1._title).getRegex();
+ inline$1.reflink = edit(inline$1.reflink).replace('label', inline$1._label).getRegex();
+ inline$1.reflinkSearch = edit(inline$1.reflinkSearch, 'g').replace('reflink', inline$1.reflink).replace('nolink', inline$1.nolink).getRegex();
+ /**
+ * Normal Inline Grammar
+ */
+
+ inline$1.normal = merge$1({}, inline$1);
+ /**
+ * Pedantic Inline Grammar
+ */
+
+ inline$1.pedantic = merge$1({}, inline$1.normal, {
+ strong: {
+ start: /^__|\*\*/,
+ middle: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
+ endAst: /\*\*(?!\*)/g,
+ endUnd: /__(?!_)/g
+ },
+ em: {
+ start: /^_|\*/,
+ middle: /^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,
+ endAst: /\*(?!\*)/g,
+ endUnd: /_(?!_)/g
+ },
+ link: edit(/^!?\[(label)\]\((.*?)\)/).replace('label', inline$1._label).getRegex(),
+ reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace('label', inline$1._label).getRegex()
+ });
+ /**
+ * GFM Inline Grammar
+ */
+
+ inline$1.gfm = merge$1({}, inline$1.normal, {
+ escape: edit(inline$1.escape).replace('])', '~|])').getRegex(),
+ _extended_email: /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,
+ url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,
+ _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,
+ del: /^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,
+ text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/
+ });
+ inline$1.gfm.url = edit(inline$1.gfm.url, 'i').replace('email', inline$1.gfm._extended_email).getRegex();
+ /**
+ * GFM + Line Breaks Inline Grammar
+ */
+
+ inline$1.breaks = merge$1({}, inline$1.gfm, {
+ br: edit(inline$1.br).replace('{2,}', '*').getRegex(),
+ text: edit(inline$1.gfm.text).replace('\\b_', '\\b_| {2,}\\n').replace(/\{2,\}/g, '*').getRegex()
+ });
+ var rules = {
+ block: block$1,
+ inline: inline$1
+ };
+
+ var Tokenizer$1 = Tokenizer_1;
+ var defaults$3 = defaults$5.exports.defaults;
+ var block = rules.block,
+ inline = rules.inline;
+ var repeatString = helpers.repeatString;
+ /**
+ * smartypants text replacement
+ */
+
+ function smartypants(text) {
+ return text // em-dashes
+ .replace(/---/g, "\u2014") // en-dashes
+ .replace(/--/g, "\u2013") // opening singles
+ .replace(/(^|[-\u2014/(\[{"\s])'/g, "$1\u2018") // closing singles & apostrophes
+ .replace(/'/g, "\u2019") // opening doubles
+ .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, "$1\u201C") // closing doubles
+ .replace(/"/g, "\u201D") // ellipses
+ .replace(/\.{3}/g, "\u2026");
+ }
+ /**
+ * mangle email addresses
+ */
+
+
+ function mangle(text) {
+ var out = '',
+ i,
+ ch;
+ var l = text.length;
+
+ for (i = 0; i < l; i++) {
+ ch = text.charCodeAt(i);
+
+ if (Math.random() > 0.5) {
+ ch = 'x' + ch.toString(16);
+ }
+
+ out += '&#' + ch + ';';
+ }
+
+ return out;
+ }
+ /**
+ * Block Lexer
+ */
+
+
+ var Lexer_1 = /*#__PURE__*/function () {
+ function Lexer(options) {
+ _classCallCheck$1(this, Lexer);
+
+ this.tokens = [];
+ this.tokens.links = Object.create(null);
+ this.options = options || defaults$3;
+ this.options.tokenizer = this.options.tokenizer || new Tokenizer$1();
+ this.tokenizer = this.options.tokenizer;
+ this.tokenizer.options = this.options;
+ var rules = {
+ block: block.normal,
+ inline: inline.normal
+ };
+
+ if (this.options.pedantic) {
+ rules.block = block.pedantic;
+ rules.inline = inline.pedantic;
+ } else if (this.options.gfm) {
+ rules.block = block.gfm;
+
+ if (this.options.breaks) {
+ rules.inline = inline.breaks;
+ } else {
+ rules.inline = inline.gfm;
+ }
+ }
+
+ this.tokenizer.rules = rules;
+ }
+ /**
+ * Expose Rules
+ */
+
+
+ _createClass$1(Lexer, [{
+ key: "lex",
+ value:
+ /**
+ * Preprocessing
+ */
+ function lex(src) {
+ src = src.replace(/\r\n|\r/g, '\n').replace(/\t/g, ' ');
+ this.blockTokens(src, this.tokens, true);
+ this.inline(this.tokens);
+ return this.tokens;
+ }
+ /**
+ * Lexing
+ */
+
+ }, {
+ key: "blockTokens",
+ value: function blockTokens(src) {
+ var tokens = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
+ var top = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
+
+ if (this.options.pedantic) {
+ src = src.replace(/^ +$/gm, '');
+ }
+
+ var token, i, l, lastToken;
+
+ while (src) {
+ // newline
+ if (token = this.tokenizer.space(src)) {
+ src = src.substring(token.raw.length);
+
+ if (token.type) {
+ tokens.push(token);
+ }
+
+ continue;
+ } // code
+
+
+ if (token = this.tokenizer.code(src)) {
+ src = src.substring(token.raw.length);
+ lastToken = tokens[tokens.length - 1]; // An indented code block cannot interrupt a paragraph.
+
+ if (lastToken && lastToken.type === 'paragraph') {
+ lastToken.raw += '\n' + token.raw;
+ lastToken.text += '\n' + token.text;
+ } else {
+ tokens.push(token);
+ }
+
+ continue;
+ } // fences
+
+
+ if (token = this.tokenizer.fences(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // heading
+
+
+ if (token = this.tokenizer.heading(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // table no leading pipe (gfm)
+
+
+ if (token = this.tokenizer.nptable(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // hr
+
+
+ if (token = this.tokenizer.hr(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // blockquote
+
+
+ if (token = this.tokenizer.blockquote(src)) {
+ src = src.substring(token.raw.length);
+ token.tokens = this.blockTokens(token.text, [], top);
+ tokens.push(token);
+ continue;
+ } // list
+
+
+ if (token = this.tokenizer.list(src)) {
+ src = src.substring(token.raw.length);
+ l = token.items.length;
+
+ for (i = 0; i < l; i++) {
+ token.items[i].tokens = this.blockTokens(token.items[i].text, [], false);
+ }
+
+ tokens.push(token);
+ continue;
+ } // html
+
+
+ if (token = this.tokenizer.html(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // def
+
+
+ if (top && (token = this.tokenizer.def(src))) {
+ src = src.substring(token.raw.length);
+
+ if (!this.tokens.links[token.tag]) {
+ this.tokens.links[token.tag] = {
+ href: token.href,
+ title: token.title
+ };
+ }
+
+ continue;
+ } // table (gfm)
+
+
+ if (token = this.tokenizer.table(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // lheading
+
+
+ if (token = this.tokenizer.lheading(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // top-level paragraph
+
+
+ if (top && (token = this.tokenizer.paragraph(src))) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // text
+
+
+ if (token = this.tokenizer.text(src)) {
+ src = src.substring(token.raw.length);
+ lastToken = tokens[tokens.length - 1];
+
+ if (lastToken && lastToken.type === 'text') {
+ lastToken.raw += '\n' + token.raw;
+ lastToken.text += '\n' + token.text;
+ } else {
+ tokens.push(token);
+ }
+
+ continue;
+ }
+
+ if (src) {
+ var errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0);
+
+ if (this.options.silent) {
+ console.error(errMsg);
+ break;
+ } else {
+ throw new Error(errMsg);
+ }
+ }
+ }
+
+ return tokens;
+ }
+ }, {
+ key: "inline",
+ value: function inline(tokens) {
+ var i, j, k, l2, row, token;
+ var l = tokens.length;
+
+ for (i = 0; i < l; i++) {
+ token = tokens[i];
+
+ switch (token.type) {
+ case 'paragraph':
+ case 'text':
+ case 'heading':
+ {
+ token.tokens = [];
+ this.inlineTokens(token.text, token.tokens);
+ break;
+ }
+
+ case 'table':
+ {
+ token.tokens = {
+ header: [],
+ cells: []
+ }; // header
+
+ l2 = token.header.length;
+
+ for (j = 0; j < l2; j++) {
+ token.tokens.header[j] = [];
+ this.inlineTokens(token.header[j], token.tokens.header[j]);
+ } // cells
+
+
+ l2 = token.cells.length;
+
+ for (j = 0; j < l2; j++) {
+ row = token.cells[j];
+ token.tokens.cells[j] = [];
+
+ for (k = 0; k < row.length; k++) {
+ token.tokens.cells[j][k] = [];
+ this.inlineTokens(row[k], token.tokens.cells[j][k]);
+ }
+ }
+
+ break;
+ }
+
+ case 'blockquote':
+ {
+ this.inline(token.tokens);
+ break;
+ }
+
+ case 'list':
+ {
+ l2 = token.items.length;
+
+ for (j = 0; j < l2; j++) {
+ this.inline(token.items[j].tokens);
+ }
+
+ break;
+ }
+ }
+ }
+
+ return tokens;
+ }
+ /**
+ * Lexing/Compiling
+ */
+
+ }, {
+ key: "inlineTokens",
+ value: function inlineTokens(src) {
+ var tokens = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
+ var inLink = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
+ var inRawBlock = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
+ var token, lastToken; // String with links masked to avoid interference with em and strong
+
+ var maskedSrc = src;
+ var match;
+ var keepPrevChar, prevChar; // Mask out reflinks
+
+ if (this.tokens.links) {
+ var links = Object.keys(this.tokens.links);
+
+ if (links.length > 0) {
+ while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) {
+ if (links.includes(match[0].slice(match[0].lastIndexOf('[') + 1, -1))) {
+ maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex);
+ }
+ }
+ }
+ } // Mask out other blocks
+
+
+ while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) {
+ maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);
+ } // Mask out escaped em & strong delimiters
+
+
+ while ((match = this.tokenizer.rules.inline.escapedEmSt.exec(maskedSrc)) != null) {
+ maskedSrc = maskedSrc.slice(0, match.index) + '++' + maskedSrc.slice(this.tokenizer.rules.inline.escapedEmSt.lastIndex);
+ }
+
+ while (src) {
+ if (!keepPrevChar) {
+ prevChar = '';
+ }
+
+ keepPrevChar = false; // escape
+
+ if (token = this.tokenizer.escape(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // tag
+
+
+ if (token = this.tokenizer.tag(src, inLink, inRawBlock)) {
+ src = src.substring(token.raw.length);
+ inLink = token.inLink;
+ inRawBlock = token.inRawBlock;
+ var _lastToken = tokens[tokens.length - 1];
+
+ if (_lastToken && token.type === 'text' && _lastToken.type === 'text') {
+ _lastToken.raw += token.raw;
+ _lastToken.text += token.text;
+ } else {
+ tokens.push(token);
+ }
+
+ continue;
+ } // link
+
+
+ if (token = this.tokenizer.link(src)) {
+ src = src.substring(token.raw.length);
+
+ if (token.type === 'link') {
+ token.tokens = this.inlineTokens(token.text, [], true, inRawBlock);
+ }
+
+ tokens.push(token);
+ continue;
+ } // reflink, nolink
+
+
+ if (token = this.tokenizer.reflink(src, this.tokens.links)) {
+ src = src.substring(token.raw.length);
+ var _lastToken2 = tokens[tokens.length - 1];
+
+ if (token.type === 'link') {
+ token.tokens = this.inlineTokens(token.text, [], true, inRawBlock);
+ tokens.push(token);
+ } else if (_lastToken2 && token.type === 'text' && _lastToken2.type === 'text') {
+ _lastToken2.raw += token.raw;
+ _lastToken2.text += token.text;
+ } else {
+ tokens.push(token);
+ }
+
+ continue;
+ } // em & strong
+
+
+ if (token = this.tokenizer.emStrong(src, maskedSrc, prevChar)) {
+ src = src.substring(token.raw.length);
+ token.tokens = this.inlineTokens(token.text, [], inLink, inRawBlock);
+ tokens.push(token);
+ continue;
+ } // code
+
+
+ if (token = this.tokenizer.codespan(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // br
+
+
+ if (token = this.tokenizer.br(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // del (gfm)
+
+
+ if (token = this.tokenizer.del(src)) {
+ src = src.substring(token.raw.length);
+ token.tokens = this.inlineTokens(token.text, [], inLink, inRawBlock);
+ tokens.push(token);
+ continue;
+ } // autolink
+
+
+ if (token = this.tokenizer.autolink(src, mangle)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // url (gfm)
+
+
+ if (!inLink && (token = this.tokenizer.url(src, mangle))) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // text
+
+
+ if (token = this.tokenizer.inlineText(src, inRawBlock, smartypants)) {
+ src = src.substring(token.raw.length);
+
+ if (token.raw.slice(-1) !== '_') {
+ // Track prevChar before string of ____ started
+ prevChar = token.raw.slice(-1);
+ }
+
+ keepPrevChar = true;
+ lastToken = tokens[tokens.length - 1];
+
+ if (lastToken && lastToken.type === 'text') {
+ lastToken.raw += token.raw;
+ lastToken.text += token.text;
+ } else {
+ tokens.push(token);
+ }
+
+ continue;
+ }
+
+ if (src) {
+ var errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0);
+
+ if (this.options.silent) {
+ console.error(errMsg);
+ break;
+ } else {
+ throw new Error(errMsg);
+ }
+ }
+ }
+
+ return tokens;
+ }
+ }], [{
+ key: "rules",
+ get: function get() {
+ return {
+ block: block,
+ inline: inline
+ };
+ }
+ /**
+ * Static Lex Method
+ */
+
+ }, {
+ key: "lex",
+ value: function lex(src, options) {
+ var lexer = new Lexer(options);
+ return lexer.lex(src);
+ }
+ /**
+ * Static Lex Inline Method
+ */
+
+ }, {
+ key: "lexInline",
+ value: function lexInline(src, options) {
+ var lexer = new Lexer(options);
+ return lexer.inlineTokens(src);
+ }
+ }]);
+
+ return Lexer;
+ }();
+
+ var defaults$2 = defaults$5.exports.defaults;
+ var cleanUrl = helpers.cleanUrl,
+ escape$2 = helpers.escape;
+ /**
+ * Renderer
+ */
+
+ var Renderer_1 = /*#__PURE__*/function () {
+ function Renderer(options) {
+ _classCallCheck$1(this, Renderer);
+
+ this.options = options || defaults$2;
+ }
+
+ _createClass$1(Renderer, [{
+ key: "code",
+ value: function code(_code, infostring, escaped) {
+ var lang = (infostring || '').match(/\S*/)[0];
+
+ if (this.options.highlight) {
+ var out = this.options.highlight(_code, lang);
+
+ if (out != null && out !== _code) {
+ escaped = true;
+ _code = out;
+ }
+ }
+
+ _code = _code.replace(/\n$/, '') + '\n';
+
+ if (!lang) {
+ return '<pre><code>' + (escaped ? _code : escape$2(_code, true)) + '</code></pre>\n';
+ }
+
+ return '<pre><code class="' + this.options.langPrefix + escape$2(lang, true) + '">' + (escaped ? _code : escape$2(_code, true)) + '</code></pre>\n';
+ }
+ }, {
+ key: "blockquote",
+ value: function blockquote(quote) {
+ return '<blockquote>\n' + quote + '</blockquote>\n';
+ }
+ }, {
+ key: "html",
+ value: function html(_html) {
+ return _html;
+ }
+ }, {
+ key: "heading",
+ value: function heading(text, level, raw, slugger) {
+ if (this.options.headerIds) {
+ return '<h' + level + ' id="' + this.options.headerPrefix + slugger.slug(raw) + '">' + text + '</h' + level + '>\n';
+ } // ignore IDs
+
+
+ return '<h' + level + '>' + text + '</h' + level + '>\n';
+ }
+ }, {
+ key: "hr",
+ value: function hr() {
+ return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
+ }
+ }, {
+ key: "list",
+ value: function list(body, ordered, start) {
+ var type = ordered ? 'ol' : 'ul',
+ startatt = ordered && start !== 1 ? ' start="' + start + '"' : '';
+ return '<' + type + startatt + '>\n' + body + '</' + type + '>\n';
+ }
+ }, {
+ key: "listitem",
+ value: function listitem(text) {
+ return '<li>' + text + '</li>\n';
+ }
+ }, {
+ key: "checkbox",
+ value: function checkbox(checked) {
+ return '<input ' + (checked ? 'checked="" ' : '') + 'disabled="" type="checkbox"' + (this.options.xhtml ? ' /' : '') + '> ';
+ }
+ }, {
+ key: "paragraph",
+ value: function paragraph(text) {
+ return '<p>' + text + '</p>\n';
+ }
+ }, {
+ key: "table",
+ value: function table(header, body) {
+ if (body) body = '<tbody>' + body + '</tbody>';
+ return '<table>\n' + '<thead>\n' + header + '</thead>\n' + body + '</table>\n';
+ }
+ }, {
+ key: "tablerow",
+ value: function tablerow(content) {
+ return '<tr>\n' + content + '</tr>\n';
+ }
+ }, {
+ key: "tablecell",
+ value: function tablecell(content, flags) {
+ var type = flags.header ? 'th' : 'td';
+ var tag = flags.align ? '<' + type + ' align="' + flags.align + '">' : '<' + type + '>';
+ return tag + content + '</' + type + '>\n';
+ } // span level renderer
+
+ }, {
+ key: "strong",
+ value: function strong(text) {
+ return '<strong>' + text + '</strong>';
+ }
+ }, {
+ key: "em",
+ value: function em(text) {
+ return '<em>' + text + '</em>';
+ }
+ }, {
+ key: "codespan",
+ value: function codespan(text) {
+ return '<code>' + text + '</code>';
+ }
+ }, {
+ key: "br",
+ value: function br() {
+ return this.options.xhtml ? '<br/>' : '<br>';
+ }
+ }, {
+ key: "del",
+ value: function del(text) {
+ return '<del>' + text + '</del>';
+ }
+ }, {
+ key: "link",
+ value: function link(href, title, text) {
+ href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
+
+ if (href === null) {
+ return text;
+ }
+
+ var out = '<a href="' + escape$2(href) + '"';
+
+ if (title) {
+ out += ' title="' + title + '"';
+ }
+
+ out += '>' + text + '</a>';
+ return out;
+ }
+ }, {
+ key: "image",
+ value: function image(href, title, text) {
+ href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
+
+ if (href === null) {
+ return text;
+ }
+
+ var out = '<img src="' + href + '" alt="' + text + '"';
+
+ if (title) {
+ out += ' title="' + title + '"';
+ }
+
+ out += this.options.xhtml ? '/>' : '>';
+ return out;
+ }
+ }, {
+ key: "text",
+ value: function text(_text) {
+ return _text;
+ }
+ }]);
+
+ return Renderer;
+ }();
+
+ var TextRenderer_1 = /*#__PURE__*/function () {
+ function TextRenderer() {
+ _classCallCheck$1(this, TextRenderer);
+ }
+
+ _createClass$1(TextRenderer, [{
+ key: "strong",
+ value: // no need for block level renderers
+ function strong(text) {
+ return text;
+ }
+ }, {
+ key: "em",
+ value: function em(text) {
+ return text;
+ }
+ }, {
+ key: "codespan",
+ value: function codespan(text) {
+ return text;
+ }
+ }, {
+ key: "del",
+ value: function del(text) {
+ return text;
+ }
+ }, {
+ key: "html",
+ value: function html(text) {
+ return text;
+ }
+ }, {
+ key: "text",
+ value: function text(_text) {
+ return _text;
+ }
+ }, {
+ key: "link",
+ value: function link(href, title, text) {
+ return '' + text;
+ }
+ }, {
+ key: "image",
+ value: function image(href, title, text) {
+ return '' + text;
+ }
+ }, {
+ key: "br",
+ value: function br() {
+ return '';
+ }
+ }]);
+
+ return TextRenderer;
+ }();
+
+ var Slugger_1 = /*#__PURE__*/function () {
+ function Slugger() {
+ _classCallCheck$1(this, Slugger);
+
+ this.seen = {};
+ }
+
+ _createClass$1(Slugger, [{
+ key: "serialize",
+ value: function serialize(value) {
+ return value.toLowerCase().trim() // remove html tags
+ .replace(/<[!\/a-z].*?>/ig, '') // remove unwanted chars
+ .replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '').replace(/\s/g, '-');
+ }
+ /**
+ * Finds the next safe (unique) slug to use
+ */
+
+ }, {
+ key: "getNextSafeSlug",
+ value: function getNextSafeSlug(originalSlug, isDryRun) {
+ var slug = originalSlug;
+ var occurenceAccumulator = 0;
+
+ if (this.seen.hasOwnProperty(slug)) {
+ occurenceAccumulator = this.seen[originalSlug];
+
+ do {
+ occurenceAccumulator++;
+ slug = originalSlug + '-' + occurenceAccumulator;
+ } while (this.seen.hasOwnProperty(slug));
+ }
+
+ if (!isDryRun) {
+ this.seen[originalSlug] = occurenceAccumulator;
+ this.seen[slug] = 0;
+ }
+
+ return slug;
+ }
+ /**
+ * Convert string to unique id
+ * @param {object} options
+ * @param {boolean} options.dryrun Generates the next unique slug without updating the internal accumulator.
+ */
+
+ }, {
+ key: "slug",
+ value: function slug(value) {
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+ var slug = this.serialize(value);
+ return this.getNextSafeSlug(slug, options.dryrun);
+ }
+ }]);
+
+ return Slugger;
+ }();
+
+ var Renderer$1 = Renderer_1;
+ var TextRenderer$1 = TextRenderer_1;
+ var Slugger$1 = Slugger_1;
+ var defaults$1 = defaults$5.exports.defaults;
+ var unescape$1 = helpers.unescape;
+ /**
+ * Parsing & Compiling
+ */
+
+ var Parser_1 = /*#__PURE__*/function () {
+ function Parser(options) {
+ _classCallCheck$1(this, Parser);
+
+ this.options = options || defaults$1;
+ this.options.renderer = this.options.renderer || new Renderer$1();
+ this.renderer = this.options.renderer;
+ this.renderer.options = this.options;
+ this.textRenderer = new TextRenderer$1();
+ this.slugger = new Slugger$1();
+ }
+ /**
+ * Static Parse Method
+ */
+
+
+ _createClass$1(Parser, [{
+ key: "parse",
+ value:
+ /**
+ * Parse Loop
+ */
+ function parse(tokens) {
+ var top = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
+ var out = '',
+ i,
+ j,
+ k,
+ l2,
+ l3,
+ row,
+ cell,
+ header,
+ body,
+ token,
+ ordered,
+ start,
+ loose,
+ itemBody,
+ item,
+ checked,
+ task,
+ checkbox;
+ var l = tokens.length;
+
+ for (i = 0; i < l; i++) {
+ token = tokens[i];
+
+ switch (token.type) {
+ case 'space':
+ {
+ continue;
+ }
+
+ case 'hr':
+ {
+ out += this.renderer.hr();
+ continue;
+ }
+
+ case 'heading':
+ {
+ out += this.renderer.heading(this.parseInline(token.tokens), token.depth, unescape$1(this.parseInline(token.tokens, this.textRenderer)), this.slugger);
+ continue;
+ }
+
+ case 'code':
+ {
+ out += this.renderer.code(token.text, token.lang, token.escaped);
+ continue;
+ }
+
+ case 'table':
+ {
+ header = ''; // header
+
+ cell = '';
+ l2 = token.header.length;
+
+ for (j = 0; j < l2; j++) {
+ cell += this.renderer.tablecell(this.parseInline(token.tokens.header[j]), {
+ header: true,
+ align: token.align[j]
+ });
+ }
+
+ header += this.renderer.tablerow(cell);
+ body = '';
+ l2 = token.cells.length;
+
+ for (j = 0; j < l2; j++) {
+ row = token.tokens.cells[j];
+ cell = '';
+ l3 = row.length;
+
+ for (k = 0; k < l3; k++) {
+ cell += this.renderer.tablecell(this.parseInline(row[k]), {
+ header: false,
+ align: token.align[k]
+ });
+ }
+
+ body += this.renderer.tablerow(cell);
+ }
+
+ out += this.renderer.table(header, body);
+ continue;
+ }
+
+ case 'blockquote':
+ {
+ body = this.parse(token.tokens);
+ out += this.renderer.blockquote(body);
+ continue;
+ }
+
+ case 'list':
+ {
+ ordered = token.ordered;
+ start = token.start;
+ loose = token.loose;
+ l2 = token.items.length;
+ body = '';
+
+ for (j = 0; j < l2; j++) {
+ item = token.items[j];
+ checked = item.checked;
+ task = item.task;
+ itemBody = '';
+
+ if (item.task) {
+ checkbox = this.renderer.checkbox(checked);
+
+ if (loose) {
+ if (item.tokens.length > 0 && item.tokens[0].type === 'text') {
+ item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
+
+ if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
+ item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text;
+ }
+ } else {
+ item.tokens.unshift({
+ type: 'text',
+ text: checkbox
+ });
+ }
+ } else {
+ itemBody += checkbox;
+ }
+ }
+
+ itemBody += this.parse(item.tokens, loose);
+ body += this.renderer.listitem(itemBody, task, checked);
+ }
+
+ out += this.renderer.list(body, ordered, start);
+ continue;
+ }
+
+ case 'html':
+ {
+ // TODO parse inline content if parameter markdown=1
+ out += this.renderer.html(token.text);
+ continue;
+ }
+
+ case 'paragraph':
+ {
+ out += this.renderer.paragraph(this.parseInline(token.tokens));
+ continue;
+ }
+
+ case 'text':
+ {
+ body = token.tokens ? this.parseInline(token.tokens) : token.text;
+
+ while (i + 1 < l && tokens[i + 1].type === 'text') {
+ token = tokens[++i];
+ body += '\n' + (token.tokens ? this.parseInline(token.tokens) : token.text);
+ }
+
+ out += top ? this.renderer.paragraph(body) : body;
+ continue;
+ }
+
+ default:
+ {
+ var errMsg = 'Token with "' + token.type + '" type was not found.';
+
+ if (this.options.silent) {
+ console.error(errMsg);
+ return;
+ } else {
+ throw new Error(errMsg);
+ }
+ }
+ }
+ }
+
+ return out;
+ }
+ /**
+ * Parse Inline Tokens
+ */
+
+ }, {
+ key: "parseInline",
+ value: function parseInline(tokens, renderer) {
+ renderer = renderer || this.renderer;
+ var out = '',
+ i,
+ token;
+ var l = tokens.length;
+
+ for (i = 0; i < l; i++) {
+ token = tokens[i];
+
+ switch (token.type) {
+ case 'escape':
+ {
+ out += renderer.text(token.text);
+ break;
+ }
+
+ case 'html':
+ {
+ out += renderer.html(token.text);
+ break;
+ }
+
+ case 'link':
+ {
+ out += renderer.link(token.href, token.title, this.parseInline(token.tokens, renderer));
+ break;
+ }
+
+ case 'image':
+ {
+ out += renderer.image(token.href, token.title, token.text);
+ break;
+ }
+
+ case 'strong':
+ {
+ out += renderer.strong(this.parseInline(token.tokens, renderer));
+ break;
+ }
+
+ case 'em':
+ {
+ out += renderer.em(this.parseInline(token.tokens, renderer));
+ break;
+ }
+
+ case 'codespan':
+ {
+ out += renderer.codespan(token.text);
+ break;
+ }
+
+ case 'br':
+ {
+ out += renderer.br();
+ break;
+ }
+
+ case 'del':
+ {
+ out += renderer.del(this.parseInline(token.tokens, renderer));
+ break;
+ }
+
+ case 'text':
+ {
+ out += renderer.text(token.text);
+ break;
+ }
+
+ default:
+ {
+ var errMsg = 'Token with "' + token.type + '" type was not found.';
+
+ if (this.options.silent) {
+ console.error(errMsg);
+ return;
+ } else {
+ throw new Error(errMsg);
+ }
+ }
+ }
+ }
+
+ return out;
+ }
+ }], [{
+ key: "parse",
+ value: function parse(tokens, options) {
+ var parser = new Parser(options);
+ return parser.parse(tokens);
+ }
+ /**
+ * Static Parse Inline Method
+ */
+
+ }, {
+ key: "parseInline",
+ value: function parseInline(tokens, options) {
+ var parser = new Parser(options);
+ return parser.parseInline(tokens);
+ }
+ }]);
+
+ return Parser;
+ }();
+
+ var Lexer = Lexer_1;
+ var Parser = Parser_1;
+ var Tokenizer = Tokenizer_1;
+ var Renderer = Renderer_1;
+ var TextRenderer = TextRenderer_1;
+ var Slugger = Slugger_1;
+ var merge = helpers.merge,
+ checkSanitizeDeprecation = helpers.checkSanitizeDeprecation,
+ escape$1 = helpers.escape;
+ var getDefaults = defaults$5.exports.getDefaults,
+ changeDefaults = defaults$5.exports.changeDefaults,
+ defaults = defaults$5.exports.defaults;
+ /**
+ * Marked
+ */
+
+ function marked(src, opt, callback) {
+ // throw error in case of non string input
+ if (typeof src === 'undefined' || src === null) {
+ throw new Error('marked(): input parameter is undefined or null');
+ }
+
+ if (typeof src !== 'string') {
+ throw new Error('marked(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected');
+ }
+
+ if (typeof opt === 'function') {
+ callback = opt;
+ opt = null;
+ }
+
+ opt = merge({}, marked.defaults, opt || {});
+ checkSanitizeDeprecation(opt);
+
+ if (callback) {
+ var highlight = opt.highlight;
+ var tokens;
+
+ try {
+ tokens = Lexer.lex(src, opt);
+ } catch (e) {
+ return callback(e);
+ }
+
+ var done = function done(err) {
+ var out;
+
+ if (!err) {
+ try {
+ if (opt.walkTokens) {
+ marked.walkTokens(tokens, opt.walkTokens);
+ }
+
+ out = Parser.parse(tokens, opt);
+ } catch (e) {
+ err = e;
+ }
+ }
+
+ opt.highlight = highlight;
+ return err ? callback(err) : callback(null, out);
+ };
+
+ if (!highlight || highlight.length < 3) {
+ return done();
+ }
+
+ delete opt.highlight;
+ if (!tokens.length) return done();
+ var pending = 0;
+ marked.walkTokens(tokens, function (token) {
+ if (token.type === 'code') {
+ pending++;
+ setTimeout(function () {
+ highlight(token.text, token.lang, function (err, code) {
+ if (err) {
+ return done(err);
+ }
+
+ if (code != null && code !== token.text) {
+ token.text = code;
+ token.escaped = true;
+ }
+
+ pending--;
+
+ if (pending === 0) {
+ done();
+ }
+ });
+ }, 0);
+ }
+ });
+
+ if (pending === 0) {
+ done();
+ }
+
+ return;
+ }
+
+ try {
+ var _tokens = Lexer.lex(src, opt);
+
+ if (opt.walkTokens) {
+ marked.walkTokens(_tokens, opt.walkTokens);
+ }
+
+ return Parser.parse(_tokens, opt);
+ } catch (e) {
+ e.message += '\nPlease report this to https://github.com/markedjs/marked.';
+
+ if (opt.silent) {
+ return '<p>An error occurred:</p><pre>' + escape$1(e.message + '', true) + '</pre>';
+ }
+
+ throw e;
+ }
+ }
+ /**
+ * Options
+ */
+
+
+ marked.options = marked.setOptions = function (opt) {
+ merge(marked.defaults, opt);
+ changeDefaults(marked.defaults);
+ return marked;
+ };
+
+ marked.getDefaults = getDefaults;
+ marked.defaults = defaults;
+ /**
+ * Use Extension
+ */
+
+ marked.use = function (extension) {
+ var opts = merge({}, extension);
+
+ if (extension.renderer) {
+ (function () {
+ var renderer = marked.defaults.renderer || new Renderer();
+
+ var _loop = function _loop(prop) {
+ var prevRenderer = renderer[prop];
+
+ renderer[prop] = function () {
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ var ret = extension.renderer[prop].apply(renderer, args);
+
+ if (ret === false) {
+ ret = prevRenderer.apply(renderer, args);
+ }
+
+ return ret;
+ };
+ };
+
+ for (var prop in extension.renderer) {
+ _loop(prop);
+ }
+
+ opts.renderer = renderer;
+ })();
+ }
+
+ if (extension.tokenizer) {
+ (function () {
+ var tokenizer = marked.defaults.tokenizer || new Tokenizer();
+
+ var _loop2 = function _loop2(prop) {
+ var prevTokenizer = tokenizer[prop];
+
+ tokenizer[prop] = function () {
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+
+ var ret = extension.tokenizer[prop].apply(tokenizer, args);
+
+ if (ret === false) {
+ ret = prevTokenizer.apply(tokenizer, args);
+ }
+
+ return ret;
+ };
+ };
+
+ for (var prop in extension.tokenizer) {
+ _loop2(prop);
+ }
+
+ opts.tokenizer = tokenizer;
+ })();
+ }
+
+ if (extension.walkTokens) {
+ var walkTokens = marked.defaults.walkTokens;
+
+ opts.walkTokens = function (token) {
+ extension.walkTokens(token);
+
+ if (walkTokens) {
+ walkTokens(token);
+ }
+ };
+ }
+
+ marked.setOptions(opts);
+ };
+ /**
+ * Run callback for every token
+ */
+
+
+ marked.walkTokens = function (tokens, callback) {
+ var _iterator = _createForOfIteratorHelper(tokens),
+ _step;
+
+ try {
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
+ var token = _step.value;
+ callback(token);
+
+ switch (token.type) {
+ case 'table':
+ {
+ var _iterator2 = _createForOfIteratorHelper(token.tokens.header),
+ _step2;
+
+ try {
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
+ var cell = _step2.value;
+ marked.walkTokens(cell, callback);
+ }
+ } catch (err) {
+ _iterator2.e(err);
+ } finally {
+ _iterator2.f();
+ }
+
+ var _iterator3 = _createForOfIteratorHelper(token.tokens.cells),
+ _step3;
+
+ try {
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
+ var row = _step3.value;
+
+ var _iterator4 = _createForOfIteratorHelper(row),
+ _step4;
+
+ try {
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
+ var _cell = _step4.value;
+ marked.walkTokens(_cell, callback);
+ }
+ } catch (err) {
+ _iterator4.e(err);
+ } finally {
+ _iterator4.f();
+ }
+ }
+ } catch (err) {
+ _iterator3.e(err);
+ } finally {
+ _iterator3.f();
+ }
+
+ break;
+ }
+
+ case 'list':
+ {
+ marked.walkTokens(token.items, callback);
+ break;
+ }
+
+ default:
+ {
+ if (token.tokens) {
+ marked.walkTokens(token.tokens, callback);
+ }
+ }
+ }
+ }
+ } catch (err) {
+ _iterator.e(err);
+ } finally {
+ _iterator.f();
+ }
+ };
+ /**
+ * Parse Inline
+ */
+
+
+ marked.parseInline = function (src, opt) {
+ // throw error in case of non string input
+ if (typeof src === 'undefined' || src === null) {
+ throw new Error('marked.parseInline(): input parameter is undefined or null');
+ }
+
+ if (typeof src !== 'string') {
+ throw new Error('marked.parseInline(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected');
+ }
+
+ opt = merge({}, marked.defaults, opt || {});
+ checkSanitizeDeprecation(opt);
+
+ try {
+ var tokens = Lexer.lexInline(src, opt);
+
+ if (opt.walkTokens) {
+ marked.walkTokens(tokens, opt.walkTokens);
+ }
+
+ return Parser.parseInline(tokens, opt);
+ } catch (e) {
+ e.message += '\nPlease report this to https://github.com/markedjs/marked.';
+
+ if (opt.silent) {
+ return '<p>An error occurred:</p><pre>' + escape$1(e.message + '', true) + '</pre>';
+ }
+
+ throw e;
+ }
+ };
+ /**
+ * Expose
+ */
+
+
+ marked.Parser = Parser;
+ marked.parser = Parser.parse;
+ marked.Renderer = Renderer;
+ marked.TextRenderer = TextRenderer;
+ marked.Lexer = Lexer;
+ marked.lexer = Lexer.lex;
+ marked.Tokenizer = Tokenizer;
+ marked.Slugger = Slugger;
+ marked.parse = marked;
+ var marked_1 = marked;
+
+ var tiler$4 = utilTiler();
+ var dispatch$5 = dispatch$8('loaded');
+ var _tileZoom$1 = 14;
+ var _osmoseUrlRoot = 'https://osmose.openstreetmap.fr/api/0.3';
+ var _osmoseData = {
+ icons: {},
+ items: []
+ }; // This gets reassigned if reset
+
+ var _cache;
+
+ function abortRequest$4(controller) {
+ if (controller) {
+ controller.abort();
+ }
+ }
+
+ function abortUnwantedRequests$1(cache, tiles) {
+ Object.keys(cache.inflightTile).forEach(function (k) {
+ var wanted = tiles.find(function (tile) {
+ return k === tile.id;
+ });
+
+ if (!wanted) {
+ abortRequest$4(cache.inflightTile[k]);
+ delete cache.inflightTile[k];
+ }
+ });
+ }
+
+ function encodeIssueRtree(d) {
+ return {
+ minX: d.loc[0],
+ minY: d.loc[1],
+ maxX: d.loc[0],
+ maxY: d.loc[1],
+ data: d
+ };
+ } // Replace or remove QAItem from rtree
+
+
+ function updateRtree$1(item, replace) {
+ _cache.rtree.remove(item, function (a, b) {
+ return a.data.id === b.data.id;
+ });
+
+ if (replace) {
+ _cache.rtree.insert(item);
+ }
+ } // Issues shouldn't obscure each other
+
+
+ function preventCoincident(loc) {
+ var coincident = false;
+
+ do {
+ // first time, move marker up. after that, move marker right.
+ var delta = coincident ? [0.00001, 0] : [0, 0.00001];
+ loc = geoVecAdd(loc, delta);
+ var bbox = geoExtent(loc).bbox();
+ coincident = _cache.rtree.search(bbox).length;
+ } while (coincident);
+
+ return loc;
+ }
+
+ var serviceOsmose = {
+ title: 'osmose',
+ init: function init() {
+ _mainFileFetcher.get('qa_data').then(function (d) {
+ _osmoseData = d.osmose;
+ _osmoseData.items = Object.keys(d.osmose.icons).map(function (s) {
+ return s.split('-')[0];
+ }).reduce(function (unique, item) {
+ return unique.indexOf(item) !== -1 ? unique : [].concat(_toConsumableArray(unique), [item]);
+ }, []);
+ });
+
+ if (!_cache) {
+ this.reset();
+ }
+
+ this.event = utilRebind(this, dispatch$5, 'on');
+ },
+ reset: function reset() {
+ var _strings = {};
+ var _colors = {};
+
+ if (_cache) {
+ Object.values(_cache.inflightTile).forEach(abortRequest$4); // Strings and colors are static and should not be re-populated
+
+ _strings = _cache.strings;
+ _colors = _cache.colors;
+ }
+
+ _cache = {
+ data: {},
+ loadedTile: {},
+ inflightTile: {},
+ inflightPost: {},
+ closed: {},
+ rtree: new RBush(),
+ strings: _strings,
+ colors: _colors
+ };
+ },
+ loadIssues: function loadIssues(projection) {
+ var _this = this;
+
+ var params = {
+ // Tiles return a maximum # of issues
+ // So we want to filter our request for only types iD supports
+ item: _osmoseData.items
+ }; // determine the needed tiles to cover the view
+
+ var tiles = tiler$4.zoomExtent([_tileZoom$1, _tileZoom$1]).getTiles(projection); // abort inflight requests that are no longer needed
+
+ abortUnwantedRequests$1(_cache, tiles); // issue new requests..
+
+ tiles.forEach(function (tile) {
+ if (_cache.loadedTile[tile.id] || _cache.inflightTile[tile.id]) return;
+
+ var _tile$xyz = _slicedToArray(tile.xyz, 3),
+ x = _tile$xyz[0],
+ y = _tile$xyz[1],
+ z = _tile$xyz[2];
+
+ var url = "".concat(_osmoseUrlRoot, "/issues/").concat(z, "/").concat(x, "/").concat(y, ".json?") + utilQsString(params);
+ var controller = new AbortController();
+ _cache.inflightTile[tile.id] = controller;
+ d3_json(url, {
+ signal: controller.signal
+ }).then(function (data) {
+ delete _cache.inflightTile[tile.id];
+ _cache.loadedTile[tile.id] = true;
+
+ if (data.features) {
+ data.features.forEach(function (issue) {
+ var _issue$properties = issue.properties,
+ item = _issue$properties.item,
+ cl = _issue$properties["class"],
+ id = _issue$properties.uuid;
+ /* Osmose issues are uniquely identified by a unique
+ `item` and `class` combination (both integer values) */
+
+ var itemType = "".concat(item, "-").concat(cl); // Filter out unsupported issue types (some are too specific or advanced)
+
+ if (itemType in _osmoseData.icons) {
+ var loc = issue.geometry.coordinates; // lon, lat
+
+ loc = preventCoincident(loc);
+ var d = new QAItem(loc, _this, itemType, id, {
+ item: item
+ }); // Setting elems here prevents UI detail requests
+
+ if (item === 8300 || item === 8360) {
+ d.elems = [];
+ }
+
+ _cache.data[d.id] = d;
+
+ _cache.rtree.insert(encodeIssueRtree(d));
+ }
+ });
+ }
+
+ dispatch$5.call('loaded');
+ })["catch"](function () {
+ delete _cache.inflightTile[tile.id];
+ _cache.loadedTile[tile.id] = true;
+ });
+ });
+ },
+ loadIssueDetail: function loadIssueDetail(issue) {
+ var _this2 = this;
+
+ // Issue details only need to be fetched once
+ if (issue.elems !== undefined) {
+ return Promise.resolve(issue);
+ }
+
+ var url = "".concat(_osmoseUrlRoot, "/issue/").concat(issue.id, "?langs=").concat(_mainLocalizer.localeCode());
+
+ var cacheDetails = function cacheDetails(data) {
+ // Associated elements used for highlighting
+ // Assign directly for immediate use in the callback
+ issue.elems = data.elems.map(function (e) {
+ return e.type.substring(0, 1) + e.id;
+ }); // Some issues have instance specific detail in a subtitle
+
+ issue.detail = data.subtitle ? marked_1(data.subtitle.auto) : '';
+
+ _this2.replaceItem(issue);
+ };
+
+ return d3_json(url).then(cacheDetails).then(function () {
+ return issue;
+ });
+ },
+ loadStrings: function loadStrings() {
+ var locale = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _mainLocalizer.localeCode();
+ var items = Object.keys(_osmoseData.icons);
+
+ if (locale in _cache.strings && Object.keys(_cache.strings[locale]).length === items.length) {
+ return Promise.resolve(_cache.strings[locale]);
+ } // May be partially populated already if some requests were successful
+
+
+ if (!(locale in _cache.strings)) {
+ _cache.strings[locale] = {};
+ } // Only need to cache strings for supported issue types
+ // Using multiple individual item + class requests to reduce fetched data size
+
+
+ var allRequests = items.map(function (itemType) {
+ // No need to request data we already have
+ if (itemType in _cache.strings[locale]) return null;
+
+ var cacheData = function cacheData(data) {
+ // Bunch of nested single value arrays of objects
+ var _data$categories = _slicedToArray(data.categories, 1),
+ _data$categories$ = _data$categories[0],
+ cat = _data$categories$ === void 0 ? {
+ items: []
+ } : _data$categories$;
+
+ var _cat$items = _slicedToArray(cat.items, 1),
+ _cat$items$ = _cat$items[0],
+ item = _cat$items$ === void 0 ? {
+ "class": []
+ } : _cat$items$;
+
+ var _item$class = _slicedToArray(item["class"], 1),
+ _item$class$ = _item$class[0],
+ cl = _item$class$ === void 0 ? null : _item$class$; // If null default value is reached, data wasn't as expected (or was empty)
+
+
+ if (!cl) {
+ /* eslint-disable no-console */
+ console.log("Osmose strings request (".concat(itemType, ") had unexpected data"));
+ /* eslint-enable no-console */
+
+ return;
+ } // Cache served item colors to automatically style issue markers later
+
+
+ var itemInt = item.item,
+ color = item.color;
+
+ if (/^#[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/.test(color)) {
+ _cache.colors[itemInt] = color;
+ } // Value of root key will be null if no string exists
+ // If string exists, value is an object with key 'auto' for string
+
+
+ var title = cl.title,
+ detail = cl.detail,
+ fix = cl.fix,
+ trap = cl.trap; // Osmose titles shouldn't contain markdown
+
+ var issueStrings = {};
+ if (title) issueStrings.title = title.auto;
+ if (detail) issueStrings.detail = marked_1(detail.auto);
+ if (trap) issueStrings.trap = marked_1(trap.auto);
+ if (fix) issueStrings.fix = marked_1(fix.auto);
+ _cache.strings[locale][itemType] = issueStrings;
+ };
+
+ var _itemType$split = itemType.split('-'),
+ _itemType$split2 = _slicedToArray(_itemType$split, 2),
+ item = _itemType$split2[0],
+ cl = _itemType$split2[1]; // Osmose API falls back to English strings where untranslated or if locale doesn't exist
+
+
+ var url = "".concat(_osmoseUrlRoot, "/items/").concat(item, "/class/").concat(cl, "?langs=").concat(locale);
+ return d3_json(url).then(cacheData);
+ }).filter(Boolean);
+ return Promise.all(allRequests).then(function () {
+ return _cache.strings[locale];
+ });
+ },
+ getStrings: function getStrings(itemType) {
+ var locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _mainLocalizer.localeCode();
+ // No need to fallback to English, Osmose API handles this for us
+ return locale in _cache.strings ? _cache.strings[locale][itemType] : {};
+ },
+ getColor: function getColor(itemType) {
+ return itemType in _cache.colors ? _cache.colors[itemType] : '#FFFFFF';
+ },
+ postUpdate: function postUpdate(issue, callback) {
+ var _this3 = this;
+
+ if (_cache.inflightPost[issue.id]) {
+ return callback({
+ message: 'Issue update already inflight',
+ status: -2
+ }, issue);
+ } // UI sets the status to either 'done' or 'false'
+
+
+ var url = "".concat(_osmoseUrlRoot, "/issue/").concat(issue.id, "/").concat(issue.newStatus);
+ var controller = new AbortController();
+
+ var after = function after() {
+ delete _cache.inflightPost[issue.id];
+
+ _this3.removeItem(issue);
+
+ if (issue.newStatus === 'done') {
+ // Keep track of the number of issues closed per `item` to tag the changeset
+ if (!(issue.item in _cache.closed)) {
+ _cache.closed[issue.item] = 0;
+ }
+
+ _cache.closed[issue.item] += 1;
+ }
+
+ if (callback) callback(null, issue);
+ };
+
+ _cache.inflightPost[issue.id] = controller;
+ fetch(url, {
+ signal: controller.signal
+ }).then(after)["catch"](function (err) {
+ delete _cache.inflightPost[issue.id];
+ if (callback) callback(err.message);
+ });
+ },
+ // Get all cached QAItems covering the viewport
+ getItems: function getItems(projection) {
+ var viewport = projection.clipExtent();
+ var min = [viewport[0][0], viewport[1][1]];
+ var max = [viewport[1][0], viewport[0][1]];
+ var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
+ return _cache.rtree.search(bbox).map(function (d) {
+ return d.data;
+ });
+ },
+ // Get a QAItem from cache
+ // NOTE: Don't change method name until UI v3 is merged
+ getError: function getError(id) {
+ return _cache.data[id];
+ },
+ // get the name of the icon to display for this item
+ getIcon: function getIcon(itemType) {
+ return _osmoseData.icons[itemType];
+ },
+ // Replace a single QAItem in the cache
+ replaceItem: function replaceItem(item) {
+ if (!(item instanceof QAItem) || !item.id) return;
+ _cache.data[item.id] = item;
+ updateRtree$1(encodeIssueRtree(item), true); // true = replace
+
+ return item;
+ },
+ // Remove a single QAItem from the cache
+ removeItem: function removeItem(item) {
+ if (!(item instanceof QAItem) || !item.id) return;
+ delete _cache.data[item.id];
+ updateRtree$1(encodeIssueRtree(item), false); // false = remove
+ },
+ // Used to populate `closed:osmose:*` changeset tags
+ getClosedCounts: function getClosedCounts() {
+ return _cache.closed;
+ },
+ itemURL: function itemURL(item) {
+ return "https://osmose.openstreetmap.fr/en/error/".concat(item.id);
+ }
+ };
+
+ var ieee754$1 = {};
+
+ /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
+
+ ieee754$1.read = function (buffer, offset, isLE, mLen, nBytes) {
+ var e, m;
+ var eLen = nBytes * 8 - mLen - 1;
+ var eMax = (1 << eLen) - 1;
+ var eBias = eMax >> 1;
+ var nBits = -7;
+ var i = isLE ? nBytes - 1 : 0;
+ var d = isLE ? -1 : 1;
+ var s = buffer[offset + i];
+ i += d;
+ e = s & (1 << -nBits) - 1;
+ s >>= -nBits;
+ nBits += eLen;
+
+ for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+
+ m = e & (1 << -nBits) - 1;
+ e >>= -nBits;
+ nBits += mLen;
+
+ for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+
+ if (e === 0) {
+ e = 1 - eBias;
+ } else if (e === eMax) {
+ return m ? NaN : (s ? -1 : 1) * Infinity;
+ } else {
+ m = m + Math.pow(2, mLen);
+ e = e - eBias;
+ }
+
+ return (s ? -1 : 1) * m * Math.pow(2, e - mLen);
+ };
+
+ ieee754$1.write = function (buffer, value, offset, isLE, mLen, nBytes) {
+ var e, m, c;
+ var eLen = nBytes * 8 - mLen - 1;
+ var eMax = (1 << eLen) - 1;
+ var eBias = eMax >> 1;
+ var rt = mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0;
+ var i = isLE ? 0 : nBytes - 1;
+ var d = isLE ? 1 : -1;
+ var s = value < 0 || value === 0 && 1 / value < 0 ? 1 : 0;
+ value = Math.abs(value);
+
+ if (isNaN(value) || value === Infinity) {
+ m = isNaN(value) ? 1 : 0;
+ e = eMax;
+ } else {
+ e = Math.floor(Math.log(value) / Math.LN2);
+
+ if (value * (c = Math.pow(2, -e)) < 1) {
+ e--;
+ c *= 2;
+ }
+
+ if (e + eBias >= 1) {
+ value += rt / c;
+ } else {
+ value += rt * Math.pow(2, 1 - eBias);
+ }
+
+ if (value * c >= 2) {
+ e++;
+ c /= 2;
+ }
+
+ if (e + eBias >= eMax) {
+ m = 0;
+ e = eMax;
+ } else if (e + eBias >= 1) {
+ m = (value * c - 1) * Math.pow(2, mLen);
+ e = e + eBias;
+ } else {
+ m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
+ e = 0;
+ }
+ }
+
+ for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
+
+ e = e << mLen | m;
+ eLen += mLen;
+
+ for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
+
+ buffer[offset + i - d] |= s * 128;
+ };
+
+ var pbf = Pbf;
+ var ieee754 = ieee754$1;
+
+ function Pbf(buf) {
+ this.buf = ArrayBuffer.isView && ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0);
+ this.pos = 0;
+ this.type = 0;
+ this.length = this.buf.length;
+ }
+
+ Pbf.Varint = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum
+
+ Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64
+
+ Pbf.Bytes = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields
+
+ Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32
+
+ var SHIFT_LEFT_32 = (1 << 16) * (1 << 16),
+ SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32; // Threshold chosen based on both benchmarking and knowledge about browser string
+ // data structures (which currently switch structure types at 12 bytes or more)
+
+ var TEXT_DECODER_MIN_LENGTH = 12;
+ var utf8TextDecoder = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf8');
+ Pbf.prototype = {
+ destroy: function destroy() {
+ this.buf = null;
+ },
+ // === READING =================================================================
+ readFields: function readFields(readField, result, end) {
+ end = end || this.length;
+
+ while (this.pos < end) {
+ var val = this.readVarint(),
+ tag = val >> 3,
+ startPos = this.pos;
+ this.type = val & 0x7;
+ readField(tag, result, this);
+ if (this.pos === startPos) this.skip(val);
+ }
+
+ return result;
+ },
+ readMessage: function readMessage(readField, result) {
+ return this.readFields(readField, result, this.readVarint() + this.pos);
+ },
+ readFixed32: function readFixed32() {
+ var val = readUInt32(this.buf, this.pos);
+ this.pos += 4;
+ return val;
+ },
+ readSFixed32: function readSFixed32() {
+ var val = readInt32(this.buf, this.pos);
+ this.pos += 4;
+ return val;
+ },
+ // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed)
+ readFixed64: function readFixed64() {
+ var val = readUInt32(this.buf, this.pos) + readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
+ this.pos += 8;
+ return val;
+ },
+ readSFixed64: function readSFixed64() {
+ var val = readUInt32(this.buf, this.pos) + readInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
+ this.pos += 8;
+ return val;
+ },
+ readFloat: function readFloat() {
+ var val = ieee754.read(this.buf, this.pos, true, 23, 4);
+ this.pos += 4;
+ return val;
+ },
+ readDouble: function readDouble() {
+ var val = ieee754.read(this.buf, this.pos, true, 52, 8);
+ this.pos += 8;
+ return val;
+ },
+ readVarint: function readVarint(isSigned) {
+ var buf = this.buf,
+ val,
+ b;
+ b = buf[this.pos++];
+ val = b & 0x7f;
+ if (b < 0x80) return val;
+ b = buf[this.pos++];
+ val |= (b & 0x7f) << 7;
+ if (b < 0x80) return val;
+ b = buf[this.pos++];
+ val |= (b & 0x7f) << 14;
+ if (b < 0x80) return val;
+ b = buf[this.pos++];
+ val |= (b & 0x7f) << 21;
+ if (b < 0x80) return val;
+ b = buf[this.pos];
+ val |= (b & 0x0f) << 28;
+ return readVarintRemainder(val, isSigned, this);
+ },
+ readVarint64: function readVarint64() {
+ // for compatibility with v2.0.1
+ return this.readVarint(true);
+ },
+ readSVarint: function readSVarint() {
+ var num = this.readVarint();
+ return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding
+ },
+ readBoolean: function readBoolean() {
+ return Boolean(this.readVarint());
+ },
+ readString: function readString() {
+ var end = this.readVarint() + this.pos;
+ var pos = this.pos;
+ this.pos = end;
+
+ if (end - pos >= TEXT_DECODER_MIN_LENGTH && utf8TextDecoder) {
+ // longer strings are fast with the built-in browser TextDecoder API
+ return readUtf8TextDecoder(this.buf, pos, end);
+ } // short strings are fast with our custom implementation
+
+
+ return readUtf8(this.buf, pos, end);
+ },
+ readBytes: function readBytes() {
+ var end = this.readVarint() + this.pos,
+ buffer = this.buf.subarray(this.pos, end);
+ this.pos = end;
+ return buffer;
+ },
+ // verbose for performance reasons; doesn't affect gzipped size
+ readPackedVarint: function readPackedVarint(arr, isSigned) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readVarint(isSigned));
+ var end = readPackedEnd(this);
+ arr = arr || [];
+
+ while (this.pos < end) {
+ arr.push(this.readVarint(isSigned));
+ }
+
+ return arr;
+ },
+ readPackedSVarint: function readPackedSVarint(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readSVarint());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+
+ while (this.pos < end) {
+ arr.push(this.readSVarint());
+ }
+
+ return arr;
+ },
+ readPackedBoolean: function readPackedBoolean(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readBoolean());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+
+ while (this.pos < end) {
+ arr.push(this.readBoolean());
+ }
+
+ return arr;
+ },
+ readPackedFloat: function readPackedFloat(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readFloat());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+
+ while (this.pos < end) {
+ arr.push(this.readFloat());
+ }
+
+ return arr;
+ },
+ readPackedDouble: function readPackedDouble(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readDouble());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+
+ while (this.pos < end) {
+ arr.push(this.readDouble());
+ }
+
+ return arr;
+ },
+ readPackedFixed32: function readPackedFixed32(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readFixed32());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+
+ while (this.pos < end) {
+ arr.push(this.readFixed32());
+ }
+
+ return arr;
+ },
+ readPackedSFixed32: function readPackedSFixed32(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed32());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+
+ while (this.pos < end) {
+ arr.push(this.readSFixed32());
+ }
+
+ return arr;
+ },
+ readPackedFixed64: function readPackedFixed64(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readFixed64());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+
+ while (this.pos < end) {
+ arr.push(this.readFixed64());
+ }
+
+ return arr;
+ },
+ readPackedSFixed64: function readPackedSFixed64(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed64());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+
+ while (this.pos < end) {
+ arr.push(this.readSFixed64());
+ }
+
+ return arr;
+ },
+ skip: function skip(val) {
+ var type = val & 0x7;
+ if (type === Pbf.Varint) while (this.buf[this.pos++] > 0x7f) {} else if (type === Pbf.Bytes) this.pos = this.readVarint() + this.pos;else if (type === Pbf.Fixed32) this.pos += 4;else if (type === Pbf.Fixed64) this.pos += 8;else throw new Error('Unimplemented type: ' + type);
+ },
+ // === WRITING =================================================================
+ writeTag: function writeTag(tag, type) {
+ this.writeVarint(tag << 3 | type);
+ },
+ realloc: function realloc(min) {
+ var length = this.length || 16;
+
+ while (length < this.pos + min) {
+ length *= 2;
+ }
+
+ if (length !== this.length) {
+ var buf = new Uint8Array(length);
+ buf.set(this.buf);
+ this.buf = buf;
+ this.length = length;
+ }
+ },
+ finish: function finish() {
+ this.length = this.pos;
+ this.pos = 0;
+ return this.buf.subarray(0, this.length);
+ },
+ writeFixed32: function writeFixed32(val) {
+ this.realloc(4);
+ writeInt32(this.buf, val, this.pos);
+ this.pos += 4;
+ },
+ writeSFixed32: function writeSFixed32(val) {
+ this.realloc(4);
+ writeInt32(this.buf, val, this.pos);
+ this.pos += 4;
+ },
+ writeFixed64: function writeFixed64(val) {
+ this.realloc(8);
+ writeInt32(this.buf, val & -1, this.pos);
+ writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
+ this.pos += 8;
+ },
+ writeSFixed64: function writeSFixed64(val) {
+ this.realloc(8);
+ writeInt32(this.buf, val & -1, this.pos);
+ writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
+ this.pos += 8;
+ },
+ writeVarint: function writeVarint(val) {
+ val = +val || 0;
+
+ if (val > 0xfffffff || val < 0) {
+ writeBigVarint(val, this);
+ return;
+ }
+
+ this.realloc(4);
+ this.buf[this.pos++] = val & 0x7f | (val > 0x7f ? 0x80 : 0);
+ if (val <= 0x7f) return;
+ this.buf[this.pos++] = (val >>>= 7) & 0x7f | (val > 0x7f ? 0x80 : 0);
+ if (val <= 0x7f) return;
+ this.buf[this.pos++] = (val >>>= 7) & 0x7f | (val > 0x7f ? 0x80 : 0);
+ if (val <= 0x7f) return;
+ this.buf[this.pos++] = val >>> 7 & 0x7f;
+ },
+ writeSVarint: function writeSVarint(val) {
+ this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2);
+ },
+ writeBoolean: function writeBoolean(val) {
+ this.writeVarint(Boolean(val));
+ },
+ writeString: function writeString(str) {
+ str = String(str);
+ this.realloc(str.length * 4);
+ this.pos++; // reserve 1 byte for short string length
+
+ var startPos = this.pos; // write the string directly to the buffer and see how much was written
+
+ this.pos = writeUtf8(this.buf, str, this.pos);
+ var len = this.pos - startPos;
+ if (len >= 0x80) makeRoomForExtraLength(startPos, len, this); // finally, write the message length in the reserved place and restore the position
+
+ this.pos = startPos - 1;
+ this.writeVarint(len);
+ this.pos += len;
+ },
+ writeFloat: function writeFloat(val) {
+ this.realloc(4);
+ ieee754.write(this.buf, val, this.pos, true, 23, 4);
+ this.pos += 4;
+ },
+ writeDouble: function writeDouble(val) {
+ this.realloc(8);
+ ieee754.write(this.buf, val, this.pos, true, 52, 8);
+ this.pos += 8;
+ },
+ writeBytes: function writeBytes(buffer) {
+ var len = buffer.length;
+ this.writeVarint(len);
+ this.realloc(len);
+
+ for (var i = 0; i < len; i++) {
+ this.buf[this.pos++] = buffer[i];
+ }
+ },
+ writeRawMessage: function writeRawMessage(fn, obj) {
+ this.pos++; // reserve 1 byte for short message length
+ // write the message directly to the buffer and see how much was written
+
+ var startPos = this.pos;
+ fn(obj, this);
+ var len = this.pos - startPos;
+ if (len >= 0x80) makeRoomForExtraLength(startPos, len, this); // finally, write the message length in the reserved place and restore the position
+
+ this.pos = startPos - 1;
+ this.writeVarint(len);
+ this.pos += len;
+ },
+ writeMessage: function writeMessage(tag, fn, obj) {
+ this.writeTag(tag, Pbf.Bytes);
+ this.writeRawMessage(fn, obj);
+ },
+ writePackedVarint: function writePackedVarint(tag, arr) {
+ if (arr.length) this.writeMessage(tag, _writePackedVarint, arr);
+ },
+ writePackedSVarint: function writePackedSVarint(tag, arr) {
+ if (arr.length) this.writeMessage(tag, _writePackedSVarint, arr);
+ },
+ writePackedBoolean: function writePackedBoolean(tag, arr) {
+ if (arr.length) this.writeMessage(tag, _writePackedBoolean, arr);
+ },
+ writePackedFloat: function writePackedFloat(tag, arr) {
+ if (arr.length) this.writeMessage(tag, _writePackedFloat, arr);
+ },
+ writePackedDouble: function writePackedDouble(tag, arr) {
+ if (arr.length) this.writeMessage(tag, _writePackedDouble, arr);
+ },
+ writePackedFixed32: function writePackedFixed32(tag, arr) {
+ if (arr.length) this.writeMessage(tag, _writePackedFixed, arr);
+ },
+ writePackedSFixed32: function writePackedSFixed32(tag, arr) {
+ if (arr.length) this.writeMessage(tag, _writePackedSFixed, arr);
+ },
+ writePackedFixed64: function writePackedFixed64(tag, arr) {
+ if (arr.length) this.writeMessage(tag, _writePackedFixed2, arr);
+ },
+ writePackedSFixed64: function writePackedSFixed64(tag, arr) {
+ if (arr.length) this.writeMessage(tag, _writePackedSFixed2, arr);
+ },
+ writeBytesField: function writeBytesField(tag, buffer) {
+ this.writeTag(tag, Pbf.Bytes);
+ this.writeBytes(buffer);
+ },
+ writeFixed32Field: function writeFixed32Field(tag, val) {
+ this.writeTag(tag, Pbf.Fixed32);
+ this.writeFixed32(val);
+ },
+ writeSFixed32Field: function writeSFixed32Field(tag, val) {
+ this.writeTag(tag, Pbf.Fixed32);
+ this.writeSFixed32(val);
+ },
+ writeFixed64Field: function writeFixed64Field(tag, val) {
+ this.writeTag(tag, Pbf.Fixed64);
+ this.writeFixed64(val);
+ },
+ writeSFixed64Field: function writeSFixed64Field(tag, val) {
+ this.writeTag(tag, Pbf.Fixed64);
+ this.writeSFixed64(val);
+ },
+ writeVarintField: function writeVarintField(tag, val) {
+ this.writeTag(tag, Pbf.Varint);
+ this.writeVarint(val);
+ },
+ writeSVarintField: function writeSVarintField(tag, val) {
+ this.writeTag(tag, Pbf.Varint);
+ this.writeSVarint(val);
+ },
+ writeStringField: function writeStringField(tag, str) {
+ this.writeTag(tag, Pbf.Bytes);
+ this.writeString(str);
+ },
+ writeFloatField: function writeFloatField(tag, val) {
+ this.writeTag(tag, Pbf.Fixed32);
+ this.writeFloat(val);
+ },
+ writeDoubleField: function writeDoubleField(tag, val) {
+ this.writeTag(tag, Pbf.Fixed64);
+ this.writeDouble(val);
+ },
+ writeBooleanField: function writeBooleanField(tag, val) {
+ this.writeVarintField(tag, Boolean(val));
+ }
+ };
+
+ function readVarintRemainder(l, s, p) {
+ var buf = p.buf,
+ h,
+ b;
+ b = buf[p.pos++];
+ h = (b & 0x70) >> 4;
+ if (b < 0x80) return toNum(l, h, s);
+ b = buf[p.pos++];
+ h |= (b & 0x7f) << 3;
+ if (b < 0x80) return toNum(l, h, s);
+ b = buf[p.pos++];
+ h |= (b & 0x7f) << 10;
+ if (b < 0x80) return toNum(l, h, s);
+ b = buf[p.pos++];
+ h |= (b & 0x7f) << 17;
+ if (b < 0x80) return toNum(l, h, s);
+ b = buf[p.pos++];
+ h |= (b & 0x7f) << 24;
+ if (b < 0x80) return toNum(l, h, s);
+ b = buf[p.pos++];
+ h |= (b & 0x01) << 31;
+ if (b < 0x80) return toNum(l, h, s);
+ throw new Error('Expected varint not more than 10 bytes');
+ }