diff --git a/modules/core/context.js b/modules/core/context.js index ed4bf64ef..0b54db23a 100644 --- a/modules/core/context.js +++ b/modules/core/context.js @@ -18,7 +18,7 @@ import { presetManager } from '../presets'; import { rendererBackground, rendererFeatures, rendererMap, rendererPhotos } from '../renderer'; import { services } from '../services'; import { uiInit } from '../ui/init'; -import { utilKeybinding, utilRebind, utilStringQs } from '../util'; +import { utilKeybinding, utilRebind, utilStringQs, utilUnicodeCharsTruncated } from '../util'; export function coreContext() { @@ -196,6 +196,27 @@ export function coreContext() { context.maxCharsForTagValue = () => 255; context.maxCharsForRelationRole = () => 255; + function cleanOsmString(val, maxChars) { + // be lenient with input + if (val === undefined || val === null) { + val = ''; + } else { + val = val.toString(); + } + + // remove whitespace + val = val.trim(); + + // use the canonical form of the string + if (val.normalize) val = val.normalize('NFC'); + + // trim to the number of allowed characters + return utilUnicodeCharsTruncated(val, maxChars); + } + context.cleanTagKey = (val) => cleanOsmString(val, context.maxCharsForTagKey()); + context.cleanTagValue = (val) => cleanOsmString(val, context.maxCharsForTagValue()); + context.cleanRelationRole = (val) => cleanOsmString(val, context.maxCharsForRelationRole()); + /* History */ let _inIntro = false; diff --git a/modules/ui/commit.js b/modules/ui/commit.js index 246535dab..a0f494952 100644 --- a/modules/ui/commit.js +++ b/modules/ui/commit.js @@ -12,7 +12,7 @@ import { uiChangesetEditor } from './changeset_editor'; import { uiSectionChanges } from './sections/changes'; import { uiCommitWarnings } from './commit_warnings'; import { uiSectionRawTagEditor } from './sections/raw_tag_editor'; -import { utilArrayGroupBy, utilRebind, utilUnicodeCharsTruncated, utilUniqueDomId } from '../util'; +import { utilArrayGroupBy, utilRebind, utilUniqueDomId } from '../util'; import { utilDetect } from '../util/detect'; @@ -63,8 +63,6 @@ export function uiCommit(context) { function initChangeset() { - var tagCharLimit = context.maxCharsForTagValue(); - // expire stored comment, hashtags, source after cutoff datetime - #3947 #4899 var commentDate = +prefs('commentDate') || 0; var currDate = Date.now(); @@ -92,9 +90,9 @@ export function uiCommit(context) { var detected = utilDetect(); var tags = { comment: prefs('comment') || '', - created_by: utilUnicodeCharsTruncated('iD ' + context.version, tagCharLimit), - host: utilUnicodeCharsTruncated(detected.host, tagCharLimit), - locale: utilUnicodeCharsTruncated(localizer.localeCode(), tagCharLimit) + created_by: context.cleanTagValue('iD ' + context.version), + host: context.cleanTagValue(detected.host), + locale: context.cleanTagValue(localizer.localeCode()) }; // call findHashtags initially - this will remove stored @@ -126,7 +124,7 @@ export function uiCommit(context) { } }); - tags.source = utilUnicodeCharsTruncated(sources.join(';'), tagCharLimit); + tags.source = context.cleanTagValue(sources.join(';')); } context.changeset = new osmChangeset({ tags: tags }); @@ -139,36 +137,34 @@ export function uiCommit(context) { var osm = context.connection(); if (!osm) return; - var tagCharLimit = context.maxCharsForTagValue(); - var tags = Object.assign({}, context.changeset.tags); // shallow copy // assign tags for imagery used - var imageryUsed = utilUnicodeCharsTruncated(context.history().imageryUsed().join(';'), tagCharLimit); + var imageryUsed = context.cleanTagValue(context.history().imageryUsed().join(';')); tags.imagery_used = imageryUsed || 'None'; // assign tags for closed issues and notes var osmClosed = osm.getClosedIDs(); var itemType; if (osmClosed.length) { - tags['closed:note'] = utilUnicodeCharsTruncated(osmClosed.join(';'), tagCharLimit); + tags['closed:note'] = context.cleanTagValue(osmClosed.join(';')); } if (services.keepRight) { var krClosed = services.keepRight.getClosedIDs(); if (krClosed.length) { - tags['closed:keepright'] = utilUnicodeCharsTruncated(krClosed.join(';'), tagCharLimit); + tags['closed:keepright'] = context.cleanTagValue(krClosed.join(';')); } } if (services.improveOSM) { var iOsmClosed = services.improveOSM.getClosedCounts(); for (itemType in iOsmClosed) { - tags['closed:improveosm:' + itemType] = utilUnicodeCharsTruncated(iOsmClosed[itemType].toString(), tagCharLimit); + tags['closed:improveosm:' + itemType] = context.cleanTagValue(iOsmClosed[itemType].toString()); } } if (services.osmose) { var osmoseClosed = services.osmose.getClosedCounts(); for (itemType in osmoseClosed) { - tags['closed:osmose:' + itemType] = utilUnicodeCharsTruncated(osmoseClosed[itemType].toString(), tagCharLimit); + tags['closed:osmose:' + itemType] = context.cleanTagValue(osmoseClosed[itemType].toString()); } } @@ -187,10 +183,10 @@ export function uiCommit(context) { var issuesBySubtype = utilArrayGroupBy(issuesOfType, 'subtype'); for (var issueSubtype in issuesBySubtype) { var issuesOfSubtype = issuesBySubtype[issueSubtype]; - tags[prefix + ':' + issueType + ':' + issueSubtype] = utilUnicodeCharsTruncated(issuesOfSubtype.length.toString(), tagCharLimit); + tags[prefix + ':' + issueType + ':' + issueSubtype] = context.cleanTagValue(issuesOfSubtype.length.toString()); } } else { - tags[prefix + ':' + issueType] = utilUnicodeCharsTruncated(issuesOfType.length.toString(), tagCharLimit); + tags[prefix + ':' + issueType] = context.cleanTagValue(issuesOfType.length.toString()); } } } @@ -546,18 +542,16 @@ export function uiCommit(context) { function updateChangeset(changed, onInput) { var tags = Object.assign({}, context.changeset.tags); // shallow copy - var tagCharLimit = context.maxCharsForTagValue(); - Object.keys(changed).forEach(function(k) { var v = changed[k]; - k = utilUnicodeCharsTruncated(k.trim(), tagCharLimit); + k = context.cleanTagKey(k); if (readOnlyTags.indexOf(k) !== -1) return; if (k !== '' && v !== undefined) { if (onInput) { tags[k] = v; } else { - tags[k] = utilUnicodeCharsTruncated(v.trim(), tagCharLimit); + tags[k] = context.cleanTagValue(v); } } else { delete tags[k]; @@ -569,7 +563,7 @@ export function uiCommit(context) { var commentOnly = changed.hasOwnProperty('comment') && (changed.comment !== ''); var arr = findHashtags(tags, commentOnly); if (arr.length) { - tags.hashtags = utilUnicodeCharsTruncated(arr.join(';'), tagCharLimit); + tags.hashtags = context.cleanTagValue(arr.join(';')); prefs('hashtags', tags.hashtags); } else { delete tags.hashtags; diff --git a/modules/ui/fields/access.js b/modules/ui/fields/access.js index fcc346970..d7f4c8d6f 100644 --- a/modules/ui/fields/access.js +++ b/modules/ui/fields/access.js @@ -47,7 +47,6 @@ export function uiFieldAccess(field, context) { .attr('class', 'preset-input-access-wrap') .append('input') .attr('type', 'text') - .attr('maxlength', context.maxCharsForTagValue()) .attr('class', function(d) { return 'preset-input-access preset-input-access-' + d; }) .call(utilNoAuto) .each(function(d) { @@ -69,7 +68,7 @@ export function uiFieldAccess(field, context) { function change(d) { var tag = {}; - var value = utilGetSetValue(d3_select(this)); + var value = context.cleanTagValue(utilGetSetValue(d3_select(this))); // don't override multiple values with blank string if (!value && typeof _tags[d] !== 'string') return; diff --git a/modules/ui/fields/address.js b/modules/ui/fields/address.js index 2520a1531..a56bc3449 100644 --- a/modules/ui/fields/address.js +++ b/modules/ui/fields/address.js @@ -186,7 +186,6 @@ export function uiFieldAddress(field, context) { .append('input') .property('type', 'text') .call(updatePlaceholder) - .attr('maxlength', context.maxCharsForTagValue()) .attr('class', function (d) { return 'addr-' + d.id; }) .call(utilNoAuto) .each(addDropdown) @@ -259,10 +258,12 @@ export function uiFieldAddress(field, context) { .each(function (subfield) { var key = field.key + ':' + subfield.id; - // don't override multiple values with blank string - if (Array.isArray(_tags[key]) && !this.value) return; + var value = context.cleanTagValue(this.value); - tags[key] = this.value || undefined; + // don't override multiple values with blank string + if (Array.isArray(_tags[key]) && !value) return; + + tags[key] = value || undefined; }); dispatch.call('change', this, tags, onInput); diff --git a/modules/ui/fields/combo.js b/modules/ui/fields/combo.js index e0a8d26fe..6ef3c51be 100644 --- a/modules/ui/fields/combo.js +++ b/modules/ui/fields/combo.js @@ -290,6 +290,7 @@ export function uiFieldCombo(field, context) { var old = _tags[key]; if (typeof old === 'string' && old.toLowerCase() !== 'no') return; } + key = context.cleanTagKey(key); field.keys.push(key); t[key] = 'yes'; }); @@ -297,7 +298,7 @@ export function uiFieldCombo(field, context) { } else if (isSemi) { var arr = _multiData.map(function(d) { return d.key; }); arr = arr.concat(vals); - t[field.key] = utilArrayUniq(arr).filter(Boolean).join(';'); + t[field.key] = context.cleanTagValue(utilArrayUniq(arr).filter(Boolean).join(';')); } window.setTimeout(function() { input.node().focus(); }, 10); @@ -308,7 +309,7 @@ export function uiFieldCombo(field, context) { // don't override multiple values with blank string if (!rawValue && Array.isArray(_tags[field.key])) return; - val = tagValue(rawValue); + val = context.cleanTagValue(tagValue(rawValue)); t[field.key] = val; } @@ -383,7 +384,6 @@ export function uiFieldCombo(field, context) { .append('input') .attr('type', 'text') .attr('id', field.domId) - .attr('maxlength', context.maxCharsForTagValue()) .call(utilNoAuto) .call(initCombo, selection) .merge(input); @@ -456,8 +456,8 @@ export function uiFieldCombo(field, context) { var commonValues; if (Array.isArray(tags[field.key])) { - tags[field.key].forEach(function(tagValue) { - var thisVals = utilArrayUniq((tagValue || '').split(';')).filter(Boolean); + tags[field.key].forEach(function(tagVal) { + var thisVals = utilArrayUniq((tagVal || '').split(';')).filter(Boolean); allValues = allValues.concat(thisVals); if (!commonValues) { commonValues = thisVals; @@ -544,9 +544,6 @@ export function uiFieldCombo(field, context) { .attr('class', 'remove') .text('×'); - container.selectAll('input[type="text"]') - .attr('maxlength', maxLength); - } else { var isMixed = Array.isArray(tags[field.key]); diff --git a/modules/ui/fields/cycleway.js b/modules/ui/fields/cycleway.js index a89035930..acfc37ed5 100644 --- a/modules/ui/fields/cycleway.js +++ b/modules/ui/fields/cycleway.js @@ -56,7 +56,6 @@ export function uiFieldCycleway(field, context) { .attr('class', 'preset-input-cycleway-wrap') .append('input') .attr('type', 'text') - .attr('maxlength', context.maxCharsForTagValue()) .attr('class', function(d) { return 'preset-input-cycleway preset-input-' + stripcolon(d); }) .call(utilNoAuto) .each(function(d) { @@ -77,7 +76,7 @@ export function uiFieldCycleway(field, context) { function change(key) { - var newValue = utilGetSetValue(d3_select(this)); + var newValue = context.cleanTagValue(utilGetSetValue(d3_select(this))); // don't override multiple values with blank string if (!newValue && (Array.isArray(_tags.cycleway) || Array.isArray(_tags[key]))) return; diff --git a/modules/ui/fields/input.js b/modules/ui/fields/input.js index 4882402b5..9997b2b65 100644 --- a/modules/ui/fields/input.js +++ b/modules/ui/fields/input.js @@ -56,7 +56,6 @@ export function uiFieldText(field, context) { .append('input') .attr('type', field.type === 'identifier' ? 'text' : field.type) .attr('id', field.domId) - .attr('maxlength', context.maxCharsForTagValue()) .classed(field.type, true) .call(utilNoAuto) .merge(input); @@ -167,13 +166,13 @@ export function uiFieldText(field, context) { function change(onInput) { return function() { var t = {}; - var val = utilGetSetValue(input).trim() || undefined; + var val = context.cleanTagValue(utilGetSetValue(input)); // don't override multiple values with blank string if (!val && Array.isArray(_tags[field.key])) return; if (!onInput) { - if (field.type === 'number' && val !== undefined) { + if (field.type === 'number' && val) { var vals = val.split(';'); vals = vals.map(function(v) { var num = parseFloat(v.trim(), 10); @@ -181,9 +180,9 @@ export function uiFieldText(field, context) { }); val = vals.join(';'); } - utilGetSetValue(input, val || ''); + utilGetSetValue(input, val); } - t[field.key] = val; + t[field.key] = val || undefined; dispatch.call('change', this, t, onInput); }; } diff --git a/modules/ui/fields/localized.js b/modules/ui/fields/localized.js index 6be23976f..9cd606a18 100644 --- a/modules/ui/fields/localized.js +++ b/modules/ui/fields/localized.js @@ -170,7 +170,6 @@ export function uiFieldLocalized(field, context) { .attr('type', 'text') .attr('id', field.domId) .attr('class', 'localized-main') - .attr('maxlength', context.maxCharsForTagValue()) .call(utilNoAuto) .merge(input); @@ -385,13 +384,15 @@ export function uiFieldLocalized(field, context) { d3_event.preventDefault(); return; } - var t = {}; - var val = utilGetSetValue(d3_select(this)) || undefined; + + var val = context.cleanTagValue(utilGetSetValue(d3_select(this))); // don't override multiple values with blank string if (!val && Array.isArray(_tags[field.key])) return; - t[field.key] = val; + var t = {}; + + t[field.key] = val || undefined; dispatch.call('change', this, t, onInput); }; } @@ -420,12 +421,14 @@ export function uiFieldLocalized(field, context) { tags[key(d.lang)] = undefined; } + var newKey = lang && context.cleanTagKey(key(lang)); + var value = utilGetSetValue(d3_select(this.parentNode).selectAll('.localized-value')); - if (lang && value) { - tags[key(lang)] = value; - } else if (lang && _wikiTitles && _wikiTitles[d.lang]) { - tags[key(lang)] = _wikiTitles[d.lang]; + if (newKey && value) { + tags[newKey] = value; + } else if (newKey && _wikiTitles && _wikiTitles[d.lang]) { + tags[newKey] = _wikiTitles[d.lang]; } d.lang = lang; @@ -435,7 +438,7 @@ export function uiFieldLocalized(field, context) { function changeValue(d) { if (!d.lang) return; - var value = utilGetSetValue(d3_select(this)) || undefined; + var value = context.cleanTagValue(utilGetSetValue(d3_select(this))) || undefined; // don't override multiple values with blank string if (!value && Array.isArray(d.value)) return; @@ -549,7 +552,6 @@ export function uiFieldLocalized(field, context) { wrap .append('input') .attr('type', 'text') - .attr('maxlength', context.maxCharsForTagValue()) .attr('class', 'localized-value') .on('blur', changeValue) .on('change', changeValue); diff --git a/modules/ui/fields/maxspeed.js b/modules/ui/fields/maxspeed.js index 0bbee9f10..4e5f230b3 100644 --- a/modules/ui/fields/maxspeed.js +++ b/modules/ui/fields/maxspeed.js @@ -43,7 +43,6 @@ export function uiFieldMaxspeed(field, context) { .attr('type', 'text') .attr('class', 'maxspeed-number') .attr('id', field.domId) - .attr('maxlength', context.maxCharsForTagValue() - 4) .call(utilNoAuto) .call(speedCombo) .merge(input); @@ -95,7 +94,7 @@ export function uiFieldMaxspeed(field, context) { function change() { var tag = {}; - var value = utilGetSetValue(input); + var value = utilGetSetValue(input).trim(); // don't override multiple values with blank string if (!value && Array.isArray(_tags[field.key])) return; @@ -103,9 +102,9 @@ export function uiFieldMaxspeed(field, context) { if (!value) { tag[field.key] = undefined; } else if (isNaN(value) || !_isImperial) { - tag[field.key] = value; + tag[field.key] = context.cleanTagValue(value); } else { - tag[field.key] = value + ' mph'; + tag[field.key] = context.cleanTagValue(value + ' mph'); } dispatch.call('change', this, tag); diff --git a/modules/ui/fields/textarea.js b/modules/ui/fields/textarea.js index 718c62c84..ac5393e7b 100644 --- a/modules/ui/fields/textarea.js +++ b/modules/ui/fields/textarea.js @@ -30,7 +30,6 @@ export function uiFieldTextarea(field, context) { input = input.enter() .append('textarea') .attr('id', field.domId) - .attr('maxlength', context.maxCharsForTagValue()) .call(utilNoAuto) .on('input', change(true)) .on('blur', change()) @@ -42,13 +41,13 @@ export function uiFieldTextarea(field, context) { function change(onInput) { return function() { - var val = utilGetSetValue(input) || undefined; + var val = context.cleanTagValue(utilGetSetValue(input)); // don't override multiple values with blank string if (!val && Array.isArray(_tags[field.key])) return; var t = {}; - t[field.key] = val; + t[field.key] = val || undefined; dispatch.call('change', this, t, onInput); }; } diff --git a/modules/ui/fields/wikidata.js b/modules/ui/fields/wikidata.js index e27316558..51586b55b 100644 --- a/modules/ui/fields/wikidata.js +++ b/modules/ui/fields/wikidata.js @@ -14,8 +14,7 @@ import { svgIcon } from '../../svg/icon'; import { utilGetSetValue, utilNoAuto, - utilRebind, - utilUnicodeCharsTruncated + utilRebind } from '../../util'; import { t } from '../../core/localizer'; @@ -237,7 +236,7 @@ export function uiFieldWikidata(field, context) { } if (newWikipediaValue) { - newWikipediaValue = utilUnicodeCharsTruncated(newWikipediaValue, context.maxCharsForTagValue()); + newWikipediaValue = context.cleanTagValue(newWikipediaValue); } if (typeof newWikipediaValue === 'undefined') return; diff --git a/modules/ui/fields/wikipedia.js b/modules/ui/fields/wikipedia.js index ad62f32a3..3c988b958 100644 --- a/modules/ui/fields/wikipedia.js +++ b/modules/ui/fields/wikipedia.js @@ -7,7 +7,7 @@ import { actionChangeTags } from '../../actions/change_tags'; import { services } from '../../services/index'; import { svgIcon } from '../../svg/icon'; import { uiCombobox } from '../combobox'; -import { utilGetSetValue, utilNoAuto, utilRebind, utilUnicodeCharsTruncated } from '../../util'; +import { utilGetSetValue, utilNoAuto, utilRebind } from '../../util'; export function uiFieldWikipedia(field, context) { @@ -115,7 +115,6 @@ export function uiFieldWikipedia(field, context) { .attr('type', 'text') .attr('class', 'wiki-title') .attr('id', field.domId) - .attr('maxlength', context.maxCharsForTagValue() - 4) .call(utilNoAuto) .call(titleCombo) .merge(_titleInput); @@ -192,7 +191,7 @@ export function uiFieldWikipedia(field, context) { } if (value) { - syncTags.wikipedia = utilUnicodeCharsTruncated(language()[2] + ':' + value, context.maxCharsForTagValue()); + syncTags.wikipedia = context.cleanTagValue(language()[2] + ':' + value); } else { syncTags.wikipedia = undefined; } diff --git a/modules/ui/sections/raw_member_editor.js b/modules/ui/sections/raw_member_editor.js index ed6e7c478..f99f01438 100644 --- a/modules/ui/sections/raw_member_editor.js +++ b/modules/ui/sections/raw_member_editor.js @@ -81,7 +81,7 @@ export function uiSectionRawMemberEditor(context) { function changeRole(d) { var oldRole = d.role; - var newRole = d3_select(this).property('value'); + var newRole = context.cleanRelationRole(d3_select(this).property('value')); if (oldRole !== newRole) { var member = { id: d.id, type: d.type, role: newRole }; @@ -232,7 +232,6 @@ export function uiSectionRawMemberEditor(context) { return d.domId; }) .property('type', 'text') - .attr('maxlength', context.maxCharsForRelationRole()) .attr('placeholder', t('inspector.role')) .call(utilNoAuto); diff --git a/modules/ui/sections/raw_membership_editor.js b/modules/ui/sections/raw_membership_editor.js index d3d1dd974..476603c01 100644 --- a/modules/ui/sections/raw_membership_editor.js +++ b/modules/ui/sections/raw_membership_editor.js @@ -67,7 +67,7 @@ export function uiSectionRawMembershipEditor(context) { if (_inChange) return; // avoid accidental recursive call #5731 var oldRole = d.member.role; - var newRole = d3_select(this).property('value'); + var newRole = context.cleanRelationRole(d3_select(this).property('value')); if (oldRole !== newRole) { _inChange = true; @@ -267,7 +267,6 @@ export function uiSectionRawMembershipEditor(context) { return d.domId; }) .property('type', 'text') - .attr('maxlength', context.maxCharsForRelationRole()) .attr('placeholder', t('inspector.role')) .call(utilNoAuto) .property('value', function(d) { return d.member.role; }) @@ -315,7 +314,6 @@ export function uiSectionRawMembershipEditor(context) { .append('input') .attr('class', 'member-role') .property('type', 'text') - .attr('maxlength', context.maxCharsForRelationRole()) .attr('placeholder', t('inspector.role')) .call(utilNoAuto); @@ -387,7 +385,7 @@ export function uiSectionRawMembershipEditor(context) { // remove hover-higlighting if (d.relation) utilHighlightEntities([d.relation.id], false, context); - var role = list.selectAll('.member-row-new .member-role').property('value'); + var role = context.cleanRelationRole(list.selectAll('.member-row-new .member-role').property('value')); addMembership(d, role); } diff --git a/modules/ui/sections/raw_tag_editor.js b/modules/ui/sections/raw_tag_editor.js index 643429f3b..7b25ff41d 100644 --- a/modules/ui/sections/raw_tag_editor.js +++ b/modules/ui/sections/raw_tag_editor.js @@ -9,7 +9,7 @@ import { uiTagReference } from '../tag_reference'; import { prefs } from '../../core/preferences'; import { t } from '../../core/localizer'; import { utilArrayDifference, utilArrayIdentical } from '../../util/array'; -import { utilGetSetValue, utilNoAuto, utilRebind, utilTagDiff, utilUnicodeCharsTruncated } from '../../util'; +import { utilGetSetValue, utilNoAuto, utilRebind, utilTagDiff } from '../../util'; export function uiSectionRawTagEditor(id, context) { @@ -187,7 +187,6 @@ export function uiSectionRawTagEditor(id, context) { .append('input') .property('type', 'text') .attr('class', 'key') - .attr('maxlength', context.maxCharsForTagKey()) .call(utilNoAuto) .on('blur', keyChange) .on('change', keyChange); @@ -198,7 +197,6 @@ export function uiSectionRawTagEditor(id, context) { .append('input') .property('type', 'text') .attr('class', 'value') - .attr('maxlength', context.maxCharsForTagValue()) .call(utilNoAuto) .on('blur', valueChange) .on('change', valueChange) @@ -338,13 +336,11 @@ export function uiSectionRawTagEditor(id, context) { function textChanged() { var newText = this.value.trim(); var newTags = {}; - var maxKeyLength = context.maxCharsForTagKey(); - var maxValueLength = context.maxCharsForTagValue(); newText.split('\n').forEach(function(row) { var m = row.match(/^\s*([^=]+)=(.*)$/); if (m !== null) { - var k = utilUnicodeCharsTruncated(unstringify(m[1].trim()), maxKeyLength); - var v = utilUnicodeCharsTruncated(unstringify(m[2].trim()), maxValueLength); + var k = context.cleanTagKey(unstringify(m[1].trim())); + var v = context.cleanTagValue(unstringify(m[2].trim())); newTags[k] = v; } }); @@ -461,10 +457,11 @@ export function uiSectionRawTagEditor(id, context) { if (d3_select(this).attr('readonly')) return; var kOld = d.key; - var kNew = this.value.trim(); - var row = this.parentNode.parentNode; - var inputVal = d3_select(row).selectAll('input.value'); - var vNew = utilGetSetValue(inputVal); + + // exit if we are currently about to delete this row anyway - #6366 + if (_pendingChange && _pendingChange.hasOwnProperty(kOld) && _pendingChange[kOld] === undefined) return; + + var kNew = context.cleanTagKey(this.value.trim()); // allow no change if the key should be readonly if (isReadOnly({ key: kNew })) { @@ -472,26 +469,29 @@ export function uiSectionRawTagEditor(id, context) { return; } - // switch focus if key is already in use - if (kNew && kNew !== kOld) { - if (_tags[kNew] !== undefined) { // new key is already in use - this.value = kOld; // reset the key - section.selection().selectAll('.tag-list input.value') - .each(function(d) { - if (d.key === kNew) { // send focus to that other value combo instead - var input = d3_select(this).node(); - input.focus(); - input.select(); - } - }); - return; - } + if (kNew && + kNew !== kOld && + _tags[kNew] !== undefined) { + // new key is already in use, switch focus to the existing row + + this.value = kOld; // reset the key + section.selection().selectAll('.tag-list input.value') + .each(function(d) { + if (d.key === kNew) { // send focus to that other value combo instead + var input = d3_select(this).node(); + input.focus(); + input.select(); + } + }); + return; } - _pendingChange = _pendingChange || {}; - // exit if we are currently about to delete this row anyway - #6366 - if (_pendingChange.hasOwnProperty(d.key) && _pendingChange[d.key] === undefined) return; + var row = this.parentNode.parentNode; + var inputVal = d3_select(row).selectAll('input.value'); + var vNew = context.cleanTagValue(utilGetSetValue(inputVal)); + + _pendingChange = _pendingChange || {}; if (kOld) { _pendingChange[kOld] = undefined; @@ -517,12 +517,12 @@ export function uiSectionRawTagEditor(id, context) { // exit if this is a multiselection and no value was entered if (typeof d.value !== 'string' && !this.value) return; - _pendingChange = _pendingChange || {}; - // exit if we are currently about to delete this row anyway - #6366 - if (_pendingChange.hasOwnProperty(d.key) && _pendingChange[d.key] === undefined) return; + if (_pendingChange && _pendingChange.hasOwnProperty(d.key) && _pendingChange[d.key] === undefined) return; - _pendingChange[d.key] = this.value; + _pendingChange = _pendingChange || {}; + + _pendingChange[d.key] = context.cleanTagValue(this.value); scheduleChange(); }