<commit_before>/*jshint bitwise: true, camelcase: true, curly: true, eqeqeq: true, forin: true, immed: true, indent: 4, latedef: true, newcap: true, noarg: true, noempty: true, nonew: true, regexp: true, undef: true, unused: true, strict: true*/
/*global define*/
define(["util"], function (util) {

    "use strict";

    function error(msg) {
        return function (state) {
            throw new Error(msg + " (line " + state.lineNum + ", char " + state.charNum + ")");
        };
    }

    function createForm(type, value, extras) {
        var form = { type: type, value: value };
        return extras ? util.copyProps(form, extras) : form;
    }

    var skip = { toString: function () { return "{skip}"; } };

    function newline(state) {
        state.i++;
        state.lineNum++;
        state.charNum = 1;
        return read(state);
    }

    function readList(state) {
        var origState = state.clone();
        var openChr = state.input.charAt(state.i);
        var closeChr = openChr === "(" ? ")" :
                       openChr === "[" ? "]" :
                       openChr === "{" ? "}" :
                       error("Unknown list opener '" + openChr + "'")(state);
        var sym = openChr === "(" ? "paren" :
                  openChr === "[" ? "bracket" :
                  openChr === "{" ? "brace" : null;
        state.i++;
        var values = [];
        var closed = false;
        while (state.i < state.l) {
            if (state.input.charAt(state.i) === closeChr) {
                state.i++;
                closed = true;
                break;
            } else {
                var s = read(state);
                values.push(s);
            }
        }
        if (!closed) {
            error("Expected '" + closeChr + "'")(origState);
        }
        return createForm("list", values, { sym: sym });
    }

    function readNum(state) {
        var rxNum = /^[0-9\.]+$/;
        var rxHex = /^0x[0-9A-F]+$/i;
        var chunk = readToDelim(state);
        if (rxNum.test(chunk)) {
            return createForm("literal", parseFloat(chunk, 10));
        } else if (rxHex.test(chunk)) {
            return createForm("literal", parseInt(chunk, 16));
        } else {
            error("Invalid characters in number literal '" + chunk + "'")(state);
        }
    }

    function readString(state) {
        // TODO: escaping probably needs more work
        state.i++;
        var origState = state.clone();
        var chunk = "";
        var escaped = false;
        var input = state.input;
        while (state.i < state.l) {
            var chr = input.charAt(state.i);
            if (chr === '"' && !escaped) {
                break;
            }
            if (escaped || chr !== "\\") {
                chunk += chr;
                escaped = false;
            } else if (chr === "\\") {
                escaped = true;
            }
            state.i++;
            state.charNum++;
        }

        if (state.i === state.l) {
            error("Unclosed string")(origState);
        }
        state.i++;
        return createForm("literal", chunk);
    }

    function readSpecial(state) {
        var chunk = readToDelim(state);
        if (chunk === "#t") {
            return createForm("literal", true);
        } else if (chunk === "#f") {
            return createForm("literal", false);
        }
    }

    function readSymbol(state) {
        // TODO: this might be a tad too relaxed
        return createForm("symbol", readToDelim(state));
    }

    function readComment(state) {
        var text = "";
        state.i++;
        while (state.i < state.l) {
            var chr = state.input.charAt(state.i++);
            if (chr === "\n") {
                break;
            } else if (chr !== "\r") {
                text += chr;
            }
        }
        return createForm("list", [createForm("symbol", "comment"), text]);
    }

    function readQuote(type) {
        return function (state) {
            state.i++;
            var x = read(state);
            if (x === null) {
                error("Expected something for " + type.substring(0, type.length - 1) + "ing")(state);
            }
            return createForm("list", [createForm("symbol", type), x]);
        };
    }

    function readUnquote(state) {
        state.i++;
        var type = "unquote";
        if (state.input.charAt(state.i) === "@") {
            type += "-splicing";
            state.i++;
        }
        var x = read(state);
        if (x === null) {
            error("Expected something for " + (type === "unquote" ? "unquoting" : type))(state);
        }
        return createForm("list", [createForm("symbol", type), x]);
    }

    var delimiters = {
        "(" : true,
        "[" : true,
        "{" : true,
        ")" : true,
        "]" : true,
        "}" : true,
        '"' : true,
        "'" : true,
        "`" : true,
        "," : true,
        ";" : true,
        " " : true,
        "\t": true,
        "\r": true,
        "\n": true
    };

    function readToDelim(state) {
        var chunk = "";
        var input = state.input;
        while (state.i < state.l) {
            var chr = input.charAt(state.i);
            if (delimiters.hasOwnProperty(chr)) {
                break;
            } else {
                chunk += chr;
                state.i++;
                state.charNum++;
            }
        }
        return chunk;
    }

    var readers = {
        "(" : readList,
        "[" : readList,
        "{" : readList,
        ")" : error("Unexpected ')' in input"),
        "]" : error("Unexpected ']' in input"),
        "}" : error("Unexpected '}' in input"),
        "'" : readQuote("quote"),
        "`" : readQuote("quasiquote"),
        "," : readUnquote,
        ";" : readComment,
        "0" : readNum,
        "1" : readNum,
        "2" : readNum,
        "3" : readNum,
        "4" : readNum,
        "5" : readNum,
        "6" : readNum,
        "7" : readNum,
        "8" : readNum,
        "9" : readNum,
        '"' : readString,
        '#' : readSpecial,
        " " : skip,
        "\t": skip,
        "\n": newline,
        "\r": skip
    };

    function read(state) {
        while (state.i < state.l) {
            var chr = state.input.charAt(state.i);
            var reader = readers[chr];
            if (reader === skip) {
                state.i++;
                continue;
            } else if (reader) {
                return reader(state);
            } else {
                return readSymbol(state);
            }
        }
        return null;
    }

    function readAll(input) {
        var result = [];
        var state = {
            input: input,
            i: 0,
            l: input.length,
            lineNum: 1,
            charNum: 1,
            clone: function () {
                return util.copyProps(state, {});
            }
        };
        while (true) {
            var atom = read(state);
            if (atom === null) {
                return result;
            } else {
                result.push(atom);
            }
        }
    }

    return { read: readAll };

});<commit_msg>Ensure only valid forms can be constructed<commit_after>/*jshint bitwise: true, camelcase: true, curly: true, eqeqeq: true, forin: true, immed: true, indent: 4, latedef: true, newcap: true, noarg: true, noempty: true, nonew: true, regexp: true, undef: true, unused: true, strict: true*/
/*global define*/
define(["util"], function (util) {

    "use strict";

    function error(msg) {
        return function (state) {
            throw new Error(msg + " (line " + state.lineNum + ", char " + state.charNum + ")");
        };
    }

    function createForm(type, value, extras) {
        if (type !== "literal" && type !== "symbol" && type !== "list") {
            error("Form must be a literal, symbol, or list")(state);
        }
        var form = { type: type, value: value };
        return extras ? util.copyProps(form, extras) : form;
    }

    var skip = { toString: function () { return "{skip}"; } };

    function newline(state) {
        state.i++;
        state.lineNum++;
        state.charNum = 1;
        return read(state);
    }

    function readList(state) {
        var origState = state.clone();
        var openChr = state.input.charAt(state.i);
        var closeChr = openChr === "(" ? ")" :
                       openChr === "[" ? "]" :
                       openChr === "{" ? "}" :
                       error("Unknown list opener '" + openChr + "'")(state);
        var sym = openChr === "(" ? "paren" :
                  openChr === "[" ? "bracket" :
                  openChr === "{" ? "brace" : null;
        state.i++;
        var values = [];
        var closed = false;
        while (state.i < state.l) {
            if (state.input.charAt(state.i) === closeChr) {
                state.i++;
                closed = true;
                break;
            } else {
                var s = read(state);
                values.push(s);
            }
        }
        if (!closed) {
            error("Expected '" + closeChr + "'")(origState);
        }
        return createForm("list", values, { sym: sym });
    }

    function readNum(state) {
        var rxNum = /^[0-9\.]+$/;
        var rxHex = /^0x[0-9A-F]+$/i;
        var chunk = readToDelim(state);
        if (rxNum.test(chunk)) {
            return createForm("literal", parseFloat(chunk, 10));
        } else if (rxHex.test(chunk)) {
            return createForm("literal", parseInt(chunk, 16));
        } else {
            error("Invalid characters in number literal '" + chunk + "'")(state);
        }
    }

    function readString(state) {
        // TODO: escaping probably needs more work
        state.i++;
        var origState = state.clone();
        var chunk = "";
        var escaped = false;
        var input = state.input;
        while (state.i < state.l) {
            var chr = input.charAt(state.i);
            if (chr === '"' && !escaped) {
                break;
            }
            if (escaped || chr !== "\\") {
                chunk += chr;
                escaped = false;
            } else if (chr === "\\") {
                escaped = true;
            }
            state.i++;
            state.charNum++;
        }

        if (state.i === state.l) {
            error("Unclosed string")(origState);
        }
        state.i++;
        return createForm("literal", chunk);
    }

    function readSpecial(state) {
        var chunk = readToDelim(state);
        if (chunk === "#t") {
            return createForm("literal", true);
        } else if (chunk === "#f") {
            return createForm("literal", false);
        }
    }

    function readSymbol(state) {
        // TODO: this might be a tad too relaxed
        return createForm("symbol", readToDelim(state));
    }

    function readComment(state) {
        var text = "";
        state.i++;
        while (state.i < state.l) {
            var chr = state.input.charAt(state.i++);
            if (chr === "\n") {
                break;
            } else if (chr !== "\r") {
                text += chr;
            }
        }
        return createForm("list", [createForm("symbol", "comment"), text]);
    }

    function readQuote(type) {
        return function (state) {
            state.i++;
            var x = read(state);
            if (x === null) {
                error("Expected something for " + type.substring(0, type.length - 1) + "ing")(state);
            }
            return createForm("list", [createForm("symbol", type), x]);
        };
    }

    function readUnquote(state) {
        state.i++;
        var type = "unquote";
        if (state.input.charAt(state.i) === "@") {
            type += "-splicing";
            state.i++;
        }
        var x = read(state);
        if (x === null) {
            error("Expected something for " + (type === "unquote" ? "unquoting" : type))(state);
        }
        return createForm("list", [createForm("symbol", type), x]);
    }

    var delimiters = {
        "(" : true,
        "[" : true,
        "{" : true,
        ")" : true,
        "]" : true,
        "}" : true,
        '"' : true,
        "'" : true,
        "`" : true,
        "," : true,
        ";" : true,
        " " : true,
        "\t": true,
        "\r": true,
        "\n": true
    };

    function readToDelim(state) {
        var chunk = "";
        var input = state.input;
        while (state.i < state.l) {
            var chr = input.charAt(state.i);
            if (delimiters.hasOwnProperty(chr)) {
                break;
            } else {
                chunk += chr;
                state.i++;
                state.charNum++;
            }
        }
        return chunk;
    }

    var readers = {
        "(" : readList,
        "[" : readList,
        "{" : readList,
        ")" : error("Unexpected ')' in input"),
        "]" : error("Unexpected ']' in input"),
        "}" : error("Unexpected '}' in input"),
        "'" : readQuote("quote"),
        "`" : readQuote("quasiquote"),
        "," : readUnquote,
        ";" : readComment,
        "0" : readNum,
        "1" : readNum,
        "2" : readNum,
        "3" : readNum,
        "4" : readNum,
        "5" : readNum,
        "6" : readNum,
        "7" : readNum,
        "8" : readNum,
        "9" : readNum,
        '"' : readString,
        '#' : readSpecial,
        " " : skip,
        "\t": skip,
        "\n": newline,
        "\r": skip
    };

    function read(state) {
        while (state.i < state.l) {
            var chr = state.input.charAt(state.i);
            var reader = readers[chr];
            if (reader === skip) {
                state.i++;
                continue;
            } else if (reader) {
                return reader(state);
            } else {
                return readSymbol(state);
            }
        }
        return null;
    }

    function readAll(input) {
        var result = [];
        var state = {
            input: input,
            i: 0,
            l: input.length,
            lineNum: 1,
            charNum: 1,
            clone: function () {
                return util.copyProps(state, {});
            }
        };
        while (true) {
            var atom = read(state);
            if (atom === null) {
                return result;
            } else {
                result.push(atom);
            }
        }
    }

    return { read: readAll };

});<|endoftext|>


