Skip to content

Commit d9364ae

Browse files
committed
Make foreign keys clickable using tag2link
1 parent d74c8ff commit d9364ae

File tree

3 files changed

+120
-15
lines changed

3 files changed

+120
-15
lines changed

lib/featureDiff/DiffColumn.js

+29-8
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ import React from 'react';
22
import PropTypes from 'prop-types';
33
import { config } from '../config';
44

5-
export const DiffColumn = function({ diff, prop, type, propClass }) {
6-
return (
7-
<td className={propClass}>
8-
<DiffColumnContent diff={diff} prop={prop} type={type} />
9-
</td>
10-
);
11-
};
5+
export class DiffColumn extends React.Component {
6+
render() {
7+
return (
8+
<td className={this.props.propClass}>
9+
<DiffColumnContent {...this.props} {...this.context} />
10+
</td>
11+
);
12+
}
13+
}
1214

13-
const DiffColumnContent = function({ diff, prop, type }) {
15+
const DiffColumnContent = function({ diff, prop, type, tag2link }) {
1416
let renderOutput;
1517
const value = diff[prop][type],
1618
propIsWikidata = typeof prop == 'string' && /wikidata$/.test(prop);
@@ -54,6 +56,21 @@ const DiffColumnContent = function({ diff, prop, type }) {
5456
}
5557
});
5658
renderOutput = <span>{renderArray}</span>;
59+
} else if (tag2link[prop] && typeof value === 'string') {
60+
// This is a foreign key which is defined in the tag2link DB.
61+
// So, we render a clickable link.
62+
renderOutput = (
63+
<span>
64+
<a
65+
target="_blank"
66+
rel="noopener noreferrer"
67+
className="cmap-wikidata-link"
68+
href={tag2link[prop].replace(/\$1/g, value)}
69+
>
70+
{value}
71+
</a>
72+
</span>
73+
);
5774
} else {
5875
// Standard tag, no processing needed
5976
renderOutput = <span>{value}</span>;
@@ -67,3 +84,7 @@ DiffColumn.propTypes = {
6784
type: PropTypes.string.isRequired,
6885
propClass: PropTypes.string
6986
};
87+
88+
DiffColumn.contextTypes = {
89+
tag2link: PropTypes.objectOf(PropTypes.string)
90+
};

lib/featureDiff/DiffTable.js

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33
import { DiffRows } from './DiffRows';
4+
import { Tag2LinkContext } from '../tag2link';
45

56
//Renders the markup for a table
67

@@ -30,13 +31,15 @@ export const DiffTable = function({ diff, ignoreList, header }) {
3031
</tr>
3132
</thead>
3233
)}
33-
<DiffRows
34-
diff={diff}
35-
sortedProps={sortedProps}
36-
types={types}
37-
isAddedFeature={isAddedFeature}
38-
ignoreList={ignoreList}
39-
/>
34+
<Tag2LinkContext>
35+
<DiffRows
36+
diff={diff}
37+
sortedProps={sortedProps}
38+
types={types}
39+
isAddedFeature={isAddedFeature}
40+
ignoreList={ignoreList}
41+
/>
42+
</Tag2LinkContext>
4043
</table>
4144
);
4245
};

lib/tag2link.js

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// @ts-check
2+
import React from 'react';
3+
import PropTypes from 'prop-types';
4+
5+
const URL = 'https://cdn.jsdelivr.net/gh/JOSM/tag2link@master/index.json';
6+
7+
const RANKS = ['deprecated', 'normal', 'preferred'];
8+
9+
/** @type {Promise<State> | undefined} */
10+
let promise;
11+
12+
/**
13+
* @param {Tag2LinkItem[]} input
14+
* @returns {State}
15+
*/
16+
function convertSourceData(input) {
17+
/** @type {Record<string, string>} */
18+
const output = {};
19+
20+
const allKeys = new Set(input.map(item => item.key));
21+
22+
for (const key of allKeys) {
23+
// find the item with the best rank
24+
const bestDefinition = input
25+
.filter(item => item.key === key)
26+
.sort((a, b) => RANKS.indexOf(b.rank) - RANKS.indexOf(a.rank))[0];
27+
28+
output[key.replace('Key:', '')] = bestDefinition.url;
29+
}
30+
31+
return { tag2link: output };
32+
}
33+
34+
/**
35+
* @typedef {{
36+
* key: `Key:${string}`;
37+
* url: string;
38+
* source: string;
39+
* rank: "normal" | "preferred";
40+
* }} Tag2LinkItem
41+
*
42+
* @typedef {{
43+
* tag2link: Record<string, string>;
44+
* }} State
45+
*
46+
* @typedef {React.PropsWithChildren} Props
47+
*/
48+
49+
/** @type {React.Component<Props, State>} */
50+
export class Tag2LinkContext extends React.Component {
51+
/** @param {Props} props */
52+
constructor(props) {
53+
super(props);
54+
this.state = { tag2link: {} };
55+
}
56+
57+
componentDidMount() {
58+
if (!promise) {
59+
promise = fetch(URL)
60+
.then(r => r.json())
61+
.then(convertSourceData);
62+
}
63+
promise.then(state => this.setState(state));
64+
}
65+
66+
getChildContext() {
67+
return this.state;
68+
}
69+
70+
render() {
71+
return this.props.children;
72+
}
73+
}
74+
75+
Tag2LinkContext.propTypes = {
76+
children: PropTypes.node
77+
};
78+
79+
Tag2LinkContext.childContextTypes = {
80+
tag2link: PropTypes.objectOf(PropTypes.string)
81+
};

0 commit comments

Comments
 (0)