summaryrefslogtreecommitdiffstats
path: root/packages/gitbook-core/src/components/PJAXWrapper.js
blob: f35a5545a393891cf58596b4d27e8d39d62e2221 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
const React = require('react');
const ReactRedux = require('react-redux');
const navigation = require('../actions/navigation');

/**
 * Check if an element is inside a link
 * @param {DOMElement} el
 * @param {String} name
 * @return {DOMElement|undefined
 */
function findParentByTagName(el, name) {
    while (el && el !== document) {
        if (el.tagName && el.tagName.toUpperCase() === name) {
            return el;
        }
        el = el.parentNode;
    }

    return false;
}

/**
 * Internal: Return the `href` component of given URL object with the hash
 * portion removed.
 *
 * @param {Location|HTMLAnchorElement} location
 * @return {String}
 */
function stripHash(location) {
    return location.href.replace(/#.*/, '');
}

/**
 * Test if a click event should be handled,
 * return the new url if it's a normal lcick
 */
function getHrefForEvent(event) {
    const link = findParentByTagName(event.target, 'A');

    if (!link)
        return;

    // Middle click, cmd click, and ctrl click should open
    // links in a new tab as normal.
    if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
        return;

    // Ignore cross origin links
    if (location.protocol !== link.protocol || location.hostname !== link.hostname)
        return;

    // Ignore case when a hash is being tacked on the current URL
    if (link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location))
        return;

    // Ignore event with default prevented
    if (event.defaultPrevented)
        return;

    // Explicitly ignored
    if (link.getAttribute('data-nopjax'))
        return;

    return link.href;
}

/*
    Wrapper to bind all navigation events to fetch pages.
 */

const PJAXWrapper = React.createClass({
    propTypes: {
        children: React.PropTypes.node,
        dispatch: React.PropTypes.func
    },

    onClick(event) {
        const { dispatch } = this.props;
        const href = getHrefForEvent(event);

        if (!href) {
            return;
        }

        event.preventDefault();
        dispatch(navigation.fetchPage(href));

    },

    onPopState(event) {
        const { dispatch } = this.props;
        event.preventDefault();

        dispatch(navigation.fetchPage(location.href, { replace: true }));
    },

    componentDidMount() {
        document.addEventListener('click', this.onClick, false);
        window.addEventListener('popstate', this.onPopState, false);
    },

    componentWillUnmount() {
        document.removeEventListener('click', this.onClick, false);
        window.removeEventListener('popstate', this.onPopState, false);
    },

    render() {
        return React.Children.only(this.props.children);
    }
});

module.exports = ReactRedux.connect()(PJAXWrapper);