about summary refs log tree commit diff
path: root/src/librustdoc/html/static/js/scrape-examples.js
blob: d641405c87532422c4cc1aa1b19d96db80454cfc (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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/* global addClass, hasClass, removeClass, onEachLazy */

// Eventually fix this.
// @ts-nocheck

"use strict";

(function() {
    // Number of lines shown when code viewer is not expanded.
    // DEFAULT is the first example shown by default, while HIDDEN is
    // the examples hidden beneath the "More examples" toggle.
    //
    // NOTE: these values MUST be synchronized with certain rules in rustdoc.css!
    const DEFAULT_MAX_LINES = 5;
    const HIDDEN_MAX_LINES = 10;

    // Scroll code block to the given code location
    function scrollToLoc(elt, loc, isHidden) {
        const lines = elt.querySelectorAll("[data-nosnippet]");
        let scrollOffset;

        // If the block is greater than the size of the viewer,
        // then scroll to the top of the block. Otherwise scroll
        // to the middle of the block.
        const maxLines = isHidden ? HIDDEN_MAX_LINES : DEFAULT_MAX_LINES;
        if (loc[1] - loc[0] > maxLines) {
            const line = Math.max(0, loc[0] - 1);
            scrollOffset = lines[line].offsetTop;
        } else {
            const halfHeight = elt.offsetHeight / 2;
            const offsetTop = lines[loc[0]].offsetTop;
            const lastLine = lines[loc[1]];
            const offsetBot = lastLine.offsetTop + lastLine.offsetHeight;
            const offsetMid = (offsetTop + offsetBot) / 2;
            scrollOffset = offsetMid - halfHeight;
        }

        lines[0].parentElement.scrollTo(0, scrollOffset);
        elt.querySelector(".rust").scrollTo(0, scrollOffset);
    }

    function createScrapeButton(parent, className, content) {
        const button = document.createElement("button");
        button.className = className;
        button.title = content;
        parent.insertBefore(button, parent.firstChild);
        return button;
    }

    window.updateScrapedExample = (example, buttonHolder) => {
        let locIndex = 0;
        const highlights = Array.prototype.slice.call(example.querySelectorAll(".highlight"));
        const link = example.querySelector(".scraped-example-title a");
        let expandButton = null;

        if (!example.classList.contains("expanded")) {
            expandButton = createScrapeButton(buttonHolder, "expand", "Show all");
        }
        const isHidden = example.parentElement.classList.contains("more-scraped-examples");

        const locs = example.locs;
        if (locs.length > 1) {
            const next = createScrapeButton(buttonHolder, "next", "Next usage");
            const prev = createScrapeButton(buttonHolder, "prev", "Previous usage");

            // Toggle through list of examples in a given file
            const onChangeLoc = changeIndex => {
                removeClass(highlights[locIndex], "focus");
                changeIndex();
                scrollToLoc(example, locs[locIndex][0], isHidden);
                addClass(highlights[locIndex], "focus");

                const url = locs[locIndex][1];
                const title = locs[locIndex][2];

                link.href = url;
                link.innerHTML = title;
            };

            prev.addEventListener("click", () => {
                onChangeLoc(() => {
                    locIndex = (locIndex - 1 + locs.length) % locs.length;
                });
            });

            next.addEventListener("click", () => {
                onChangeLoc(() => {
                    locIndex = (locIndex + 1) % locs.length;
                });
            });
        }

        if (expandButton) {
            expandButton.addEventListener("click", () => {
                if (hasClass(example, "expanded")) {
                    removeClass(example, "expanded");
                    removeClass(expandButton, "collapse");
                    expandButton.title = "Show all";
                    scrollToLoc(example, locs[0][0], isHidden);
                } else {
                    addClass(example, "expanded");
                    addClass(expandButton, "collapse");
                    expandButton.title = "Show single example";
                }
            });
        }
    };

    function setupLoc(example, isHidden) {
        example.locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent);
        // Start with the first example in view
        scrollToLoc(example, example.locs[0][0], isHidden);
    }

    const firstExamples = document.querySelectorAll(".scraped-example-list > .scraped-example");
    onEachLazy(firstExamples, el => setupLoc(el, false));
    onEachLazy(document.querySelectorAll(".more-examples-toggle"), toggle => {
        // Allow users to click the left border of the <details> section to close it,
        // since the section can be large and finding the [+] button is annoying.
        onEachLazy(toggle.querySelectorAll(".toggle-line, .hide-more"), button => {
            button.addEventListener("click", () => {
                toggle.open = false;
            });
        });

        const moreExamples = toggle.querySelectorAll(".scraped-example");
        toggle.querySelector("summary").addEventListener("click", () => {
            // Wrapping in setTimeout ensures the update happens after the elements are actually
            // visible. This is necessary since setupLoc calls scrollToLoc which
            // depends on offsetHeight, a property that requires an element to be visible to
            // compute correctly.
            setTimeout(() => {
                onEachLazy(moreExamples, el => setupLoc(el, true));
            });
        }, {once: true});
    });
})();