<commit_before>(function () {

    // Variables

    var service = new RecipeService();

    // Templates

    RecipeListView.prototype.template =  Handlebars.compile($("#recipe-list-tpl").html());
    RecipeView.prototype.template = Handlebars.compile($("#recipe-tpl").html());
    FavListView.prototype.template = Handlebars.compile($("#fav-list-tpl").html());

    // Helpers

    Handlebars.registerHelper('recipeImage', function(image_id) {
      var recipe_image = _.find(recipe_images, function(image){ return image.id == image_id; });
      var recipe_image_url = recipe_image ? recipe_image.source_url : null;
      return recipe_image_url ? '' : "";
    });

    Handlebars.registerHelper('recipeImageThumbnail', function(image_id) {
      var recipe_image = _.find(recipe_images, function(image){ return image.id == image_id; });
      var recipe_image_url = null;
      if(recipe_image && recipe_image.media_details.sizes.tinyfeatured){
        recipe_image_url = recipe_image.media_details.sizes.tinyfeatured.source_url;
      }else if (recipe_image && recipe_image.media_details.sizes.thumbnail){
        recipe_image_url = recipe_image.media_details.sizes.thumbnail.source_url;
      }
      return recipe_image_url ? '' : "";
    });

    Handlebars.registerHelper('recipeImageUrl', function(image_id) {
      var recipe = _.find(recipe_images, function(recipe){ return recipe.id == image_id; });
      var recipe_url = recipe ? recipe.source_url : null;
      return recipe_url ? recipe_url : "";
    });

    Handlebars.registerHelper('recipeFav', function(recipe_id) {
      return BVB.getFavSpanOnLoad(recipe_id);
    });

    // Routes

    service.initialize().done(function () {
      router.addRoute('', function() {
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        BVB.activateMenu("home");
        service.printRecipes();
      });

      router.addRoute('recipes/:id', function(id) {
          service.findById(parseInt(id)).done(function(recipe) {
            document.body.scrollTop = document.documentElement.scrollTop = 0;
            BVB.deactivateAllMenu();
            $('#content').html(new RecipeView(recipe).render().$el);
          });
      });

      router.addRoute('search', function() {
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        BVB.activateMenu("search");
        $('#content').load("search.html");
      });

      router.addRoute('about', function() {
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        BVB.activateMenu("about-me");
        $('#content').load("about.html");
      });

      router.addRoute('contact', function() {
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        BVB.activateMenu("contact");
        $('#content').load("contact.html");
      });

      router.addRoute('favs', function() {
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        BVB.activateMenu("favs");
        new FavListView(recipes).render().$el;
      });

      router.start();
    });

}());
<commit_msg>Set current page to 1 every time we click in home<commit_after>(function () {

    // Variables

    var service = new RecipeService();

    // Templates

    RecipeListView.prototype.template =  Handlebars.compile($("#recipe-list-tpl").html());
    RecipeView.prototype.template = Handlebars.compile($("#recipe-tpl").html());
    FavListView.prototype.template = Handlebars.compile($("#fav-list-tpl").html());

    // Helpers

    Handlebars.registerHelper('recipeImage', function(image_id) {
      var recipe_image = _.find(recipe_images, function(image){ return image.id == image_id; });
      var recipe_image_url = recipe_image ? recipe_image.source_url : null;
      return recipe_image_url ? '' : "";
    });

    Handlebars.registerHelper('recipeImageThumbnail', function(image_id) {
      var recipe_image = _.find(recipe_images, function(image){ return image.id == image_id; });
      var recipe_image_url = null;
      if(recipe_image && recipe_image.media_details.sizes.tinyfeatured){
        recipe_image_url = recipe_image.media_details.sizes.tinyfeatured.source_url;
      }else if (recipe_image && recipe_image.media_details.sizes.thumbnail){
        recipe_image_url = recipe_image.media_details.sizes.thumbnail.source_url;
      }
      return recipe_image_url ? '' : "";
    });

    Handlebars.registerHelper('recipeImageUrl', function(image_id) {
      var recipe = _.find(recipe_images, function(recipe){ return recipe.id == image_id; });
      var recipe_url = recipe ? recipe.source_url : null;
      return recipe_url ? recipe_url : "";
    });

    Handlebars.registerHelper('recipeFav', function(recipe_id) {
      return BVB.getFavSpanOnLoad(recipe_id);
    });

    // Routes

    service.initialize().done(function () {
      router.addRoute('', function() {
        current_page = 1;
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        BVB.activateMenu("home");
        service.printRecipes();
      });

      router.addRoute('recipes/:id', function(id) {
          service.findById(parseInt(id)).done(function(recipe) {
            document.body.scrollTop = document.documentElement.scrollTop = 0;
            BVB.deactivateAllMenu();
            $('#content').html(new RecipeView(recipe).render().$el);
          });
      });

      router.addRoute('search', function() {
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        BVB.activateMenu("search");
        $('#content').load("search.html");
      });

      router.addRoute('about', function() {
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        BVB.activateMenu("about-me");
        $('#content').load("about.html");
      });

      router.addRoute('contact', function() {
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        BVB.activateMenu("contact");
        $('#content').load("contact.html");
      });

      router.addRoute('favs', function() {
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        BVB.activateMenu("favs");
        new FavListView(recipes).render().$el;
      });

      router.start();
    });

}());
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import Waypoint from 'react-waypoint'

import { Wrapper, LoadingIndicator } from 'src/common-ui/components'
import * as actions from './actions'
import * as selectors from './selectors'
import ResultList from './components/ResultList'
import Overview from './components/Overview'
import PageResultItem from './components/PageResultItem'
import ResultsMessage from './components/ResultsMessage'

class OverviewContainer extends Component {
    static propTypes = {
        grabFocusOnMount: PropTypes.bool.isRequired,
        onInputChange: PropTypes.func.isRequired,
        onBottomReached: PropTypes.func.isRequired,
        isLoading: PropTypes.bool.isRequired,
        isNewSearchLoading: PropTypes.bool.isRequired,
        noResults: PropTypes.bool.isRequired,
        isBadTerm: PropTypes.bool.isRequired,
        showInitSearchMsg: PropTypes.bool.isRequired,
        searchResults: PropTypes.arrayOf(PropTypes.object).isRequired,
        totalResultCount: PropTypes.number.isRequired,
        shouldShowCount: PropTypes.bool.isRequired,
        needsWaypoint: PropTypes.bool.isRequired,
        handleTrashBtnClick: PropTypes.func.isRequired,
        handleToggleBm: PropTypes.func.isRequired,
    }

