mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-15 13:38:26 +02:00
Autocomplete labels in the Wikidata field (close #5544)
This commit is contained in:
+24
-18
@@ -1591,7 +1591,7 @@ button.preset-favorite-button.active .icon {
|
||||
|
||||
/* Buttons inside fields */
|
||||
.form-field-button {
|
||||
flex: 0 0 32px;
|
||||
flex: 0 0 auto;
|
||||
height: 30px;
|
||||
width: 32px;
|
||||
position: relative;
|
||||
@@ -1635,7 +1635,7 @@ button.preset-favorite-button.active .icon {
|
||||
|
||||
/* Field - lists with labeled input items
|
||||
------------------------------------------------------- */
|
||||
.form-field ul.labeled-inputs {
|
||||
.form-field ul.rows {
|
||||
flex: 1 1 auto;
|
||||
border: 1px solid #ccc;
|
||||
border-top: 0;
|
||||
@@ -1643,32 +1643,36 @@ button.preset-favorite-button.active .icon {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.form-field ul.labeled-inputs li {
|
||||
.form-field ul.rows li {
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
.form-field ul.rows li:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
.form-field ul.rows li {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
.form-field ul.labeled-inputs li:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
.form-field ul.labeled-inputs li > span,
|
||||
.form-field ul.labeled-inputs li > div {
|
||||
.form-field ul.rows li.labeled-input > span,
|
||||
.form-field ul.rows li.labeled-input > div {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
.form-field ul.labeled-inputs li input {
|
||||
.form-field ul.rows li input {
|
||||
border-radius: 0;
|
||||
border-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.form-field ul.labeled-inputs li input,
|
||||
.form-field ul.labeled-inputs li button {
|
||||
.form-field ul.rows li button {
|
||||
border-width: 0;
|
||||
}
|
||||
[dir='ltr'] .form-field ul.rows li.labeled-input input,
|
||||
[dir='ltr'] .form-field ul.rows li button {
|
||||
border-left-width: 1px;
|
||||
}
|
||||
[dir='rtl'] .form-field ul.labeled-inputs li input,
|
||||
[dir='rtl'] .form-field ul.labeled-inputs li button {
|
||||
border-left-width: 0;
|
||||
[dir='rtl'] .form-field ul.rows li.labeled-input input,
|
||||
[dir='rtl'] .form-field ul.rows li button {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
@@ -1683,7 +1687,7 @@ button.preset-favorite-button.active .icon {
|
||||
border-top: 0px;
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
.structure-extras-wrap > ul.labeled-inputs {
|
||||
.structure-extras-wrap > ul.rows {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
@@ -2058,10 +2062,12 @@ button.preset-favorite-button.active .icon {
|
||||
[dir='rtl'] .wiki-title-container > input.wiki-title {
|
||||
border-radius: 0 0 4px 0;
|
||||
}
|
||||
.wiki-title-container > button.wiki-link {
|
||||
.wiki-title-container > button.wiki-link,
|
||||
.form-field-wikidata ul.rows li:last-child button.form-field-button:last-child {
|
||||
border-radius: 0 0 4px 0;
|
||||
}
|
||||
[dir='rtl'] .wiki-title-container > button.wiki-link {
|
||||
[dir='rtl'] .wiki-title-container > button.wiki-link,
|
||||
[dir='rtl'] .form-field-wikidata ul.rows li:last-child button.form-field-button:last-child {
|
||||
border-radius: 0 0 0 4px;
|
||||
}
|
||||
|
||||
@@ -2218,7 +2224,7 @@ div.combobox {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
width: 30px !important;
|
||||
margin-left: -30px;
|
||||
vertical-align: top;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -16,6 +16,35 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
// Search for Wikidata items matching the query
|
||||
itemsForSearchQuery: function(query, callback) {
|
||||
if (!query) {
|
||||
callback('No query', {});
|
||||
return;
|
||||
}
|
||||
|
||||
d3_json(apibase + utilQsString({
|
||||
action: 'wbsearchentities',
|
||||
format: 'json',
|
||||
formatversion: 2,
|
||||
search: query,
|
||||
type: 'item',
|
||||
language: this.languagesToQuery()[0],
|
||||
limit: 10,
|
||||
origin: '*'
|
||||
}), function(err, data) {
|
||||
if (data && data.error) {
|
||||
err = data.error;
|
||||
}
|
||||
if (err) {
|
||||
callback(err, {});
|
||||
} else {
|
||||
callback(null, data.search || {});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// Given a Wikipedia language and article title, return an array of
|
||||
// corresponding Wikidata entities.
|
||||
itemsByTitle: function(lang, title, callback) {
|
||||
@@ -46,6 +75,13 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
languagesToQuery: function() {
|
||||
return utilArrayUniq([
|
||||
currentLocale.toLowerCase(),
|
||||
currentLocale.split('-', 2)[0].toLowerCase(),
|
||||
'en'
|
||||
]);
|
||||
},
|
||||
|
||||
entityByQID: function(qid, callback) {
|
||||
if (!qid) {
|
||||
@@ -57,11 +93,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
var langs = utilArrayUniq([
|
||||
currentLocale.toLowerCase(),
|
||||
currentLocale.split('-', 2)[0].toLowerCase(),
|
||||
'en'
|
||||
]);
|
||||
var langs = this.languagesToQuery();
|
||||
|
||||
d3_json(apibase + utilQsString({
|
||||
action: 'wbgetentities',
|
||||
@@ -144,11 +176,7 @@ export default {
|
||||
|
||||
if (entity.sitelinks) {
|
||||
// must be one of these that we requested..
|
||||
var langs = utilArrayUniq([
|
||||
currentLocale.toLowerCase(),
|
||||
currentLocale.split('-', 2)[0].toLowerCase(),
|
||||
'en'
|
||||
]);
|
||||
var langs = this.languagesToQuery();
|
||||
var englishLocale = (currentLocale.split('-', 2)[0].toLowerCase() === 'en');
|
||||
|
||||
for (i = 0; i < langs.length; i++) { // check each, in order of preference
|
||||
|
||||
@@ -23,7 +23,7 @@ export function uiFieldAccess(field, context) {
|
||||
|
||||
list = list.enter()
|
||||
.append('ul')
|
||||
.attr('class', 'labeled-inputs')
|
||||
.attr('class', 'rows')
|
||||
.merge(list);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export function uiFieldAccess(field, context) {
|
||||
// Enter
|
||||
var enter = items.enter()
|
||||
.append('li')
|
||||
.attr('class', function(d) { return 'preset-access-' + d; });
|
||||
.attr('class', function(d) { return 'labeled-input preset-access-' + d; });
|
||||
|
||||
enter
|
||||
.append('span')
|
||||
|
||||
@@ -31,7 +31,7 @@ export function uiFieldCycleway(field, context) {
|
||||
|
||||
div = div.enter()
|
||||
.append('ul')
|
||||
.attr('class', 'labeled-inputs')
|
||||
.attr('class', 'rows')
|
||||
.merge(div);
|
||||
|
||||
var keys = ['cycleway:left', 'cycleway:right'];
|
||||
@@ -41,7 +41,7 @@ export function uiFieldCycleway(field, context) {
|
||||
|
||||
var enter = items.enter()
|
||||
.append('li')
|
||||
.attr('class', function(d) { return 'preset-cycleway-' + stripcolon(d); });
|
||||
.attr('class', function(d) { return 'labeled-input preset-cycleway-' + stripcolon(d); });
|
||||
|
||||
enter
|
||||
.append('span')
|
||||
|
||||
@@ -97,7 +97,7 @@ export function uiFieldRadio(field, context) {
|
||||
|
||||
list = list.enter()
|
||||
.append('ul')
|
||||
.attr('class', 'labeled-inputs')
|
||||
.attr('class', 'rows')
|
||||
.merge(list);
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ export function uiFieldRadio(field, context) {
|
||||
// Enter
|
||||
var typeEnter = typeItem.enter()
|
||||
.insert('li', ':first-child')
|
||||
.attr('class', 'structure-type-item');
|
||||
.attr('class', 'labeled-input structure-type-item');
|
||||
|
||||
typeEnter
|
||||
.append('span')
|
||||
@@ -167,7 +167,7 @@ export function uiFieldRadio(field, context) {
|
||||
// Enter
|
||||
var layerEnter = layerItem.enter()
|
||||
.append('li')
|
||||
.attr('class', 'structure-layer-item');
|
||||
.attr('class', 'labeled-input structure-layer-item');
|
||||
|
||||
layerEnter
|
||||
.append('span')
|
||||
|
||||
+124
-73
@@ -5,6 +5,8 @@ import {
|
||||
event as d3_event
|
||||
} from 'd3-selection';
|
||||
|
||||
import { uiCombobox } from '../index';
|
||||
|
||||
import { services } from '../../services/index';
|
||||
|
||||
import { svgIcon } from '../../svg/index';
|
||||
@@ -17,13 +19,18 @@ import {
|
||||
import { t } from '../../util/locale';
|
||||
|
||||
|
||||
export function uiFieldWikidata(field) {
|
||||
export function uiFieldWikidata(field, context) {
|
||||
var wikidata = services.wikidata;
|
||||
var dispatch = d3_dispatch('change');
|
||||
var link = d3_select(null);
|
||||
var title = d3_select(null);
|
||||
var searchInput = d3_select(null);
|
||||
var _qid = null;
|
||||
var _wikidataEntity = null;
|
||||
var _wikiURL = '';
|
||||
var _entity;
|
||||
|
||||
var combobox = uiCombobox(context, 'combo-' + field.safeid)
|
||||
.caseSensitive(true)
|
||||
.minItems(1);
|
||||
|
||||
function wiki(selection) {
|
||||
|
||||
@@ -41,18 +48,59 @@ export function uiFieldWikidata(field) {
|
||||
|
||||
list = list.enter()
|
||||
.append('ul')
|
||||
.attr('class', 'labeled-inputs')
|
||||
.attr('class', 'rows')
|
||||
.merge(list);
|
||||
|
||||
var wikidataProperties = ['identifier', 'label', 'description'];
|
||||
var searchRow = list.selectAll('li.wikidata-search')
|
||||
.data([0]);
|
||||
|
||||
var items = list.selectAll('li')
|
||||
var searchRowEnter = searchRow.enter()
|
||||
.append('li')
|
||||
.attr('class', 'wikidata-search');
|
||||
|
||||
searchInput = searchRowEnter
|
||||
.append('input')
|
||||
.attr('type', 'text')
|
||||
.style('flex', '1')
|
||||
.call(utilNoAuto);
|
||||
|
||||
searchInput
|
||||
.on('focus', function() {
|
||||
var node = d3_select(this).node();
|
||||
node.setSelectionRange(0, node.value.length);
|
||||
})
|
||||
.on('blur', function() {
|
||||
setLabelForEntity();
|
||||
})
|
||||
.call(combobox.fetcher(fetchWikidataItems));
|
||||
|
||||
combobox.on('accept', function(d) {
|
||||
_qid = d.id;
|
||||
change();
|
||||
}).on('cancel', function() {
|
||||
setLabelForEntity();
|
||||
});
|
||||
|
||||
searchRowEnter
|
||||
.append('button')
|
||||
.attr('class', 'form-field-button wiki-link')
|
||||
.attr('title', t('icons.open_wikidata'))
|
||||
.attr('tabindex', -1)
|
||||
.call(svgIcon('#iD-icon-out-link'))
|
||||
.on('click', function() {
|
||||
d3_event.preventDefault();
|
||||
if (_wikiURL) window.open(_wikiURL, '_blank');
|
||||
});
|
||||
|
||||
var wikidataProperties = ['description', 'identifier'];
|
||||
|
||||
var items = list.selectAll('li.labeled-input')
|
||||
.data(wikidataProperties);
|
||||
|
||||
// Enter
|
||||
var enter = items.enter()
|
||||
.append('li')
|
||||
.attr('class', function(d) { return 'preset-wikidata-' + d; });
|
||||
.attr('class', function(d) { return 'labeled-input preset-wikidata-' + d; });
|
||||
|
||||
enter
|
||||
.append('span')
|
||||
@@ -60,55 +108,17 @@ export function uiFieldWikidata(field) {
|
||||
.attr('for', function(d) { return 'preset-input-wikidata-' + d; })
|
||||
.text(function(d) { return t('wikidata.' + d); });
|
||||
|
||||
var inputWrap = enter
|
||||
.append('div')
|
||||
.attr('class', 'input-wrap');
|
||||
|
||||
inputWrap
|
||||
enter
|
||||
.append('input')
|
||||
.attr('type', 'text')
|
||||
.attr('class', 'preset-input-wikidata')
|
||||
.attr('id', function(d) { return 'preset-input-wikidata-' + d; });
|
||||
|
||||
|
||||
title = wrap.select('.preset-wikidata-identifier input')
|
||||
.attr('id', function(d) { return 'preset-input-wikidata-' + d; })
|
||||
.call(utilNoAuto)
|
||||
.merge(title);
|
||||
|
||||
title
|
||||
.on('blur', blur)
|
||||
.on('change', change);
|
||||
|
||||
var idItem = wrap.select('.preset-wikidata-identifier');
|
||||
|
||||
idItem.select('button')
|
||||
.remove();
|
||||
|
||||
link = idItem
|
||||
.append('button')
|
||||
.attr('class', 'form-field-button wiki-link')
|
||||
.attr('title', t('icons.open_wikidata'))
|
||||
.attr('tabindex', -1)
|
||||
.call(svgIcon('#iD-icon-out-link'))
|
||||
.merge(link);
|
||||
|
||||
link
|
||||
.on('click', function() {
|
||||
d3_event.preventDefault();
|
||||
if (_wikiURL) window.open(_wikiURL, '_blank');
|
||||
});
|
||||
|
||||
var readOnlyItems = wrap.selectAll('li:not(.preset-wikidata-identifier)');
|
||||
|
||||
readOnlyItems.select('input')
|
||||
.classed('disabled', 'true')
|
||||
.attr('readonly', 'true');
|
||||
|
||||
readOnlyItems.select('button')
|
||||
.remove();
|
||||
|
||||
readOnlyItems.append('button')
|
||||
.attr('class', 'form-field-button wiki-link')
|
||||
enter
|
||||
.append('button')
|
||||
.attr('class', 'form-field-button')
|
||||
.attr('title', t('icons.copy'))
|
||||
.attr('tabindex', -1)
|
||||
.call(svgIcon('#iD-operation-copy'))
|
||||
@@ -120,55 +130,76 @@ export function uiFieldWikidata(field) {
|
||||
.select();
|
||||
document.execCommand('copy');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function fetchWikidataItems(q, callback) {
|
||||
|
||||
function blur() {
|
||||
change();
|
||||
if (!q && _entity) {
|
||||
q = context.entity(_entity.id).tags.name || '';
|
||||
}
|
||||
|
||||
wikidata.itemsForSearchQuery(q, function(err, data) {
|
||||
if (err) return;
|
||||
|
||||
for (var i in data) {
|
||||
data[i].value = data[i].label + ' (' + data[i].id + ')';
|
||||
data[i].title = data[i].description;
|
||||
}
|
||||
|
||||
if (callback) callback(data);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function change() {
|
||||
var syncTags = {
|
||||
wikidata: utilGetSetValue(title)
|
||||
wikidata: _qid
|
||||
};
|
||||
dispatch.call('change', this, syncTags);
|
||||
}
|
||||
|
||||
function setLabelForEntity() {
|
||||
var label = '';
|
||||
if (_wikidataEntity) {
|
||||
if (_wikidataEntity.labels && Object.keys(_wikidataEntity.labels).length > 0) {
|
||||
label = _wikidataEntity.labels[Object.keys(_wikidataEntity.labels)[0]].value;
|
||||
}
|
||||
if (label.length === 0) {
|
||||
label = _wikidataEntity.id.toString();
|
||||
}
|
||||
}
|
||||
utilGetSetValue(d3_select('li.wikidata-search input'), label);
|
||||
}
|
||||
|
||||
|
||||
wiki.tags = function(tags) {
|
||||
var value = tags[field.key] || '';
|
||||
utilGetSetValue(title, value);
|
||||
_qid = tags[field.key] || '';
|
||||
|
||||
if (!/^Q[0-9]*$/.test(value)) { // not a proper QID
|
||||
if (!/^Q[0-9]*$/.test(_qid)) { // not a proper QID
|
||||
unrecognized();
|
||||
return;
|
||||
}
|
||||
|
||||
// QID value in correct format
|
||||
_wikiURL = 'https://wikidata.org/wiki/' + value;
|
||||
wikidata.entityByQID(value, function(err, entity) {
|
||||
_wikiURL = 'https://wikidata.org/wiki/' + _qid;
|
||||
wikidata.entityByQID(_qid, function(err, entity) {
|
||||
if (err) {
|
||||
unrecognized();
|
||||
return;
|
||||
}
|
||||
_wikidataEntity = entity;
|
||||
|
||||
setLabelForEntity();
|
||||
|
||||
var label = '';
|
||||
var description = '';
|
||||
|
||||
if (entity.labels && Object.keys(entity.labels).length > 0) {
|
||||
label = entity.labels[Object.keys(entity.labels)[0]].value;
|
||||
}
|
||||
if (entity.descriptions && Object.keys(entity.descriptions).length > 0) {
|
||||
description = entity.descriptions[Object.keys(entity.descriptions)[0]].value;
|
||||
}
|
||||
|
||||
d3_select('.preset-wikidata-label')
|
||||
.style('display', function(){
|
||||
return label.length > 0 ? 'flex' : 'none';
|
||||
})
|
||||
.select('input')
|
||||
.attr('value', label);
|
||||
d3_select('.form-field-wikidata button.wiki-link')
|
||||
.classed('disabled', false);
|
||||
|
||||
d3_select('.preset-wikidata-description')
|
||||
.style('display', function(){
|
||||
@@ -176,18 +207,31 @@ export function uiFieldWikidata(field) {
|
||||
})
|
||||
.select('input')
|
||||
.attr('value', description);
|
||||
|
||||
d3_select('.preset-wikidata-identifier')
|
||||
.style('display', function(){
|
||||
return entity.id ? 'flex' : 'none';
|
||||
})
|
||||
.select('input')
|
||||
.attr('value', entity.id);
|
||||
});
|
||||
|
||||
|
||||
// not a proper QID
|
||||
function unrecognized() {
|
||||
d3_select('.preset-wikidata-label')
|
||||
.style('display', 'none');
|
||||
_wikidataEntity = null;
|
||||
setLabelForEntity();
|
||||
|
||||
d3_select('.preset-wikidata-description')
|
||||
.style('display', 'none');
|
||||
d3_select('.preset-wikidata-identifier')
|
||||
.style('display', 'none');
|
||||
|
||||
if (value && value !== '') {
|
||||
_wikiURL = 'https://wikidata.org/wiki/Special:Search?search=' + value;
|
||||
d3_select('.form-field-wikidata button.wiki-link')
|
||||
.classed('disabled', true);
|
||||
|
||||
if (_qid && _qid !== '') {
|
||||
_wikiURL = 'https://wikidata.org/wiki/Special:Search?search=' + _qid;
|
||||
} else {
|
||||
_wikiURL = '';
|
||||
}
|
||||
@@ -195,8 +239,15 @@ export function uiFieldWikidata(field) {
|
||||
};
|
||||
|
||||
|
||||
wiki.entity = function(val) {
|
||||
if (!arguments.length) return _entity;
|
||||
_entity = val;
|
||||
return wiki;
|
||||
};
|
||||
|
||||
|
||||
wiki.focus = function() {
|
||||
title.node().focus();
|
||||
searchInput.node().focus();
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user