mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-25 09:34:04 +02:00
Merge pull request #6302 from openstreetmap/text-raw-tag-editor
Text raw tag editor / Copy-paste tags
This commit is contained in:
@@ -17,7 +17,7 @@ import { uiTagReference } from './tag_reference';
|
||||
import { uiPresetEditor } from './preset_editor';
|
||||
import { uiEntityIssues } from './entity_issues';
|
||||
import { uiTooltipHtml } from './tooltipHtml';
|
||||
import { utilCleanTags, utilRebind } from '../util';
|
||||
import { utilCallWhenIdle, utilCleanTags, utilRebind } from '../util';
|
||||
|
||||
|
||||
export function uiEntityEditor(context) {
|
||||
@@ -25,6 +25,7 @@ export function uiEntityEditor(context) {
|
||||
var _state = 'select';
|
||||
var _coalesceChanges = false;
|
||||
var _modified = false;
|
||||
var _scrolled = false;
|
||||
var _base;
|
||||
var _entityID;
|
||||
var _activePreset;
|
||||
@@ -83,7 +84,8 @@ export function uiEntityEditor(context) {
|
||||
// Enter
|
||||
var bodyEnter = body.enter()
|
||||
.append('div')
|
||||
.attr('class', 'inspector-body');
|
||||
.attr('class', 'inspector-body')
|
||||
.on('scroll.entity-editor', function() { _scrolled = true; });
|
||||
|
||||
bodyEnter
|
||||
.append('div')
|
||||
@@ -327,9 +329,14 @@ export function uiEntityEditor(context) {
|
||||
_coalesceChanges = false;
|
||||
|
||||
// reset the scroll to the top of the inspector (warning: triggers reflow)
|
||||
var body = d3_selectAll('.entity-editor-pane .inspector-body');
|
||||
if (!body.empty()) {
|
||||
body.node().scrollTop = 0;
|
||||
if (_scrolled) {
|
||||
utilCallWhenIdle(function() {
|
||||
var body = d3_selectAll('.entity-editor-pane .inspector-body');
|
||||
if (!body.empty()) {
|
||||
_scrolled = false;
|
||||
body.node().scrollTop = 0;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
var presetMatch = context.presets().match(context.entity(_entityID), _base);
|
||||
|
||||
+147
-23
@@ -7,12 +7,18 @@ import { svgIcon } from '../svg/icon';
|
||||
import { uiCombobox } from './combobox';
|
||||
import { uiDisclosure } from './disclosure';
|
||||
import { uiTagReference } from './tag_reference';
|
||||
import { utilArrayDifference, utilGetSetValue, utilNoAuto, utilRebind } from '../util';
|
||||
import { utilArrayDifference, utilGetSetValue, utilNoAuto, utilRebind, utilTagDiff } from '../util';
|
||||
|
||||
|
||||
export function uiRawTagEditor(context) {
|
||||
var taginfo = services.taginfo;
|
||||
var dispatch = d3_dispatch('change');
|
||||
var availableViews = [
|
||||
{ id: 'text', icon: '#fas-i-cursor' },
|
||||
{ id: 'list', icon: '#fas-th-list' }
|
||||
];
|
||||
|
||||
var _tagView = (context.storage('raw-tag-editor-view') || 'list'); // 'list, 'text'
|
||||
var _readOnlyTags = [];
|
||||
var _indexedKeys = [];
|
||||
var _showBlank = false;
|
||||
@@ -75,13 +81,80 @@ export function uiRawTagEditor(context) {
|
||||
rowData.push({ index: _indexedKeys.length, key: '', value: '' });
|
||||
}
|
||||
|
||||
// List of tags
|
||||
|
||||
// View Options
|
||||
var options = wrap.selectAll('.raw-tag-options')
|
||||
.data([0]);
|
||||
|
||||
var optionsEnter = options.enter()
|
||||
.append('div')
|
||||
.attr('class', 'raw-tag-options');
|
||||
|
||||
var optionEnter = optionsEnter.selectAll('.raw-tag-option')
|
||||
.data(availableViews, function(d) { return d.id; })
|
||||
.enter();
|
||||
|
||||
optionEnter
|
||||
.append('button')
|
||||
.attr('class', function(d) {
|
||||
return 'raw-tag-option raw-tag-option-' + d.id + (_tagView === d.id ? ' selected' : '');
|
||||
})
|
||||
.attr('title', function(d) { return d.id; })
|
||||
.on('click', function(d) {
|
||||
_tagView = d.id;
|
||||
context.storage('raw-tag-editor-view', d.id);
|
||||
|
||||
wrap.selectAll('.raw-tag-option')
|
||||
.classed('selected', function(datum) { return datum === d; });
|
||||
|
||||
wrap.selectAll('.tag-text')
|
||||
.classed('hide', (d.id !== 'text'))
|
||||
.each(setTextareaHeight);
|
||||
|
||||
wrap.selectAll('.tag-list, .add-row')
|
||||
.classed('hide', (d.id !== 'list'));
|
||||
})
|
||||
.each(function(d) {
|
||||
d3_select(this)
|
||||
.call(svgIcon(d.icon));
|
||||
});
|
||||
|
||||
|
||||
// View as Text
|
||||
var textData = rowsToText(rowData);
|
||||
var textarea = wrap.selectAll('.tag-text')
|
||||
.data([0]);
|
||||
|
||||
textarea = textarea.enter()
|
||||
.append('textarea')
|
||||
.attr('class', 'tag-text' + (_tagView !== 'text' ? ' hide' : ''))
|
||||
.call(utilNoAuto)
|
||||
.attr('spellcheck', 'false')
|
||||
.merge(textarea);
|
||||
|
||||
textarea
|
||||
.call(utilGetSetValue, textData)
|
||||
.each(setTextareaHeight)
|
||||
.on('input', setTextareaHeight)
|
||||
.on('blur', textChanged)
|
||||
.on('change', textChanged);
|
||||
|
||||
// If All Fields section is hidden, focus textarea and put cursor at end..
|
||||
var fieldsExpanded = d3_select('.hide-toggle-preset_fields.expanded').size();
|
||||
if (_state !== 'hover' && _tagView === 'text' && !fieldsExpanded) {
|
||||
var element = textarea.node();
|
||||
element.focus();
|
||||
element.setSelectionRange(textData.length, textData.length);
|
||||
}
|
||||
|
||||
|
||||
// View as List
|
||||
var list = wrap.selectAll('.tag-list')
|
||||
.data([0]);
|
||||
|
||||
list = list.enter()
|
||||
.append('ul')
|
||||
.attr('class', 'tag-list')
|
||||
.attr('class', 'tag-list' + (_tagView !== 'list' ? ' hide' : ''))
|
||||
.merge(list);
|
||||
|
||||
|
||||
@@ -90,7 +163,7 @@ export function uiRawTagEditor(context) {
|
||||
.data([0])
|
||||
.enter()
|
||||
.append('div')
|
||||
.attr('class', 'add-row');
|
||||
.attr('class', 'add-row' + (_tagView !== 'list' ? ' hide' : ''));
|
||||
|
||||
addRowEnter
|
||||
.append('button')
|
||||
@@ -217,6 +290,75 @@ export function uiRawTagEditor(context) {
|
||||
}
|
||||
|
||||
|
||||
function setTextareaHeight() {
|
||||
if (_tagView !== 'text') return;
|
||||
|
||||
var selection = d3_select(this);
|
||||
selection.style('height', null);
|
||||
selection.style('height', selection.node().scrollHeight + 5 + 'px');
|
||||
}
|
||||
|
||||
|
||||
function stringify(s) {
|
||||
return JSON.stringify(s).slice(1, -1); // without leading/trailing "
|
||||
}
|
||||
|
||||
function unstringify(s) {
|
||||
var leading = '';
|
||||
var trailing = '';
|
||||
if (s.length < 1 || s.charAt(0) !== '"') {
|
||||
leading = '"';
|
||||
}
|
||||
if (s.length < 2 || s.charAt(s.length - 1) !== '"' ||
|
||||
(s.charAt(s.length - 1) === '"' && s.charAt(s.length - 2) === '\\')
|
||||
) {
|
||||
trailing = '"';
|
||||
}
|
||||
return JSON.parse(leading + s + trailing);
|
||||
}
|
||||
|
||||
|
||||
function rowsToText(rows) {
|
||||
var str = rows
|
||||
.filter(function(row) { return row.key && row.key.trim() !== ''; })
|
||||
.map(function(row) { return stringify(row.key) + '=' + stringify(row.value); })
|
||||
.join('\n');
|
||||
|
||||
return _state === 'hover' ? str : str + '\n';
|
||||
}
|
||||
|
||||
|
||||
function textChanged() {
|
||||
var newText = this.value.trim();
|
||||
var newTags = {};
|
||||
newText.split('\n').forEach(function(row) {
|
||||
var m = row.match(/^\s*([^=]+)=(.*)$/);
|
||||
if (m !== null) {
|
||||
var k = unstringify(m[1].trim());
|
||||
var v = unstringify(m[2].trim());
|
||||
newTags[k] = v;
|
||||
}
|
||||
});
|
||||
|
||||
var tagDiff = utilTagDiff(_tags, newTags);
|
||||
if (!tagDiff.length) return;
|
||||
|
||||
_pendingChange = _pendingChange || {};
|
||||
|
||||
tagDiff.forEach(function(change) {
|
||||
if (isReadOnly({ key: change.key })) return;
|
||||
|
||||
if (change.type === '-') {
|
||||
_pendingChange[change.key] = undefined;
|
||||
} else if (change.type === '+') {
|
||||
_pendingChange[change.key] = change.newVal || '';
|
||||
}
|
||||
});
|
||||
|
||||
scheduleChange();
|
||||
}
|
||||
|
||||
|
||||
function pushMore() {
|
||||
if (d3_event.keyCode === 9 && !d3_event.shiftKey &&
|
||||
list.selectAll('li:last-child input.value').node() === this) {
|
||||
@@ -317,25 +459,7 @@ export function uiRawTagEditor(context) {
|
||||
_pendingChange[kOld] = undefined;
|
||||
}
|
||||
|
||||
// if the key looks like "key=value key2=value2", split them up - #5024
|
||||
var keys = (kNew.match(/[\w_]+=/g) || []).map(function (key) { return key.slice(0, -1); });
|
||||
var vals = keys.length === 0
|
||||
? []
|
||||
: kNew
|
||||
.split(new RegExp(keys.map(function (key) { return key.replace('_', '\\_'); }).join('|')))
|
||||
.splice(1)
|
||||
.map(function (val) { return val.slice(1).trim(); });
|
||||
|
||||
if (keys.length > 0) {
|
||||
kNew = keys[0];
|
||||
vNew = vals[0];
|
||||
|
||||
keys.forEach(function (key, i) {
|
||||
_pendingChange[key] = vals[i];
|
||||
});
|
||||
} else {
|
||||
_pendingChange[kNew] = vNew;
|
||||
}
|
||||
_pendingChange[kNew] = vNew;
|
||||
|
||||
d.key = kNew; // update datum to avoid exit/enter on tag update
|
||||
d.value = vNew;
|
||||
|
||||
@@ -38,6 +38,7 @@ export { utilRebind } from './rebind';
|
||||
export { utilSetTransform } from './util';
|
||||
export { utilSessionMutex } from './session_mutex';
|
||||
export { utilStringQs } from './util';
|
||||
export { utilTagDiff } from './util';
|
||||
export { utilTagText } from './util';
|
||||
export { utilTiler } from './tiler';
|
||||
export { utilTriggerEvent } from './trigger_event';
|
||||
|
||||
+34
-2
@@ -1,8 +1,10 @@
|
||||
import { t, textDirection } from './locale';
|
||||
import { utilDetect } from './detect';
|
||||
import { remove as removeDiacritics } from 'diacritics';
|
||||
import { fixRTLTextForSvg, rtlRegex } from './svg_paths_rtl_fix';
|
||||
|
||||
import { t, textDirection } from './locale';
|
||||
import { utilArrayUnion } from './array';
|
||||
import { utilDetect } from './detect';
|
||||
|
||||
|
||||
export function utilTagText(entity) {
|
||||
var obj = (entity && entity.tags) || {};
|
||||
@@ -12,6 +14,36 @@ export function utilTagText(entity) {
|
||||
}
|
||||
|
||||
|
||||
export function utilTagDiff(oldTags, newTags) {
|
||||
var tagDiff = [];
|
||||
var keys = utilArrayUnion(Object.keys(oldTags), Object.keys(newTags)).sort();
|
||||
keys.forEach(function(k) {
|
||||
var oldVal = oldTags[k];
|
||||
var newVal = newTags[k];
|
||||
|
||||
if (oldVal && (!newVal || newVal !== oldVal)) {
|
||||
tagDiff.push({
|
||||
type: '-',
|
||||
key: k,
|
||||
oldVal: oldVal,
|
||||
newVal: newVal,
|
||||
display: '- ' + k + '=' + oldVal
|
||||
});
|
||||
}
|
||||
if (newVal && (!oldVal || newVal !== oldVal)) {
|
||||
tagDiff.push({
|
||||
type: '+',
|
||||
key: k,
|
||||
oldVal: oldVal,
|
||||
newVal: newVal,
|
||||
display: '+ ' + k + '=' + newVal
|
||||
});
|
||||
}
|
||||
});
|
||||
return tagDiff;
|
||||
}
|
||||
|
||||
|
||||
export function utilEntitySelector(ids) {
|
||||
return ids.length ? '.' + ids.join(',.') : 'nothing';
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { actionChangePreset } from '../actions/change_preset';
|
||||
import { actionChangeTags } from '../actions/change_tags';
|
||||
import { actionUpgradeTags } from '../actions/upgrade_tags';
|
||||
import { osmIsOldMultipolygonOuterMember, osmOldMultipolygonOuterMemberOfRelation } from '../osm/multipolygon';
|
||||
import { utilArrayUnion, utilDisplayLabel } from '../util';
|
||||
import { utilDisplayLabel, utilTagDiff } from '../util';
|
||||
import { validationIssue, validationIssueFix } from '../core/validation';
|
||||
|
||||
|
||||
@@ -48,20 +48,7 @@ export function validationOutdatedTags() {
|
||||
}
|
||||
|
||||
// determine diff
|
||||
var keys = utilArrayUnion(Object.keys(oldTags), Object.keys(newTags)).sort();
|
||||
var tagDiff = [];
|
||||
keys.forEach(function(k) {
|
||||
var oldVal = oldTags[k];
|
||||
var newVal = newTags[k];
|
||||
|
||||
if (oldVal && (!newVal || newVal !== oldVal)) {
|
||||
tagDiff.push('- ' + k + '=' + oldVal);
|
||||
}
|
||||
if (newVal && (!oldVal || newVal !== oldVal)) {
|
||||
tagDiff.push('+ ' + k + '=' + newVal);
|
||||
}
|
||||
});
|
||||
|
||||
var tagDiff = utilTagDiff(oldTags, newTags);
|
||||
if (!tagDiff.length) return [];
|
||||
|
||||
return [new validationIssue({
|
||||
@@ -113,10 +100,10 @@ export function validationOutdatedTags() {
|
||||
.attr('class', 'tagDiff-row')
|
||||
.append('td')
|
||||
.attr('class', function(d) {
|
||||
var klass = d.charAt(0) === '+' ? 'add' : 'remove';
|
||||
var klass = d.type === '+' ? 'add' : 'remove';
|
||||
return 'tagDiff-cell tagDiff-cell-' + klass;
|
||||
})
|
||||
.text(function(d) { return d; });
|
||||
.text(function(d) { return d.display; });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { actionChangeTags } from '../actions/change_tags';
|
||||
import { t } from '../util/locale';
|
||||
import { utilDisplayLabel } from '../util';
|
||||
import { utilDisplayLabel, utilTagDiff } from '../util';
|
||||
import { validationIssue, validationIssueFix } from '../core/validation';
|
||||
|
||||
|
||||
@@ -40,20 +40,17 @@ export function validationPrivateData() {
|
||||
|
||||
var validation = function checkPrivateData(entity, context) {
|
||||
var tags = entity.tags;
|
||||
var keepTags = {};
|
||||
var tagDiff = [];
|
||||
if (!tags.building || !privateBuildingValues[tags.building]) return [];
|
||||
|
||||
var keepTags = {};
|
||||
for (var k in tags) {
|
||||
if (publicKeys[k]) return []; // probably a public feature
|
||||
|
||||
if (personalTags[k]) {
|
||||
tagDiff.push('- ' + k + '=' + tags[k]);
|
||||
} else {
|
||||
if (!personalTags[k]) {
|
||||
keepTags[k] = tags[k];
|
||||
}
|
||||
}
|
||||
|
||||
var tagDiff = utilTagDiff(tags, keepTags);
|
||||
if (!tagDiff.length) return [];
|
||||
|
||||
var fixID = tagDiff.length === 1 ? 'remove_tag' : 'remove_tags';
|
||||
@@ -110,10 +107,10 @@ export function validationPrivateData() {
|
||||
.attr('class', 'tagDiff-row')
|
||||
.append('td')
|
||||
.attr('class', function(d) {
|
||||
var klass = d.charAt(0) === '+' ? 'add' : 'remove';
|
||||
var klass = d.type === '+' ? 'add' : 'remove';
|
||||
return 'tagDiff-cell tagDiff-cell-' + klass;
|
||||
})
|
||||
.text(function(d) { return d; });
|
||||
.text(function(d) { return d.display; });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user