UNPKG

7.47 kBJSXView Raw
1import React, {Component} from "react";
2import {match} from "react-router";
3import PropTypes from "prop-types";
4import {connect} from "react-redux";
5import Loading from "components/Loading";
6import d3plus from "d3plus.js";
7import Helmet from "react-helmet";
8import urllite from "urllite";
9import {Portal, Toaster} from "@blueprintjs/core";
10
11import "@blueprintjs/core/lib/css/blueprint.css";
12import "@blueprintjs/icons/lib/css/blueprint-icons.css";
13
14/**
15blueprint tooltip IE fix; check that the window exists and that we're in IE first
16related issue: https://github.com/DataScienceSquad/open-compass/issues/247
17*/
18if (typeof window !== "undefined" && typeof document !== "undefined" && (/*@cc_on!@*/false || !!document.documentMode)) require("dom4"); // eslint-disable-line spaced-comment
19
20class CanonProvider extends Component {
21
22 constructor(props) {
23
24 super(props);
25 this.toastRef = React.createRef();
26
27 if (typeof window !== "undefined") {
28
29 /**
30 * innerHTML property for SVGElement
31 * Copyright(c) 2010, Jeff Schiller
32 *
33 * Licensed under the Apache License, Version 2
34 *
35 * Minor modifications by Chris Price to only polyfill when required.
36 */
37 (function(SVGElement) {
38
39 if (!SVGElement || "innerHTML" in SVGElement.prototype) {
40 return;
41 }
42
43 /** serializeXML polyfill */
44 function serializeXML(node, output) {
45 const nodeType = node.nodeType;
46 if (nodeType === 3) { // TEXT nodes.
47 // Replace special XML characters with their entities.
48 output.push(node.textContent.replace(/&/, "&amp;").replace(/</, "&lt;").replace(">", "&gt;"));
49 }
50 else if (nodeType === 1) { // ELEMENT nodes.
51 // Serialize Element nodes.
52 output.push("<", node.tagName);
53 if (node.hasAttributes()) {
54 const attrMap = node.attributes;
55 for (let i = 0, len = attrMap.length; i < len; ++i) {
56 const attrNode = attrMap.item(i);
57 output.push(" ", attrNode.name, "='", attrNode.value, "'");
58 }
59 }
60 if (node.hasChildNodes()) {
61 output.push(">");
62 const childNodes = node.childNodes;
63 for (let i = 0, len = childNodes.length; i < len; ++i) {
64 serializeXML(childNodes.item(i), output);
65 }
66 output.push("</", node.tagName, ">");
67 }
68 else {
69 output.push("/>");
70 }
71 }
72 else if (nodeType == 8) {
73 // TODO(codedread): Replace special characters with XML entities?
74 output.push("<!--", node.nodeValue, "-->");
75 }
76 else {
77 // TODO: Handle CDATA nodes.
78 // TODO: Handle ENTITY nodes.
79 // TODO: Handle DOCUMENT nodes.
80 throw `Error serializing XML. Unhandled node of type: ${nodeType}`;
81 }
82 }
83
84 // The innerHTML DOM property for SVGElement.
85 Object.defineProperty(SVGElement.prototype, "innerHTML", {
86 get() {
87 const output = [];
88 let childNode = this.firstChild;
89 while (childNode) {
90 serializeXML(childNode, output);
91 childNode = childNode.nextSibling;
92 }
93 return output.join("");
94 },
95 set(markupText) {
96 // Wipe out the current contents of the element.
97 while (this.firstChild) {
98 this.removeChild(this.firstChild);
99 }
100
101 try {
102 // Parse the markup into valid nodes.
103 const dXML = new DOMParser();
104 dXML.async = false;
105 // Wrap the markup into a SVG node to ensure parsing works.
106 const sXML = `<svg xmlns='http://www.w3.org/2000/svg'>${ markupText }</svg>`;
107 const svgDocElement = dXML.parseFromString(sXML, "text/xml").documentElement;
108
109 // Now take each node, import it and append to this element.
110 let childNode = svgDocElement.firstChild;
111 while (childNode) {
112 this.appendChild(this.ownerDocument.importNode(childNode, true));
113 childNode = childNode.nextSibling;
114 }
115 }
116 catch (e) {
117 throw new Error("Error parsing XML string");
118 }
119 }
120 });
121
122 }((1, eval)("this").SVGElement));
123 }
124
125 }
126
127 getChildContext() {
128 const {data, helmet, locale, router} = this.props;
129 const toast = this.toastRef;
130 return {d3plus, data, helmet, locale, router, toast};
131 }
132
133 onClick(e) {
134
135 // Ignore canceled events, modified clicks, and right clicks.
136 if (e.defaultPrevented) return;
137 if (e.metaKey || e.ctrlKey || e.shiftKey) return;
138 if (e.button !== 0) return;
139
140 // Get the <a> element.
141 let el = e.target;
142 while (el && el.nodeName !== "A") el = el.parentNode;
143
144 // Ignore clicks from non-a elements.
145 if (!el) return;
146
147 // Ignore the click if the element has a target.
148 if (el.target && el.target !== "_self") return;
149
150 // Ignore the click if it's a download link. (We use this method of
151 // detecting the presence of the attribute for old IE versions.)
152 if (el.attributes.download) return;
153
154 // Allow links to refresh the page if a "data-refresh" attribute
155 // is present.
156 if (el.attributes["data-refresh"]) return;
157
158 // Ignore hash (used often instead of javascript:void(0) in strict CSP envs)
159 if (el.getAttribute("href") === "#") return;
160
161 // Use a regular expression to parse URLs instead of relying on the browser
162 // to do it for us (because IE).
163 const url = urllite(el.href);
164 const windowURL = urllite(window.location.href);
165
166 // Ignore links that don't share a protocol and host with ours.
167 if (url.protocol !== windowURL.protocol || url.host !== windowURL.host) return;
168
169 // Ignore 'rel="external"' links.
170 if (el.rel && (/(?:^|\s+)external(?:\s+|$)/).test(el.rel)) return;
171
172 // Prevent :focus from sticking; preventDefault() stops blur in some browsers
173 el.blur();
174 e.preventDefault();
175
176 const {push, routes} = this.props.router;
177
178 const serverRoutes = ["/auth/logout"];
179 match({location: url, routes}, (err, redirect, props) => {
180 if (err) console.error(err);
181 if (serverRoutes.includes(url.pathname)) window.location.href = el.href;
182 else if (props) push(`${url.pathname}${url.search}`);
183 else window.location.href = el.href;
184 });
185
186 }
187
188 render() {
189
190 const {children, helmet, loading, locale} = this.props;
191
192 return <div id="Canon" onClick={this.onClick.bind(this)}>
193 <Helmet
194 htmlAttributes={{lang: locale, amp: undefined}}
195 defaultTitle={helmet.title}
196 titleTemplate={`%s | ${helmet.title}`}
197 meta={helmet.meta}
198 link={helmet.link}
199 />
200 { loading ? <Loading /> : <div>{ children }</div> }
201 <Portal>
202 <Toaster ref={this.toastRef} />
203 </Portal>
204 </div>;
205 }
206}
207
208CanonProvider.childContextTypes = {
209 data: PropTypes.object,
210 d3plus: PropTypes.object,
211 helmet: PropTypes.object,
212 locale: PropTypes.string,
213 router: PropTypes.object,
214 toast: PropTypes.object
215};
216
217CanonProvider.defaultProps = {
218 helmet: {},
219 data: {}
220};
221
222CanonProvider = connect(state => ({
223 data: state.data,
224 loading: state.loading
225}))(CanonProvider);
226
227export default CanonProvider;