linkify keys & tags in the preset docs from the wiki (#10763)

This commit is contained in:
Kyℓe Hensel
2025-02-13 01:42:29 +11:00
committed by GitHub
parent dbcc219c24
commit c1ca888b72
4 changed files with 108 additions and 3 deletions
+2
View File
@@ -40,6 +40,7 @@ _Breaking developer changes, which may affect downstream projects or sites that
#### :sparkles: Usability & Accessibility
* Autocomplete changeset `source` tag with sources of the previous 100 changesets of the user ([#10764], thanks [@k-yle])
* Also show search result for coordinates in `lon/lat` order in search results ([#10720], thanks [@Deeptanshu-sankhwar])
* Linkify keys & tags in the preset docs from the wiki ([#10763], thanks [@k-yle])
* Allow broken (unclosed) areas to be continued ([#9635], thanks [@k-yle])
#### :scissors: Operations
* Fix splitting of closed ways (or areas) when two or more split-points are selected
@@ -69,6 +70,7 @@ _Breaking developer changes, which may affect downstream projects or sites that
[#10747]: https://github.com/openstreetmap/iD/issues/10747
[#10748]: https://github.com/openstreetmap/iD/issues/10748
[#10755]: https://github.com/openstreetmap/iD/issues/10755
[#10763]: https://github.com/openstreetmap/iD/pull/10763
[#10764]: https://github.com/openstreetmap/iD/issues/10764
[#10766]: https://github.com/openstreetmap/iD/pull/10766
[@hlfan]: https://github.com/hlfan
+33 -1
View File
@@ -102,6 +102,38 @@ export default {
return result.replace(/_/g, ' ').trim();
},
/**
* Converts text like `tag:...=...` into clickable links
*
* @param {string} unsafeText - unsanitized text
*/
linkifyWikiText(unsafeText) {
/** @param {import('d3').Selection} selection */
return (selection) => {
const segments = unsafeText.split(/(key|tag):([\w-]+)(=([\w-]+))?/g);
for (let i = 0; i < segments.length; i += 5) {
const [plainText, , key, , value] = segments.slice(i);
if (plainText) {
selection
.append('span')
.text(plainText);
}
if (key) {
selection
.append('a')
.attr('href', `https://wiki.openstreetmap.org/wiki/${this.toSitelink(key, value)}`)
.attr('target', '_blank')
.attr('rel', 'noreferrer')
.append('code')
.text(`${key}=${value || '*'}`);
}
}
};
},
//
// Pass params object of the form:
@@ -269,7 +301,7 @@ export default {
// prepare result
var result = {
title: entity.title,
description: description ? description.value : '',
description: that.linkifyWikiText(description?.value || ''),
descriptionLocaleCode: description ? description.language : '',
editURL: 'https://wiki.openstreetmap.org/wiki/' + entity.title
};
+2 -2
View File
@@ -53,7 +53,7 @@ export function uiTagReference(what) {
_body
.append('img')
.attr('class', 'tag-reference-wiki-image')
.attr('alt', docs.description)
.attr('alt', docs.title)
.attr('src', docs.imageURL)
.on('load', function() { done(); })
.on('error', function() { d3_select(this).remove(); done(); });
@@ -69,7 +69,7 @@ export function uiTagReference(what) {
tagReferenceDescription = tagReferenceDescription
.attr('class', 'localized-text')
.attr('lang', docs.descriptionLocaleCode || 'und')
.text(docs.description);
.call(docs.description);
} else {
tagReferenceDescription = tagReferenceDescription
.call(t.append('inspector.no_documentation_key'));
+71
View File
@@ -331,4 +331,75 @@ describe('iD.serviceOsmWikibase', function () {
});
});
describe('linkifyWikiText', () => {
it('handles normal text', () => {
const main = document.createElement('main');
d3.select(main).call(iD.serviceOsmWikibase.linkifyWikiText('hello'));
expect(main.innerHTML).toBe('<span>hello</span>');
expect(main.textContent).toBe('hello');
});
it('prevents XSS attacks', () => {
const main = document.createElement('main');
d3.select(main).call(iD.serviceOsmWikibase.linkifyWikiText('123 <script>bad</script> 456'));
expect(main.innerHTML).toBe('<span>123 &lt;script&gt;bad&lt;/script&gt; 456</span>');
expect(main.textContent).toBe('123 <script>bad</script> 456');
});
it('linkifies the tag: and key: syntax', () => {
const main = document.createElement('main');
d3.select(main).call(iD.serviceOsmWikibase.linkifyWikiText('use tag:natural=water with key:water instead'));
expect(main.innerHTML).toBe([
'<span>use </span>',
'<a href="https://wiki.openstreetmap.org/wiki/Tag:natural=water" target="_blank" rel="noreferrer"><code>natural=water</code></a>',
'<span> with </span>',
'<a href="https://wiki.openstreetmap.org/wiki/Key:water" target="_blank" rel="noreferrer"><code>water=*</code></a>',
'<span> instead</span>'
].join(''));
expect(main.textContent).toBe('use natural=water with water=* instead');
});
it('works if the string is 100% a link', () => {
const main = document.createElement('main');
d3.select(main).call(iD.serviceOsmWikibase.linkifyWikiText('tag:natural=water'));
expect(main.innerHTML).toBe([
'<a href="https://wiki.openstreetmap.org/wiki/Tag:natural=water" target="_blank" rel="noreferrer"><code>natural=water</code></a>',
].join(''));
expect(main.textContent).toBe('natural=water');
});
it('works if the link is the first part of the string', () => {
const main = document.createElement('main');
d3.select(main).call(iD.serviceOsmWikibase.linkifyWikiText('tag:craft=sailmaker is better'));
expect(main.innerHTML).toBe([
'<a href="https://wiki.openstreetmap.org/wiki/Tag:craft=sailmaker" target="_blank" rel="noreferrer"><code>craft=sailmaker</code></a>',
'<span> is better</span>'
].join(''));
expect(main.textContent).toBe('craft=sailmaker is better');
});
it('works if the link is the last part of the string', () => {
const main = document.createElement('main');
d3.select(main).call(iD.serviceOsmWikibase.linkifyWikiText('prefer tag:craft=sailmaker'));
expect(main.innerHTML).toBe([
'<span>prefer </span>',
'<a href="https://wiki.openstreetmap.org/wiki/Tag:craft=sailmaker" target="_blank" rel="noreferrer"><code>craft=sailmaker</code></a>',
].join(''));
expect(main.textContent).toBe('prefer craft=sailmaker');
});
it('handles empty strings', () => {
const main = document.createElement('main');
d3.select(main).call(iD.serviceOsmWikibase.linkifyWikiText(''));
expect(main.innerHTML).toBe('');
expect(main.textContent).toBe('');
});
});
});