    componentDidMount() {
        if (this.props.grabFocusOnMount) {
            this.inputQueryEl.focus()
        }
    }

    setInputRef = element => {
        this.inputQueryEl = element
    }

    handleInputChange = event => {
        const input = event.target

        this.props.onInputChange(input.value)
    }

    renderResultItems() {
        const resultItems = this.props.searchResults.map((doc, i) => (
            <PageResultItem
                key={i}
                onTrashBtnClick={this.props.handleTrashBtnClick(doc.url)}
                onToggleBookmarkClick={this.props.handleToggleBm(doc.url, i)}
                {...doc}
            />
        ))

        // Insert waypoint at the end of results to trigger loading new items when
        // scrolling down
        if (this.props.needsWaypoint) {
            resultItems.push(
                <Waypoint
                    onEnter={this.props.onBottomReached}
                    key="waypoint"
                />,
            )
        }

        // Add loading spinner to the list end, if loading
        if (this.props.isLoading) {
            resultItems.push(<LoadingIndicator key="loading" />)
        }

        return resultItems
    }

    renderResults() {
        if (this.props.isBadTerm) {
            return (
                <ResultsMessage>
                    Your search terms are very vague, please try and use more
                    unique language
                </ResultsMessage>
            )
        }

        if (this.props.showInitSearchMsg) {
            return (
                <ResultsMessage>
                    You have not made any history yet.

First, you need to visit some websites or{' '}

                        import your existing history & bookmarks
                    .

                    Tip: Read the{' '}

                        quick tutorial
                    .

                </ResultsMessage>
            )
        }

        if (this.props.noResults) {
            return (
                <ResultsMessage>
                    No results found for this query. ¯\_(ツ)_/¯{' '}
                </ResultsMessage>
            )
        }

        if (this.props.isNewSearchLoading) {
            return (
                <ResultList>
                    <LoadingIndicator />
                </ResultList>
            )
        }

        // No issues; render out results list view
        return (
            <Wrapper>
                {this.props.shouldShowCount && (
                    <ResultsMessage small>
                        Found
                            {this.props.totalResultCount}
                        {' '}
                        results in your digital memory
                    </ResultsMessage>
                )}
                <ResultList>{this.renderResultItems()}</ResultList>
            </Wrapper>
        )
    }

    render() {
        return (
            <Overview
                {...this.props}
                setInputRef={this.setInputRef}
                onInputChange={this.handleInputChange}
            >
                {this.renderResults()}
            </Overview>
        )
    }
}

const mapStateToProps = state => ({
    isLoading: selectors.isLoading(state),
    isNewSearchLoading: selectors.isNewSearchLoading(state),
    currentQueryParams: selectors.currentQueryParams(state),
    noResults: selectors.noResults(state),
    isBadTerm: selectors.isBadTerm(state),
    searchResults: selectors.results(state),
    isDeleteConfShown: selectors.isDeleteConfShown(state),
    needsWaypoint: selectors.needsPagWaypoint(state),
    showFilter: selectors.showFilter(state),
    showOnlyBookmarks: selectors.showOnlyBookmarks(state),
    showInitSearchMsg: selectors.showInitSearchMsg(state),
    totalResultCount: selectors.totalResultCount(state),
    shouldShowCount: selectors.shouldShowCount(state),
})

const mapDispatchToProps = dispatch => ({
    ...bindActionCreators(
        {
            onInputChange: actions.setQuery,
            onStartDateChange: actions.setStartDate,
            onEndDateChange: actions.setEndDate,
            onBottomReached: actions.getMoreResults,
            hideDeleteConfirm: actions.hideDeleteConfirm,
            deleteDocs: actions.deleteDocs,
            onShowFilterChange: actions.showFilter,
            onShowOnlyBookmarksChange: actions.toggleBookmarkFilter,
        },
        dispatch,
    ),
    handleTrashBtnClick: url => event => {
        event.preventDefault()
        dispatch(actions.showDeleteConfirm(url))
    },
    handleToggleBm: (url, index) => event => {
        event.preventDefault()
        dispatch(actions.toggleBookmark(url, index))
    },
})

export default connect(mapStateToProps, mapDispatchToProps)(OverviewContainer)
<commit_msg>Move overview input event listener to mapDispatchToProps<commit_after>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import Waypoint from 'react-waypoint'

import { Wrapper, LoadingIndicator } from 'src/common-ui/components'
import * as actions from './actions'
import * as selectors from './selectors'
import ResultList from './components/ResultList'
import Overview from './components/Overview'
import PageResultItem from './components/PageResultItem'
import ResultsMessage from './components/ResultsMessage'

class OverviewContainer extends Component {
    static propTypes = {
        grabFocusOnMount: PropTypes.bool.isRequired,
        handleInputChange: PropTypes.func.isRequired,
        onBottomReached: PropTypes.func.isRequired,
        isLoading: PropTypes.bool.isRequired,
        isNewSearchLoading: PropTypes.bool.isRequired,
        noResults: PropTypes.bool.isRequired,
        isBadTerm: PropTypes.bool.isRequired,
        showInitSearchMsg: PropTypes.bool.isRequired,
        searchResults: PropTypes.arrayOf(PropTypes.object).isRequired,
        totalResultCount: PropTypes.number.isRequired,
        shouldShowCount: PropTypes.bool.isRequired,
        needsWaypoint: PropTypes.bool.isRequired,
        handleTrashBtnClick: PropTypes.func.isRequired,
        handleToggleBm: PropTypes.func.isRequired,
    }

    componentDidMount() {
        if (this.props.grabFocusOnMount) {
            this.inputQueryEl.focus()
        }
    }

    setInputRef = element => {
        this.inputQueryEl = element
    }

    renderResultItems() {
        const resultItems = this.props.searchResults.map((doc, i) => (
            <PageResultItem
                key={i}
                onTrashBtnClick={this.props.handleTrashBtnClick(doc.url)}
                onToggleBookmarkClick={this.props.handleToggleBm(doc.url, i)}
                {...doc}
            />
        ))

        // Insert waypoint at the end of results to trigger loading new items when
        // scrolling down
        if (this.props.needsWaypoint) {
            resultItems.push(
                <Waypoint
                    onEnter={this.props.onBottomReached}
                    key="waypoint"
                />,
            )
        }

        // Add loading spinner to the list end, if loading
        if (this.props.isLoading) {
            resultItems.push(<LoadingIndicator key="loading" />)
        }

        return resultItems
    }

    renderResults() {
        if (this.props.isBadTerm) {
            return (
                <ResultsMessage>
                    Your search terms are very vague, please try and use more
                    unique language
                </ResultsMessage>
            )
        }

        if (this.props.showInitSearchMsg) {
            return (
                <ResultsMessage>
                    You have not made any history yet.

First, you need to visit some websites or{' '}

                        import your existing history & bookmarks
                    .

                    Tip: Read the{' '}

                        quick tutorial
                    .

                </ResultsMessage>
            )
        }

        if (this.props.noResults) {
            return (
                <ResultsMessage>
                    No results found for this query. ¯\_(ツ)_/¯{' '}
                </ResultsMessage>
            )
        }

        if (this.props.isNewSearchLoading) {
            return (
                <ResultList>
                    <LoadingIndicator />
                </ResultList>
            )
        }

        // No issues; render out results list view
        return (
            <Wrapper>
                {this.props.shouldShowCount && (
                    <ResultsMessage small>
                        Found
                            {this.props.totalResultCount}
                        {' '}
                        results in your digital memory
                    </ResultsMessage>
                )}
                <ResultList>{this.renderResultItems()}</ResultList>
            </Wrapper>
        )
    }

    render() {
        return (
            <Overview
                {...this.props}
                setInputRef={this.setInputRef}
                onInputChange={this.props.handleInputChange}
            >
                {this.renderResults()}
            </Overview>
        )
    }
}

const mapStateToProps = state => ({
    isLoading: selectors.isLoading(state),
    isNewSearchLoading: selectors.isNewSearchLoading(state),
    currentQueryParams: selectors.currentQueryParams(state),
    noResults: selectors.noResults(state),
    isBadTerm: selectors.isBadTerm(state),
    searchResults: selectors.results(state),
    isDeleteConfShown: selectors.isDeleteConfShown(state),
    needsWaypoint: selectors.needsPagWaypoint(state),
    showFilter: selectors.showFilter(state),
    showOnlyBookmarks: selectors.showOnlyBookmarks(state),
    showInitSearchMsg: selectors.showInitSearchMsg(state),
    totalResultCount: selectors.totalResultCount(state),
    shouldShowCount: selectors.shouldShowCount(state),
})

const mapDispatchToProps = dispatch => ({
    ...bindActionCreators(
        {
            onStartDateChange: actions.setStartDate,
            onEndDateChange: actions.setEndDate,
            onBottomReached: actions.getMoreResults,
            hideDeleteConfirm: actions.hideDeleteConfirm,
            deleteDocs: actions.deleteDocs,
            onShowFilterChange: actions.showFilter,
            onShowOnlyBookmarksChange: actions.toggleBookmarkFilter,
        },
        dispatch,
    ),
    handleInputChange: event => {
        const input = event.target
        dispatch(actions.setQuery(input.value))
    },
    handleTrashBtnClick: url => event => {
        event.preventDefault()
        dispatch(actions.showDeleteConfirm(url))
    },
    handleToggleBm: (url, index) => event => {
        event.preventDefault()
        dispatch(actions.toggleBookmark(url, index))
    },
})

