<commit_before>import $ from "jquery";

import render_dropdown_list from "../templates/settings/dropdown_list.hbs";

import * as blueslip from "./blueslip";
import * as ListWidget from "./list_widget";

export const DropdownListWidget = function ({
    widget_name,
    data,
    default_text,
    render_text = (item_name) => item_name,
    null_value = null,
    include_current_item = true,
    value,
    on_update = () => {},
}) {
    const container_id = `${widget_name}_widget`;
    const value_id = `id_${widget_name}`;
    if (value === undefined) {
        value = null_value;
        blueslip.warn("dropdown-list-widget: Called without a default value; using null value");
    }

    const render_default_text = (elem) => {
        elem.text(default_text);
        elem.addClass("text-warning");
        elem.closest(".input-group").find(".dropdown_list_reset_button:enabled").hide();
    };

    const render = (value) => {
        $(`#${CSS.escape(container_id)} #${CSS.escape(value_id)}`).data("value", value);

        const elem = $(`#${CSS.escape(container_id)} #${CSS.escape(widget_name)}_name`);

        if (!value || value === null_value) {
            render_default_text(elem);
            return;
        }

        // Happy path
        const item = data.find((x) => x.value === value.toString());

        if (item === undefined) {
            render_default_text(elem);
            return;
        }

        const text = render_text(item.name);
        elem.text(text);
        elem.removeClass("text-warning");
        elem.closest(".input-group").find(".dropdown_list_reset_button:enabled").show();
    };

    const update = (value) => {
        render(value);
        on_update(value);
    };

    const register_event_handlers = () => {
        $(`#${CSS.escape(container_id)} .dropdown-list-body`).on(
            "click keypress",
            ".list_item",
            function (e) {
                const setting_elem = $(this).closest(`.${CSS.escape(widget_name)}_setting`);
                if (e.type === "keypress") {
                    if (e.key === "Enter") {
                        setting_elem.find(".dropdown-menu").dropdown("toggle");
                    } else {
                        return;
                    }
                }
                const value = $(this).attr("data-value");
                update(value);
            },
        );
        $(`#${CSS.escape(container_id)} .dropdown_list_reset_button`).on("click", (e) => {
            update(null_value);
            e.preventDefault();
        });
    };

    const setup = () => {
        // populate the dropdown
        const dropdown_list_body = $(
            `#${CSS.escape(container_id)} .dropdown-list-body`,
        ).expectOne();
        const search_input = $(`#${CSS.escape(container_id)} .dropdown-search > input[type=text]`);
        const dropdown_toggle = $(`#${CSS.escape(container_id)} .dropdown-toggle`);
        const get_data = (data) => {
            if (include_current_item) {
                return data;
            }
            return data.filter((x) => x.value !== value.toString());
        };

        ListWidget.create(dropdown_list_body, get_data(data), {
            name: `${CSS.escape(widget_name)}_list`,
            modifier(item) {
                return render_dropdown_list({item});
            },
            filter: {
                element: search_input,
                predicate(item, value) {
                    return item.name.toLowerCase().includes(value);
                },
            },
            simplebar_container: $(`#${CSS.escape(container_id)} .dropdown-list-wrapper`),
        });
        $(`#${CSS.escape(container_id)} .dropdown-search`).on("click", (e) => {
            e.stopPropagation();
        });

        dropdown_toggle.on("click", () => {
            search_input.val("").trigger("input");
        });

        dropdown_toggle.on("focus", (e) => {
            // On opening a Bootstrap Dropdown, the parent element receives focus.
            // Here, we want our search input to have focus instead.
            e.preventDefault();
            // This function gets called twice when focusing the
            // dropdown, and only in the second call is the input
            // field visible in the DOM; so the following visibility
            // check ensures we wait for the second call to focus.
            if (dropdown_list_body.is(":visible")) {
                search_input.trigger("focus");
            }
        });

        search_input.on("keydown", (e) => {
            const {key, keyCode, which} = e;
            if (!/(ArrowUp|ArrowDown|Escape)/.test(key)) {
                return;
            }
            e.preventDefault();
            e.stopPropagation();

            // We pass keyCode instead of key here because the outdated
            // bootstrap library we have at static/third/ still uses the
            // deprecated keyCode & which properties.
            const custom_event = new $.Event("keydown.dropdown.data-api", {keyCode, which});
            dropdown_toggle.trigger(custom_event);
        });

        render(value);
        register_event_handlers();
    };

    const get_value = () => {
        let val = $(`#${CSS.escape(container_id)} #${CSS.escape(value_id)}`).data("value");
        if (val === null) {
            val = "";
        }
        return val;
    };

    // Run setup() automatically on initialization.
    setup();

    return {
        render,
        value: get_value,
        update,
    };
};
<commit_msg>minor: Use Array.includes instead of a regex to check for values.<commit_after>import $ from "jquery";

import render_dropdown_list from "../templates/settings/dropdown_list.hbs";

import * as blueslip from "./blueslip";
import * as ListWidget from "./list_widget";

export const DropdownListWidget = function ({
    widget_name,
    data,
    default_text,
    render_text = (item_name) => item_name,
    null_value = null,
    include_current_item = true,
    value,
    on_update = () => {},
}) {
    const container_id = `${widget_name}_widget`;
    const value_id = `id_${widget_name}`;
    if (value === undefined) {
        value = null_value;
        blueslip.warn("dropdown-list-widget: Called without a default value; using null value");
    }

    const render_default_text = (elem) => {
        elem.text(default_text);
        elem.addClass("text-warning");
        elem.closest(".input-group").find(".dropdown_list_reset_button:enabled").hide();
    };

    const render = (value) => {
        $(`#${CSS.escape(container_id)} #${CSS.escape(value_id)}`).data("value", value);

        const elem = $(`#${CSS.escape(container_id)} #${CSS.escape(widget_name)}_name`);

        if (!value || value === null_value) {
            render_default_text(elem);
            return;
        }

        // Happy path
        const item = data.find((x) => x.value === value.toString());

        if (item === undefined) {
            render_default_text(elem);
            return;
        }

        const text = render_text(item.name);
        elem.text(text);
        elem.removeClass("text-warning");
        elem.closest(".input-group").find(".dropdown_list_reset_button:enabled").show();
    };

    const update = (value) => {
        render(value);
        on_update(value);
    };

    const register_event_handlers = () => {
        $(`#${CSS.escape(container_id)} .dropdown-list-body`).on(
            "click keypress",
            ".list_item",
            function (e) {
                const setting_elem = $(this).closest(`.${CSS.escape(widget_name)}_setting`);
                if (e.type === "keypress") {
                    if (e.key === "Enter") {
                        setting_elem.find(".dropdown-menu").dropdown("toggle");
                    } else {
                        return;
                    }
                }
                const value = $(this).attr("data-value");
                update(value);
            },
        );
        $(`#${CSS.escape(container_id)} .dropdown_list_reset_button`).on("click", (e) => {
            update(null_value);
            e.preventDefault();
        });
    };

    const setup = () => {
        // populate the dropdown
        const dropdown_list_body = $(
            `#${CSS.escape(container_id)} .dropdown-list-body`,
        ).expectOne();
        const search_input = $(`#${CSS.escape(container_id)} .dropdown-search > input[type=text]`);
        const dropdown_toggle = $(`#${CSS.escape(container_id)} .dropdown-toggle`);
        const get_data = (data) => {
            if (include_current_item) {
                return data;
            }
            return data.filter((x) => x.value !== value.toString());
        };

        ListWidget.create(dropdown_list_body, get_data(data), {
            name: `${CSS.escape(widget_name)}_list`,
            modifier(item) {
                return render_dropdown_list({item});
            },
            filter: {
                element: search_input,
                predicate(item, value) {
                    return item.name.toLowerCase().includes(value);
                },
            },
            simplebar_container: $(`#${CSS.escape(container_id)} .dropdown-list-wrapper`),
        });
        $(`#${CSS.escape(container_id)} .dropdown-search`).on("click", (e) => {
            e.stopPropagation();
        });

        dropdown_toggle.on("click", () => {
            search_input.val("").trigger("input");
        });

        dropdown_toggle.on("focus", (e) => {
            // On opening a Bootstrap Dropdown, the parent element receives focus.
            // Here, we want our search input to have focus instead.
            e.preventDefault();
            // This function gets called twice when focusing the
            // dropdown, and only in the second call is the input
            // field visible in the DOM; so the following visibility
            // check ensures we wait for the second call to focus.
            if (dropdown_list_body.is(":visible")) {
                search_input.trigger("focus");
            }
        });

        search_input.on("keydown", (e) => {
            const {key, keyCode, which} = e;
            const navigation_keys = ["ArrowUp", "ArrowDown", "Escape"];
            if (!navigation_keys.includes(key)) {
                return;
            }
            e.preventDefault();
            e.stopPropagation();

            // We pass keyCode instead of key here because the outdated
            // bootstrap library we have at static/third/ still uses the
            // deprecated keyCode & which properties.
            const custom_event = new $.Event("keydown.dropdown.data-api", {keyCode, which});
            dropdown_toggle.trigger(custom_event);
        });

        render(value);
        register_event_handlers();
    };

    const get_value = () => {
        let val = $(`#${CSS.escape(container_id)} #${CSS.escape(value_id)}`).data("value");
        if (val === null) {
            val = "";
        }
        return val;
    };

    // Run setup() automatically on initialization.
    setup();

    return {
        render,
        value: get_value,
        update,
    };
};
<|endoftext|>


