diff --git a/lib/featureDiff/DiffColumn.js b/lib/featureDiff/DiffColumn.js
index ddc0dc2..9ee19a3 100644
--- a/lib/featureDiff/DiffColumn.js
+++ b/lib/featureDiff/DiffColumn.js
@@ -2,15 +2,17 @@ import React from 'react';
import PropTypes from 'prop-types';
import { config } from '../config';
-export const DiffColumn = function({ diff, prop, type, propClass }) {
- return (
-
-
- |
- );
-};
+export class DiffColumn extends React.Component {
+ render() {
+ return (
+
+
+ |
+ );
+ }
+}
-const DiffColumnContent = function({ diff, prop, type }) {
+const DiffColumnContent = function({ diff, prop, type, tag2link }) {
let renderOutput;
const value = diff[prop][type],
propIsWikidata = typeof prop == 'string' && /wikidata$/.test(prop);
@@ -54,6 +56,21 @@ const DiffColumnContent = function({ diff, prop, type }) {
}
});
renderOutput = {renderArray};
+ } else if (tag2link[prop] && typeof value === 'string') {
+ // This is a foreign key which is defined in the tag2link DB.
+ // So, we render a clickable link.
+ renderOutput = (
+
+
+ {value}
+
+
+ );
} else {
// Standard tag, no processing needed
renderOutput = {value};
@@ -67,3 +84,7 @@ DiffColumn.propTypes = {
type: PropTypes.string.isRequired,
propClass: PropTypes.string
};
+
+DiffColumn.contextTypes = {
+ tag2link: PropTypes.objectOf(PropTypes.string)
+};
diff --git a/lib/featureDiff/DiffTable.js b/lib/featureDiff/DiffTable.js
index 453789a..f7ebb92 100644
--- a/lib/featureDiff/DiffTable.js
+++ b/lib/featureDiff/DiffTable.js
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { DiffRows } from './DiffRows';
+import { Tag2LinkContext } from '../tag2link';
//Renders the markup for a table
@@ -30,13 +31,15 @@ export const DiffTable = function({ diff, ignoreList, header }) {
)}
-
+
+
+
);
};
diff --git a/lib/tag2link.js b/lib/tag2link.js
new file mode 100644
index 0000000..aed1186
--- /dev/null
+++ b/lib/tag2link.js
@@ -0,0 +1,81 @@
+// @ts-check
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const URL = 'https://cdn.jsdelivr.net/gh/JOSM/tag2link@master/index.json';
+
+const RANKS = ['deprecated', 'normal', 'preferred'];
+
+/** @type {Promise | undefined} */
+let promise;
+
+/**
+ * @param {Tag2LinkItem[]} input
+ * @returns {State}
+ */
+function convertSourceData(input) {
+ /** @type {Record} */
+ const output = {};
+
+ const allKeys = new Set(input.map(item => item.key));
+
+ for (const key of allKeys) {
+ // find the item with the best rank
+ const bestDefinition = input
+ .filter(item => item.key === key)
+ .sort((a, b) => RANKS.indexOf(b.rank) - RANKS.indexOf(a.rank))[0];
+
+ output[key.replace('Key:', '')] = bestDefinition.url;
+ }
+
+ return { tag2link: output };
+}
+
+/**
+ * @typedef {{
+ * key: `Key:${string}`;
+ * url: string;
+ * source: string;
+ * rank: "normal" | "preferred";
+ * }} Tag2LinkItem
+ *
+ * @typedef {{
+ * tag2link: Record;
+ * }} State
+ *
+ * @typedef {React.PropsWithChildren} Props
+ */
+
+/** @type {React.Component} */
+export class Tag2LinkContext extends React.Component {
+ /** @param {Props} props */
+ constructor(props) {
+ super(props);
+ this.state = { tag2link: {} };
+ }
+
+ componentDidMount() {
+ if (!promise) {
+ promise = fetch(URL)
+ .then(r => r.json())
+ .then(convertSourceData);
+ }
+ promise.then(state => this.setState(state));
+ }
+
+ getChildContext() {
+ return this.state;
+ }
+
+ render() {
+ return this.props.children;
+ }
+}
+
+Tag2LinkContext.propTypes = {
+ children: PropTypes.node
+};
+
+Tag2LinkContext.childContextTypes = {
+ tag2link: PropTypes.objectOf(PropTypes.string)
+};