export default connect(mapStateToProps, mapDispatchToProps)(OverviewContainer)
<|endoftext|>


<commit_before>'use strict';
//===== Private =====

// helpers for more readable code
function invoke (fns) { fns.forEach(function (fn) { fn(); }); }
function isFunction (maybe) { return typeof maybe === 'function'; }
function isObjectOrFunction (maybe) {
  return maybe && (typeof maybe).match(/function|object/);
}

// ancillary promise handler group constructor
function ChainLink (onFulfilled, onRejected) {
  this.onFulfilled = isFunction(onFulfilled) ? onFulfilled : null;
  this.onRejected = isFunction(onRejected) ? onRejected : null;
  this.downstream = new Deferral();
}

// making sure only platform code is on the stack before running handlers
function Schedule () {
  var fns = [];
  this.add = fns.push.bind(fns);
  this.run = function() { setImmediate(function(){ invoke(fns); }); };
}

//----- Promise class -----

function $Promise () {
  this.state = 'pending';
  this.chains = [];
}

$Promise.prototype.then = function then (onFulfilled, onRejected) {
  var newChain = new ChainLink(onFulfilled, onRejected);
  this.chains.push(newChain);
  this.tryHandlers();
  return newChain.downstream.promise;
};

$Promise.prototype.tryHandlers = function tryHandlers () {
  if (this.state === 'pending') return;
  var chain, handler, schedule = new Schedule();
  while (this.chains.length) {
    chain = this.chains.shift();
    handler = this.state === 'resolved' ? chain.onFulfilled : chain.onRejected;
    if (handler) schedule.add(executes(handler, this.value, chain.downstream));
    else if (this.state === 'resolved') chain.downstream.resolve(this.value);
    else if (this.state === 'rejected') chain.downstream.reject(this.value);
  }
  schedule.run();
};

function executes (handler, value, downstream) {
  return function executing () {
    try {
      var x = handler(value);
      promiseResolutionProcedure(downstream, x);
    } catch (err) { downstream.reject(err); }
  };
}

function promiseResolutionProcedure (downstream, x) { // 2.3
  var promise = downstream.promise,
      resolve = downstream.resolve.bind(downstream),
      reject = downstream.reject.bind(downstream);
  if (promise === x) { // 2.3.1
    reject( new TypeError('handlers must not return current chain') );
  } else if ( x instanceof $Promise ) { // 2.3.2
    x.then( promiseResolutionProcedure.bind(downstream), reject );
  } else if ( isObjectOrFunction(x) ) { // 2.3.3
    try {
      var then = x.then; // 2.3.3.1
      if ( isFunction(then) ) { // 2.3.3.3
        var ran = 0; // 2.3.3.3.3
        try {
          then.call(
            x,
            function resolvePromise (y) {
              if (!ran++) promiseResolutionProcedure(downstream, y); // 2.3.3.3.1
            },
            function rejectPromise (y) { if (!ran++) reject(y); } // 2.3.3.3.2
          );
        } catch (errCallingThen) { if (!ran) reject(errCallingThen); } // 2.3.3.3.4
      } else resolve(x); // 2.3.3.4
    } catch (errAccessingThen) { reject(errAccessingThen); } // 2.3.3.2
  } else { // 2.3.4
    resolve(x);
  }
}

//----- Deferral class -----

function Deferral () {
  this.promise = new $Promise();
  this.resolve = this.resolve.bind(this);
  this.reject = this.reject.bind(this);
}

Deferral.prototype.settle = function settle (state, value) {
  if (this.promise.state === 'pending' && state.match(/resolved|rejected/) ) {
    this.promise.state = state;
    this.promise.value = value;
    this.promise.tryHandlers();
  }
  return this.promise;
};

Deferral.prototype.resolve = function resolve (value) {
  return this.settle('resolved', value);
};

Deferral.prototype.reject = function reject (reason) {
  return this.settle('rejected', reason);
};

//===== Public =====

// Library namespace & ES6-style constructor wrapper
function Pledge (executor) {
  if (!isFunction(executor)) throw new TypeError('Pledge takes a function');
  var deferral = Pledge.defer();
  executor( deferral.resolve.bind(deferral), deferral.reject.bind(deferral) );
  return deferral.promise;
}

// Deferral factory function
Pledge.defer = function defer () {
  return new Deferral();
};

//----- Library methods -----

// returns a new promise resolved with `value`
Pledge.resolved = function resolved (value) {
  return new Deferral().resolve(value);
};

// returns a new promise rejected with `reason`
Pledge.rejected = function rejected (reason) {
  return new Deferral().reject(reason);
};

// It's alive!
module.exports = Pledge;
<commit_msg>bind fix<commit_after>'use strict';
//===== Private =====

// helpers for more readable code
function invoke (fns) { fns.forEach(function (fn) { fn(); }); }
function isFunction (maybe) { return typeof maybe === 'function'; }
function isObjectOrFunction (maybe) {
  return maybe && (typeof maybe).match(/function|object/);
}

// ancillary promise handler group constructor
function ChainLink (onFulfilled, onRejected) {
  this.onFulfilled = isFunction(onFulfilled) ? onFulfilled : null;
  this.onRejected = isFunction(onRejected) ? onRejected : null;
  this.downstream = new Deferral();
}

// making sure only platform code is on the stack before running handlers
function Schedule () {
  var fns = [];
  this.add = fns.push.bind(fns);
  this.run = function() { setImmediate(function(){ invoke(fns); }); };
}

//----- Promise class -----

function $Promise () {
  this.state = 'pending';
  this.chains = [];
}

$Promise.prototype.then = function then (onFulfilled, onRejected) {
  var newChain = new ChainLink(onFulfilled, onRejected);
  this.chains.push(newChain);
  this.tryHandlers();
  return newChain.downstream.promise;
};

$Promise.prototype.tryHandlers = function tryHandlers () {
  if (this.state === 'pending') return;
  var chain, handler, schedule = new Schedule();
  while (this.chains.length) {
    chain = this.chains.shift();
    handler = this.state === 'resolved' ? chain.onFulfilled : chain.onRejected;
    if (handler) schedule.add(executes(handler, this.value, chain.downstream));
    else if (this.state === 'resolved') chain.downstream.resolve(this.value);
    else if (this.state === 'rejected') chain.downstream.reject(this.value);
  }
  schedule.run();
};

function executes (handler, value, downstream) {
  return function executing () {
    try {
      var x = handler(value);
      promiseResolutionProcedure(downstream, x);
    } catch (err) { downstream.reject(err); }
  };
}

function promiseResolutionProcedure (downstream, x) { // 2.3
  var promise = downstream.promise,
      resolve = downstream.resolve.bind(downstream),
      reject = downstream.reject.bind(downstream);
  if (promise === x) { // 2.3.1
    reject( new TypeError('handlers must not return current chain') );
  } else if ( x instanceof $Promise ) { // 2.3.2
    x.then( promiseResolutionProcedure.bind(null, downstream), reject );
  } else if ( isObjectOrFunction(x) ) { // 2.3.3
    try {
      var then = x.then; // 2.3.3.1
      if ( isFunction(then) ) { // 2.3.3.3
        var ran = 0; // 2.3.3.3.3
        try {
          then.call(
            x,
            function resolvePromise (y) {
              if (!ran++) promiseResolutionProcedure(downstream, y); // 2.3.3.3.1
            },
            function rejectPromise (y) { if (!ran++) reject(y); } // 2.3.3.3.2
          );
        } catch (errCallingThen) { if (!ran) reject(errCallingThen); } // 2.3.3.3.4
      } else resolve(x); // 2.3.3.4
    } catch (errAccessingThen) { reject(errAccessingThen); } // 2.3.3.2
  } else { // 2.3.4
    resolve(x);
  }
}

//----- Deferral class -----

function Deferral () {
  this.promise = new $Promise();
  this.resolve = this.resolve.bind(this);
  this.reject = this.reject.bind(this);
}

Deferral.prototype.settle = function settle (state, value) {
  if (this.promise.state === 'pending' && state.match(/resolved|rejected/) ) {
    this.promise.state = state;
    this.promise.value = value;
    this.promise.tryHandlers();
  }
  return this.promise;
};

Deferral.prototype.resolve = function resolve (value) {
  return this.settle('resolved', value);
};

Deferral.prototype.reject = function reject (reason) {
  return this.settle('rejected', reason);
};

//===== Public =====

// Library namespace & ES6-style constructor wrapper
function Pledge (executor) {
  if (!isFunction(executor)) throw new TypeError('Pledge takes a function');
  var deferral = Pledge.defer();
  executor( deferral.resolve.bind(deferral), deferral.reject.bind(deferral) );
  return deferral.promise;
}

// Deferral factory function
Pledge.defer = function defer () {
  return new Deferral();
};

//----- Library methods -----

// returns a new promise resolved with `value`
Pledge.resolved = function resolved (value) {
  return new Deferral().resolve(value);
};

// returns a new promise rejected with `reason`
Pledge.rejected = function rejected (reason) {
  return new Deferral().reject(reason);
};

// It's alive!
module.exports = Pledge;
<|endoftext|>


