mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-23 14:14:11 +00:00
Merge branch 'master' of github.com:systemed/iD
This commit is contained in:
101
CONTRIBUTING.md
Normal file
101
CONTRIBUTING.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Contributing to iD
|
||||
|
||||
Thinking of contributing to iD? High five! Here are some basics for our habits
|
||||
so that you can write code that fits in perfectly.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
We'd love to hear what you think about iD, about any specific problems or
|
||||
concerns you have. Here's a quick list of things to consider:
|
||||
|
||||
Please [search for your issue before filing it: many bugs and improvements have already been reported](https://github.com/systemed/iD/issues/search?q=)
|
||||
|
||||
To report a bug:
|
||||
|
||||
* Write specifically what browser (type and version, like Firefox 22), OS, and browser extensions you have installed
|
||||
* Write steps to replicate the error: when did it happen? What did you expect to happen? What happened instead?
|
||||
* Please keep bug reports professional and straightforward: trust us, we share your dismay at software breaking.
|
||||
* If you can, [enable web developer extensions](http://macwright.org/enable-web-developer-extensions/) and report the
|
||||
Javascript error message.
|
||||
|
||||
When in doubt, be over-descriptive of the bug and how you discovered it.
|
||||
|
||||
To request a feature:
|
||||
|
||||
* If the feature is available in some other software (like Potlatch), link to that software and the implementation.
|
||||
We care about prior art.
|
||||
* Understand that iD is meant to be a simple editor and doesn't aim to be
|
||||
as complete or complicated as JOSM or similar.
|
||||
|
||||
## Javascript
|
||||
|
||||
We use the [Airbnb style for Javascript](https://github.com/airbnb/javascript) with
|
||||
only one difference:
|
||||
|
||||
**4 space soft tabs always for Javascript, not 2.**
|
||||
|
||||
No aligned `=`, no aligned arguments, spaces are either indents or the 1
|
||||
space between expressions. No hard tabs, ever.
|
||||
|
||||
Javascript code should pass through [JSHint](http://www.jshint.com/) with no
|
||||
warnings.
|
||||
|
||||
## HTML
|
||||
|
||||
There isn't much HTML in iD, but what there is is similar to JS: 4 spaces
|
||||
always, indented by the level of the tree:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<div></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## CSS
|
||||
|
||||
Just like HTML and Javascript, 4 space soft tabs always.
|
||||
|
||||
```css
|
||||
.radial-menu-tooltip {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
```
|
||||
|
||||
We write vanilla CSS with no preprocessing step. Since iD targets modern browsers,
|
||||
feel free to use newer features wisely.
|
||||
|
||||
## Tests
|
||||
|
||||
Test your code and make sure it passes. Our testing harness requires [node.js](http://nodejs.org/)
|
||||
and a few modules:
|
||||
|
||||
1. [Install node.js](http://nodejs.org/) - 'Install' will download a package for your OS
|
||||
2. Go to the directory where you have checked out `iD`
|
||||
3. Run `npm install`
|
||||
4. Run `npm test` to see whether your tests pass or fail.
|
||||
|
||||
## Licensing
|
||||
|
||||
iD is under the [WTFPL](http://www.wtfpl.net/). Some of the libraries it uses
|
||||
are under different licenses. If you're contributing to iD, you're contributing
|
||||
WTFPL code.
|
||||
|
||||
## Submitting Changes
|
||||
|
||||
Let's say that you've thought of a great improvement to iD - a change that
|
||||
turns everything red (please do not do this, we like colors other than red).
|
||||
|
||||
In your local copy, make a branch for this change:
|
||||
|
||||
git checkout -b make-red
|
||||
|
||||
Make your changes to source files. By source files we mean the files in `js/`.
|
||||
the `iD.js` and `iD.min.js` files in this project are autogenerated - don't edit
|
||||
them.
|
||||
|
||||
So let's say you've changed `js/ui/confirm.js`.
|
||||
|
||||
1. Run `jshint js/id` to make sure your code is clean
|
||||
2. Run tests with `npm test`
|
||||
3. Commit your changes with an informative commit message
|
||||
4. [Submit a pull request](https://help.github.com/articles/using-pull-requests) to the `systemed/iD` project.
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
## Participate!
|
||||
|
||||
* [Read NOTES.md, our ongoing dev journal](https://github.com/systemed/iD/blob/master/NOTES.md)
|
||||
* Fork this project. We eagerly accept pull requests.
|
||||
* [Read up on Contributing and the code style of iD](CONTRIBUTING.md)
|
||||
* See [open issues in the issue tracker if you're looking for something to do](https://github.com/systemed/iD/issues?state=open)
|
||||
|
||||
To run the code locally, just fork this project and run it from a local webserver.
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
<script src='js/id/actions/move_node.js'></script>
|
||||
<script src='js/id/actions/move_way.js'></script>
|
||||
<script src='js/id/actions/circularize.js'></script>
|
||||
<script src='js/id/actions/orthogonalize.js'></script>
|
||||
<script src='js/id/actions/noop.js'></script>
|
||||
<script src='js/id/actions/reverse.js'></script>
|
||||
<script src='js/id/actions/split.js'></script>
|
||||
@@ -112,6 +113,7 @@
|
||||
|
||||
<script src='js/id/operations.js'></script>
|
||||
<script src='js/id/operations/circularize.js'></script>
|
||||
<script src='js/id/operations/orthogonalize.js'></script>
|
||||
<script src='js/id/operations/delete.js'></script>
|
||||
<script src='js/id/operations/disconnect.js'></script>
|
||||
<script src='js/id/operations/merge.js'></script>
|
||||
|
||||
132
js/id/actions/orthogonalize.js
Normal file
132
js/id/actions/orthogonalize.js
Normal file
@@ -0,0 +1,132 @@
|
||||
iD.actions.Orthogonalize = function(wayId, projection) {
|
||||
var action = function(graph) {
|
||||
var way = graph.entity(wayId),
|
||||
nodes = graph.childNodes(way),
|
||||
points = nodes.map(function(n) { return projection(n.loc); }),
|
||||
quad_nodes = [], i, j;
|
||||
|
||||
var score = squareness();
|
||||
for (i = 0; i < 1000; i++) {
|
||||
var motions = points.map(stepMap);
|
||||
for (j = 0; j < motions.length; j++) {
|
||||
points[j] = addPoints(points[j],motions[j]);
|
||||
}
|
||||
var newScore = squareness();
|
||||
if (newScore > score) {
|
||||
return graph;
|
||||
}
|
||||
score = newScore;
|
||||
if (score < 1.0e-8) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < points.length - 1; i++) {
|
||||
quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) }));
|
||||
}
|
||||
|
||||
for (i = 0; i < nodes.length; i++) {
|
||||
if (graph.parentWays(nodes[i]).length > 1) {
|
||||
var closest, closest_dist = Infinity, dist;
|
||||
for (j = 0; j < quad_nodes.length; j++) {
|
||||
dist = iD.geo.dist(quad_nodes[j].loc, nodes[i].loc);
|
||||
if (dist < closest_dist) {
|
||||
closest_dist = dist;
|
||||
closest = j;
|
||||
}
|
||||
}
|
||||
quad_nodes.splice(closest, 1, nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < quad_nodes.length; i++) {
|
||||
graph = graph.replace(quad_nodes[i]);
|
||||
}
|
||||
|
||||
var ids = _.pluck(quad_nodes, 'id'),
|
||||
difference = _.difference(_.uniq(way.nodes), ids);
|
||||
|
||||
ids.push(ids[0]);
|
||||
|
||||
graph = graph.replace(way.update({nodes: ids}));
|
||||
|
||||
for (i = 0; i < difference.length; i++) {
|
||||
graph = iD.actions.DeleteNode(difference[i])(graph);
|
||||
}
|
||||
|
||||
return graph;
|
||||
|
||||
function stepMap(b, i, array) {
|
||||
var a = array[(i - 1 + array.length) % array.length],
|
||||
c = array[(i + 1) % array.length],
|
||||
p = subtractPoints(a, b),
|
||||
q = subtractPoints(c, b);
|
||||
|
||||
var scale = p.length + q.length;
|
||||
p = normalizePoint(p, 1.0);
|
||||
q = normalizePoint(q, 1.0);
|
||||
|
||||
var dotp = p[0] *q[0] + p[1] *q[1];
|
||||
// nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270).
|
||||
if (dotp < -0.707106781186547) {
|
||||
dotp += 1.0;
|
||||
}
|
||||
|
||||
return normalizePoint(addPoints(p, q), 0.1 * dotp * scale);
|
||||
}
|
||||
|
||||
function squareness() {
|
||||
var g = 0.0;
|
||||
for (var i = 1; i < points.length - 1; i++) {
|
||||
var score = scoreOfPoints(points[i - 1], points[i], points[i + 1]);
|
||||
g += score;
|
||||
}
|
||||
var startScore = scoreOfPoints(points[points.length - 1], points[0], points[1]);
|
||||
var endScore = scoreOfPoints(points[points.length - 2], points[points.length - 1], points[0]);
|
||||
g += startScore;
|
||||
g += endScore;
|
||||
return g;
|
||||
}
|
||||
|
||||
function scoreOfPoints(a, b, c) {
|
||||
var p = subtractPoints(a, b),
|
||||
q = subtractPoints(c, b);
|
||||
|
||||
p = normalizePoint(p, 1.0);
|
||||
q = normalizePoint(q, 1.0);
|
||||
|
||||
var dotp = p[0] * q[0] + p[1] * q[1];
|
||||
// score is constructed so that +1, -1 and 0 are all scored 0, any other angle
|
||||
// is scored higher.
|
||||
return 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 1)));
|
||||
}
|
||||
|
||||
function subtractPoints(a, b) {
|
||||
return [a[0] - b[0], a[1] - b[1]];
|
||||
}
|
||||
|
||||
function addPoints(a, b) {
|
||||
return [a[0] + b[0], a[1] + b[1]];
|
||||
}
|
||||
|
||||
function normalizePoint(point, thickness) {
|
||||
var vector = [0, 0];
|
||||
var length = Math.sqrt(point[0] * point[0] + point[1] * point[1]);
|
||||
if (length !== 0) {
|
||||
vector[0] = point[0] / length;
|
||||
vector[1] = point[1] / length;
|
||||
}
|
||||
|
||||
vector[0] *= thickness;
|
||||
vector[1] *= thickness;
|
||||
|
||||
return vector;
|
||||
}
|
||||
};
|
||||
|
||||
action.enabled = function(graph) {
|
||||
return graph.entity(wayId).isClosed();
|
||||
};
|
||||
|
||||
return action;
|
||||
};
|
||||
@@ -17,7 +17,7 @@ iD.actions.Split = function(nodeId, newWayId) {
|
||||
return parents.filter(function (parent) {
|
||||
return parent.first() !== nodeId &&
|
||||
parent.last() !== nodeId;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
var action = function(graph) {
|
||||
|
||||
@@ -9,7 +9,7 @@ iD.behavior.DragMidpoint = function(context) {
|
||||
|
||||
context.perform(iD.actions.AddMidpoint(d, node));
|
||||
|
||||
var vertex = d3.selectAll('.vertex')
|
||||
var vertex = context.surface().selectAll('.vertex')
|
||||
.filter(function(data) { return data.id === node.id; });
|
||||
|
||||
behavior.target(vertex.node(), vertex.datum());
|
||||
|
||||
@@ -82,7 +82,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
|
||||
return graph
|
||||
.replace(way.removeNode(nodeId).addNode(newNode.id, index))
|
||||
.remove(node);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Accept the current position of the temporary node and continue drawing.
|
||||
|
||||
@@ -127,7 +127,7 @@ iD.History = function() {
|
||||
modified: difference.modified(),
|
||||
created: difference.created(),
|
||||
deleted: difference.deleted()
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
hasChanges: function() {
|
||||
|
||||
@@ -47,6 +47,6 @@ _.extend(iD.Node.prototype, {
|
||||
type: 'Point',
|
||||
coordinates: this.loc
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1 +1 @@
|
||||
iD.operations = {}
|
||||
iD.operations = {};
|
||||
|
||||
25
js/id/operations/orthogonalize.js
Normal file
25
js/id/operations/orthogonalize.js
Normal file
@@ -0,0 +1,25 @@
|
||||
iD.operations.Orthogonalize = function(selection, context) {
|
||||
var entityId = selection[0],
|
||||
action = iD.actions.Orthogonalize(entityId, context.projection);
|
||||
|
||||
var operation = function() {
|
||||
var annotation = t('operations.orthogonalize.annotation.' + context.geometry(entityId));
|
||||
context.perform(action, annotation);
|
||||
};
|
||||
|
||||
operation.available = function() {
|
||||
return selection.length === 1 &&
|
||||
context.entity(entityId).type === 'way';
|
||||
};
|
||||
|
||||
operation.enabled = function() {
|
||||
return action.enabled(context.graph());
|
||||
};
|
||||
|
||||
operation.id = "orthogonalize";
|
||||
operation.key = t('operations.orthogonalize.key');
|
||||
operation.title = t('operations.orthogonalize.title');
|
||||
operation.description = t('operations.orthogonalize.description');
|
||||
|
||||
return operation;
|
||||
};
|
||||
@@ -49,6 +49,7 @@ iD.Map = function(context) {
|
||||
|
||||
map.size(selection.size());
|
||||
map.surface = surface;
|
||||
map.tilesurface = tilegroup;
|
||||
|
||||
supersurface
|
||||
.call(tail);
|
||||
|
||||
@@ -40,7 +40,7 @@ iD.taginfo = function() {
|
||||
}
|
||||
|
||||
function popularValues(parameters) {
|
||||
return function(d) { return parseFloat(d['fraction']) > 0.01; };
|
||||
return function(d) { return parseFloat(d.fraction) > 0.01; };
|
||||
}
|
||||
|
||||
function valKey(d) { return { value: d.key }; }
|
||||
|
||||
18
js/id/svg.js
18
js/id/svg.js
@@ -1,19 +1,19 @@
|
||||
iD.svg = {
|
||||
RoundProjection: function (projection) {
|
||||
return function (d) {
|
||||
RoundProjection: function(projection) {
|
||||
return function(d) {
|
||||
return iD.geo.roundCoords(projection(d));
|
||||
};
|
||||
},
|
||||
|
||||
PointTransform: function (projection) {
|
||||
return function (entity) {
|
||||
PointTransform: function(projection) {
|
||||
return function(entity) {
|
||||
return 'translate(' + projection(entity.loc) + ')';
|
||||
};
|
||||
},
|
||||
|
||||
LineString: function (projection, graph) {
|
||||
LineString: function(projection, graph) {
|
||||
var cache = {};
|
||||
return function (entity) {
|
||||
return function(entity) {
|
||||
if (cache[entity.id] !== undefined) {
|
||||
return cache[entity.id];
|
||||
}
|
||||
@@ -23,7 +23,9 @@ iD.svg = {
|
||||
}
|
||||
|
||||
return (cache[entity.id] =
|
||||
'M' + graph.childNodes(entity).map(function (n) { return projection(n.loc); }).join('L'));
|
||||
}
|
||||
'M' + graph.childNodes(entity).map(function(n) {
|
||||
return projection(n.loc);
|
||||
}).join('L'));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -226,7 +226,7 @@ iD.svg.Labels = function(projection) {
|
||||
d3.select(surface.node().parentNode)
|
||||
.on('mousemove.hidelabels', hideOnMouseover);
|
||||
|
||||
var hidePoints = !d3.select('.node.point').node();
|
||||
var hidePoints = !surface.select('.node.point').node();
|
||||
|
||||
var labelable = [], i, k, entity;
|
||||
for (i = 0; i < label_stack.length; i++) labelable.push([]);
|
||||
@@ -254,7 +254,6 @@ iD.svg.Labels = function(projection) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var positions = {
|
||||
point: [],
|
||||
line: [],
|
||||
|
||||
@@ -140,8 +140,10 @@ iD.ui = function (context) {
|
||||
var about = container.append('div')
|
||||
.attr('class','col12 about-block fillD pad1');
|
||||
|
||||
about.append('div')
|
||||
.attr('class', 'user-container')
|
||||
var userContainer = about.append('div')
|
||||
.attr('class', 'user-container');
|
||||
|
||||
userContainer
|
||||
.append('div')
|
||||
.attr('class', 'hello');
|
||||
|
||||
@@ -243,7 +245,7 @@ iD.ui = function (context) {
|
||||
map.centerZoom([-77.02271, 38.90085], 20);
|
||||
}
|
||||
|
||||
d3.select('.user-container').call(iD.ui.userpanel(connection)
|
||||
userContainer.call(iD.ui.userpanel(connection)
|
||||
.on('logout.editor', connection.logout)
|
||||
.on('login.editor', connection.authenticate));
|
||||
|
||||
|
||||
@@ -35,11 +35,12 @@ iD.ui.commit = function(context) {
|
||||
|
||||
// Comment Box
|
||||
var comment_section = body.append('div').attr('class','modal-section fillD');
|
||||
comment_section.append('textarea')
|
||||
var commentField = comment_section.append('textarea')
|
||||
.attr('class', 'changeset-comment')
|
||||
.attr('placeholder', 'Brief Description of your contributions')
|
||||
.property('value', context.storage('comment') || '')
|
||||
.node().select();
|
||||
.property('value', context.storage('comment') || '');
|
||||
|
||||
commentField.node().select();
|
||||
|
||||
var commit_info =
|
||||
comment_section
|
||||
@@ -73,7 +74,7 @@ iD.ui.commit = function(context) {
|
||||
.append('button')
|
||||
.attr('class', 'save action col6 button')
|
||||
.on('click.save', function() {
|
||||
var comment = d3.select('textarea.changeset-comment').node().value;
|
||||
var comment = commentField.node().value;
|
||||
localStorage.comment = comment;
|
||||
event.save({
|
||||
comment: comment
|
||||
|
||||
@@ -38,9 +38,8 @@ iD.ui.geocoder = function() {
|
||||
function setVisible(show) {
|
||||
button.classed('active', show);
|
||||
gcForm.classed('hide', !show);
|
||||
var input_node = d3.select('.map-overlay input').node();
|
||||
if (show) input_node.focus();
|
||||
else input_node.blur();
|
||||
if (show) inputNode.node().focus();
|
||||
else inputNode.node().blur();
|
||||
}
|
||||
|
||||
var button = selection.append('button')
|
||||
@@ -51,7 +50,7 @@ iD.ui.geocoder = function() {
|
||||
|
||||
var gcForm = selection.append('form');
|
||||
|
||||
gcForm.attr('class','content fillD map-overlay hide')
|
||||
var inputNode = gcForm.attr('class','content fillD map-overlay hide')
|
||||
.append('input')
|
||||
.attr({ type: 'text', placeholder: t('geocoder.find_a_place') })
|
||||
.on('keydown', keydown);
|
||||
|
||||
@@ -61,9 +61,10 @@ iD.ui.layerswitcher = function(context) {
|
||||
|
||||
opa.append('h4').text(t('layerswitcher.layers'));
|
||||
|
||||
opa.append('ul')
|
||||
.attr('class', 'opacity-options')
|
||||
.selectAll('div.opacity')
|
||||
var opacityList = opa.append('ul')
|
||||
.attr('class', 'opacity-options');
|
||||
|
||||
opacityList.selectAll('div.opacity')
|
||||
.data(opacities)
|
||||
.enter()
|
||||
.append('li')
|
||||
@@ -71,11 +72,11 @@ iD.ui.layerswitcher = function(context) {
|
||||
return t('layerswitcher.percent_brightness', { opacity: (d * 100) });
|
||||
})
|
||||
.on('click.set-opacity', function(d) {
|
||||
d3.select('#tile-g')
|
||||
context.map().tilesurface
|
||||
.transition()
|
||||
.style('opacity', d)
|
||||
.attr('data-opacity', d);
|
||||
d3.selectAll('.opacity-options li')
|
||||
opacityList.selectAll('li')
|
||||
.classed('selected', false);
|
||||
d3.select(this)
|
||||
.classed('selected', true);
|
||||
|
||||
@@ -14,6 +14,7 @@ iD.ui.save = function(context) {
|
||||
.on('click', function() {
|
||||
|
||||
function commit(e) {
|
||||
|
||||
d3.select('.shaded').remove();
|
||||
var l = iD.ui.loading(t('uploading_changes'), true);
|
||||
connection.putChangeset(history.changes(), e.comment, history.imagery_used(), function(err, changeset_id) {
|
||||
@@ -40,6 +41,7 @@ iD.ui.save = function(context) {
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (history.hasChanges()) {
|
||||
|
||||
@@ -2,15 +2,20 @@ iD.ui.splash = function() {
|
||||
var modal = iD.ui.modal();
|
||||
|
||||
modal.select('.modal')
|
||||
.attr('class', 'modal-splash modal')
|
||||
.attr('class', 'modal-splash modal');
|
||||
|
||||
var introModal = modal.select('.content')
|
||||
.append('div')
|
||||
.attr('class', 'modal-section fillL');
|
||||
|
||||
introModal.append('div').attr('class','logo');
|
||||
introModal.append('div')
|
||||
.attr('class','logo');
|
||||
|
||||
introModal.append('div').html("<h2 class>Welcome to the iD OpenStreetMap editor</h2><p>This is development version 0.0.0-alpha1. For more information see <a href='http://ideditor.com/'>ideditor.com</a> and report bugs at <a href='https://github.com'>github.com.systemed/iD</a>.</p>");
|
||||
introModal.append('div')
|
||||
.html("<h2 class>Welcome to the iD OpenStreetMap editor</h2><p>" +
|
||||
"This is development version 0.0.0-alpha1. " +
|
||||
"For more information see <a href='http://ideditor.com/'>ideditor.com</a>" +
|
||||
" and report bugs at <a href='https://github.com'>github.com.systemed/iD</a>.</p>");
|
||||
|
||||
return modal;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -65,6 +65,15 @@ locale.en = {
|
||||
area: "Made an area circular."
|
||||
}
|
||||
},
|
||||
orthogonalize: {
|
||||
title: "Orthogonalize",
|
||||
description: "Square these corners.",
|
||||
key: "Q",
|
||||
annotation: {
|
||||
line: "Squared the corners of a line.",
|
||||
area: "Squared the corners of an area."
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
title: "Delete",
|
||||
description: "Remove this from the map.",
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
<script src='../js/id/actions/add_vertex.js'></script>
|
||||
<script src='../js/id/actions/change_tags.js'></script>
|
||||
<script src='../js/id/actions/circularize.js'></script>
|
||||
<script src='../js/id/actions/orthogonalize.js'></script>
|
||||
<script src="../js/id/actions/delete_multiple.js"></script>
|
||||
<script src="../js/id/actions/delete_node.js"></script>
|
||||
<script src="../js/id/actions/delete_relation.js"></script>
|
||||
@@ -108,6 +109,7 @@
|
||||
|
||||
<script src='../js/id/operations.js'></script>
|
||||
<script src='../js/id/operations/circularize.js'></script>
|
||||
<script src='../js/id/operations/orthogonalize.js'></script>
|
||||
<script src='../js/id/operations/delete.js'></script>
|
||||
<script src='../js/id/operations/disconnect.js'></script>
|
||||
<script src='../js/id/operations/merge.js'></script>
|
||||
|
||||
Reference in New Issue
Block a user