mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-14 01:33:03 +00:00
Merge branch 'wikidata-new'
This commit is contained in:
1
Makefile
1
Makefile
@@ -67,6 +67,7 @@ dist/iD.js: \
|
||||
js/id/services/mapillary.js \
|
||||
js/id/services/nominatim.js \
|
||||
js/id/services/taginfo.js \
|
||||
js/id/services/wikidata.js \
|
||||
js/id/services/wikipedia.js \
|
||||
js/id/util.js \
|
||||
js/id/util/session_mutex.js \
|
||||
|
||||
@@ -1045,7 +1045,7 @@ en:
|
||||
# 'width=*'
|
||||
label: Width (Meters)
|
||||
wikipedia:
|
||||
# 'wikipedia=*'
|
||||
# 'wikipedia=*, wikidata=*'
|
||||
label: Wikipedia
|
||||
presets:
|
||||
address:
|
||||
|
||||
@@ -1414,6 +1414,10 @@
|
||||
},
|
||||
"wikipedia": {
|
||||
"key": "wikipedia",
|
||||
"keys": [
|
||||
"wikipedia",
|
||||
"wikidata"
|
||||
],
|
||||
"type": "wikipedia",
|
||||
"icon": "wikipedia",
|
||||
"universal": true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"key": "wikipedia",
|
||||
"keys": ["wikipedia", "wikidata"],
|
||||
"type": "wikipedia",
|
||||
"icon": "wikipedia",
|
||||
"universal": true,
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
<script src='js/id/services/mapillary.js'></script>
|
||||
<script src='js/id/services/nominatim.js'></script>
|
||||
<script src='js/id/services/taginfo.js'></script>
|
||||
<script src='js/id/services/wikidata.js'></script>
|
||||
<script src='js/id/services/wikipedia.js'></script>
|
||||
|
||||
<script src='data/data_dev.js'></script>
|
||||
|
||||
22
js/id/services/wikidata.js
Normal file
22
js/id/services/wikidata.js
Normal file
@@ -0,0 +1,22 @@
|
||||
iD.services.wikidata = function() {
|
||||
var wiki = {},
|
||||
endpoint = 'https://www.wikidata.org/w/api.php?';
|
||||
|
||||
// Given a Wikipedia language and article title, return an array of
|
||||
// corresponding Wikidata entities.
|
||||
wiki.itemsByTitle = function(lang, title, callback) {
|
||||
lang = lang || 'en';
|
||||
d3.jsonp(endpoint + iD.util.qsString({
|
||||
action: 'wbgetentities',
|
||||
format: 'json',
|
||||
sites: lang.replace(/-/g, '_') + 'wiki',
|
||||
titles: title,
|
||||
languages: 'en', // shrink response by filtering to one language
|
||||
callback: '{callback}'
|
||||
}), function(data) {
|
||||
callback(title, data.entities || {});
|
||||
});
|
||||
};
|
||||
|
||||
return wiki;
|
||||
};
|
||||
@@ -4,7 +4,7 @@ iD.ui.preset.localized = function(field, context) {
|
||||
input, localizedInputs, wikiTitles,
|
||||
entity;
|
||||
|
||||
function i(selection) {
|
||||
function localized(selection) {
|
||||
input = selection.selectAll('.localized-main')
|
||||
.data([0]);
|
||||
|
||||
@@ -203,7 +203,7 @@ iD.ui.preset.localized = function(field, context) {
|
||||
.value(function(d) { return d.value; });
|
||||
}
|
||||
|
||||
i.tags = function(tags) {
|
||||
localized.tags = function(tags) {
|
||||
// Fetch translations from wikipedia
|
||||
if (tags.wikipedia && !wikiTitles) {
|
||||
wikiTitles = {};
|
||||
@@ -228,13 +228,15 @@ iD.ui.preset.localized = function(field, context) {
|
||||
localizedInputs.call(render, postfixed.reverse());
|
||||
};
|
||||
|
||||
i.focus = function() {
|
||||
localized.focus = function() {
|
||||
input.node().focus();
|
||||
};
|
||||
|
||||
i.entity = function(_) {
|
||||
localized.entity = function(_) {
|
||||
if (!arguments.length) return entity;
|
||||
entity = _;
|
||||
return localized;
|
||||
};
|
||||
|
||||
return d3.rebind(i, dispatch, 'on');
|
||||
return d3.rebind(localized, dispatch, 'on');
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ iD.ui.preset.textarea = function(field) {
|
||||
var dispatch = d3.dispatch('change'),
|
||||
input;
|
||||
|
||||
function i(selection) {
|
||||
function textarea(selection) {
|
||||
input = selection.selectAll('textarea')
|
||||
.data([0]);
|
||||
|
||||
@@ -25,13 +25,13 @@ iD.ui.preset.textarea = function(field) {
|
||||
};
|
||||
}
|
||||
|
||||
i.tags = function(tags) {
|
||||
textarea.tags = function(tags) {
|
||||
input.value(tags[field.key] || '');
|
||||
};
|
||||
|
||||
i.focus = function() {
|
||||
textarea.focus = function() {
|
||||
input.node().focus();
|
||||
};
|
||||
|
||||
return d3.rebind(i, dispatch, 'on');
|
||||
return d3.rebind(textarea, dispatch, 'on');
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
iD.ui.preset.wikipedia = function(field, context) {
|
||||
var dispatch = d3.dispatch('change'),
|
||||
wikipedia = iD.services.wikipedia(),
|
||||
wikidata = iD.services.wikidata(),
|
||||
link, entity, lang, title;
|
||||
|
||||
function i(selection) {
|
||||
function wiki(selection) {
|
||||
var langcombo = d3.combobox()
|
||||
.fetcher(function(value, cb) {
|
||||
var v = value.toLowerCase();
|
||||
@@ -54,7 +55,7 @@ iD.ui.preset.wikipedia = function(field, context) {
|
||||
|
||||
title
|
||||
.call(titlecombo)
|
||||
.on('blur', change)
|
||||
.on('blur', blur)
|
||||
.on('change', change);
|
||||
|
||||
link = selection.selectAll('a.wiki-link')
|
||||
@@ -81,14 +82,19 @@ iD.ui.preset.wikipedia = function(field, context) {
|
||||
|
||||
function changeLang() {
|
||||
lang.value(language()[1]);
|
||||
change();
|
||||
change(true);
|
||||
}
|
||||
|
||||
function change() {
|
||||
function blur() {
|
||||
change(true);
|
||||
}
|
||||
|
||||
function change(skipWikidata) {
|
||||
var value = title.value(),
|
||||
m = value.match(/https?:\/\/([-a-z]+)\.wikipedia\.org\/(?:wiki|\1-[-a-z]+)\/([^#]+)(?:#(.+))?/),
|
||||
l = m && _.find(iD.data.wikipedia, function(d) { return m[1] === d[2]; }),
|
||||
anchor;
|
||||
anchor,
|
||||
syncTags = {};
|
||||
|
||||
if (l) {
|
||||
// Normalize title http://www.mediawiki.org/wiki/API:Query#Title_normalization
|
||||
@@ -107,12 +113,47 @@ iD.ui.preset.wikipedia = function(field, context) {
|
||||
title.value(value);
|
||||
}
|
||||
|
||||
var t = {};
|
||||
t[field.key] = value ? language()[2] + ':' + value : undefined;
|
||||
dispatch.change(t);
|
||||
syncTags.wikipedia = value ? language()[2] + ':' + value : undefined;
|
||||
if (!skipWikidata) {
|
||||
syncTags.wikidata = undefined;
|
||||
}
|
||||
|
||||
dispatch.change(syncTags);
|
||||
|
||||
|
||||
if (skipWikidata || !value || !language()[2]) return;
|
||||
|
||||
// attempt asynchronous update of wikidata tag..
|
||||
var initEntityId = entity.id,
|
||||
initWikipedia = context.entity(initEntityId).tags.wikipedia;
|
||||
|
||||
wikidata.itemsByTitle(language()[2], value, function (title, data) {
|
||||
// 1. most recent change was a tag change
|
||||
var annotation = t('operations.change_tags.annotation'),
|
||||
currAnnotation = context.history().undoAnnotation();
|
||||
if (currAnnotation !== annotation) return;
|
||||
|
||||
// 2. same entity exists and still selected
|
||||
var selectedIds = context.selectedIDs(),
|
||||
currEntityId = selectedIds.length > 0 && selectedIds[0];
|
||||
if (currEntityId !== initEntityId) return;
|
||||
|
||||
// 3. wikipedia value has not changed
|
||||
var currTags = _.clone(context.entity(currEntityId).tags),
|
||||
qids = data && Object.keys(data);
|
||||
if (initWikipedia !== currTags.wikipedia) return;
|
||||
|
||||
// ok to coalesce the update of wikidata tag into the previous tag change
|
||||
currTags.wikidata = qids && _.find(qids, function (id) {
|
||||
return id.match(/^Q\d+$/);
|
||||
});
|
||||
|
||||
context.overwrite(iD.actions.ChangeTags(currEntityId, currTags), annotation);
|
||||
dispatch.change(currTags);
|
||||
});
|
||||
}
|
||||
|
||||
i.tags = function(tags) {
|
||||
wiki.tags = function(tags) {
|
||||
var value = tags[field.key] || '',
|
||||
m = value.match(/([^:]+):([^#]+)(?:#(.+))?/),
|
||||
l = m && _.find(iD.data.wikipedia, function(d) { return m[1] === d[2]; }),
|
||||
@@ -131,7 +172,7 @@ iD.ui.preset.wikipedia = function(field, context) {
|
||||
}
|
||||
}
|
||||
link.attr('href', 'https://' + m[1] + '.wikipedia.org/wiki/' +
|
||||
m[2].replace(/ /g, '_') + (anchor ? ('#' + anchor) : ''));
|
||||
m[2].replace(/ /g, '_') + (anchor ? ('#' + anchor) : ''));
|
||||
|
||||
// unrecognized value format
|
||||
} else {
|
||||
@@ -143,13 +184,15 @@ iD.ui.preset.wikipedia = function(field, context) {
|
||||
}
|
||||
};
|
||||
|
||||
i.entity = function(_) {
|
||||
wiki.entity = function(_) {
|
||||
if (!arguments.length) return entity;
|
||||
entity = _;
|
||||
return wiki;
|
||||
};
|
||||
|
||||
i.focus = function() {
|
||||
wiki.focus = function() {
|
||||
title.node().focus();
|
||||
};
|
||||
|
||||
return d3.rebind(i, dispatch, 'on');
|
||||
return d3.rebind(wiki, dispatch, 'on');
|
||||
};
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
<script src='../js/id/services/mapillary.js'></script>
|
||||
<script src='../js/id/services/nominatim.js'></script>
|
||||
<script src='../js/id/services/taginfo.js'></script>
|
||||
<script src='../js/id/services/wikidata.js'></script>
|
||||
<script src='../js/id/services/wikipedia.js'></script>
|
||||
|
||||
<script src='../data/data_dev.js'></script>
|
||||
|
||||
@@ -1,13 +1,46 @@
|
||||
describe('iD.ui.preset.wikipedia', function() {
|
||||
var selection, field;
|
||||
var entity, context, selection, field, wikiDelay, selectedId;
|
||||
|
||||
function wikidataStub() {
|
||||
wikidataStub.itemsByTitle = function(lang, title, callback) {
|
||||
var data = {Q216353: {id: 'Q216353'}};
|
||||
if (wikiDelay) {
|
||||
window.setTimeout(function () { callback(title, data); }, wikiDelay);
|
||||
}
|
||||
else {
|
||||
callback(title, data);
|
||||
}
|
||||
}
|
||||
return wikidataStub;
|
||||
}
|
||||
|
||||
function changeTags(changed) {
|
||||
var annotation = t('operations.change_tags.annotation');
|
||||
var tags = _.extend({}, entity.tags, changed);
|
||||
context.perform(iD.actions.ChangeTags(entity.id, tags), annotation);
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
entity = iD.Node({id: 'n12345'});
|
||||
selectedId = entity.id;
|
||||
context = iD();
|
||||
context.history().merge([entity]);
|
||||
selection = d3.select(document.createElement('div'));
|
||||
field = iD().presets(iD.data.presets).presets().field('wikipedia');
|
||||
field = context.presets(iD.data.presets).presets().field('wikipedia');
|
||||
wikiDelay = 0;
|
||||
|
||||
sinon.stub(iD.services, 'wikidata', wikidataStub);
|
||||
sinon.stub(context, 'selectedIDs', function() { return [selectedId]; });
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
iD.services.wikidata.restore();
|
||||
context.selectedIDs.restore();
|
||||
});
|
||||
|
||||
|
||||
it('recognizes lang:title format', function() {
|
||||
var wikipedia = iD.ui.preset.wikipedia(field, {});
|
||||
var wikipedia = iD.ui.preset.wikipedia(field, context);
|
||||
selection.call(wikipedia);
|
||||
wikipedia.tags({wikipedia: 'en:Title'});
|
||||
expect(selection.selectAll('.wiki-lang').value()).to.equal('English');
|
||||
@@ -15,44 +48,108 @@ describe('iD.ui.preset.wikipedia', function() {
|
||||
expect(selection.selectAll('.wiki-link').attr('href')).to.equal('https://en.wikipedia.org/wiki/Title');
|
||||
});
|
||||
|
||||
it('sets a new value', function() {
|
||||
var wikipedia = iD.ui.preset.wikipedia(field, {});
|
||||
it('sets language, value, wikidata', function() {
|
||||
var wikipedia = iD.ui.preset.wikipedia(field, context).entity(entity);
|
||||
wikipedia.on('change', changeTags);
|
||||
selection.call(wikipedia);
|
||||
|
||||
wikipedia.on('change', function(tags) {
|
||||
expect(tags).to.eql({wikipedia: undefined});
|
||||
});
|
||||
|
||||
var spy = sinon.spy();
|
||||
wikipedia.on('change.spy', spy);
|
||||
selection.selectAll('.wiki-lang').value('Deutsch');
|
||||
happen.once(selection.selectAll('.wiki-lang').node(), {type: 'change'});
|
||||
|
||||
wikipedia.on('change', function(tags) {
|
||||
expect(tags).to.eql({wikipedia: 'de:Title'});
|
||||
});
|
||||
happen.once(selection.selectAll('.wiki-lang').node(), { type: 'change' });
|
||||
happen.once(selection.selectAll('.wiki-lang').node(), { type: 'blur' });
|
||||
expect(spy.callCount).to.equal(2);
|
||||
expect(spy.firstCall).to.have.been.calledWith({ wikipedia: undefined }); // on change
|
||||
expect(spy.secondCall).to.have.been.calledWith({ wikipedia: undefined }); // on blur
|
||||
|
||||
spy = sinon.spy();
|
||||
wikipedia.on('change.spy', spy);
|
||||
selection.selectAll('.wiki-title').value('Title');
|
||||
happen.once(selection.selectAll('.wiki-title').node(), {type: 'change'});
|
||||
happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' });
|
||||
happen.once(selection.selectAll('.wiki-title').node(), { type: 'blur' });
|
||||
expect(spy.callCount).to.equal(3);
|
||||
expect(spy.firstCall).to.have.been.calledWith({ wikipedia: 'de:Title', wikidata: undefined }); // on change
|
||||
expect(spy.secondCall).to.have.been.calledWith({ wikipedia: 'de:Title', wikidata: 'Q216353' }); // wikidata async
|
||||
expect(spy.thirdCall).to.have.been.calledWith({ wikipedia: 'de:Title' }); // on blur
|
||||
});
|
||||
|
||||
it('recognizes pasted URLs', function() {
|
||||
var wikipedia = iD.ui.preset.wikipedia(field, {});
|
||||
var wikipedia = iD.ui.preset.wikipedia(field, context).entity(entity);
|
||||
wikipedia.on('change', changeTags);
|
||||
selection.call(wikipedia);
|
||||
|
||||
selection.selectAll('.wiki-title').value('http://de.wikipedia.org/wiki/Title');
|
||||
happen.once(selection.selectAll('.wiki-title').node(), {type: 'change'});
|
||||
|
||||
happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' });
|
||||
expect(selection.selectAll('.wiki-lang').value()).to.equal('Deutsch');
|
||||
expect(selection.selectAll('.wiki-title').value()).to.equal('Title');
|
||||
});
|
||||
|
||||
it('preserves existing language', function() {
|
||||
selection.call(iD.ui.preset.wikipedia(field, {}));
|
||||
selection.call(iD.ui.preset.wikipedia(field, context));
|
||||
selection.selectAll('.wiki-lang').value('Deutsch');
|
||||
|
||||
var wikipedia = iD.ui.preset.wikipedia(field, {});
|
||||
var wikipedia = iD.ui.preset.wikipedia(field, context);
|
||||
selection.call(wikipedia);
|
||||
wikipedia.tags({});
|
||||
|
||||
expect(selection.selectAll('.wiki-lang').value()).to.equal('Deutsch');
|
||||
});
|
||||
|
||||
it('does not set delayed wikidata tag if wikipedia field has changed', function(done) {
|
||||
var wikipedia = iD.ui.preset.wikipedia(field, context).entity(entity);
|
||||
wikipedia.on('change', changeTags);
|
||||
selection.call(wikipedia);
|
||||
wikiDelay = 20;
|
||||
|
||||
var spy = sinon.spy();
|
||||
wikipedia.on('change.spy', spy);
|
||||
selection.selectAll('.wiki-lang').value('Deutsch');
|
||||
selection.selectAll('.wiki-title').value('Skip');
|
||||
happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' });
|
||||
happen.once(selection.selectAll('.wiki-title').node(), { type: 'blur' });
|
||||
|
||||
window.setTimeout(function() {
|
||||
selection.selectAll('.wiki-title').value('Title');
|
||||
happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' });
|
||||
happen.once(selection.selectAll('.wiki-title').node(), { type: 'blur' });
|
||||
}, 10);
|
||||
|
||||
window.setTimeout(function() {
|
||||
expect(spy.callCount).to.equal(5);
|
||||
expect(spy.getCall(0)).to.have.been.calledWith({ wikipedia: 'de:Skip', wikidata: undefined }); // 'Skip' on change
|
||||
expect(spy.getCall(1)).to.have.been.calledWith({ wikipedia: 'de:Skip' }); // 'Skip' on blur
|
||||
expect(spy.getCall(2)).to.have.been.calledWith({ wikipedia: 'de:Title', wikidata: undefined }); // 'Title' on change +10ms
|
||||
expect(spy.getCall(3)).to.have.been.calledWith({ wikipedia: 'de:Title' }); // 'Title' on blur +10ms
|
||||
// skip delayed wikidata for 'Skip' // 'Skip' wikidata +20ms
|
||||
expect(spy.getCall(4)).to.have.been.calledWith({ wikipedia: 'de:Title', wikidata: 'Q216353' }); // 'Title' wikidata +40ms
|
||||
done();
|
||||
}, 50);
|
||||
});
|
||||
|
||||
it('does not set delayed wikidata tag if selected entity has changed', function(done) {
|
||||
var wikipedia = iD.ui.preset.wikipedia(field, context).entity(entity);
|
||||
wikipedia.on('change', changeTags);
|
||||
selection.call(wikipedia);
|
||||
wikiDelay = 20;
|
||||
|
||||
var spy = sinon.spy();
|
||||
wikipedia.on('change.spy', spy);
|
||||
selection.selectAll('.wiki-lang').value('Deutsch');
|
||||
selection.selectAll('.wiki-title').value('Title');
|
||||
happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' });
|
||||
happen.once(selection.selectAll('.wiki-title').node(), { type: 'blur' });
|
||||
|
||||
window.setTimeout(function() {
|
||||
selectedId = 'w-123'; // user clicked on something else..
|
||||
}, 10);
|
||||
|
||||
window.setTimeout(function() {
|
||||
expect(spy.callCount).to.equal(2);
|
||||
expect(spy.getCall(0)).to.have.been.calledWith({ wikipedia: 'de:Title', wikidata: undefined }); // 'Title' on change
|
||||
expect(spy.getCall(1)).to.have.been.calledWith({ wikipedia: 'de:Title' }); // 'Title' on blur
|
||||
// wikidata tag not changed because another entity is now selected
|
||||
done();
|
||||
}, 50);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user