<commit_before>define([
  './mixin',
  './event-emitter',
], function (
  mixin,
  eventEmitter
) {
  'use strict';

  function validateInput(val, propName, propSchema) {
    var type = typeof val;
    if (propSchema.type && !(type === propSchema.type || type === 'undefined')) {
      throw 'Property ' + propName + ' is of type ' + propSchema.type +
          ' and can not be assigned a value of type ' + type;
    }
    if (propSchema.forbiddenValues && propSchema.forbiddenValues.indexOf(val) !== -1) {
      throw 'Property ' + propName + ' is not allowed to be ' + val;
    }
  }

  function makeSetter(propName, propSchema) {
    var evName = 'change:' + propName;

    var nDependers = (propSchema.dependers || []).length;
    if (nDependers > 0) {
      var oldDependerValues = new Array(nDependers);
      var dependerEvents = new Array(nDependers);
      for (var i = 0; i < nDependers; ++i) {
        dependerEvents[i] = 'change:' + propSchema.dependers[i];
      }
    }

    // TODO: make two different setter functions (in outer scope) and select (+bind)
    // one of the depending of whether the propery has any dependers
    return function setter(val) {
      // If there is a custom setter, use it to transform the value
      propSchema.setter && (val = propSchema.setter.call(this, val));

      // Input validation
      validateInput(val, propName, propSchema);

      for (var i = 0; i < nDependers; ++i) {
        oldDependerValues[i] = this[propSchema.dependers[i]];
      }

      var oldVal = this[propName];
      this.__private.properties[propName] = val;
      var newVal = this[propName];

      if (newVal === oldVal) { return; }

      this.emit(evName, [ val, oldVal, this ]);

      for (i = 0; i < nDependers; ++i) {
        var depender = propSchema.dependers[i];
        var newDependerValue = this[depender];
        var oldDependerValue = oldDependerValues[i];
        newDependerValue === oldDependerValue ||
            this.emit(dependerEvents[i], [ newDependerValue, oldDependerValue, this ]);
      }
    };
  }

  function makeGetter(propName, propSchema) {
    return propSchema.getter ?
        function getter() { return propSchema.getter.call(this, this.__private.properties[propName]); } :
        function getter() { return this.__private.properties[propName]; };
  }

  function Record(schema, properties) {
    Object.defineProperty(this, '__private', { writable: true, value: {}, });
    this.__private.properties = {};

    properties = properties || {};
    schema = schema || {};

    var property, propertySchema;
    var propertiesUnion = {};

    // Set up dependers and remove dependencies
    for (property in schema) {
      propertySchema = schema[property] = schema[property] || {};

      if (!propertySchema.dependencies) { continue; }

      while (propertySchema.dependencies.length) {
        var dependency = propertySchema.dependencies.pop();
        var dependencySchema = schema[dependency] = schema[dependency] || {};
        dependencySchema.dependers = dependencySchema.dependers || [];
        dependencySchema.dependers.push(property);
      }
      delete propertySchema.dependencies;
    }

    for (property in schema) {
      propertySchema = schema[property];
      typeof propertySchema === 'string' && (propertySchema = schema[property] = { type: propertySchema });

      var descriptor = {
        enumerable: true,
      };
      if (propertySchema.getter !== false) {
        descriptor.get = makeGetter(property, propertySchema);
      }
      if (propertySchema.setter !== false) {
        descriptor.set = makeSetter(property, propertySchema);
      }
      Object.defineProperty(this, property, descriptor);

      propertiesUnion[property] = propertySchema.defaultValue;
    }

    // Disabled, for now
    // Object.preventExtensions(this);

    for (property in properties) {
      if (!schema.hasOwnProperty(property)) {
        throw 'Cannot assign undeclared property ' + property;
      }
      propertiesUnion[property] = properties[property];
    }

    // Silently assign initial values
    for (property in propertiesUnion) {
      var val = propertiesUnion[property];
      schema[property].setter && (val = schema[property].setter.call(this, val));
      validateInput(val, property, schema[property]);
      this.__private.properties[property] = val;
    }
  }

  // toJSON() actually returns an object, which is a bit misleading. For compatibility reasons.
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON_behavior
  Record.prototype.toJSON = function () {
    return JSON.parse(this.toJSONString());
  };

  Record.prototype.toJSONString = Record.prototype.toString = function () {
    var obj = {};
    for (var property in this.__private.properties) {
      obj[property] = this[property];
    }
    return JSON.stringify(obj);
  };

  mixin(Record, eventEmitter);

  return Record;

});
<commit_msg>Rename private var "properties"->"values"<commit_after>define([
  './mixin',
  './event-emitter',
], function (
  mixin,
  eventEmitter
) {
  'use strict';

  function validateInput(val, propName, propSchema) {
    var type = typeof val;
    if (propSchema.type && !(type === propSchema.type || type === 'undefined')) {
      throw 'Property ' + propName + ' is of type ' + propSchema.type +
          ' and can not be assigned a value of type ' + type;
    }
    if (propSchema.forbiddenValues && propSchema.forbiddenValues.indexOf(val) !== -1) {
      throw 'Property ' + propName + ' is not allowed to be ' + val;
    }
  }

  function makeSetter(propName, propSchema) {
    var evName = 'change:' + propName;

    var nDependers = (propSchema.dependers || []).length;
    if (nDependers > 0) {
      var oldDependerValues = new Array(nDependers);
      var dependerEvents = new Array(nDependers);
      for (var i = 0; i < nDependers; ++i) {
        dependerEvents[i] = 'change:' + propSchema.dependers[i];
      }
    }

    // TODO: make two different setter functions (in outer scope) and select (+bind)
    // one of the depending of whether the propery has any dependers
    return function setter(val) {
      // If there is a custom setter, use it to transform the value
      propSchema.setter && (val = propSchema.setter.call(this, val));

      // Input validation
      validateInput(val, propName, propSchema);

      for (var i = 0; i < nDependers; ++i) {
        oldDependerValues[i] = this[propSchema.dependers[i]];
      }

      var oldVal = this[propName];
      this.__private.values[propName] = val;
      var newVal = this[propName];

      if (newVal === oldVal) { return; }

      this.emit(evName, [ val, oldVal, this ]);

      for (i = 0; i < nDependers; ++i) {
        var depender = propSchema.dependers[i];
        var newDependerValue = this[depender];
        var oldDependerValue = oldDependerValues[i];
        newDependerValue === oldDependerValue ||
            this.emit(dependerEvents[i], [ newDependerValue, oldDependerValue, this ]);
      }
    };
  }

  function makeGetter(propName, propSchema) {
    return propSchema.getter ?
        function getter() { return propSchema.getter.call(this, this.__private.values[propName]); } :
        function getter() { return this.__private.values[propName]; };
  }

  function Record(schema, values) {
    Object.defineProperty(this, '__private', { writable: true, value: {}, });
    this.__private.values = {};

    values = values || {};
    schema = schema || {};

    var property, propertySchema;
    var propertiesUnion = {};

    // Set up dependers and remove dependencies
    for (property in schema) {
      propertySchema = schema[property] = schema[property] || {};

      if (!propertySchema.dependencies) { continue; }

      while (propertySchema.dependencies.length) {
        var dependency = propertySchema.dependencies.pop();
        var dependencySchema = schema[dependency] = schema[dependency] || {};
        dependencySchema.dependers = dependencySchema.dependers || [];
        dependencySchema.dependers.push(property);
      }
      delete propertySchema.dependencies;
    }

    for (property in schema) {
      propertySchema = schema[property];
      typeof propertySchema === 'string' && (propertySchema = schema[property] = { type: propertySchema });

      var descriptor = {
        enumerable: true,
      };
      if (propertySchema.getter !== false) {
        descriptor.get = makeGetter(property, propertySchema);
      }
      if (propertySchema.setter !== false) {
        descriptor.set = makeSetter(property, propertySchema);
      }
      Object.defineProperty(this, property, descriptor);

      propertiesUnion[property] = propertySchema.defaultValue;
    }

    // Disabled, for now
    // Object.preventExtensions(this);

    for (property in values) {
      if (!schema.hasOwnProperty(property)) {
        throw 'Cannot assign undeclared property ' + property;
      }
      propertiesUnion[property] = values[property];
    }

    // Silently assign initial values
    for (property in propertiesUnion) {
      var val = propertiesUnion[property];
      schema[property].setter && (val = schema[property].setter.call(this, val));
      validateInput(val, property, schema[property]);
      this.__private.values[property] = val;
    }
  }

  // toJSON() actually returns an object, which is a bit misleading. For compatibility reasons.
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON_behavior
  Record.prototype.toJSON = function () {
    return JSON.parse(this.toJSONString());
  };

  Record.prototype.toJSONString = Record.prototype.toString = function () {
    var obj = {};
    for (var property in this.__private.values) {
      obj[property] = this[property];
    }
    return JSON.stringify(obj);
  };

  mixin(Record, eventEmitter);

  return Record;

});
<|endoftext|>


<commit_before>import React from 'react'
import styled from 'styled-components'
import { Flex, Text, Sheet } from '@hackclub/design-system'
import { theme } from './style'
import { ColoredTitle, Lead } from 'components/Content'
import Slide from 'react-reveal/Slide'

const Screen = styled(Flex).attrs({
  flexDirection: 'column',
  justify: 'center',
  bg: 'dark',
  py: [3, 4],
  px: 2,
  width: 1
})`
  background: url('/camp/landing.jpg');
  background-position: center;
  background-size: cover;
  max-width: 100vw;
  height: 100vh;
  position: relative;
  p {
    line-height: 1.125;
  }
`