<commit_before>import * as google_analytics from "./google-analytics";
import {detect_user_os} from "./tabbed-instructions";
import render_tabs from "./team";

export function path_parts() {
    return window.location.pathname.split("/").filter((chunk) => chunk !== "");
}

const hello_events = function () {
    let counter = 0;
    $(window).on("scroll", function () {
        if (counter % 2 === 0) {
            $(".screen.hero-screen .message-feed").css(
                "transform",
                "translateY(-" + $(this).scrollTop() / 5 + "px)",
            );
        }
        counter += 1;
    });

    $(".footer").addClass("hello");
};

const apps_events = function () {
    const info = {
        windows: {
            image: "/static/images/landing-page/microsoft.png",
            alt: "Windows",
            description:
                "Zulip for Windows is even better than Zulip on the web, with a cleaner look, tray integration, native notifications, and support for multiple Zulip accounts.",
            download_link: "/apps/download/windows",
            show_instructions: true,
            install_guide: "/help/desktop-app-install-guide",
            app_type: "desktop",
        },
        mac: {
            image: "/static/images/landing-page/macbook.png",
            alt: "macOS",
            description:
                "Zulip on macOS is even better than Zulip on the web, with a cleaner look, tray integration, native notifications, and support for multiple Zulip accounts.",
            download_link: "/apps/download/mac",
            show_instructions: true,
            install_guide: "/help/desktop-app-install-guide",
            app_type: "desktop",
        },
        android: {
            image: "/static/images/app-screenshots/zulip-android.png",
            alt: "Android",
            description: "Zulip's native Android app makes it easy to keep up while on the go.",
            show_instructions: false,
            link: "https://play.google.com/store/apps/details?id=com.zulipmobile",
            app_type: "mobile",
        },
        ios: {
            image: "/static/images/app-screenshots/zulip-iphone-rough.png",
            alt: "iOS",
            description: "Zulip's native iOS app makes it easy to keep up while on the go.",
            show_instructions: false,
            link: "https://itunes.apple.com/us/app/zulip/id1203036395",
            app_type: "mobile",
        },
        linux: {
            image: "/static/images/landing-page/ubuntu.png",
            alt: "Linux",
            description:
                "Zulip on the Linux desktop is even better than Zulip on the web, with a cleaner look, tray integration, native notifications, and support for multiple Zulip accounts.",
            download_link: "/apps/download/linux",
            show_instructions: true,
            install_guide: "/help/desktop-app-install-guide",
            app_type: "desktop",
        },
    };

    let version;

    function get_version_from_path() {
        let result;
        const parts = path_parts();

        Object.keys(info).forEach((version) => {
            if (parts.includes(version)) {
                result = version;
            }
        });

        result = result || detect_user_os();
        return result;
    }

    function get_path_from_version() {
        return "/apps/" + version;
    }

    function update_path() {
        const next_path = get_path_from_version();
        history.pushState(version, "", next_path);
    }

    const update_page = function () {
        const $download_instructions = $(".download-instructions");
        const $third_party_apps = $("#third-party-apps");
        const $download_android_apk = $("#download-android-apk");
        const $download_from_google_play_store = $(".download-from-google-play-store");
        const $download_from_apple_app_store = $(".download-from-apple-app-store");
        const $desktop_download_link = $(".desktop-download-link");
        const version_info = info[version];

        $(".info .platform").text(version_info.alt);
        $(".info .description").text(version_info.description);
        $(".info .desktop-download-link").attr("href", version_info.download_link);
        $(".download-from-google-play-store").attr("href", version_info.link);
        $(".download-from-apple-app-store").attr("href", version_info.link);
        $(".image img").attr("src", version_info.image);
        $download_instructions.find("a").attr("href", version_info.install_guide);

        $download_instructions.toggle(version_info.show_instructions);

        $third_party_apps.toggle(version_info.app_type === "desktop");
        $desktop_download_link.toggle(version_info.app_type === "desktop");
        $download_android_apk.toggle(version === "android");
        $download_from_google_play_store.toggle(version === "android");
        $download_from_apple_app_store.toggle(version === "ios");
    };

    $(window).on("popstate", () => {
        version = get_version_from_path();
        update_page();
        $("body").animate({scrollTop: 0}, 200);
        google_analytics.config({page_path: window.location.pathname});
    });

    $(".apps a .icon").on("click", (e) => {
        const next_version = $(e.target).closest("a").attr("href").replace("/apps/", "");
        version = next_version;

        update_path();
        update_page();
        $("body").animate({scrollTop: 0}, 200);
        google_analytics.config({page_path: window.location.pathname});

        return false;
    });

    // init
    version = get_version_from_path();
    history.replaceState(version, "", get_path_from_version());
    update_page();
};

