diff --git a/modules/services/osm_wikibase.js b/modules/services/osm_wikibase.js index b6f44a87b..f600b6f0f 100644 --- a/modules/services/osm_wikibase.js +++ b/modules/services/osm_wikibase.js @@ -3,6 +3,7 @@ import _forEach from 'lodash-es/forEach'; import { json as d3_json } from 'd3-request'; +import { utilDetect } from '../util/detect'; import { utilQsString } from '../util'; @@ -109,6 +110,17 @@ export default { }, + // + // Pass params object of the form: + // { + // key: 'string', // required + // value: 'string' // optional + // } + // -or- + // { + // rtype: 'rtype' // relation type (e.g. 'multipolygon') + // } + // getEntity: function(params, callback) { var doRequest = params.debounce ? debouncedRequest : request; var self = this; @@ -202,11 +214,112 @@ export default { }, + // + // Pass params object of the form: + // { + // key: 'string', // required + // value: 'string' // optional + // } + // -or- + // { + // rtype: 'rtype' // relation type (e.g. 'multipolygon') + // } + // + // Get an result object used to display tag documentation + // { + // title: 'string', + // description: 'string', + // editURL: 'string', + // imageURL: 'string', + // wiki: { title: 'string', text: 'string', url: 'string' } + // } + // + getDocs: function(params, callback) { + var that = this; + var langCode = utilDetect().locale.toLowerCase(); + params.langCode = langCode; + + this.getEntity(params, function(err, data) { + if (err) { + callback(err); + return; + } + + var entity = data.tag || data.key; + if (!entity) { + callback('No entity'); + return; + } + + // prepare result + var result = { + title: entity.title, + description: entity.description, + editURL: 'https://wiki.openstreetmap.org/wiki/' + entity.title + }; + + // add image + if (entity.claims) { + var imageroot; + var image = that.claimToValue(entity, 'P4', langCode); + if (image) { + imageroot = 'https://commons.wikimedia.org/w/index.php'; + } else { + image = that.claimToValue(entity, 'P28', langCode); + if (image) { + imageroot = 'https://wiki.openstreetmap.org/w/index.php'; + } + } + if (imageroot && image) { + result.imageURL = imageroot + '?' + utilQsString({ + title: 'Special:Redirect/file/' + image, + width: 100, + height: 100 + }); + } + } + + // Try to get a wiki page from tag data item first, followed by the corresponding key data item. + // If neither tag nor key data item contain a wiki page in the needed language nor English, + // get the first found wiki page from either the tag or the key item. + var tagWiki = that.monolingualClaimToValueObj(data.tag, 'P31'); + var keyWiki = that.monolingualClaimToValueObj(data.key, 'P31'); + + // If exact language code does not exist, try to find the first part before the '-' + // BUG: in some cases, a more elaborate fallback logic might be needed + var langPrefix = langCode.split('-', 2)[0]; + + result.wiki = + getWikiInfo(tagWiki, langCode, 'inspector.wiki_reference') || + getWikiInfo(tagWiki, langPrefix, 'inspector.wiki_reference') || + getWikiInfo(tagWiki, 'en', 'inspector.wiki_en_reference') || + getWikiInfo(keyWiki, langCode, 'inspector.wiki_reference') || + getWikiInfo(keyWiki, langPrefix, 'inspector.wiki_reference') || + getWikiInfo(keyWiki, 'en', 'inspector.wiki_en_reference'); + + callback(null, result); + + + // Helper method to get wiki info if a given language exists + function getWikiInfo(wiki, langCode, tKey) { + if (wiki && wiki[langCode]) { + return { + title: wiki[langCode], + text: tKey, + url: 'https://wiki.openstreetmap.org/wiki/' + wiki[langCode] + }; + } + } + }); + }, + + addLocale: function(langCode, qid) { // Makes it easier to unit test _localeIDs[langCode] = qid; }, + apibase: function(val) { if (!arguments.length) return apibase; apibase = val; diff --git a/modules/ui/raw_tag_editor.js b/modules/ui/raw_tag_editor.js index 47069e159..e4895bb7e 100644 --- a/modules/ui/raw_tag_editor.js +++ b/modules/ui/raw_tag_editor.js @@ -16,7 +16,7 @@ export function uiRawTagEditor(context) { var taginfo = services.taginfo; var dispatch = d3_dispatch('change'); var _readOnlyTags = []; - var _sortKeys = false; + var _orderedKeys = []; var _showBlank = false; var _updatePreference = true; var _expanded = false; @@ -53,19 +53,34 @@ export function uiRawTagEditor(context) { function content(wrap) { - var entries = []; - var keys = Object.keys(_tags); - if (_sortKeys) { - _sortKeys = false; - keys = keys.sort(); - } - for (var i = 0; i < keys.length; i++) { - entries.push({ key: keys[i], value: _tags[keys[i]] }); + var rowData = []; + var seen = {}; + var allKeys = Object.keys(_tags); + var i, k; + + // When switching to a different entity or changing the state (hover/select) + // we reorder the keys. Otherwise leave them as the user entered - #5857 + if (!_orderedKeys.length) { + _orderedKeys = allKeys.sort(); } - if (!entries.length || _showBlank) { + // push ordered keys first + for (i = 0; i < _orderedKeys.length; i++) { + k = _orderedKeys[i]; + if (_tags[k] === undefined) continue; // e.g. tag was removed + seen[k] = true; + rowData.push({ key: k, value: _tags[k] }); + } + // push unknown keys after - these are tags the user added + for (i = 0; i < allKeys.length; i++) { + k = allKeys[i]; + if (seen[k]) continue; + rowData.push({ key: k, value: _tags[k] }); + } + // push blank row last, if necessary + if (!rowData.length || _showBlank) { _showBlank = false; - entries.push({ key: '', value: '' }); + rowData.push({ key: '', value: '' }); } // List of tags @@ -102,7 +117,7 @@ export function uiRawTagEditor(context) { // Tag list items var items = list.selectAll('.tag-row') - .data(entries, function(d) { return d.key; }); + .data(rowData, function(d) { return d.key; }); items.exit() .each(unbind) @@ -110,12 +125,12 @@ export function uiRawTagEditor(context) { // Enter - var enter = items.enter() + var itemsEnter = items.enter() .append('li') .attr('class', 'tag-row') .classed('readonly', isReadOnly); - var innerWrap = enter.append('div') + var innerWrap = itemsEnter.append('div') .attr('class', 'inner-wrap'); innerWrap @@ -151,7 +166,7 @@ export function uiRawTagEditor(context) { // Update items = items - .merge(enter) + .merge(itemsEnter) .order(); items @@ -373,7 +388,10 @@ export function uiRawTagEditor(context) { rawTagEditor.state = function(val) { if (!arguments.length) return _state; - _state = val; + if (_state !== val) { + _orderedKeys = []; + _state = val; + } return rawTagEditor; }; @@ -402,9 +420,9 @@ export function uiRawTagEditor(context) { rawTagEditor.entityID = function(val) { if (!arguments.length) return _entityID; if (_entityID !== val) { - _sortKeys = true; + _orderedKeys = []; + _entityID = val; } - _entityID = val; return rawTagEditor; }; diff --git a/modules/ui/tag_reference.js b/modules/ui/tag_reference.js index c7e0ebaa3..0123a9460 100644 --- a/modules/ui/tag_reference.js +++ b/modules/ui/tag_reference.js @@ -4,12 +4,19 @@ import { } from 'd3-selection'; import { t } from '../util/locale'; -import { utilDetect } from '../util/detect'; import { services } from '../services'; import { svgIcon } from '../svg'; -import { utilQsString } from '../util'; +// Pass `tag` object of the form: +// { +// key: 'string', // required +// value: 'string' // optional +// } +// -or- +// { +// rtype: 'rtype' // relation type (e.g. 'multipolygon') +// } export function uiTagReference(tag) { var wikibase = services.osmWikibase; var tagReference = {}; @@ -19,79 +26,14 @@ export function uiTagReference(tag) { var _loaded; var _showing; - /** - * @returns {{itemTitle: String, description: String, image: String|null}|null} - **/ - function findLocal(data) { - var entity = data.tag || data.key; - if (!entity) return null; - var result = { - title: entity.title, - description: entity.description, - }; - - if (entity.claims) { - var langCode = utilDetect().locale.toLowerCase(); - var url; - var image = wikibase.claimToValue(entity, 'P4', langCode); - if (image) { - url = 'https://commons.wikimedia.org/w/index.php'; - } else { - image = wikibase.claimToValue(entity, 'P28', langCode); - if (image) { - url = 'https://wiki.openstreetmap.org/w/index.php'; - } - } - if (image) { - result.image = { - url: url, - title: 'Special:Redirect/file/' + image - }; - } - } - - // Helper method to get wiki info if a given language exists - function getWikiInfo(wiki, langCode, msg) { - if (wiki && wiki[langCode]) { - return {title: wiki[langCode], text: t(msg)}; - } - } - - // Try to get a wiki page from tag data item first, followed by the corresponding key data item. - // If neither tag nor key data item contain a wiki page in the needed language nor English, - // get the first found wiki page from either the tag or the key item. - var tagWiki = wikibase.monolingualClaimToValueObj(data.tag, 'P31'); - var keyWiki = wikibase.monolingualClaimToValueObj(data.key, 'P31'); - - // If exact language code does not exist, try to find the first part before the '-' - // BUG: in some cases, a more elaborate fallback logic might be needed - var langPrefix = langCode.split('-', 2)[0]; - - result.wiki = - getWikiInfo(tagWiki, langCode, 'inspector.wiki_reference') || - getWikiInfo(tagWiki, langPrefix, 'inspector.wiki_reference') || - getWikiInfo(tagWiki, 'en', 'inspector.wiki_en_reference') || - getWikiInfo(keyWiki, langCode, 'inspector.wiki_reference') || - getWikiInfo(keyWiki, langPrefix, 'inspector.wiki_reference') || - getWikiInfo(keyWiki, 'en', 'inspector.wiki_en_reference'); - - return result; - } - - - function load(param) { + function load() { if (!wikibase) return; _button .classed('tag-reference-loading', true); - wikibase.getEntity(param, function show(err, data) { - var docs; - if (!err && data) { - docs = findLocal(data); - } - + wikibase.getDocs(tag, function(err, docs) { _body.html(''); if (!docs || !docs.title) { @@ -103,17 +45,11 @@ export function uiTagReference(tag) { return; } - if (docs.image) { - var imageUrl = docs.image.url + '?' + utilQsString({ - title: docs.image.title, - width: 100, - height: 100, - }); - + if (docs.imageURL) { _body .append('img') .attr('class', 'tag-reference-wiki-image') - .attr('src', imageUrl) + .attr('src', docs.imageURL) .on('load', function() { done(); }) .on('error', function() { d3_select(this).remove(); done(); }); } else { @@ -129,7 +65,7 @@ export function uiTagReference(tag) { .attr('target', '_blank') .attr('tabindex', -1) .attr('title', t('inspector.edit_reference')) - .attr('href', 'https://wiki.openstreetmap.org/wiki/' + docs.title) + .attr('href', docs.editURL) .call(svgIcon('#iD-icon-edit', 'inline')); if (docs.wiki) { @@ -138,14 +74,14 @@ export function uiTagReference(tag) { .attr('class', 'tag-reference-link') .attr('target', '_blank') .attr('tabindex', -1) - .attr('href', 'https://wiki.openstreetmap.org/wiki/' + docs.wiki.title) + .attr('href', docs.wiki.url) .call(svgIcon('#iD-icon-out-link', 'inline')) .append('span') - .text(docs.wiki.text); + .text(t(docs.wiki.text)); } // Add link to info about "good changeset comments" - #2923 - if (param.key === 'comment') { + if (tag.key === 'comment') { _body .append('a') .attr('class', 'tag-reference-comment-link') @@ -212,8 +148,7 @@ export function uiTagReference(tag) { } else if (_loaded) { done(); } else { - tag.langCode = utilDetect().locale.toLowerCase(); - load(tag); + load(); } }); }; @@ -240,9 +175,9 @@ export function uiTagReference(tag) { }; - tagReference.showing = function(_) { + tagReference.showing = function(val) { if (!arguments.length) return _showing; - _showing = _; + _showing = val; return tagReference; };