const Content = styled(Sheet).attrs({
  align: 'center',
  maxWidth: 36,
  py: [3, 4, 5],
  px: [1, 4, 5]
})`
  background-color: rgba(0, 0, 0, 0.75);
  @supports (-webkit-backdrop-filter: none) or (backdrop-filter: none) {
    background-color: rgba(32, 34, 36, 0.875);
    -webkit-backdrop-filter: saturate(180%) blur(16px);
  }
`

export default () => (
  <Screen>
    <Slide top>
      <Content>
        <ColoredTitle
          colors={[
            theme.colors.primary,
            theme.colors.yellow,
            theme.colors.green
          ]}
          mb={0}
        >
          Hack Camp
        </ColoredTitle>
        <Lead
          color="white"
          fontSize={[3, 4, 5]}
          maxWidth={32}
          mt={[-1, -2]}
          mb={3}
          mx="auto"
        >
          A 1-week summer camp

to get started coding.
        </Lead>
        <Text color="smoke" fontSize={2} mb={3}>
          July 2019 · Grades 8–12 · San Francisco, CA
        </Text>
        <Text color="primary" fontSize={[3, 4]} bold={true}>
          Apply by May 26th for $75 off!
        </Text>
      </Content>
    </Slide>
  </Screen>
)
<commit_msg>Redesign deadline promo<commit_after>import React from 'react'
import styled from 'styled-components'
import { Box, Flex, Text, Sheet } from '@hackclub/design-system'
import { theme } from './style'
import { ColoredTitle, Lead } from 'components/Content'
import Slide from 'react-reveal/Slide'

const Screen = styled(Flex).attrs({
  flexDirection: 'column',
  justify: 'center',
  bg: 'dark',
  py: [3, 4],
  px: 2,
  width: 1
})`
  background: url('/camp/landing.jpg');
  background-position: center;
  background-size: cover;
  max-width: 100vw;
  height: 100vh;
  position: relative;
  p {
    line-height: 1.125;
  }
`

const Content = styled(Sheet).attrs({
  align: 'center',
  maxWidth: 36,
  py: [3, 4, 5],
  px: [1, 4, 5]
})`
  background-color: rgba(0, 0, 0, 0.75);
  @supports (-webkit-backdrop-filter: none) or (backdrop-filter: none) {
    background-color: rgba(32, 34, 36, 0.875);
    -webkit-backdrop-filter: saturate(180%) blur(16px);
  }
`

const Promo = styled(Box).attrs({
  bg: theme.colors.red,
  color: theme.colors.white,
  mt: [3, 4, 5],
  mb: [-3, -4, -5],
  mx: [-1, -4, -5],
  p: 2
})``

export default () => (
  <Screen>
    <Slide top>
      <Content>
        <ColoredTitle
          colors={[
            theme.colors.primary,
            theme.colors.yellow,
            theme.colors.green
          ]}
          mb={0}
        >
          Hack Camp
        </ColoredTitle>
        <Lead
          color="white"
          fontSize={[3, 4, 5]}
          maxWidth={32}
          mt={[-1, -2]}
          mb={3}
          mx="auto"
        >
          A 1-week summer camp

          to get started coding.
        </Lead>
        <Text color="smoke" fontSize={2} mb={3}>
          July 2019 · Grades 8–12 · San Francisco, CA
        </Text>
        <Promo>
          <Text fontSize={[2, 3]} bold>
            Apply by May 26th for $75 off!
          </Text>
        </Promo>
      </Content>
    </Slide>
  </Screen>
)
<|endoftext|>