const events = function () {
    // get the location url like `zulip.com/features/`, cut off the trailing
    // `/` and then split by `/` to get ["zulip.com", "features"], then
    // pop the last element to get the current section (eg. `features`).
    const location = window.location.pathname.replace(/\/#*$/, "").split(/\//).pop();

    $("[data-on-page='" + location + "']").addClass("active");

    $("body").on("click", (e) => {
        const $e = $(e.target);

        if ($e.is("nav ul .exit")) {
            $("nav ul").removeClass("show");
        }

        if ($("nav ul.show") && !$e.closest("nav ul.show").length && !$e.is("nav ul.show")) {
            $("nav ul").removeClass("show");
        }
    });

    $(".hamburger").on("click", (e) => {
        $("nav ul").addClass("show");
        e.stopPropagation();
    });

    if (path_parts().includes("apps")) {
        apps_events();
    }

    if (path_parts().includes("hello")) {
        hello_events();
    }
};

$(() => {
    // Initiate the bootstrap carousel logic
    $(".carousel").carousel({
        interval: false,
    });

    // Move to the next slide on clicking inside the carousel container
    $(".carousel-inner .item-container").on("click", function (e) {
        const get_tag_name = e.target.tagName.toLowerCase();
        const is_button = get_tag_name === "button";
        const is_link = get_tag_name === "a";
        const is_last_slide = $("#tour-carousel .carousel-inner .item:last-child").hasClass(
            "active",
        );

        // Do not trigger this event if user clicks on a button, link
        // or if it's the last slide
        const move_slide_forward = !is_button && !is_link && !is_last_slide;

        if (move_slide_forward) {
            $(this).closest(".carousel").carousel("next");
        }
    });

    $(".carousel").on("slid", function () {
        const $this = $(this);
        $this.find(".visibility-control").show();
        if ($this.find(".carousel-inner .item").first().hasClass("active")) {
            $this.find(".left.visibility-control").hide();
        } else if ($this.find(".carousel-inner .item").last().hasClass("active")) {
            $this.find(".right.visibility-control").hide();
        }
    });

    // Set up events / categories / search
    events();

    if (window.location.pathname === "/team/") {
        render_tabs();
    }
});
<commit_msg>apps: Link android download button directly to APK file.<commit_after>import * as google_analytics from "./google-analytics";
import {detect_user_os} from "./tabbed-instructions";
import render_tabs from "./team";

export function path_parts() {
    return window.location.pathname.split("/").filter((chunk) => chunk !== "");
}

const hello_events = function () {
    let counter = 0;
    $(window).on("scroll", function () {
        if (counter % 2 === 0) {
            $(".screen.hero-screen .message-feed").css(
                "transform",
                "translateY(-" + $(this).scrollTop() / 5 + "px)",
            );
        }
        counter += 1;
    });

    $(".footer").addClass("hello");
};

const apps_events = function () {
    const info = {
        windows: {
            image: "/static/images/landing-page/microsoft.png",
            alt: "Windows",
            description:
                "Zulip for Windows is even better than Zulip on the web, with a cleaner look, tray integration, native notifications, and support for multiple Zulip accounts.",
            download_link: "/apps/download/windows",
            show_instructions: true,
            install_guide: "/help/desktop-app-install-guide",
            app_type: "desktop",
        },
        mac: {
            image: "/static/images/landing-page/macbook.png",
            alt: "macOS",
            description:
                "Zulip on macOS is even better than Zulip on the web, with a cleaner look, tray integration, native notifications, and support for multiple Zulip accounts.",
            download_link: "/apps/download/mac",
            show_instructions: true,
            install_guide: "/help/desktop-app-install-guide",
            app_type: "desktop",
        },
        android: {
            image: "/static/images/app-screenshots/zulip-android.png",
            alt: "Android",
            description: "Zulip's native Android app makes it easy to keep up while on the go.",
            show_instructions: false,
            play_store_link: "https://play.google.com/store/apps/details?id=com.zulipmobile",
            download_link:
                "https://github.com/zulip/zulip-mobile/releases/latest/download/app-release.apk",
            app_type: "mobile",
        },
        ios: {
            image: "/static/images/app-screenshots/zulip-iphone-rough.png",
            alt: "iOS",
            description: "Zulip's native iOS app makes it easy to keep up while on the go.",
            show_instructions: false,
            app_store_link: "https://itunes.apple.com/us/app/zulip/id1203036395",
            app_type: "mobile",
        },
        linux: {
            image: "/static/images/landing-page/ubuntu.png",
            alt: "Linux",
            description:
                "Zulip on the Linux desktop is even better than Zulip on the web, with a cleaner look, tray integration, native notifications, and support for multiple Zulip accounts.",
            download_link: "/apps/download/linux",
            show_instructions: true,
            install_guide: "/help/desktop-app-install-guide",
            app_type: "desktop",
        },
    };

    let version;

    function get_version_from_path() {
        let result;
        const parts = path_parts();

        Object.keys(info).forEach((version) => {
            if (parts.includes(version)) {
                result = version;
            }
        });

        result = result || detect_user_os();
        return result;
    }

    function get_path_from_version() {
        return "/apps/" + version;
    }

    function update_path() {
        const next_path = get_path_from_version();
        history.pushState(version, "", next_path);
    }

    const update_page = function () {
        const $download_instructions = $(".download-instructions");
        const $third_party_apps = $("#third-party-apps");
        const $download_android_apk = $("#download-android-apk");
        const $download_from_google_play_store = $(".download-from-google-play-store");
        const $download_from_apple_app_store = $(".download-from-apple-app-store");
        const $desktop_download_link = $(".desktop-download-link");
        const version_info = info[version];

        $(".info .platform").text(version_info.alt);
        $(".info .description").text(version_info.description);
        $(".info .desktop-download-link").attr("href", version_info.download_link);
        $(".download-from-google-play-store").attr("href", version_info.play_store_link);
        $(".download-from-apple-app-store").attr("href", version_info.app_store_link);
        $("#download-android-apk a").attr("href", version_info.download_link);
        $(".image img").attr("src", version_info.image);
        $download_instructions.find("a").attr("href", version_info.install_guide);

        $download_instructions.toggle(version_info.show_instructions);

        $third_party_apps.toggle(version_info.app_type === "desktop");
        $desktop_download_link.toggle(version_info.app_type === "desktop");
        $download_android_apk.toggle(version === "android");
        $download_from_google_play_store.toggle(version === "android");
        $download_from_apple_app_store.toggle(version === "ios");
    };

    $(window).on("popstate", () => {
        version = get_version_from_path();
        update_page();
        $("body").animate({scrollTop: 0}, 200);
        google_analytics.config({page_path: window.location.pathname});
    });

    $(".apps a .icon").on("click", (e) => {
        const next_version = $(e.target).closest("a").attr("href").replace("/apps/", "");
        version = next_version;

        update_path();
        update_page();
        $("body").animate({scrollTop: 0}, 200);
        google_analytics.config({page_path: window.location.pathname});

        return false;
    });

    // init
    version = get_version_from_path();
    history.replaceState(version, "", get_path_from_version());
    update_page();
};

const events = function () {
    // get the location url like `zulip.com/features/`, cut off the trailing
    // `/` and then split by `/` to get ["zulip.com", "features"], then
    // pop the last element to get the current section (eg. `features`).
    const location = window.location.pathname.replace(/\/#*$/, "").split(/\//).pop();

    $("[data-on-page='" + location + "']").addClass("active");

    $("body").on("click", (e) => {
        const $e = $(e.target);

        if ($e.is("nav ul .exit")) {
            $("nav ul").removeClass("show");
        }

        if ($("nav ul.show") && !$e.closest("nav ul.show").length && !$e.is("nav ul.show")) {
            $("nav ul").removeClass("show");
        }
    });

    $(".hamburger").on("click", (e) => {
        $("nav ul").addClass("show");
        e.stopPropagation();
    });

    if (path_parts().includes("apps")) {
        apps_events();
    }

    if (path_parts().includes("hello")) {
        hello_events();
    }
};

$(() => {
    // Initiate the bootstrap carousel logic
    $(".carousel").carousel({
        interval: false,
    });

    // Move to the next slide on clicking inside the carousel container
    $(".carousel-inner .item-container").on("click", function (e) {
        const get_tag_name = e.target.tagName.toLowerCase();
        const is_button = get_tag_name === "button";
        const is_link = get_tag_name === "a";
        const is_last_slide = $("#tour-carousel .carousel-inner .item:last-child").hasClass(
            "active",
        );

        // Do not trigger this event if user clicks on a button, link
        // or if it's the last slide
        const move_slide_forward = !is_button && !is_link && !is_last_slide;

        if (move_slide_forward) {
            $(this).closest(".carousel").carousel("next");
        }
    });

    $(".carousel").on("slid", function () {
        const $this = $(this);
        $this.find(".visibility-control").show();
        if ($this.find(".carousel-inner .item").first().hasClass("active")) {
            $this.find(".left.visibility-control").hide();
        } else if ($this.find(".carousel-inner .item").last().hasClass("active")) {
            $this.find(".right.visibility-control").hide();
        }
    });

    // Set up events / categories / search
    events();

    if (window.location.pathname === "/team/") {
        render_tabs();
    }
});
<|endoftext|>


<commit_before>/*!
 * moving.js
 *
 * Copyright © 2016 Jorge M. Peláez | MIT license
 * http://j-pel.github.io/dynamize
 *
 */

(function(exports) {

  'use strict';

  /* properties */
  var storeChanges = function(changes){return(0)};
  var ongoingTouches = new Array();
  var moving = new Object();
  var touchMoving = false;

  /* client API */

  /*!
   * init(store)
   * Starts the handling of the proposed feature
   * A default id is assigned to elements with no id given.
   *
   * @param {store} callback to receive the elements' changes.
   * @api public
   */
  var init = exports.init = function(store) {
    storeChanges = store;
    var elementList = document.getElementsByClassName('movable');
    for (var i = 0; i < elementList.length; i++) {
      var ele = elementList[i];
      ele.addEventListener('mousedown', handleMouseDown, false);
      ele.style.cursor = 'move';
      if (!ele.id) {
        ele.id = "nn_m_"+i; // for unidentified nodes: important for storing
      }
    }
    document.addEventListener('touchstart', handleTouchStart, false);
    document.addEventListener("touchmove", handleTouchMove, true);
    document.addEventListener("touchleave", handleTouchEnd, true);
    document.addEventListener("touchend", handleTouchEnd, true);
    return 0;
  }

  /*!
   * stop()
   * Stops the handling of the proposed feature.
   *
   * @api public
   */
  var stop = exports.stop = function() {
    var elementList = document.getElementsByClassName('movable');
    for (var i = 0; i < elementList.length; i++) {
      var ele = elementList[i];
      ele.removeEventListener('mousedown', handleMouseDown, false);
      ele.style.cursor = 'auto';
    }
    document.removeEventListener('touchstart', handleTouchStart, false);
    document.removeEventListener("touchmove", handleTouchMove, true);
    document.removeEventListener("touchleave", handleTouchEnd, true);
    document.removeEventListener("touchend", handleTouchEnd, true);
    return 0;
  }

  /* private helpers */

  function log(msg) {
    var p = document.getElementById('status');
    p.innerHTML = msg + "\n" + p.innerHTML;
  }

  var handleMouseDown = function (event) {
    event = event || window.event;
    moving=this;
    moving.started = true;
    //log("Start moving " + this.innerHTML);
    document.addEventListener("mousemove", handleMouseMove, true);
    document.addEventListener("mouseup", handleMouseUp, true);
    event.preventDefault();
    if (!window.scrollX) {
      moving.mouseX = event.clientX + document.documentElement.scrollLeft +
        document.body.scrollLeft;
      moving.mouseY = event.clientY + document.documentElement.scrollTop +
        document.body.scrollTop;
    } else {
      moving.mouseX = event.clientX + window.scrollX;
      moving.mouseY = event.clientY + window.scrollY;
    }
    moving.startX = moving.offsetLeft;
    moving.startY = moving.offsetTop;
  }

  var handleMouseMove = function (event) {
    if (!moving.started) return;
    event = event || window.event;
    var x, y;
    if (!window.scrollX) {
      x = event.clientX + document.documentElement.scrollLeft + document.body.scrollLeft;
      y = event.clientY + document.documentElement.scrollTop + document.body.scrollTop;
    } else {
      x = event.clientX + window.scrollX;
      y = event.clientY + window.scrollY;
    }
    x = moving.startX + x - moving.mouseX;
    y = moving.startY + y - moving.mouseY;

    //log("Moving x = " + x + ", y = " + y);
    moving.style.left=(x < 0)? '0px' : x+'px';
    moving.style.top=(y < 0)? '0px' : y+'px';
    if (moving.firstChild.classList) {
      if (moving.firstChild.classList.contains("top-fixed")) {
        var evt = new CustomEvent("scroll",{detail: {}, bubbles: true, cancelable: true});
        moving.dispatchEvent(evt);
      }
    }
  }

  var handleMouseUp = function (event) {
    //log ("Stop moving " + moving.innerText);
    moving.started = false;
    var changes = [];
    if (parseInt(moving.startX) != parseInt(moving.style.left)) {
      changes.push(moving.id+".style.left="+moving.style.left);
    }
    if (parseInt(moving.startY) != parseInt(moving.style.top)) {
      changes.push(moving.id+".style.top="+moving.style.top);
    }
    storeChanges(changes);
    document.removeEventListener("mousemove", handleMouseMove, true);
    document.removeEventListener("mouseup", handleMouseUp, true);
  }

  var handleTouchStart = function (event) {
    var touches = event.changedTouches;
    for (var i=0; i < touches.length; i++) {
      if (touches[i].target.classList.contains('movable')) {
        touchMoving = true;
        ongoingTouches.push(copyTouch(touches[i]));
        //log("start touch "+i+ " on " +touches[i].target.innerHTML+" ("+touches[i].pageX+", "+touches[i].pageY+")");
      }
    }
  }

  var handleTouchMove = function (event) {
    if (!touchMoving) return;
    event.preventDefault();
    var touches = event.changedTouches;
    var x = 0;
    var y = 0;
    for (var i=0; i < touches.length; i++) {
      var idx = ongoingTouchIndexById(touches[i].identifier);
      if(idx >= 0) {
        x = touches[i].pageX - ongoingTouches[idx].pageX;
        y = touches[i].pageY - ongoingTouches[idx].pageY;
        //log("touch moving "+idx+" on "+ongoingTouches[idx].target.innerHTML+" ("+x+", "+y+")");
        ongoingTouches[idx].target.style.left = ongoingTouches[idx].target.offsetLeft + x + "px";
        ongoingTouches[idx].target.style.top = ongoingTouches[idx].target.offsetTop + y + "px";
        ongoingTouches.splice(idx, 1, copyTouch(touches[i]));  // swap in the new touch record
      } else {
        log("can't figure out which touch to continue");
      }
    }
  }

  var handleTouchEnd = function (event) {
    if (!touchMoving) return;
    touchMoving = false;
    event.preventDefault();
    var touches = event.changedTouches;
    for (var i=0; i < touches.length; i++) {
      var idx = ongoingTouchIndexById(touches[i].identifier);
      if(idx >= 0) {
        //log("touch finishing "+idx+" on "+ongoingTouches[idx].target.innerHTML);
        ongoingTouches.splice(idx, 1);  // remove it; we're done
      } else {
        log("can't figure out which touch to end");
      }
    }
  }

  function copyTouch(touch) {
    return {
      identifier: touch.identifier,
      target: touch.target,
      pageX: touch.pageX,
      pageY: touch.pageY
    };
  }

  function ongoingTouchIndexById(idToFind) {
    for (var i=0; i < ongoingTouches.length; i++) {
      var id = ongoingTouches[i].identifier;

      if (id == idToFind) {
        return i;
      }
    }
    return -1;    // not found
  }

})(typeof exports === 'undefined'? this['moving']={}: exports);
<commit_msg>Detail of pending operation in moving<commit_after>/*!
 * moving.js
 *
 * Copyright © 2016 Jorge M. Peláez | MIT license
 * http://j-pel.github.io/dynamize
 *
 * Pending: insert icon for moving.
 */

(function(exports) {

  'use strict';

  /* properties */
  var storeChanges = function(changes){return(0)};
  var ongoingTouches = new Array();
  var moving = new Object();
  var touchMoving = false;

  /* client API */

  /*!
   * init(store)
   * Starts the handling of the proposed feature
   * A default id is assigned to elements with no id given.
   *
   * @param {store} callback to receive the elements' changes.
   * @api public
   */
  var init = exports.init = function(store) {
    storeChanges = store;
    var elementList = document.getElementsByClassName('movable');
    for (var i = 0; i < elementList.length; i++) {
      var ele = elementList[i];
      ele.addEventListener('mousedown', handleMouseDown, false);
      ele.style.cursor = 'move';
      if (!ele.id) {
        ele.id = "nn_m_"+i; // for unidentified nodes: important for storing
      }
    }
    document.addEventListener('touchstart', handleTouchStart, false);
    document.addEventListener("touchmove", handleTouchMove, true);
    document.addEventListener("touchleave", handleTouchEnd, true);
    document.addEventListener("touchend", handleTouchEnd, true);
    return 0;
  }

  /*!
   * stop()
   * Stops the handling of the proposed feature.
   *
   * @api public
   */
  var stop = exports.stop = function() {
    var elementList = document.getElementsByClassName('movable');
    for (var i = 0; i < elementList.length; i++) {
      var ele = elementList[i];
      ele.removeEventListener('mousedown', handleMouseDown, false);
      ele.style.cursor = 'auto';
    }
    document.removeEventListener('touchstart', handleTouchStart, false);
    document.removeEventListener("touchmove", handleTouchMove, true);
    document.removeEventListener("touchleave", handleTouchEnd, true);
    document.removeEventListener("touchend", handleTouchEnd, true);
    return 0;
  }

  /* private helpers */

  function log(msg) {
    var p = document.getElementById('status');
    p.innerHTML = msg + "\n" + p.innerHTML;
  }

  var handleMouseDown = function (event) {
    event = event || window.event;
    moving=this;
    moving.started = true;
    //log("Start moving " + this.innerHTML);
    document.addEventListener("mousemove", handleMouseMove, true);
    document.addEventListener("mouseup", handleMouseUp, true);
    event.preventDefault();
    if (!window.scrollX) {
      moving.mouseX = event.clientX + document.documentElement.scrollLeft +
        document.body.scrollLeft;
      moving.mouseY = event.clientY + document.documentElement.scrollTop +
        document.body.scrollTop;
    } else {
      moving.mouseX = event.clientX + window.scrollX;
      moving.mouseY = event.clientY + window.scrollY;
    }
    moving.startX = moving.offsetLeft;
    moving.startY = moving.offsetTop;
  }

  var handleMouseMove = function (event) {
    if (!moving.started) return;
    event = event || window.event;
    var x, y;
    if (!window.scrollX) {
      x = event.clientX + document.documentElement.scrollLeft + document.body.scrollLeft;
      y = event.clientY + document.documentElement.scrollTop + document.body.scrollTop;
    } else {
      x = event.clientX + window.scrollX;
      y = event.clientY + window.scrollY;
    }
    x = moving.startX + x - moving.mouseX;
    y = moving.startY + y - moving.mouseY;

    //log("Moving x = " + x + ", y = " + y);
    moving.style.left=(x < 0)? '0px' : x+'px';
    moving.style.top=(y < 0)? '0px' : y+'px';
    if (moving.firstChild.classList) {
      if (moving.firstChild.classList.contains("top-fixed")) {
        var evt = new CustomEvent("scroll",{detail: {}, bubbles: true, cancelable: true});
        moving.dispatchEvent(evt);
      }
    }
  }

  var handleMouseUp = function (event) {
    //log ("Stop moving " + moving.innerText);
    moving.started = false;
    var changes = [];
    if (parseInt(moving.startX) != parseInt(moving.style.left)) {
      changes.push(moving.id+".style.left="+moving.style.left);
    }
    if (parseInt(moving.startY) != parseInt(moving.style.top)) {
      changes.push(moving.id+".style.top="+moving.style.top);
    }
    storeChanges(changes);
    document.removeEventListener("mousemove", handleMouseMove, true);
    document.removeEventListener("mouseup", handleMouseUp, true);
  }

  var handleTouchStart = function (event) {
    var touches = event.changedTouches;
    for (var i=0; i < touches.length; i++) {
      if (touches[i].target.classList.contains('movable')) {
        touchMoving = true;
        ongoingTouches.push(copyTouch(touches[i]));
        //log("start touch "+i+ " on " +touches[i].target.innerHTML+" ("+touches[i].pageX+", "+touches[i].pageY+")");
      }
    }
  }

  var handleTouchMove = function (event) {
    if (!touchMoving) return;
    event.preventDefault();
    var touches = event.changedTouches;
    var x = 0;
    var y = 0;
    for (var i=0; i < touches.length; i++) {
      var idx = ongoingTouchIndexById(touches[i].identifier);
      if(idx >= 0) {
        x = touches[i].pageX - ongoingTouches[idx].pageX;
        y = touches[i].pageY - ongoingTouches[idx].pageY;
        //log("touch moving "+idx+" on "+ongoingTouches[idx].target.innerHTML+" ("+x+", "+y+")");
        ongoingTouches[idx].target.style.left = ongoingTouches[idx].target.offsetLeft + x + "px";
        ongoingTouches[idx].target.style.top = ongoingTouches[idx].target.offsetTop + y + "px";
        ongoingTouches.splice(idx, 1, copyTouch(touches[i]));  // swap in the new touch record
      } else {
        log("can't figure out which touch to continue");
      }
    }
  }

  var handleTouchEnd = function (event) {
    if (!touchMoving) return;
    touchMoving = false;
    event.preventDefault();
    var touches = event.changedTouches;
    for (var i=0; i < touches.length; i++) {
      var idx = ongoingTouchIndexById(touches[i].identifier);
      if(idx >= 0) {
        //log("touch finishing "+idx+" on "+ongoingTouches[idx].target.innerHTML);
        ongoingTouches.splice(idx, 1);  // remove it; we're done
      } else {
        log("can't figure out which touch to end");
      }
    }
  }

  function copyTouch(touch) {
    return {
      identifier: touch.identifier,
      target: touch.target,
      pageX: touch.pageX,
      pageY: touch.pageY
    };
  }

  function ongoingTouchIndexById(idToFind) {
    for (var i=0; i < ongoingTouches.length; i++) {
      var id = ongoingTouches[i].identifier;

      if (id == idToFind) {
        return i;
      }
    }
    return -1;    // not found
  }

})(typeof exports === 'undefined'? this['moving']={}: exports);
<|endoftext|>


<commit_before>var _ = require('lodash');

var addMatch2Scoreboard = (board, match) => {
  var scoreA = parseInt(match.score[0] || 0);
  var scoreB = parseInt(match.score[1] || 0);
  if (scoreA == scoreB) {
    addMatchForTeamMembers(board, match.teams[0], false);
    addMatchForTeamMembers(board, match.teams[1], false);
  } else {
    var winner = scoreA > scoreB ? 0 : 1;
    addMatchForTeamMembers(board, match.teams[0], winner == 0);
    addMatchForTeamMembers(board, match.teams[1], winner == 1);
  }
  return board;
}

var addMatchForTeamMembers = (board, teamArray, winner) => {
  _(teamArray).each((player) => {
    var isWinner = winner;
    var points = isWinner ? 3 : 0;
    if (board[player] == null) {
      var entry = { playername: player, games: 0, wins: 0, loss: 0, points: 0 }
      board[player] = entry;
    }
    var entry = board[player];
    entry.points = entry.points + points;
    entry.games = entry.games + 1;
    if (isWinner) entry.wins = entry.wins + 1;
    else entry.loss = entry.loss + 1;
  })
}

function Scoreboard(matches) {
  var scoreboard = {};

  matches
  .filter((match) => {
    return !match.invalid;
  })
  .forEach((match) => {
    addMatch2Scoreboard(scoreboard, match);
  })

  return Object.keys(scoreboard).map((t) => {
    return scoreboard[t]
  })

}

module.exports = Scoreboard;
<commit_msg>(feat) Do lowercase matching on players<commit_after>var _ = require('lodash');

var addMatch2Scoreboard = (board, match) => {
  var scoreA = parseInt(match.score[0] || 0);
  var scoreB = parseInt(match.score[1] || 0);
  if (scoreA == scoreB) {
    addMatchForTeamMembers(board, match.teams[0], false);
    addMatchForTeamMembers(board, match.teams[1], false);
  } else {
    var winner = scoreA > scoreB ? 0 : 1;
    addMatchForTeamMembers(board, match.teams[0], winner == 0);
    addMatchForTeamMembers(board, match.teams[1], winner == 1);
  }
  return board;
}

var addMatchForTeamMembers = (board, teamArray, winner) => {
  _(teamArray).each((playerCaseSens) => {
    var player = playerCaseSens.toLowerCase();
    var isWinner = winner;
    var points = isWinner ? 3 : 0;
    if (board[player] == null) {
      var entry = { playername: player, games: 0, wins: 0, loss: 0, points: 0 }
      board[player] = entry;
    }
    var entry = board[player];
    entry.points = entry.points + points;
    entry.games = entry.games + 1;
    if (isWinner) entry.wins = entry.wins + 1;
    else entry.loss = entry.loss + 1;
  })
}

function Scoreboard(matches) {
  var scoreboard = {};

  matches
  .filter((match) => {
    return !match.invalid;
  })
  .forEach((match) => {
    addMatch2Scoreboard(scoreboard, match);
  })

  return Object.keys(scoreboard).map((t) => {
    return scoreboard[t]
  })

}

module.exports = Scoreboard;
<|endoftext|>


<commit_before>import $ from "jquery";

import {detect_user_os} from "./tabbed-instructions";
import render_tabs from "./team";

export function path_parts() {
    return window.location.pathname.split("/").filter((chunk) => chunk !== "");
}

const hello_events = function () {
    let counter = 0;
    $(window).on("scroll", function () {
        if (counter % 2 === 0) {
            $(".screen.hero-screen .message-feed").css(
                "transform",
                "translateY(-" + $(this).scrollTop() / 5 + "px)",
            );
        }
        counter += 1;
    });

    $(".footer").addClass("hello");
};

const apps_events = function () {
    const info = {
        windows: {
            image: "/static/images/landing-page/microsoft.png",
            alt: "Windows",
            description:
                "Zulip for Windows is even better than Zulip on the web, with a cleaner look, tray integration, native notifications, and support for multiple Zulip accounts.",
            download_link: "/apps/download/windows",
            show_instructions: true,
            install_guide: "/help/desktop-app-install-guide",
            app_type: "desktop",
        },
        mac: {
            image: "/static/images/landing-page/macbook.png",
            alt: "macOS",
            description:
                "Zulip on macOS is even better than Zulip on the web, with a cleaner look, tray integration, native notifications, and support for multiple Zulip accounts.",
            download_link: "/apps/download/mac",
            mac_arm64_link: "/apps/download/mac-arm64",
            show_instructions: true,
            install_guide: "/help/desktop-app-install-guide",
            app_type: "desktop",
        },
        android: {
            image: "/static/images/app-screenshots/zulip-android.png",
            alt: "Android",
            description: "Zulip's native Android app makes it easy to keep up while on the go.",
            show_instructions: false,
            play_store_link: "https://play.google.com/store/apps/details?id=com.zulipmobile",
            download_link:
                "https://github.com/zulip/zulip-mobile/releases/latest/download/app-release.apk",
            app_type: "mobile",
        },
        ios: {
            image: "/static/images/app-screenshots/zulip-iphone-rough.png",
            alt: "iOS",
            description: "Zulip's native iOS app makes it easy to keep up while on the go.",
            show_instructions: false,
            app_store_link: "https://itunes.apple.com/us/app/zulip/id1203036395",
            app_type: "mobile",
        },
        linux: {
            image: "/static/images/landing-page/ubuntu.png",
            alt: "Linux",
            description:
                "Zulip on the Linux desktop is even better than Zulip on the web, with a cleaner look, tray integration, native notifications, and support for multiple Zulip accounts.",
            download_link: "/apps/download/linux",
            show_instructions: true,
            install_guide: "/help/desktop-app-install-guide",
            app_type: "desktop",
        },
    };

    let version;

    function get_version_from_path() {
        let result;
        const parts = path_parts();

        for (const version of Object.keys(info)) {
            if (parts.includes(version)) {
                result = version;
            }
        }

        result = result || detect_user_os();
        return result;
    }

    const update_page = function () {
        const $download_instructions = $(".download-instructions");
        const $third_party_apps = $("#third-party-apps");
        const $download_android_apk = $("#download-android-apk");
        const $download_from_google_play_store = $(".download-from-google-play-store");
        const $download_from_apple_app_store = $(".download-from-apple-app-store");
        const $download_mac_arm64 = $("#download-mac-arm64");
        const $desktop_download_link = $(".desktop-download-link");
        const version_info = info[version];

        $(".info .platform").text(version_info.alt);
        $(".info .description").text(version_info.description);
        $desktop_download_link.attr("href", version_info.download_link);
        $download_from_google_play_store.attr("href", version_info.play_store_link);
        $download_from_apple_app_store.attr("href", version_info.app_store_link);
        $download_android_apk.attr("href", version_info.download_link);
        $download_mac_arm64.attr("href", version_info.mac_arm64_link);
        $(".image img").attr("src", version_info.image);
        $download_instructions.find("a").attr("href", version_info.install_guide);

        $download_instructions.toggle(version_info.show_instructions);

        $third_party_apps.toggle(version_info.app_type === "desktop");
        $desktop_download_link.toggle(version_info.app_type === "desktop");
        $download_android_apk.toggle(version === "android");
        $download_from_google_play_store.toggle(version === "android");
        $download_from_apple_app_store.toggle(version === "ios");
        $download_mac_arm64.toggle(version === "mac");
    };

    // init
    version = get_version_from_path();
    update_page();
};

const events = function () {
    // get the location url like `zulip.com/features/`, cut off the trailing
    // `/` and then split by `/` to get ["zulip.com", "features"], then
    // pop the last element to get the current section (eg. `features`).
    const location = window.location.pathname.replace(/\/$/, "").split(/\//).pop();

    $(`[data-on-page='${CSS.escape(location)}']`).addClass("active");

    $("body").on("click", (e) => {
        const $e = $(e.target);

        if ($e.is("nav ul .exit")) {
            $("nav ul").css("transform", "translate(-350px, 0)");
            // See https://ishadeed.com/article/layout-flickering/ for
            // more context as to why the following timeout is important.
            setTimeout(() => {
                $("nav ul").removeClass("show");
                $("nav ul").css("transform", "");
                $("body").removeClass("noscroll");
            }, 500);
        }

        if ($("nav ul.show") && !$e.closest("nav ul.show").length && !$e.is("nav ul.show")) {
            $("nav ul").removeClass("show");
            $("body").removeClass("noscroll");
        }
    });

    $(".hamburger").on("click", (e) => {
        $("nav ul").addClass("show");
        $("body").addClass("noscroll");
        e.stopPropagation();
    });

    if (path_parts().includes("apps")) {
        apps_events();
    }

    if (path_parts().includes("hello")) {
        hello_events();
    }
};

$(() => {
    // Initiate the bootstrap carousel logic
    $(".carousel").carousel({
        interval: false,
    });

    // Move to the next slide on clicking inside the carousel container
    $(".carousel-inner .item-container").on("click", function (e) {
        const get_tag_name = e.target.tagName.toLowerCase();
        const is_button = get_tag_name === "button";
        const is_link = get_tag_name === "a";
        const is_last_slide = $("#tour-carousel .carousel-inner .item:last-child").hasClass(
            "active",
        );

        // Do not trigger this event if user clicks on a button, link
        // or if it's the last slide
        const move_slide_forward = !is_button && !is_link && !is_last_slide;

        if (move_slide_forward) {
            $(this).closest(".carousel").carousel("next");
        }
    });

    $(".carousel").on("slid", function () {
        const $this = $(this);
        $this.find(".visibility-control").show();
        if ($this.find(".carousel-inner .item").first().hasClass("active")) {
            $this.find(".left.visibility-control").hide();
        } else if ($this.find(".carousel-inner .item").last().hasClass("active")) {
            $this.find(".right.visibility-control").hide();
        }
    });

    // Set up events / categories / search
    events();

    if (window.location.pathname === "/team/") {
        render_tabs();
    }

    // Source: https://stackoverflow.com/questions/819416/adjust-width-and-height-of-iframe-to-fit-with-content-in-it
    // Resize tweet to avoid overlapping with image. Since tweet uses an iframe which doesn't adjust with
    // screen resize, we need to manually adjust its width.

    function resizeIFrameToFitContent(iFrame) {
        $(iFrame).width("38vw");
    }

    window.addEventListener("resize", () => {
        const iframes = document.querySelectorAll(".twitter-tweet iframe");
        for (const iframe of iframes) {
            resizeIFrameToFitContent(iframe);
        }
    });
});

// Scroll to anchor link when clicked. Note that help.js has a similar
// function; this file and help.js are never included on the same
// page.
$(document).on("click", ".markdown h1, .markdown h2, .markdown h3", function () {
    window.location.hash = $(this).attr("id");
});
<commit_msg>apps: Link to release page for Android, rather than directly to APK.<commit_after>import $ from "jquery";

import {detect_user_os} from "./tabbed-instructions";
import render_tabs from "./team";

export function path_parts() {
    return window.location.pathname.split("/").filter((chunk) => chunk !== "");
}

const hello_events = function () {
    let counter = 0;
    $(window).on("scroll", function () {
        if (counter % 2 === 0) {
            $(".screen.hero-screen .message-feed").css(
                "transform",
                "translateY(-" + $(this).scrollTop() / 5 + "px)",
            );
        }
        counter += 1;
    });

    $(".footer").addClass("hello");
};

const apps_events = function () {
    const info = {
        windows: {
            image: "/static/images/landing-page/microsoft.png",
            alt: "Windows",
            description:
                "Zulip for Windows is even better than Zulip on the web, with a cleaner look, tray integration, native notifications, and support for multiple Zulip accounts.",
            download_link: "/apps/download/windows",
            show_instructions: true,
            install_guide: "/help/desktop-app-install-guide",
            app_type: "desktop",
        },
        mac: {
            image: "/static/images/landing-page/macbook.png",
            alt: "macOS",
            description:
                "Zulip on macOS is even better than Zulip on the web, with a cleaner look, tray integration, native notifications, and support for multiple Zulip accounts.",
            download_link: "/apps/download/mac",
            mac_arm64_link: "/apps/download/mac-arm64",
            show_instructions: true,
            install_guide: "/help/desktop-app-install-guide",
            app_type: "desktop",
        },
        android: {
            image: "/static/images/app-screenshots/zulip-android.png",
            alt: "Android",
            description: "Zulip's native Android app makes it easy to keep up while on the go.",
            show_instructions: false,
            play_store_link: "https://play.google.com/store/apps/details?id=com.zulipmobile",
            download_link: "https://github.com/zulip/zulip-mobile/releases/latest",
            app_type: "mobile",
        },
        ios: {
            image: "/static/images/app-screenshots/zulip-iphone-rough.png",
            alt: "iOS",
            description: "Zulip's native iOS app makes it easy to keep up while on the go.",
            show_instructions: false,
            app_store_link: "https://itunes.apple.com/us/app/zulip/id1203036395",
            app_type: "mobile",
        },
        linux: {
            image: "/static/images/landing-page/ubuntu.png",
            alt: "Linux",
            description:
                "Zulip on the Linux desktop is even better than Zulip on the web, with a cleaner look, tray integration, native notifications, and support for multiple Zulip accounts.",
            download_link: "/apps/download/linux",
            show_instructions: true,
            install_guide: "/help/desktop-app-install-guide",
            app_type: "desktop",
        },
    };

    let version;

    function get_version_from_path() {
        let result;
        const parts = path_parts();

        for (const version of Object.keys(info)) {
            if (parts.includes(version)) {
                result = version;
            }
        }

        result = result || detect_user_os();
        return result;
    }

    const update_page = function () {
        const $download_instructions = $(".download-instructions");
        const $third_party_apps = $("#third-party-apps");
        const $download_android_apk = $("#download-android-apk");
        const $download_from_google_play_store = $(".download-from-google-play-store");
        const $download_from_apple_app_store = $(".download-from-apple-app-store");
        const $download_mac_arm64 = $("#download-mac-arm64");
        const $desktop_download_link = $(".desktop-download-link");
        const version_info = info[version];

        $(".info .platform").text(version_info.alt);
        $(".info .description").text(version_info.description);
        $desktop_download_link.attr("href", version_info.download_link);
        $download_from_google_play_store.attr("href", version_info.play_store_link);
        $download_from_apple_app_store.attr("href", version_info.app_store_link);
        $download_android_apk.attr("href", version_info.download_link);
        $download_mac_arm64.attr("href", version_info.mac_arm64_link);
        $(".image img").attr("src", version_info.image);
        $download_instructions.find("a").attr("href", version_info.install_guide);

        $download_instructions.toggle(version_info.show_instructions);

        $third_party_apps.toggle(version_info.app_type === "desktop");
        $desktop_download_link.toggle(version_info.app_type === "desktop");
        $download_android_apk.toggle(version === "android");
        $download_from_google_play_store.toggle(version === "android");
        $download_from_apple_app_store.toggle(version === "ios");
        $download_mac_arm64.toggle(version === "mac");
    };

    // init
    version = get_version_from_path();
    update_page();
};

const events = function () {
    // get the location url like `zulip.com/features/`, cut off the trailing
    // `/` and then split by `/` to get ["zulip.com", "features"], then
    // pop the last element to get the current section (eg. `features`).
    const location = window.location.pathname.replace(/\/$/, "").split(/\//).pop();

    $(`[data-on-page='${CSS.escape(location)}']`).addClass("active");

    $("body").on("click", (e) => {
        const $e = $(e.target);

        if ($e.is("nav ul .exit")) {
            $("nav ul").css("transform", "translate(-350px, 0)");
            // See https://ishadeed.com/article/layout-flickering/ for
            // more context as to why the following timeout is important.
            setTimeout(() => {
                $("nav ul").removeClass("show");
                $("nav ul").css("transform", "");
                $("body").removeClass("noscroll");
            }, 500);
        }

        if ($("nav ul.show") && !$e.closest("nav ul.show").length && !$e.is("nav ul.show")) {
            $("nav ul").removeClass("show");
            $("body").removeClass("noscroll");
        }
    });

    $(".hamburger").on("click", (e) => {
        $("nav ul").addClass("show");
        $("body").addClass("noscroll");
        e.stopPropagation();
    });

    if (path_parts().includes("apps")) {
        apps_events();
    }

    if (path_parts().includes("hello")) {
        hello_events();
    }
};

$(() => {
    // Initiate the bootstrap carousel logic
    $(".carousel").carousel({
        interval: false,
    });

    // Move to the next slide on clicking inside the carousel container
    $(".carousel-inner .item-container").on("click", function (e) {
        const get_tag_name = e.target.tagName.toLowerCase();
        const is_button = get_tag_name === "button";
        const is_link = get_tag_name === "a";
        const is_last_slide = $("#tour-carousel .carousel-inner .item:last-child").hasClass(
            "active",
        );

        // Do not trigger this event if user clicks on a button, link
        // or if it's the last slide
        const move_slide_forward = !is_button && !is_link && !is_last_slide;

        if (move_slide_forward) {
            $(this).closest(".carousel").carousel("next");
        }
    });

    $(".carousel").on("slid", function () {
        const $this = $(this);
        $this.find(".visibility-control").show();
        if ($this.find(".carousel-inner .item").first().hasClass("active")) {
            $this.find(".left.visibility-control").hide();
        } else if ($this.find(".carousel-inner .item").last().hasClass("active")) {
            $this.find(".right.visibility-control").hide();
        }
    });

    // Set up events / categories / search
    events();

    if (window.location.pathname === "/team/") {
        render_tabs();
    }

    // Source: https://stackoverflow.com/questions/819416/adjust-width-and-height-of-iframe-to-fit-with-content-in-it
    // Resize tweet to avoid overlapping with image. Since tweet uses an iframe which doesn't adjust with
    // screen resize, we need to manually adjust its width.

    function resizeIFrameToFitContent(iFrame) {
        $(iFrame).width("38vw");
    }

    window.addEventListener("resize", () => {
        const iframes = document.querySelectorAll(".twitter-tweet iframe");
        for (const iframe of iframes) {
            resizeIFrameToFitContent(iframe);
        }
    });
});

// Scroll to anchor link when clicked. Note that help.js has a similar
// function; this file and help.js are never included on the same
// page.
$(document).on("click", ".markdown h1, .markdown h2, .markdown h3", function () {
    window.location.hash = $(this).attr("id");
});
<|endoftext|>


<commit_before>var _ = require('lodash'),
    expect = require('chai').expect,
    runtime = require('../../index'),
    sdk = require('postman-collection');

describe('Option', function () {
    describe('Delay', function () {
        it('should add a delay between item execution', function (mochaDone) {
            var errored = false,
                runner = new runtime.Runner(),
                rawCollection = {
                    'variables': [],
                    'info': {
                        'name': 'test',
                        '_postman_id': 'cd9e83b1-03dd-18ae-ff02-574414594a87',
                        'description': '',
                        'schema': 'https://schema.getpostman.com/json/collection/v2.0.0/collection.json'
                    },
                    'item': [
                        {
                            'name': 'Request Methods',
                            // eslint-disable-next-line max-len
                            'description': 'HTTP has multiple request "verbs", such as `GET`, `PUT`, `POST`, `DELETE`,\n`PATCH`, `HEAD`, etc. \n\nAn HTTP Method (verb) defines how a request should be interpreted by a server. \nThe endpoints in this section demonstrate various HTTP Verbs. Postman supports \nall the HTTP Verbs, including some rarely used ones, such as `PROPFIND`, `UNLINK`, \netc.\n\nFor details about HTTP Verbs, refer to [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9)\n',
                            'item': [
                                {
                                    'name': 'First Request',
                                    'event': [
                                        {
                                            'listen': 'test',
                                            'script': {
                                                'type': 'text/javascript',
                                                // eslint-disable-next-line max-len
                                                'exec': 'tests["Body contains headers"] = responseBody.has("headers");\ntests["Body contains args"] = responseBody.has("args");\ntests["Body contains url"] = responseBody.has("url");\n\nvar data = JSON.parse(responseBody)\n\ntests["Args key contains argument passed as url parameter"] = \'test\' in data.args'
                                            }
                                        }
                                    ],
                                    'request': {
                                        'url': 'https://postman-echo.com/get?test=123',
                                        'method': 'GET'
                                    }
                                },
                                {
                                    'name': 'Second Request',
                                    'event': [
                                        {
                                            'listen': 'prerequest',
                                            'script': {
                                                'type': 'text/javascript',
                                                'exec': 'if (iteration === 1) { throw new Error(\'omg!\'); }'
                                            }
                                        },
                                        {
                                            'listen': 'test',
                                            'script': {
                                                'type': 'text/javascript',
                                                'exec': ';'
                                            }
                                        }
                                    ],
                                    'request': {
                                        'url': 'https://postman-echo.com/get?test=123',
                                        'method': 'GET'
                                    }
                                },
                                {
                                    'name': 'Third Request',
                                    'event': [
                                        {
                                            'listen': 'test',
                                            'script': {
                                                'type': 'text/javascript',
                                                'exec': ';'
                                            }
                                        }
                                    ],
                                    'request': {
                                        'url': 'https://postman-echo.com/get?test=123',
                                        'method': 'GET'
                                    }
                                }
                            ]
                        }
                    ]
                },
                collection = new sdk.Collection(rawCollection),
                testables = {
                    iterationsStarted: [],
                    iterationsComplete: [],
                    itemsStarted: {},
                    itemsComplete: {}
                }, // populate during the run, and then perform tests on it, at the end.

                /**
                 * Since each callback runs in a separate callstack, this helper function
                 * ensures that any errors are forwarded to mocha
                 *
                 * @param func
                 */
                check = function (func) {
                    try { func(); }
                    catch (e) { (errored = true) && mochaDone(e); }
                };

            runner.run(collection, {
                iterationCount: 2,
                delay: {
                    item: 500
                }
            }, function (err, run) {
                var runStore = {}; // Used for validations *during* the run. Cursor increments, etc.

                expect(err).to.be.null;
                run.start({
                    start: function (err, cursor) {
                        check(function () {
                            expect(err).to.be.null;
                            expect(cursor).to.deep.include({
                                position: 0,
                                iteration: 0,
                                length: 3,
                                cycles: 2,
                                eof: false,
                                empty: false,
                                bof: true,
                                cr: false
                            });
                            expect(cursor).to.have.property('ref');

                            // Set this to true, and verify at the end, so that the test will fail even if this
                            // callback is never called.
                            testables.started = true;
                        });
                    },
                    beforeIteration: function (err, cursor) {
                        check(function () {
                            expect(err).to.be.null;

                            testables.iterationsStarted.push(cursor.iteration);
                            runStore.iteration = cursor.iteration;
                        });
                    },
                    iteration: function (err, cursor) {
                        check(function () {
                            expect(err).to.be.null;
                            expect(cursor).to.deep.include({
                                iteration: runStore.iteration
                            });

                            testables.iterationsComplete.push(cursor.iteration);
                        });
                    },
                    beforeItem: function (err, cursor, item) {
                        check(function () {
                            expect(err).to.be.null;

                            testables.itemsStarted[cursor.iteration] = testables.itemsStarted[cursor.iteration] || [];
                            testables.itemsStarted[cursor.iteration].push(item);
                            runStore.position = cursor.position;
                            runStore.ref = cursor.ref;
                            runStore.itemStartTime = Date.now();
                        });
                    },
                    item: function (err, cursor, item) {
                        check(function () {
                            expect(err).to.be.null;
                            expect(cursor).to.deep.include({
                                position: runStore.position,
                                ref: runStore.ref
                            });

                            testables.itemsComplete[cursor.iteration] = testables.itemsComplete[cursor.iteration] || [];
                            testables.itemsComplete[cursor.iteration].push(item);
                        });
                    },
                    beforePrerequest: function (err, cursor, events, item) {
                        check(function () {
                            expect(err).to.be.null;

                            // Sanity
                            expect(cursor).to.deep.include({
                                iteration: runStore.iteration,
                                position: runStore.position,
                                ref: runStore.ref
                            });

                            expect(Date.now() - runStore.itemStartTime).to.be.greaterThan(500);

                            if (item.name === 'Second Request') {
                                expect(events).to.have.lengthOf(1);
                            }
                            else {
                                expect(events).to.be.empty;
                            }
                        });
                    },
                    prerequest: function (err, cursor) {
                        check(function () {
                            // Sanity
                            expect(cursor).to.deep.include({
                                iteration: runStore.iteration,
                                position: runStore.position,
                                ref: runStore.ref
                            });

                            expect(err).to.be.null;
                        });
                    },
                    beforeTest: function (err, cursor, events) {
                        check(function () {
                            expect(err).to.be.null;

                            // Sanity
                            expect(cursor).to.deep.include({
                                iteration: runStore.iteration,
                                position: runStore.position,
                                ref: runStore.ref
                            });

                            // This collection has no pre-request scripts
                            expect(events).to.have.lengthOf(1);
                        });
                    },
                    test: function (err, cursor, results) {
                        check(function () {
                            expect(err).to.be.null;

                            // Sanity
                            expect(cursor).to.deep.include({
                                iteration: runStore.iteration,
                                position: runStore.position,
                                ref: runStore.ref
                            });

                            var result = results[0],
                                scriptResult = results[0];

                            expect(result.error).to.be.undefined;

                            expect(scriptResult).to.deep.nested.include({
                                'result.target': 'test'
                            });
                        });
                    },
                    beforeRequest: function (err, cursor) {
                        check(function () {
                            expect(err).to.be.null;

                            // Sanity
                            expect(cursor).to.deep.include({
                                iteration: runStore.iteration,
                                position: runStore.position,
                                ref: runStore.ref
                            });
                        });
                    },
                    request: function (err, cursor, response, request) {
                        check(function () {
                            expect(err).to.be.null;

                            expect(request.url.toString()).to.be.ok;

                            // Sanity
                            expect(cursor).to.deep.include({
                                iteration: runStore.iteration,
                                position: runStore.position,
                                ref: runStore.ref
                            });

                            expect(response.code).to.equal(200);
                            expect(request).to.be.ok;
                        });
                    },
                    done: function (err) {
                        expect(err).to.be.null;
                        expect(testables).to.deep.include({
                            started: true
                        });

                        // Ensure that we ran (and completed two iterations)
                        expect(testables).to.deep.include({
                            iterationsStarted: [0, 1],
                            iterationsComplete: [0, 1]
                        });

                        // First iteration
                        expect(testables.itemsStarted[0]).to.have.lengthOf(3);
                        expect(testables.itemsComplete[0]).to.have.lengthOf(3);
                        expect(_.map(testables.itemsStarted[0], 'name')).to.eql([
                            'First Request', 'Second Request', 'Third Request'
                        ]);
                        expect(_.map(testables.itemsComplete[0], 'name')).to.eql([
                            'First Request', 'Second Request', 'Third Request'
                        ]);

                        // Second Iteration
                        expect(testables.itemsStarted[1]).to.have.lengthOf(3);
                        expect(testables.itemsComplete[1]).to.have.lengthOf(3);
                        expect(_.map(testables.itemsStarted[1], 'name')).to.eql([
                            'First Request', 'Second Request', 'Third Request'
                        ]);
                        expect(_.map(testables.itemsComplete[1], 'name')).to.eql([
                            'First Request', 'Second Request', 'Third Request'
                        ]);

                        !errored && mochaDone();
                    }
                });
            });
        });
    });
});
<commit_msg>Update delay-spec tests with to.have.property chai helper<commit_after>var _ = require('lodash'),
    expect = require('chai').expect,
    runtime = require('../../index'),
    sdk = require('postman-collection');

describe('Option', function () {
    describe('Delay', function () {
        it('should add a delay between item execution', function (mochaDone) {
            var errored = false,
                runner = new runtime.Runner(),
                rawCollection = {
                    'variables': [],
                    'info': {
                        'name': 'test',
                        '_postman_id': 'cd9e83b1-03dd-18ae-ff02-574414594a87',
                        'description': '',
                        'schema': 'https://schema.getpostman.com/json/collection/v2.0.0/collection.json'
                    },
                    'item': [
                        {
                            'name': 'Request Methods',
                            // eslint-disable-next-line max-len
                            'description': 'HTTP has multiple request "verbs", such as `GET`, `PUT`, `POST`, `DELETE`,\n`PATCH`, `HEAD`, etc. \n\nAn HTTP Method (verb) defines how a request should be interpreted by a server. \nThe endpoints in this section demonstrate various HTTP Verbs. Postman supports \nall the HTTP Verbs, including some rarely used ones, such as `PROPFIND`, `UNLINK`, \netc.\n\nFor details about HTTP Verbs, refer to [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9)\n',
                            'item': [
                                {
                                    'name': 'First Request',
                                    'event': [
                                        {
                                            'listen': 'test',
                                            'script': {
                                                'type': 'text/javascript',
                                                // eslint-disable-next-line max-len
                                                'exec': 'tests["Body contains headers"] = responseBody.has("headers");\ntests["Body contains args"] = responseBody.has("args");\ntests["Body contains url"] = responseBody.has("url");\n\nvar data = JSON.parse(responseBody)\n\ntests["Args key contains argument passed as url parameter"] = \'test\' in data.args'
                                            }
                                        }
                                    ],
                                    'request': {
                                        'url': 'https://postman-echo.com/get?test=123',
                                        'method': 'GET'
                                    }
                                },
                                {
                                    'name': 'Second Request',
                                    'event': [
                                        {
                                            'listen': 'prerequest',
                                            'script': {
                                                'type': 'text/javascript',
                                                'exec': 'if (iteration === 1) { throw new Error(\'omg!\'); }'
                                            }
                                        },
                                        {
                                            'listen': 'test',
                                            'script': {
                                                'type': 'text/javascript',
                                                'exec': ';'
                                            }
                                        }
                                    ],
                                    'request': {
                                        'url': 'https://postman-echo.com/get?test=123',
                                        'method': 'GET'
                                    }
                                },
                                {
                                    'name': 'Third Request',
                                    'event': [
                                        {
                                            'listen': 'test',
                                            'script': {
                                                'type': 'text/javascript',
                                                'exec': ';'
                                            }
                                        }
                                    ],
                                    'request': {
                                        'url': 'https://postman-echo.com/get?test=123',
                                        'method': 'GET'
                                    }
                                }
                            ]
                        }
                    ]
                },
                collection = new sdk.Collection(rawCollection),
                testables = {
                    iterationsStarted: [],
                    iterationsComplete: [],
                    itemsStarted: {},
                    itemsComplete: {}
                }, // populate during the run, and then perform tests on it, at the end.

                /**
                 * Since each callback runs in a separate callstack, this helper function
                 * ensures that any errors are forwarded to mocha
                 *
                 * @param func
                 */
                check = function (func) {
                    try { func(); }
                    catch (e) { (errored = true) && mochaDone(e); }
                };

            runner.run(collection, {
                iterationCount: 2,
                delay: {
                    item: 500
                }
            }, function (err, run) {
                var runStore = {}; // Used for validations *during* the run. Cursor increments, etc.

                expect(err).to.be.null;
                run.start({
                    start: function (err, cursor) {
                        check(function () {
                            expect(err).to.be.null;
                            expect(cursor).to.deep.include({
                                position: 0,
                                iteration: 0,
                                length: 3,
                                cycles: 2,
                                eof: false,
                                empty: false,
                                bof: true,
                                cr: false
                            });
                            expect(cursor).to.have.property('ref');

                            // Set this to true, and verify at the end, so that the test will fail even if this
                            // callback is never called.
                            testables.started = true;
                        });
                    },
                    beforeIteration: function (err, cursor) {
                        check(function () {
                            expect(err).to.be.null;

                            testables.iterationsStarted.push(cursor.iteration);
                            runStore.iteration = cursor.iteration;
                        });
                    },
                    iteration: function (err, cursor) {
                        check(function () {
                            expect(err).to.be.null;
                            expect(cursor).to.have.property('iteration', runStore.iteration);

                            testables.iterationsComplete.push(cursor.iteration);
                        });
                    },
                    beforeItem: function (err, cursor, item) {
                        check(function () {
                            expect(err).to.be.null;

                            testables.itemsStarted[cursor.iteration] = testables.itemsStarted[cursor.iteration] || [];
                            testables.itemsStarted[cursor.iteration].push(item);
                            runStore.position = cursor.position;
                            runStore.ref = cursor.ref;
                            runStore.itemStartTime = Date.now();
                        });
                    },
                    item: function (err, cursor, item) {
                        check(function () {
                            expect(err).to.be.null;
                            expect(cursor).to.deep.include({
                                position: runStore.position,
                                ref: runStore.ref
                            });

                            testables.itemsComplete[cursor.iteration] = testables.itemsComplete[cursor.iteration] || [];
                            testables.itemsComplete[cursor.iteration].push(item);
                        });
                    },
                    beforePrerequest: function (err, cursor, events, item) {
                        check(function () {
                            expect(err).to.be.null;

                            // Sanity
                            expect(cursor).to.deep.include({
                                iteration: runStore.iteration,
                                position: runStore.position,
                                ref: runStore.ref
                            });

                            expect(Date.now() - runStore.itemStartTime).to.be.greaterThan(500);

                            if (item.name === 'Second Request') {
                                expect(events).to.have.lengthOf(1);
                            }
                            else {
                                expect(events).to.be.empty;
                            }
                        });
                    },
                    prerequest: function (err, cursor) {
                        check(function () {
                            // Sanity
                            expect(cursor).to.deep.include({
                                iteration: runStore.iteration,
                                position: runStore.position,
                                ref: runStore.ref
                            });

                            expect(err).to.be.null;
                        });
                    },
                    beforeTest: function (err, cursor, events) {
                        check(function () {
                            expect(err).to.be.null;

                            // Sanity
                            expect(cursor).to.deep.include({
                                iteration: runStore.iteration,
                                position: runStore.position,
                                ref: runStore.ref
                            });

                            // This collection has no pre-request scripts
                            expect(events).to.have.lengthOf(1);
                        });
                    },
                    test: function (err, cursor, results) {
                        check(function () {
                            expect(err).to.be.null;

                            // Sanity
                            expect(cursor).to.deep.include({
                                iteration: runStore.iteration,
                                position: runStore.position,
                                ref: runStore.ref
                            });

                            var result = results[0],
                                scriptResult = results[0];

                            expect(result.error).to.be.undefined;

                            expect(scriptResult).to.deep.nested.include({
                                'result.target': 'test'
                            });
                        });
                    },
                    beforeRequest: function (err, cursor) {
                        check(function () {
                            expect(err).to.be.null;

                            // Sanity
                            expect(cursor).to.deep.include({
                                iteration: runStore.iteration,
                                position: runStore.position,
                                ref: runStore.ref
                            });
                        });
                    },
                    request: function (err, cursor, response, request) {
                        check(function () {
                            expect(err).to.be.null;

                            expect(request.url.toString()).to.be.ok;

                            // Sanity
                            expect(cursor).to.deep.include({
                                iteration: runStore.iteration,
                                position: runStore.position,
                                ref: runStore.ref
                            });

                            expect(response).to.have.property('code', 200);
                            expect(request).to.be.ok;
                        });
                    },
                    done: function (err) {
                        expect(err).to.be.null;
                        expect(testables).to.have.property('started', true);

                        // Ensure that we ran (and completed two iterations)
                        expect(testables).to.deep.include({
                            iterationsStarted: [0, 1],
                            iterationsComplete: [0, 1]
                        });

                        // First iteration
                        expect(testables.itemsStarted[0]).to.have.lengthOf(3);
                        expect(testables.itemsComplete[0]).to.have.lengthOf(3);
                        expect(_.map(testables.itemsStarted[0], 'name')).to.eql([
                            'First Request', 'Second Request', 'Third Request'
                        ]);
                        expect(_.map(testables.itemsComplete[0], 'name')).to.eql([
                            'First Request', 'Second Request', 'Third Request'
                        ]);

                        // Second Iteration
                        expect(testables.itemsStarted[1]).to.have.lengthOf(3);
                        expect(testables.itemsComplete[1]).to.have.lengthOf(3);
                        expect(_.map(testables.itemsStarted[1], 'name')).to.eql([
                            'First Request', 'Second Request', 'Third Request'
                        ]);
                        expect(_.map(testables.itemsComplete[1], 'name')).to.eql([
                            'First Request', 'Second Request', 'Third Request'
                        ]);

                        !errored && mochaDone();
                    }
                });
            });
        });
    });
});
<|endoftext|>