diff --git a/index.html b/index.html
index 9db659489..2d42c9f1c 100644
--- a/index.html
+++ b/index.html
@@ -108,6 +108,7 @@
+
diff --git a/js/id/graph/validate.js b/js/id/graph/validate.js
new file mode 100644
index 000000000..addb774ca
--- /dev/null
+++ b/js/id/graph/validate.js
@@ -0,0 +1,47 @@
+iD.validate = function(changes) {
+ var warnings = [], change;
+
+ // https://github.com/openstreetmap/josm/blob/mirror/src/org/
+ // openstreetmap/josm/data/validation/tests/UnclosedWays.java#L80
+ function tagSuggestsArea(change) {
+ if (_.isEmpty(change.tags)) return false;
+ var tags = change.tags;
+ var presence = ['landuse', 'amenities', 'tourism', 'shop'];
+ for (var i = 0; i < presence.length; i++) {
+ if (tags[presence[i]] !== undefined) {
+ return presence[i] + '=' + tags[presence[i]];
+ }
+ }
+ if (tags.building && tags.building === 'yes') return 'building=yes';
+ }
+
+ if (changes.created.length) {
+ for (var i = 0; i < changes.created.length; i++) {
+ change = changes.created[i];
+
+ if (change.geometry() === 'point' && _.isEmpty(change.tags)) {
+ warnings.push({
+ message: 'Untagged point which is not part of a line or area',
+ entity: change
+ });
+ }
+
+ if (change.geometry() === 'line' && _.isEmpty(change.tags)) {
+ warnings.push({ message: 'Untagged line', entity: change });
+ }
+
+ if (change.geometry() === 'area' && _.isEmpty(change.tags)) {
+ warnings.push({ message: 'Untagged area', entity: change });
+ }
+
+ if (change.geometry() === 'line' && tagSuggestsArea(change)) {
+ warnings.push({
+ message: 'The tag ' + tagSuggestsArea(change) + ' suggests line should be area, but it is not and area',
+ entity: change
+ });
+ }
+ }
+ }
+
+ return warnings.length ? [warnings] : [];
+};
diff --git a/js/id/id.js b/js/id/id.js
index 96f4d09d5..e22a77ca6 100644
--- a/js/id/id.js
+++ b/js/id/id.js
@@ -96,7 +96,7 @@ window.iD = function(container) {
var save_button = bar.append('button')
.attr('class', 'save action wide')
- .call(iD.ui.save().map(map));
+ .call(iD.ui.save().map(map).controller(controller));
history.on('change.warn-unload', function() {
var changes = history.changes(),
diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js
index 547f4505f..06d072275 100644
--- a/js/id/ui/commit.js
+++ b/js/id/ui/commit.js
@@ -1,5 +1,5 @@
iD.ui.commit = function() {
- var event = d3.dispatch('cancel', 'save');
+ var event = d3.dispatch('cancel', 'save', 'fix');
function zipSame(d) {
var c = [], n = -1;
@@ -58,12 +58,12 @@ iD.ui.commit = function() {
header.append('p').text('The changes you upload will be visible on all maps that use OpenStreetMap data.');
- var commit = body.append('div').attr('class','modal-section');
- commit.append('textarea')
- .attr('class', 'changeset-comment')
- .attr('placeholder', 'Brief Description of your contributions');
+ var comment_section = body.append('div').attr('class','modal-section');
+ comment_section.append('textarea')
+ .attr('class', 'changeset-comment')
+ .attr('placeholder', 'Brief Description of your contributions');
- var buttonwrap = commit.append('div')
+ var buttonwrap = comment_section.append('div')
.attr('class', 'buttons');
var savebutton = buttonwrap.append('button')
@@ -84,12 +84,39 @@ iD.ui.commit = function() {
cancelbutton.append('span').attr('class','icon close icon-pre-text');
cancelbutton.append('span').attr('class','label').text('Cancel');
+ var warnings = body.selectAll('div.warning-section')
+ .data(iD.validate(changes))
+ .enter()
+ .append('div').attr('class', 'modal-section warning-section');
+
+ warnings.append('h3')
+ .text('Warnings');
+
+ var warning_li = warnings.append('ul')
+ .attr('class', 'changeset-list')
+ .selectAll('li')
+ .data(function(d) { return d; })
+ .enter()
+ .append('li');
+
+ warning_li.append('button')
+ .attr('class', 'minor')
+ .on('click', event.fix)
+ .append('span')
+ .attr('class', 'icon inspect');
+
+ warning_li.append('strong').text(function(d) {
+ return d.message;
+ });
+
var section = body.selectAll('div.commit-section')
.data(['modified', 'deleted', 'created'].filter(changesLength))
.enter()
.append('div').attr('class', 'commit-section modal-section fillL2');
- section.append('h3').text(String)
+ section.append('h3').text(function(d) {
+ return d.charAt(0).toUpperCase() + d.slice(1);
+ })
.append('small')
.attr('class', 'count')
.text(changesLength);
diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js
index 3af39c1b7..600538e4c 100644
--- a/js/id/ui/inspector.js
+++ b/js/id/ui/inspector.js
@@ -114,12 +114,14 @@ iD.ui.inspector = function() {
inputs.append('input')
.property('type', 'text')
.attr('class', 'key')
+ .attr('maxlength', 255)
.property('value', function(d) { return d.key; })
.on('change', function(d) { d.key = this.value; });
inputs.append('input')
.property('type', 'text')
.attr('class', 'value')
+ .attr('maxlength', 255)
.property('value', function(d) { return d.value; })
.on('change', function(d) { d.value = this.value; })
.on('keydown.push-more', pushMore);
@@ -271,7 +273,7 @@ iD.ui.inspector = function() {
inspector.tags = function (tags) {
if (!arguments.length) {
- var tags = {};
+ tags = {};
tagList.selectAll('li').each(function() {
var row = d3.select(this),
key = row.selectAll('.key').property('value'),
diff --git a/js/id/ui/save.js b/js/id/ui/save.js
index 27d6ef1e5..e40125e71 100644
--- a/js/id/ui/save.js
+++ b/js/id/ui/save.js
@@ -1,6 +1,6 @@
iD.ui.save = function() {
- var map;
+ var map, controller;
function save(selection) {
@@ -59,6 +59,13 @@ iD.ui.save = function() {
.on('cancel', function() {
modal.remove();
})
+ .on('fix', function(d) {
+ var ext = d.entity.extent(map.history().graph());
+ map.extent(ext[0], ext[1]);
+ if (map.zoom() > 19) map.zoom(19);
+ controller.enter(iD.modes.Select(d.entity));
+ modal.remove();
+ })
.on('save', commit));
});
} else {
@@ -91,5 +98,11 @@ iD.ui.save = function() {
return save;
};
+ save.controller = function(_) {
+ if (!arguments.length) return controller;
+ controller = _;
+ return save;
+ };
+
return save;
};