<commit_before>$(function() {
	createGrid();

	var type = /(canvas|webgl)/.test(url.type) ? url.type : 'svg';
	var elem = document.getElementById('stage');
	var two = new Two({
		type: Two.Types[type],
		fullscreen: true,
		autostart: true
	}).appendTo(elem);
	var turtleShepherd = new TurtleShepherd();

	var stitches = two.makeGroup();
	var lines = two.makeGroup();
	var isJump = false;
	var dragged = false;
	var interpolate = false;
	var dist_min = 8;
	var dist_max = 10;

	// TODO: PAN/Zoom
	// TODO: plus pinch-to-zoom for mobile.

	/*

	var dy = 1;
	var scene_transform = {
		ticking: false,
		initX: 0,
		initY: 0,
		initScale: 1,
		x: 0,
		y: 0,
		scale: 1,
		center: {
			x: two.width / 2,
			y: two.height / 2
		},
		origin: {},
	};

	var zui = new ZUI(two);
	zui.addLimits(0.06, 8);*

	$(window).bind('mousewheel wheel', function(event) {
		var e = event.originalEvent;
		e.stopPropagation();
		e.preventDefault();
		dy = (e.wheelDeltaY || - e.deltaY) / 100;
		//zui.zoomBy(dy, e.clientX, e.clientY);

		zui.zoomBy(dy, (two.width / 2), (two.height / 2));

		scene_transform.scale = zui.scale;
		scene_transform.center.x = e.clientX;
		scene_transform.center.y = e.clientY;

		// Update canvas zoom. This will alter the X/Y coordinates by a bit.
		var matrix = zui.zoomSet(scene_transform.scale, (two.width / 2), (two.height / 2));

		// Pull the new X/Y coordinates out of ZUI and keep our stuff in sync.
		var offset = zui.updateOffset();
		scene_transform.x = offset.surfaceMatrix.elements[2];
		scene_transform.y = offset.surfaceMatrix.elements[5];

		var REL_SCALE = 1 / scene_transform.scale;
		var REL_X = Math.floor((-scene_transform.x + (two.width / 2)) / scene_transform.scale);
		var REL_Y = Math.floor((-scene_transform.y + (two.height / 2)) / scene_transform.scale);

	});
	*/
	var x, y, line, mouse = new Two.Vector(), randomness = 2;
	var lastPos = null;

	var addPoint = function(pos) {
		var point = two.makeCircle(pos.x, pos.y, 1.5);
		point.fill = '#000';
		stitches.add(point);
	};

	var addLine = function(pos1, pos2) {
		var line;
		var dist = Math.sqrt(
			Math.pow((pos1.x - pos2.x), 2) +
			Math.pow((pos1.y - pos2 .y ), 2)
		);

		if (isJump) {
			line = two.makeLine(pos1.x, pos1.y, pos2.x, pos2.y);
			line.noFill().stroke = '#f00';
			line.linewidth = 1;
			line.opacity = 0.5;
			lines.add(line);
			turtleShepherd.moveTo(pos1.x, -pos1.y, pos2.x, -pos2.y, false);
			toogleJump();
		} else {
				line = two.makeLine(pos1.x, pos1.y, pos2.x, pos2.y);
				line.noFill().stroke = '#333';
				line.linewidth = 2;
				lines.add(line);
				turtleShepherd.moveTo(pos1.x, -pos1.y, pos2.x, -pos2.y, true);
		}
	};

	var drag = function(e) {
			dragged = true;
			var pos =  new Two.Vector(e.clientX, e.clientY);
			if (lastPos) {
				var dist = Math.sqrt( Math.pow((lastPos.x - pos.x), 2) + Math.pow((lastPos.y - pos.y ), 2) );
				if  (dist > dist_max && interpolate) {
					p = lineInterpolate( lastPos, pos, dist_min );
					for (var i = 0; i < p.length-1; i++) {
						addPoint(p[i+1]);
						addLine(p[i],p[i+1]);
						lastPos = p[i+1];
					}
				} else if (dist > dist_min) {
					addPoint(pos);
					addLine(lastPos, pos);
					lastPos = pos;
				}
			} else {
				addPoint(pos);
				lastPos = pos;
			}
	};

	var dragEnd = function(e) {
		var pos =  new Two.Vector(e.clientX, e.clientY);
		if (!dragged) {
			if (lastPos) {
				var dist = Math.sqrt( Math.pow((lastPos.x - pos.x), 2) + Math.pow((lastPos.y - pos.y ), 2) );
				if  (dist > dist_max && interpolate) {
					p = lineInterpolate( lastPos, pos, dist_min );
					for (var i = 0; i < p.length-1; i++) {
						addPoint(p[i+1]);
						addLine(p[i],p[i+1]);
						lastPos = p[i+1];
					}
				} else {
					addLine(lastPos, pos);
					addPoint(pos);
					lastPos = pos;
				}
			} else {
				addPoint(pos);
				lastPos = pos;
			}
		}
		dragged = false;
		$(this)
		  .unbind('mousemove', drag)
		  .unbind('mouseup', dragEnd);
	};

	var touchDrag = function(e) {
		e.preventDefault();
		var touch = e.originalEvent.changedTouches[0];
		drag({
		  clientX: touch.pageX,
		  clientY: touch.pageY
		});
		return false;
	};

	var touchEnd = function(e) {
		e.preventDefault();
		var touch = e.originalEvent.changedTouches[0];
		dragEnd({
		  clientX: touch.pageX,
		  clientY: touch.pageY
		});
		$(this)
		  .unbind('touchmove', touchDrag)
		  .unbind('touchend', touchEnd);
		return false;
	};

	stage = $(two.renderer.domElement).parent();
	stage
	 .bind('mousedown', function(e) {
		$(this)
		.bind('mousemove', drag)
		.bind('mouseup', dragEnd);
	})
	 .bind('touchstart', function(e) {
		e.preventDefault();
		var touch = e.originalEvent.changedTouches[0];
		$(this)
			.bind('touchmove', touchDrag)
			.bind('touchend', touchEnd);
		return false;
	});

	var clear = function() {
		turtleShepherd.clear();
		two.clear();
		lines =  two.makeGroup();
		stitches = two.makeGroup();
		lastPos = null;
	};

	var undo = function() {
		turtleShepherd.undoStep();
		lines.remove(_.last(lines.children));
		stitches.remove(_.last(stitches.children));
	};

	// TODO: Save
	var save = function (format, name) {
		name = name || "noname";
		turtleShepherd.normalize();
		switch (format) {
			case "exp":
				expUintArr = turtleShepherd.toEXP();
    			blob = new Blob([expUintArr], {type: 'application/octet-stream'});
    			saveAs(blob, name + '.exp');
				break;
		    case "dst":
				expUintArr = turtleShepherd.toDST();
				blob = new Blob([expUintArr], {type: 'application/octet-stream'});
				saveAs(blob, name + '.dst');
				break;
			case "svg":
				svgStr = turtleShepherd.toSVG();
			    blob = new Blob([svgStr], {type: 'text/plain;charset=utf-8'});
				saveAs(blob, name + '.svg');
				break;
			default:
				return;
		}
	};

	$('#ts-do-save-form').submit( function(e) {
		//save("submit me");
		name = $(this).find('#projectname').val() || "unnamed";
		format =  $(this).find('input[name=format]').filter(':checked').val();
		save(format,name);
		save_dialog.close();
		return false;
	});

	$(window).bind('keydown', function(event) {
		if (event.key == 'j') {
			toogleJump();
		}
		if (event.key == 'i') {
			toogleInterpolate();
		}
		if (event.key == 'g') {
			toggleGrid();
		}
		if (event.key == 'b') {
			load_image_dialog.showModal();
		}
		if (event.key == 'z' && event.ctrlKey) {
			undo();
		}
	});

	var toogleJump = function() {
		isJump = !isJump;
		if (isJump) {
			$(".ts-jump").addClass("mdl-button--accent");
		} else {
			$(".ts-jump").removeClass("mdl-button--accent");
		}
	};

	var toogleInterpolate = function() {
		interpolate = !interpolate;
		if (interpolate) {
			$(".ts-interpolate").addClass("mdl-button--accent");
		} else {
			$(".ts-interpolate").removeClass("mdl-button--accent");
		}
	};

	var toogleGrid = function() {
		$("#grid").toggle();
		if ($(".grid").is(":visible"))
			$(".ts-grid i").html("grid_off");
		else
			$(".ts-grid i").html("grid_on");
	};

	$(".ts-clear").click ( function(e) {
		e.preventDefault();
		clear();
	});

	$(".ts-jump").click ( function(e) {
		toogleJump();
	});

	$(".ts-interpolate").click ( function(e) {
		toogleInterpolate();
	});

	$(".ts-undo").click ( function(e) {
		undo();
	});

	$(".ts-grid").click ( function(e) {
		toogleGrid();
	});

	function createGrid(s) {
		var size = s || 50;
		var two = new Two({
		  type: Two.Types.canvas, width: size, height: size
		});

		var a = two.makeLine(two.width / 2, 0, two.width / 2, two.height);
		var b = two.makeLine(0, two.height / 2, two.width, two.height / 2);
		a.stroke = b.stroke = '#6dcff6';

		two.update();

		_.defer(function() {
			$("#grid").css({
				background: 'url(' + two.renderer.domElement.toDataURL('image/png') + ') 0 0 repeat',
				backgroundSize: size + 'px ' + size + 'px'
			});
		});
	}

});
<commit_msg>change min max distance<commit_after>$(function() {
	createGrid();

	var type = /(canvas|webgl)/.test(url.type) ? url.type : 'svg';
	var elem = document.getElementById('stage');
	var two = new Two({
		type: Two.Types[type],
		fullscreen: true,
		autostart: true
	}).appendTo(elem);
	var turtleShepherd = new TurtleShepherd();

	var stitches = two.makeGroup();
	var lines = two.makeGroup();
	var isJump = false;
	var dragged = false;
	var interpolate = false;
	var dist_min = 9;
	var dist_max = 12;

	// TODO: PAN/Zoom
	// TODO: plus pinch-to-zoom for mobile.

	/*

	var dy = 1;
	var scene_transform = {
		ticking: false,
		initX: 0,
		initY: 0,
		initScale: 1,
		x: 0,
		y: 0,
		scale: 1,
		center: {
			x: two.width / 2,
			y: two.height / 2
		},
		origin: {},
	};

	var zui = new ZUI(two);
	zui.addLimits(0.06, 8);*

	$(window).bind('mousewheel wheel', function(event) {
		var e = event.originalEvent;
		e.stopPropagation();
		e.preventDefault();
		dy = (e.wheelDeltaY || - e.deltaY) / 100;
		//zui.zoomBy(dy, e.clientX, e.clientY);

		zui.zoomBy(dy, (two.width / 2), (two.height / 2));

		scene_transform.scale = zui.scale;
		scene_transform.center.x = e.clientX;
		scene_transform.center.y = e.clientY;

		// Update canvas zoom. This will alter the X/Y coordinates by a bit.
		var matrix = zui.zoomSet(scene_transform.scale, (two.width / 2), (two.height / 2));

		// Pull the new X/Y coordinates out of ZUI and keep our stuff in sync.
		var offset = zui.updateOffset();
		scene_transform.x = offset.surfaceMatrix.elements[2];
		scene_transform.y = offset.surfaceMatrix.elements[5];

		var REL_SCALE = 1 / scene_transform.scale;
		var REL_X = Math.floor((-scene_transform.x + (two.width / 2)) / scene_transform.scale);
		var REL_Y = Math.floor((-scene_transform.y + (two.height / 2)) / scene_transform.scale);

	});
	*/
	var x, y, line, mouse = new Two.Vector(), randomness = 2;
	var lastPos = null;

	var addPoint = function(pos) {
		var point = two.makeCircle(pos.x, pos.y, 1.5);
		point.fill = '#000';
		stitches.add(point);
	};

	var addLine = function(pos1, pos2) {
		var line;
		var dist = Math.sqrt(
			Math.pow((pos1.x - pos2.x), 2) +
			Math.pow((pos1.y - pos2 .y ), 2)
		);

		if (isJump) {
			line = two.makeLine(pos1.x, pos1.y, pos2.x, pos2.y);
			line.noFill().stroke = '#f00';
			line.linewidth = 1;
			line.opacity = 0.5;
			lines.add(line);
			turtleShepherd.moveTo(pos1.x, -pos1.y, pos2.x, -pos2.y, false);
			toogleJump();
		} else {
				line = two.makeLine(pos1.x, pos1.y, pos2.x, pos2.y);
				line.noFill().stroke = '#333';
				line.linewidth = 2;
				lines.add(line);
				turtleShepherd.moveTo(pos1.x, -pos1.y, pos2.x, -pos2.y, true);
		}
	};

	var drag = function(e) {
			dragged = true;
			var pos =  new Two.Vector(e.clientX, e.clientY);
			if (lastPos) {
				var dist = Math.sqrt( Math.pow((lastPos.x - pos.x), 2) + Math.pow((lastPos.y - pos.y ), 2) );
				if  (dist > dist_max && interpolate) {
					p = lineInterpolate( lastPos, pos, dist_min );
					for (var i = 0; i < p.length-1; i++) {
						addPoint(p[i+1]);
						addLine(p[i],p[i+1]);
						lastPos = p[i+1];
					}
				} else if (dist > dist_min) {
					addPoint(pos);
					addLine(lastPos, pos);
					lastPos = pos;
				}
			} else {
				addPoint(pos);
				lastPos = pos;
			}
	};

	var dragEnd = function(e) {
		var pos =  new Two.Vector(e.clientX, e.clientY);
		if (!dragged) {
			if (lastPos) {
				var dist = Math.sqrt( Math.pow((lastPos.x - pos.x), 2) + Math.pow((lastPos.y - pos.y ), 2) );
				if  (dist > dist_max && interpolate) {
					p = lineInterpolate( lastPos, pos, dist_min );
					for (var i = 0; i < p.length-1; i++) {
						addPoint(p[i+1]);
						addLine(p[i],p[i+1]);
						lastPos = p[i+1];
					}
				} else {
					addLine(lastPos, pos);
					addPoint(pos);
					lastPos = pos;
				}
			} else {
				addPoint(pos);
				lastPos = pos;
			}
		}
		dragged = false;
		$(this)
		  .unbind('mousemove', drag)
		  .unbind('mouseup', dragEnd);
	};

	var touchDrag = function(e) {
		e.preventDefault();
		var touch = e.originalEvent.changedTouches[0];
		drag({
		  clientX: touch.pageX,
		  clientY: touch.pageY
		});
		return false;
	};

	var touchEnd = function(e) {
		e.preventDefault();
		var touch = e.originalEvent.changedTouches[0];
		dragEnd({
		  clientX: touch.pageX,
		  clientY: touch.pageY
		});
		$(this)
		  .unbind('touchmove', touchDrag)
		  .unbind('touchend', touchEnd);
		return false;
	};

	stage = $(two.renderer.domElement).parent();
	stage
	 .bind('mousedown', function(e) {
		$(this)
		.bind('mousemove', drag)
		.bind('mouseup', dragEnd);
	})
	 .bind('touchstart', function(e) {
		e.preventDefault();
		var touch = e.originalEvent.changedTouches[0];
		$(this)
			.bind('touchmove', touchDrag)
			.bind('touchend', touchEnd);
		return false;
	});

	var clear = function() {
		turtleShepherd.clear();
		two.clear();
		lines =  two.makeGroup();
		stitches = two.makeGroup();
		lastPos = null;
	};

	var undo = function() {
		turtleShepherd.undoStep();
		lines.remove(_.last(lines.children));
		stitches.remove(_.last(stitches.children));
	};

	// TODO: Save
	var save = function (format, name) {
		name = name || "noname";
		turtleShepherd.normalize();
		switch (format) {
			case "exp":
				expUintArr = turtleShepherd.toEXP();
    			blob = new Blob([expUintArr], {type: 'application/octet-stream'});
    			saveAs(blob, name + '.exp');
				break;
		    case "dst":
				expUintArr = turtleShepherd.toDST();
				blob = new Blob([expUintArr], {type: 'application/octet-stream'});
				saveAs(blob, name + '.dst');
				break;
			case "svg":
				svgStr = turtleShepherd.toSVG();
			    blob = new Blob([svgStr], {type: 'text/plain;charset=utf-8'});
				saveAs(blob, name + '.svg');
				break;
			default:
				return;
		}
	};

	$('#ts-do-save-form').submit( function(e) {
		//save("submit me");
		name = $(this).find('#projectname').val() || "unnamed";
		format =  $(this).find('input[name=format]').filter(':checked').val();
		save(format,name);
		save_dialog.close();
		return false;
	});

	$(window).bind('keydown', function(event) {
		if (event.key == 'j') {
			toogleJump();
		}
		if (event.key == 'i') {
			toogleInterpolate();
		}
		if (event.key == 'g') {
			toggleGrid();
		}
		if (event.key == 'b') {
			load_image_dialog.showModal();
		}
		if (event.key == 'z' && event.ctrlKey) {
			undo();
		}
	});

	var toogleJump = function() {
		isJump = !isJump;
		if (isJump) {
			$(".ts-jump").addClass("mdl-button--accent");
		} else {
			$(".ts-jump").removeClass("mdl-button--accent");
		}
	};

	var toogleInterpolate = function() {
		interpolate = !interpolate;
		if (interpolate) {
			$(".ts-interpolate").addClass("mdl-button--accent");
		} else {
			$(".ts-interpolate").removeClass("mdl-button--accent");
		}
	};

	var toogleGrid = function() {
		$("#grid").toggle();
		if ($(".grid").is(":visible"))
			$(".ts-grid i").html("grid_off");
		else
			$(".ts-grid i").html("grid_on");
	};

	$(".ts-clear").click ( function(e) {
		e.preventDefault();
		clear();
	});

	$(".ts-jump").click ( function(e) {
		toogleJump();
	});

	$(".ts-interpolate").click ( function(e) {
		toogleInterpolate();
	});

	$(".ts-undo").click ( function(e) {
		undo();
	});

	$(".ts-grid").click ( function(e) {
		toogleGrid();
	});

	function createGrid(s) {
		var size = s || 50;
		var two = new Two({
		  type: Two.Types.canvas, width: size, height: size
		});

		var a = two.makeLine(two.width / 2, 0, two.width / 2, two.height);
		var b = two.makeLine(0, two.height / 2, two.width, two.height / 2);
		a.stroke = b.stroke = '#6dcff6';

		two.update();

		_.defer(function() {
			$("#grid").css({
				background: 'url(' + two.renderer.domElement.toDataURL('image/png') + ') 0 0 repeat',
				backgroundSize: size + 'px ' + size + 'px'
			});
		});
	}

});
<|endoftext|>


