1 | import React, {Component} from "react";
|
2 | import {match} from "react-router";
|
3 | import PropTypes from "prop-types";
|
4 | import {connect} from "react-redux";
|
5 | import Loading from "components/Loading";
|
6 | import d3plus from "d3plus.js";
|
7 | import Helmet from "react-helmet";
|
8 | import urllite from "urllite";
|
9 | import {Portal, Toaster} from "@blueprintjs/core";
|
10 |
|
11 | import "@blueprintjs/core/lib/css/blueprint.css";
|
12 | import "@blueprintjs/icons/lib/css/blueprint-icons.css";
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | if (typeof window !== "undefined" && typeof document !== "undefined" && (false || !!document.documentMode)) require("dom4");
|
19 |
|
20 | class 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 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | (function(SVGElement) {
|
38 |
|
39 | if (!SVGElement || "innerHTML" in SVGElement.prototype) {
|
40 | return;
|
41 | }
|
42 |
|
43 |
|
44 | function serializeXML(node, output) {
|
45 | const nodeType = node.nodeType;
|
46 | if (nodeType === 3) {
|
47 |
|
48 | output.push(node.textContent.replace(/&/, "&").replace(/</, "<").replace(">", ">"));
|
49 | }
|
50 | else if (nodeType === 1) {
|
51 |
|
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 |
|
74 | output.push("<!--", node.nodeValue, "-->");
|
75 | }
|
76 | else {
|
77 |
|
78 |
|
79 |
|
80 | throw `Error serializing XML. Unhandled node of type: ${nodeType}`;
|
81 | }
|
82 | }
|
83 |
|
84 |
|
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 |
|
97 | while (this.firstChild) {
|
98 | this.removeChild(this.firstChild);
|
99 | }
|
100 |
|
101 | try {
|
102 |
|
103 | const dXML = new DOMParser();
|
104 | dXML.async = false;
|
105 |
|
106 | const sXML = `<svg xmlns='http://www.w3.org/2000/svg'>${ markupText }</svg>`;
|
107 | const svgDocElement = dXML.parseFromString(sXML, "text/xml").documentElement;
|
108 |
|
109 |
|
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 |
|
136 | if (e.defaultPrevented) return;
|
137 | if (e.metaKey || e.ctrlKey || e.shiftKey) return;
|
138 | if (e.button !== 0) return;
|
139 |
|
140 |
|
141 | let el = e.target;
|
142 | while (el && el.nodeName !== "A") el = el.parentNode;
|
143 |
|
144 |
|
145 | if (!el) return;
|
146 |
|
147 |
|
148 | if (el.target && el.target !== "_self") return;
|
149 |
|
150 |
|
151 |
|
152 | if (el.attributes.download) return;
|
153 |
|
154 |
|
155 |
|
156 | if (el.attributes["data-refresh"]) return;
|
157 |
|
158 |
|
159 | if (el.getAttribute("href") === "#") return;
|
160 |
|
161 |
|
162 |
|
163 | const url = urllite(el.href);
|
164 | const windowURL = urllite(window.location.href);
|
165 |
|
166 |
|
167 | if (url.protocol !== windowURL.protocol || url.host !== windowURL.host) return;
|
168 |
|
169 |
|
170 | if (el.rel && (/(?:^|\s+)external(?:\s+|$)/).test(el.rel)) return;
|
171 |
|
172 |
|
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 |
|
208 | CanonProvider.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 |
|
217 | CanonProvider.defaultProps = {
|
218 | helmet: {},
|
219 | data: {}
|
220 | };
|
221 |
|
222 | CanonProvider = connect(state => ({
|
223 | data: state.data,
|
224 | loading: state.loading
|
225 | }))(CanonProvider);
|
226 |
|
227 | export default CanonProvider;
|