<commit_before>Minionette.Region = function(options) {
    this.cid = _.uniqueId('subview');
    this._ensureView();
    this._configure(options || {});
};

_.extend(Minionette.Region.prototype, Backbone.Events, {
    //TODO: Comments
    _View: Backbone.View.extend({tagName: 'span'}),

    _configure: function(options) {
        if (options.view) { this.view = options.view; }
    },

    _ensureView: function() {
        if (!this.view || !(this.view instanceof Backbone.View)) {
            this.view = new this._View();
        }
    },

    render: function() {
        return this.view.render();
    },

    attach: function(view, detach) {
        view = view || (new this._View());

        this.view.$el.after(view.$el).detach();

        if (!detach) {
            this.view.remove();
        }

        return this.view = view;
    },

    remove: function() {
        var v = this.view;

        this.attach();

        return v;
    },

    detach: function() {
        this._detachedView = this.view;
        this.attach(new this._View(), true);

        return this;
    },

    reattach: function() {
        var ret = this.attach(this._detachedView);
        delete this._detachedView;
        return ret;
    }
});
<commit_msg>Remove Region#_configure()<commit_after>Minionette.Region = function(options) {
    options || (options = {});
    this.cid = _.uniqueId('subview');
    if (options.view) { this.view = options.view; }
    this._ensureView();
};

_.extend(Minionette.Region.prototype, Backbone.Events, {
    //TODO: Comments
    _View: Backbone.View.extend({tagName: 'span'}),

    _ensureView: function() {
        if (!this.view || !(this.view instanceof Backbone.View)) {
            this.view = new this._View();
        }
    },

    render: function() {
        return this.view.render();
    },

    attach: function(view, detach) {
        view = view || (new this._View());

        this.view.$el.after(view.$el).detach();

        if (!detach) {
            this.view.remove();
        }

        return this.view = view;
    },

    remove: function() {
        var v = this.view;

        this.attach();

        return v;
    },

    detach: function() {
        this._detachedView = this.view;
        this.attach(new this._View(), true);

        return this;
    },

    reattach: function() {
        var ret = this.attach(this._detachedView);
        delete this._detachedView;
        return ret;
    }
});
<|endoftext|>


<commit_before>var $body;
var $content;
var $books_grid;

/*
 * Scroll to a place in the page.
 */
var smooth_scroll = function(offset_element, padding) {
    var elementOffset = offset_element.offset().top;
    var distance =  elementOffset - padding;
    $body.animate({ scrollTop: distance + "px" });
};

/*
 * Render a set of books into the grid.
 */
var load_books = function(book_list) {
    $books_grid.empty();

    _.each(book_list, function(element, index, list){
        $books_grid.append(JST.book_card({
            book: element,
            app_config: APP_CONFIG
        }));
    });
};

/*
 * Filter the list to a certain tag.
 */
var on_tag_clicked = function(){
    var tag = $(this).data('tag-slug');
    hasher.setHash(tag);
};

/*
 * Respond to url changes.
 */
var on_hash_changed = function(new_hash, old_hash){
    var book_list = BOOKS;

    if (new_hash != '') {
        book_list = _.filter(book_list, function(book){
            return book.tags.indexOf(new_hash) >= 0;
        });
    }

    load_books(book_list);

    smooth_scroll($content, 0);
};

$(function() {
    $body = $('body');
    $content = $('#content');
    $books_grid = $('#books-grid');

    // Event handlers.
    $content.on('click', 'button.tag', on_tag_clicked);

    hasher.changed.add(on_hash_changed);
    hasher.initialized.add(on_hash_changed);
    hasher.init();
});
<commit_msg>Tag hashes. Closes #72.<commit_after>var $body;
var $content;
var $books_grid;

/*
 * Scroll to a place in the page.
 */
var smooth_scroll = function(offset_element, padding) {
    var elementOffset = offset_element.offset().top;
    var distance =  elementOffset - padding;
    $body.animate({ scrollTop: distance + "px" });
};

/*
 * Render a set of books into the grid.
 */
var load_books = function(book_list) {
    $books_grid.empty();

    _.each(book_list, function(element, index, list){
        $books_grid.append(JST.book_card({
            book: element,
            app_config: APP_CONFIG
        }));
    });
};

/*
 * Filter the list to a certain tag.
 */
var on_tag_clicked = function() {
    var tag = $(this).data('tag-slug');
    hasher.setHash('tag/' + tag);

    return false;
};

/*
 * New tag hash url.
 */
var on_tag_hash = function(slug) {
    var book_list = BOOKS;

    if (slug != '') {
        book_list = _.filter(book_list, function(book) {
            return book.tags.indexOf(slug) >= 0;
        });
    }

    load_books(book_list);

    smooth_scroll($content, 0);

};

/*
 * New book hash url.
 */
var on_book_hash = function(slug) {
    // Clear current tag filter
    on_tag_hash('');

    console.log('jump to book: ' + slug);
}

/*
 * Respond to url changes.
 */
var on_hash_changed = function(new_hash, old_hash) {
    var bits = new_hash.split('/');
    var hash_type = bits[0];
    var hash_slug = bits[1];

    if (hash_type == 'tag') {
        on_tag_hash(hash_slug);
    } else if (hash_type == 'book') {
        on_book_hash(hash_slug);
    }

    return false;
};

$(function() {
    $body = $('body');
    $content = $('#content');
    $books_grid = $('#books-grid');

    // Event handlers.
    $content.on('click', 'button.tag', on_tag_clicked);

    hasher.changed.add(on_hash_changed);
    hasher.initialized.add(on_hash_changed);
    hasher.init();
});
<|endoftext|>