<commit_before>import last from 'lodash/last';
import isEmpty from 'lodash/isEmpty';

class AggregationPipeline {
  static stages = [
    'addFields',
    'bucket',
    'bucketAuto',
    'collStats',
    'count',
    'currentOp',
    'facet',
    'geoNear',
    'graphLookup',
    'group',
    'indexStats',
    'limit',
    'listLocalSessions',
    'listSessions',
    'lookup',
    'match',
    'out',
    'project',
    'redact',
    'replaceRoot',
    'sample',
    'skip',
    'sort',
    'sortByCount',
    'unwind',
  ];

  constructor(repository, pipeline = []) {
    this.repository = repository;
    this.pipeline = pipeline;
    // check if using Proxy is a better alternative
    this.constructor.stages.forEach(stage => {
      if (!this[stage]) {
        this[stage] = value => this.push({ [`$${stage}`]: value });
      }
    });
  }

  unshift(...operations) {
    operations.reverse().forEach(operation => {
      this.pipeline.unshift(...(Array.isArray(operation) ? operation : [operation]));
    });

    return this;
  }

  push(...operations) {
    operations.forEach(operation => {
      this.pipeline.push(...(Array.isArray(operation) ? operation : [operation]));
    });

    return this;
  }

  project = fields =>
    this.push({
      $project: Array.isArray(fields)
        ? fields.reduce(
            (acc, field) => ({
              ...acc,
              ...(typeof field === 'string' ? { [field]: 1 } : field),
            }),
            {},
          )
        : fields,
    });

  lookup = (relation, { as = relation, match = false, projectOne = true } = {}) => {
    if (typeof relation === 'object') {
      return this.push({
        $lookup: relation,
      });
    }

    const { type } = this.repository.getRelation(relation);

    this.push({
      $lookup: this.repository.relationToLookup(relation, { as }),
    });

    if (match) {
      this.match({
        [as]: {
          $elemMatch: match,
        },
      });
    }

    if (type === 'one' && projectOne) {
      this.addFields({
        [as]: {
          $arrayElemAt: [`$${as}`, 0],
        },
      });
    }

    return this;
  };

  sort = fields =>
    this.push({
      $sort: Object.keys(fields).reduce(
        (acc, field) => ({
          ...acc,
          [field]:
            fields[field] === 1 ||
            (typeof fields[field] === 'string' && fields[field].toLowerCase() === 'asc')
              ? 1
              : -1,
        }),
        {},
      ),
    });

  slice = (start, end) => this.push(this.skip(start), this.limit(end - start));

  count = (outputField = 'count') => this.push({ $count: outputField });

  toArray = () => {
    if (this.pipeline.length <= 1) {
      return this.pipeline;
    }

    // merge successive $match calls
    return this.pipeline.reduce((pipeline, next, index) => {
      // ignore empty $match calls
      if (next.$match && isEmpty(next.$match)) {
        return pipeline;
      }

      if (index === 0) {
        return [{ ...next }];
      }

      const current = last(pipeline);

      if (current.$match && next.$match) {
        current.$match = {
          $and: [
            ...(Array.isArray(current.$match.$and) ? current.$match.$and : [current.$match]),
            next.$match,
          ],
        };
      } else {
        pipeline.push({ ...next });
      }

      return pipeline;
    }, []);
  };

  toJSON = () => this.toArray();
}

export default AggregationPipeline;
<commit_msg>[oors-mongodb] a pipeline can merge into another one<commit_after>import last from 'lodash/last';
import isEmpty from 'lodash/isEmpty';

class AggregationPipeline {
  static stages = [
    'addFields',
    'bucket',
    'bucketAuto',
    'collStats',
    'count',
    'currentOp',
    'facet',
    'geoNear',
    'graphLookup',
    'group',
    'indexStats',
    'limit',
    'listLocalSessions',
    'listSessions',
    'lookup',
    'match',
    'out',
    'project',
    'redact',
    'replaceRoot',
    'sample',
    'skip',
    'sort',
    'sortByCount',
    'unwind',
  ];

  constructor(repository, pipeline = []) {
    this.repository = repository;
    this.pipeline = pipeline;
    // check if using Proxy is a better alternative
    this.constructor.stages.forEach(stage => {
      if (!this[stage]) {
        this[stage] = value => this.push({ [`$${stage}`]: value });
      }
    });
  }

  unshift(...operations) {
    operations.reverse().forEach(operation => {
      this.pipeline.unshift(...(Array.isArray(operation) ? operation : [operation]));
    });

    return this;
  }

  push(...operations) {
    operations.forEach(operation => {
      this.pipeline.push(...(Array.isArray(operation) ? operation : [operation]));
    });

    return this;
  }

  merge(pipeline) {
    return this.push(pipeline.toArray());
  }

  project = fields =>
    this.push({
      $project: Array.isArray(fields)
        ? fields.reduce(
            (acc, field) => ({
              ...acc,
              ...(typeof field === 'string' ? { [field]: 1 } : field),
            }),
            {},
          )
        : fields,
    });

  lookup = (relation, { as = relation, match = false, projectOne = true } = {}) => {
    if (typeof relation === 'object') {
      return this.push({
        $lookup: relation,
      });
    }

    const { type } = this.repository.getRelation(relation);

    this.push({
      $lookup: this.repository.relationToLookup(relation, { as }),
    });

    if (match) {
      this.match({
        [as]: {
          $elemMatch: match,
        },
      });
    }

    if (type === 'one' && projectOne) {
      this.addFields({
        [as]: {
          $arrayElemAt: [`$${as}`, 0],
        },
      });
    }

    return this;
  };

  sort = fields =>
    this.push({
      $sort: Object.keys(fields).reduce(
        (acc, field) => ({
          ...acc,
          [field]:
            fields[field] === 1 ||
            (typeof fields[field] === 'string' && fields[field].toLowerCase() === 'asc')
              ? 1
              : -1,
        }),
        {},
      ),
    });

  slice = (start, end) => this.push(this.skip(start), this.limit(end - start));

  count = (outputField = 'count') => this.push({ $count: outputField });

  toArray = () => {
    if (this.pipeline.length <= 1) {
      return this.pipeline;
    }

    // merge successive $match calls
    return this.pipeline.reduce((pipeline, next, index) => {
      // ignore empty $match calls
      if (next.$match && isEmpty(next.$match)) {
        return pipeline;
      }

      if (index === 0) {
        return [{ ...next }];
      }

      const current = last(pipeline);

      if (current.$match && next.$match) {
        current.$match = {
          $and: [
            ...(Array.isArray(current.$match.$and) ? current.$match.$and : [current.$match]),
            next.$match,
          ],
        };
      } else {
        pipeline.push({ ...next });
      }

      return pipeline;
    }, []);
  };

  toJSON = () => this.toArray();
}

export default AggregationPipeline;
<|endoftext|>


<commit_before>import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import { Button, Icon, Menu, MenuDivider, MenuItem, Popover, Position } from "@blueprintjs/core";
import styled from "styled-components";
import { version } from "../../../package.json";
import { ArchiveShape, ArchivesShape } from "../../shared/prop-types/archive.js";
import { VaultIcon } from "./VaultIcon";

const Container = styled.div`
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    padding: 0.5rem 0.5rem 0.25rem;
`;

class HeaderBar extends PureComponent {
    static propTypes = {
        archives: ArchivesShape,
        current: PropTypes.string,
        darkMode: PropTypes.bool,
        onItemsClick: PropTypes.func.isRequired,
        onVaultsClick: PropTypes.func.isRequired,
        onSettingsClick: PropTypes.func.isRequired,
        onAddVaultClick: PropTypes.func.isRequired,
        onUnlockVaultClick: PropTypes.func.isRequired,
        onLockAllClick: PropTypes.func.isRequired,
        onOtherSoftwareClick: PropTypes.func.isRequired,
        onAboutClick: PropTypes.func.isRequired,
        onToggleDarkMode: PropTypes.func.isRequired
    };

    handleItemsClick(event) {
        event.preventDefault();
        this.props.onItemsClick();
    }

    handleMenuClick(event) {
        event.preventDefault();
        this.props.onMenuClick();
    }

    handleVaultClick(vault) {
        this.props.onUnlockVaultClick(vault.id, vault.state);
    }

    render() {
        const { archives, location, darkMode } = this.props;
        const archiveMenu = (
            <Menu>
                <If condition={archives.length > 0}>
                    <MenuDivider title="Vaults:" />
                    <For each="vault" of={archives} index="index">
                        <MenuItem
                            icon={<VaultIcon vault={vault} />}
                            label={vault.status === "locked" ? <Icon icon="lock" /> : null}
                            text={vault.name}
                            key={index}
                            onClick={() => this.handleVaultClick(vault)}
                        />
                    </For>
                    <MenuDivider />
                </If>
                <MenuItem text="Add Vault" icon="add" onClick={::this.props.onAddVaultClick} />
                <MenuItem text="Lock All Vaults" icon="lock" onClick={::this.props.onLockAllClick} />
                <MenuItem icon="numbered-list" text="Manage Vaults" onClick={this.props.onVaultsClick} />
            </Menu>
        );
        const optionsMenu = (
            <Menu>
                <MenuItem text={`Buttercup v${version}`} icon="updated" disabled />
                <MenuItem
                    text={darkMode ? "Light theme" : "Dark theme"}
                    icon={darkMode ? "flash" : "moon"}
                    onClick={::this.props.onToggleDarkMode}
                />
                <MenuDivider />
                <MenuItem text="About Buttercup" icon="info-sign" onClick={::this.props.onAboutClick} />
                <MenuItem text="Other Applications" icon="mobile-phone" onClick={::this.props.onOtherSoftwareClick} />
                <MenuItem text="Settings" icon="cog" onClick={::this.props.onSettingsClick} />
            </Menu>
        );
        return (
            <Container>
                <Choose>
                    <When condition={location.pathname !== "/"}>
                        <Button text="Back" icon="arrow-left" onClick={::this.props.onItemsClick} />
                    </When>
                    <When condition={location.pathname === "/" && archives.length === 0}>
                        <Button icon="add" onClick={::this.props.onAddVaultClick} />
                    </When>
                    <Otherwise>
                        <Popover content={archiveMenu} position={Position.BOTTOM_LEFT}>
                            <Button icon="shield" rightIcon="caret-down" text={"Vaults"} />
                        </Popover>
                    </Otherwise>
                </Choose>
                <Popover content={optionsMenu} position={Position.BOTTOM_RIGHT}>
                    <Button icon="cog" minimal />
                </Popover>
            </Container>
        );
    }
}

export default HeaderBar;
<commit_msg>add max-height to vaults menu<commit_after>import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import { Button, Icon, Menu, MenuDivider, MenuItem, Popover, Position } from "@blueprintjs/core";
import styled from "styled-components";
import { version } from "../../../package.json";
import { ArchiveShape, ArchivesShape } from "../../shared/prop-types/archive.js";
import { VaultIcon } from "./VaultIcon";

const Container = styled.div`
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    padding: 0.5rem 0.5rem 0.25rem;
`;

const ArchiveMenu = styled(Menu)`
    max-height: 250px;
    overflow: auto;
`;

class HeaderBar extends PureComponent {
    static propTypes = {
        archives: ArchivesShape,
        current: PropTypes.string,
        darkMode: PropTypes.bool,
        onItemsClick: PropTypes.func.isRequired,
        onVaultsClick: PropTypes.func.isRequired,
        onSettingsClick: PropTypes.func.isRequired,
        onAddVaultClick: PropTypes.func.isRequired,
        onUnlockVaultClick: PropTypes.func.isRequired,
        onLockAllClick: PropTypes.func.isRequired,
        onOtherSoftwareClick: PropTypes.func.isRequired,
        onAboutClick: PropTypes.func.isRequired,
        onToggleDarkMode: PropTypes.func.isRequired
    };

    handleItemsClick(event) {
        event.preventDefault();
        this.props.onItemsClick();
    }

    handleMenuClick(event) {
        event.preventDefault();
        this.props.onMenuClick();
    }

    handleVaultClick(vault) {
        this.props.onUnlockVaultClick(vault.id, vault.state);
    }

    render() {
        const { archives, location, darkMode } = this.props;
        const archiveMenu = (
            <ArchiveMenu>
                <If condition={archives.length > 0}>
                    <MenuDivider title="Vaults:" />
                    <For each="vault" of={archives} index="index">
                        <MenuItem
                            icon={<VaultIcon vault={vault} />}
                            label={vault.status === "locked" ? <Icon icon="lock" /> : null}
                            text={vault.name}
                            key={index}
                            onClick={() => this.handleVaultClick(vault)}
                        />
                    </For>
                    <MenuDivider />
                </If>
                <MenuItem text="Add Vault" icon="add" onClick={::this.props.onAddVaultClick} />
                <MenuItem text="Lock All Vaults" icon="lock" onClick={::this.props.onLockAllClick} />
                <MenuItem icon="numbered-list" text="Manage Vaults" onClick={this.props.onVaultsClick} />
            </ArchiveMenu>
        );
        const optionsMenu = (
            <Menu>
                <MenuItem text={`Buttercup v${version}`} icon="updated" disabled />
                <MenuItem
                    text={darkMode ? "Light theme" : "Dark theme"}
                    icon={darkMode ? "flash" : "moon"}
                    onClick={::this.props.onToggleDarkMode}
                />
                <MenuDivider />
                <MenuItem text="About Buttercup" icon="info-sign" onClick={::this.props.onAboutClick} />
                <MenuItem text="Other Applications" icon="mobile-phone" onClick={::this.props.onOtherSoftwareClick} />
                <MenuItem text="Settings" icon="cog" onClick={::this.props.onSettingsClick} />
            </Menu>
        );
        return (
            <Container>
                <Choose>
                    <When condition={location.pathname !== "/"}>
                        <Button text="Back" icon="arrow-left" onClick={::this.props.onItemsClick} />
                    </When>
                    <When condition={location.pathname === "/" && archives.length === 0}>
                        <Button icon="add" onClick={::this.props.onAddVaultClick} />
                    </When>
                    <Otherwise>
                        <Popover content={archiveMenu} position={Position.BOTTOM_LEFT}>
                            <Button icon="shield" rightIcon="caret-down" text={"Vaults"} />
                        </Popover>
                    </Otherwise>
                </Choose>
                <Popover content={optionsMenu} position={Position.BOTTOM_RIGHT}>
                    <Button icon="cog" minimal />
                </Popover>
            </Container>
        );
    }
}

export default HeaderBar;
<|endoftext|>


<commit_before>YUI.add('moodle-course-dragdrop', function(Y) {

    var CSS = {
        ACTIVITY : 'activity',
        COMMANDSPAN : '.commands',
        CONTENT : 'content',
        COURSECONTENT : 'course-content',
        EDITINGMOVE : 'editing_move',
        ICONCLASS : 'iconsmall',
        JUMPMENU : 'jumpmenu',
        LEFT : 'left',
        LIGHTBOX : 'lightbox',
        MOVEDOWN : 'movedown',
        MOVEUP : 'moveup',
        PAGECONTENT : 'page-content',
        RIGHT : 'right',
        SECTION : 'section',
        SECTIONADDMENUS : 'section_add_menus',
        SECTIONHANDLE : 'section-handle',
        SUMMARY : 'summary'
    };

    var DRAGSECTION = function() {
        DRAGSECTION.superclass.constructor.apply(this, arguments);
    };
    Y.extend(DRAGSECTION, M.core.dragdrop, {
        sectionlistselector : null,

        initializer : function(params) {
            // Set group for parent class
            this.groups = ['section'];
            this.samenodeclass = M.course.format.get_sectionwrapperclass();
            this.parentnodeclass = M.course.format.get_containerclass();

            // Check if we are in single section mode
            if (Y.Node.one('.'+CSS.JUMPMENU)) {
                return false;
            }
            // Initialise sections dragging
            this.sectionlistselector = M.course.format.get_section_wrapper(Y);
            if (this.sectionlistselector) {
                this.sectionlistselector = '.'+CSS.COURSECONTENT+' '+this.sectionlistselector;
                this.setup_for_section(this.sectionlistselector);

                // Make each li element in the lists of sections draggable
                var nodeselector = this.sectionlistselector.slice(CSS.COURSECONTENT.length+2);
                var del = new Y.DD.Delegate({
                    container: '.'+CSS.COURSECONTENT,
                    nodes: nodeselector,
                    target: true,
                    handles: ['.'+CSS.LEFT],
                    dragConfig: {groups: this.groups}
                });
                del.dd.plug(Y.Plugin.DDProxy, {
                    // Don't move the node at the end of the drag
                    moveOnEnd: false
                });
                del.dd.plug(Y.Plugin.DDConstrained, {
                    // Keep it inside the .course-content
                    constrain: '#'+CSS.PAGECONTENT,
                    stickY: true
                });
                del.dd.plug(Y.Plugin.DDWinScroll);
            }
        },

         /**
         * Apply dragdrop features to the specified selector or node that refers to section(s)
         *
         * @param baseselector The CSS selector or node to limit scope to
         * @return void
         */
        setup_for_section : function(baseselector) {
            Y.Node.all(baseselector).each(function(sectionnode) {
                // Determine the section ID
                var sectionid = this.get_section_id(sectionnode);

                // We skip the top section as it is not draggable
                if (sectionid > 0) {
                    // Remove move icons
                    var movedown = sectionnode.one('.'+CSS.RIGHT+' a.'+CSS.MOVEDOWN);
                    var moveup = sectionnode.one('.'+CSS.RIGHT+' a.'+CSS.MOVEUP);

                    // Add dragger icon
                    var title = M.util.get_string('movesection', 'moodle', sectionid);
                    var cssleft = sectionnode.one('.'+CSS.LEFT);

                    if ((movedown || moveup) && cssleft) {
                        cssleft.setStyle('cursor', 'move');
                        cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true));

                        if (moveup) {
                            moveup.remove();
                        }
                        if (movedown) {
                            movedown.remove();
                        }
                    }
                }
            }, this);
        },

        get_section_id : function(node) {
            return Number(node.get('id').replace(/section-/i, ''));
        },

        /*
         * Drag-dropping related functions
         */
        drag_start : function(e) {
            // Get our drag object
            var drag = e.target;
            // Creat a dummy structure of the outer elemnents for clean styles application
            var containernode = Y.Node.create('<'+M.course.format.get_containernode()+'></'+M.course.format.get_containernode()+'>');
            containernode.addClass(M.course.format.get_containerclass());
            var sectionnode = Y.Node.create('<'+ M.course.format.get_sectionwrappernode()+'></'+ M.course.format.get_sectionwrappernode()+'>');
            sectionnode.addClass( M.course.format.get_sectionwrapperclass());
            sectionnode.setStyle('margin', 0);
            sectionnode.setContent(drag.get('node').get('innerHTML'));
            containernode.appendChild(sectionnode);
            drag.get('dragNode').setContent(containernode);
            drag.get('dragNode').addClass(CSS.COURSECONTENT);
        },

        drag_dropmiss : function(e) {
            // Missed the target, but we assume the user intended to drop it
            // on the last last ghost node location, e.drag and e.drop should be
            // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
            this.drop_hit(e);
        },

        drop_hit : function(e) {
            var drag = e.drag;
            // Get a reference to our drag node
            var dragnode = drag.get('node');
            var dropnode = e.drop.get('node');
            // Prepare some variables
            var dragnodeid = Number(this.get_section_id(dragnode));
            var dropnodeid = Number(this.get_section_id(dropnode));

            var loopstart = dragnodeid;
            var loopend = dropnodeid;

            if (this.goingup) {
                loopstart = dropnodeid;
                loopend = dragnodeid;
            }

            // Get the list of nodes
            drag.get('dragNode').removeClass(CSS.COURSECONTENT);
            var sectionlist = Y.Node.all(this.sectionlistselector);

            // Add lightbox if it not there
            var lightbox = M.util.add_lightbox(Y, dragnode);

            var params = {};

            // Handle any variables which we must pass back through to
            var pageparams = this.get('config').pageparams;
            for (varname in pageparams) {
                params[varname] = pageparams[varname];
            }

            // Prepare request parameters
            params.sesskey = M.cfg.sesskey;
            params.courseId = this.get('courseid');
            params['class'] = 'section';
            params.field = 'move';
            params.id = dragnodeid;
            params.value = dropnodeid;

            // Do AJAX request
            var uri = M.cfg.wwwroot + this.get('ajaxurl');

            Y.io(uri, {
                method: 'POST',
                data: params,
                on: {
                    start : function(tid) {
                        lightbox.show();
                    },
                    success: function(tid, response) {
                        // Update section titles, we can't simply swap them as
                        // they might have custom title
                        try {
                            var responsetext = Y.JSON.parse(response.responseText);
                            if (responsetext.error) {
                                new M.core.ajaxException(responsetext);
                            }
                            M.course.format.process_sections(Y, sectionlist, responsetext, loopstart, loopend);
                        } catch (e) {}

                        // Classic bubble sort algorithm is applied to the section
                        // nodes between original drag node location and the new one.
                        do {
                            var swapped = false;
                            for (var i = loopstart; i <= loopend; i++) {
                                if (this.get_section_id(sectionlist.item(i-1)) > this.get_section_id(sectionlist.item(i))) {
                                    // Swap section id
                                    var sectionid = sectionlist.item(i-1).get('id');
                                    sectionlist.item(i-1).set('id', sectionlist.item(i).get('id'));
                                    sectionlist.item(i).set('id', sectionid);
                                    // See what format needs to swap
                                    M.course.format.swap_sections(Y, i-1, i);
                                    // Update flag
                                    swapped = true;
                                }
                            }
                            loopend = loopend - 1;
                        } while (swapped);

                        // Finally, hide the lightbox
                        window.setTimeout(function(e) {
                            lightbox.hide();
                        }, 250);
                    },
                    failure: function(tid, response) {
                        this.ajax_failure(response);
                        lightbox.hide();
                    }
                },
                context:this
            });
        }

    }, {
        NAME : 'course-dragdrop-section',
        ATTRS : {
            courseid : {
                value : null
            },
            ajaxurl : {
                'value' : 0
            },
            config : {
                'value' : 0
            }
        }
    });

    var DRAGRESOURCE = function() {
        DRAGRESOURCE.superclass.constructor.apply(this, arguments);
    };
    Y.extend(DRAGRESOURCE, M.core.dragdrop, {
        initializer : function(params) {
            // Set group for parent class
            this.groups = ['resource'];
            this.samenodeclass = CSS.ACTIVITY;
            this.parentnodeclass = CSS.SECTION;
            this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS);

            // Go through all sections
            var sectionlistselector = M.course.format.get_section_selector(Y);
            if (sectionlistselector) {
                sectionlistselector = '.'+CSS.COURSECONTENT+' '+sectionlistselector;
                this.setup_for_section(sectionlistselector);

                // Initialise drag & drop for all resources/activities
                var nodeselector = sectionlistselector.slice(CSS.COURSECONTENT.length+2)+' li.'+CSS.ACTIVITY;
                var del = new Y.DD.Delegate({
                    container: '.'+CSS.COURSECONTENT,
                    nodes: nodeselector,
                    target: true,
                    handles: ['.' + CSS.EDITINGMOVE],
                    dragConfig: {groups: this.groups}
                });
                del.dd.plug(Y.Plugin.DDProxy, {
                    // Don't move the node at the end of the drag
                    moveOnEnd: false,
                    cloneNode: true
                });
                del.dd.plug(Y.Plugin.DDConstrained, {
                    // Keep it inside the .course-content
                    constrain: '#'+CSS.PAGECONTENT
                });
                del.dd.plug(Y.Plugin.DDWinScroll);

                M.course.coursebase.register_module(this);
                M.course.dragres = this;
            }
        },

         /**
         * Apply dragdrop features to the specified selector or node that refers to section(s)
         *
         * @param baseselector The CSS selector or node to limit scope to
         * @return void
         */
        setup_for_section : function(baseselector) {
            Y.Node.all(baseselector).each(function(sectionnode) {
                var resources = sectionnode.one('.'+CSS.CONTENT+' ul.'+CSS.SECTION);
                // See if resources ul exists, if not create one
                if (!resources) {
                    var resources = Y.Node.create('

');
                    resources.addClass(CSS.SECTION);
                    sectionnode.one('.'+CSS.CONTENT+' div.'+CSS.SUMMARY).insert(resources, 'after');
                }
                resources.setAttribute('data-draggroups', this.groups.join(' '));
                // Define empty ul as droptarget, so that item could be moved to empty list
                var tar = new Y.DD.Drop({
                    node: resources,
                    groups: this.groups,
                    padding: '20 0 20 0'
                });

                // Initialise each resource/activity in this section
                this.setup_for_resource('#'+sectionnode.get('id')+' li.'+CSS.ACTIVITY);
            }, this);
        },
        /**
         * Apply dragdrop features to the specified selector or node that refers to resource(s)
         *
         * @param baseselector The CSS selector or node to limit scope to
         * @return void
         */
        setup_for_resource : function(baseselector) {
            Y.Node.all(baseselector).each(function(resourcesnode) {
                // Replace move icons
                var move = resourcesnode.one('a.'+CSS.EDITINGMOVE);
                if (move) {
                    move.replace(this.resourcedraghandle.cloneNode(true));
                }
            }, this);
        },

        get_section_id : function(node) {
            return Number(node.get('id').replace(/section-/i, ''));
        },

        get_resource_id : function(node) {
            return Number(node.get('id').replace(/module-/i, ''));
        },

        drag_start : function(e) {
            // Get our drag object
            var drag = e.target;
            drag.get('dragNode').setContent(drag.get('node').get('innerHTML'));
            drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline');
        },

        drag_dropmiss : function(e) {
            // Missed the target, but we assume the user intended to drop it
            // on the last last ghost node location, e.drag and e.drop should be
            // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
            this.drop_hit(e);
        },

        drop_hit : function(e) {
            var drag = e.drag;
            // Get a reference to our drag node
            var dragnode = drag.get('node');
            var dropnode = e.drop.get('node');

            // Add spinner if it not there
            var spinner = M.util.add_spinner(Y, dragnode.one(CSS.COMMANDSPAN));

            var params = {};

            // Handle any variables which we must pass back through to
            var pageparams = this.get('config').pageparams;
            for (varname in pageparams) {
                params[varname] = pageparams[varname];
            }

            // Prepare request parameters
            params.sesskey = M.cfg.sesskey;
            params.courseId = this.get('courseid');
            params['class'] = 'resource';
            params.field = 'move';
            params.id = Number(this.get_resource_id(dragnode));
            params.sectionId = this.get_section_id(dropnode.ancestor(M.course.format.get_section_wrapper(Y), true));

            if (dragnode.next()) {
                params.beforeId = Number(this.get_resource_id(dragnode.next()));
            }

            // Do AJAX request
            var uri = M.cfg.wwwroot + this.get('ajaxurl');

            Y.io(uri, {
                method: 'POST',
                data: params,
                on: {
                    start : function(tid) {
                        this.lock_drag_handle(drag, CSS.EDITINGMOVE);
                        spinner.show();
                    },
                    success: function(tid, response) {
                        var responsetext = Y.JSON.parse(response.responseText);
                        var params = {element: dragnode, visible: responsetext.visible};
                        M.course.coursebase.invoke_function('set_visibility_resource_ui', params);
                        this.unlock_drag_handle(drag, CSS.EDITINGMOVE);
                        window.setTimeout(function(e) {
                            spinner.hide();
                        }, 250);
                    },
                    failure: function(tid, response) {
                        this.ajax_failure(response);
                        this.unlock_drag_handle(drag, CSS.SECTIONHANDLE);
                        spinner.hide();
                        // TODO: revert nodes location
                    }
                },
                context:this
            });
        }
    }, {
        NAME : 'course-dragdrop-resource',
        ATTRS : {
            courseid : {
                value : null
            },
            ajaxurl : {
                'value' : 0
            },
            config : {
                'value' : 0
            }
        }
    });

    M.course = M.course || {};
    M.course.init_resource_dragdrop = function(params) {
        new DRAGRESOURCE(params);
    }
    M.course.init_section_dragdrop = function(params) {
        new DRAGSECTION(params);
    }
}, '@VERSION@', {requires:['base', 'node', 'io', 'dom', 'dd', 'dd-scroll', 'moodle-core-dragdrop', 'moodle-core-notification', 'moodle-course-coursebase']});
<commit_msg>MDL-36465 Course JavaScript: Ensure that the zero section is not draggable<commit_after>YUI.add('moodle-course-dragdrop', function(Y) {

    var CSS = {
        ACTIVITY : 'activity',
        COMMANDSPAN : '.commands',
        CONTENT : 'content',
        COURSECONTENT : 'course-content',
        EDITINGMOVE : 'editing_move',
        ICONCLASS : 'iconsmall',
        JUMPMENU : 'jumpmenu',
        LEFT : 'left',
        LIGHTBOX : 'lightbox',
        MOVEDOWN : 'movedown',
        MOVEUP : 'moveup',
        PAGECONTENT : 'page-content',
        RIGHT : 'right',
        SECTION : 'section',
        SECTIONADDMENUS : 'section_add_menus',
        SECTIONHANDLE : 'section-handle',
        SUMMARY : 'summary',
        SECTIONDRAGGABLE: 'sectiondraggable'
    };

    var DRAGSECTION = function() {
        DRAGSECTION.superclass.constructor.apply(this, arguments);
    };
    Y.extend(DRAGSECTION, M.core.dragdrop, {
        sectionlistselector : null,

        initializer : function(params) {
            // Set group for parent class
            this.groups = [ CSS.SECTIONDRAGGABLE ];
            this.samenodeclass = M.course.format.get_sectionwrapperclass();
            this.parentnodeclass = M.course.format.get_containerclass();

            // Check if we are in single section mode
            if (Y.Node.one('.'+CSS.JUMPMENU)) {
                return false;
            }
            // Initialise sections dragging
            this.sectionlistselector = M.course.format.get_section_wrapper(Y);
            if (this.sectionlistselector) {
                this.sectionlistselector = '.'+CSS.COURSECONTENT+' '+this.sectionlistselector;

                this.setup_for_section(this.sectionlistselector);

                // Make each li element in the lists of sections draggable
                var del = new Y.DD.Delegate({
                    container: '.'+CSS.COURSECONTENT,
                    nodes: '.' + CSS.SECTIONDRAGGABLE,
                    target: true,
                    handles: ['.'+CSS.LEFT],
                    dragConfig: {groups: this.groups}
                });
                del.dd.plug(Y.Plugin.DDProxy, {
                    // Don't move the node at the end of the drag
                    moveOnEnd: false
                });
                del.dd.plug(Y.Plugin.DDConstrained, {
                    // Keep it inside the .course-content
                    constrain: '#'+CSS.PAGECONTENT,
                    stickY: true
                });
                del.dd.plug(Y.Plugin.DDWinScroll);
            }
        },

         /**
         * Apply dragdrop features to the specified selector or node that refers to section(s)
         *
         * @param baseselector The CSS selector or node to limit scope to
         * @return void
         */
        setup_for_section : function(baseselector) {
            Y.Node.all(baseselector).each(function(sectionnode) {
                // Determine the section ID
                var sectionid = this.get_section_id(sectionnode);

                // We skip the top section as it is not draggable
                if (sectionid > 0) {
                    // Remove move icons
                    var movedown = sectionnode.one('.'+CSS.RIGHT+' a.'+CSS.MOVEDOWN);
                    var moveup = sectionnode.one('.'+CSS.RIGHT+' a.'+CSS.MOVEUP);

                    // Add dragger icon
                    var title = M.util.get_string('movesection', 'moodle', sectionid);
                    var cssleft = sectionnode.one('.'+CSS.LEFT);

                    if ((movedown || moveup) && cssleft) {
                        cssleft.setStyle('cursor', 'move');
                        cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true));

                        if (moveup) {
                            moveup.remove();
                        }
                        if (movedown) {
                            movedown.remove();
                        }

                        // This section can be moved - add the class to indicate this to Y.DD.
                        sectionnode.addClass(CSS.SECTIONDRAGGABLE);
                    }
                }
            }, this);
        },

        get_section_id : function(node) {
            return Number(node.get('id').replace(/section-/i, ''));
        },

        /*
         * Drag-dropping related functions
         */
        drag_start : function(e) {
            // Get our drag object
            var drag = e.target;
            // Creat a dummy structure of the outer elemnents for clean styles application
            var containernode = Y.Node.create('<'+M.course.format.get_containernode()+'></'+M.course.format.get_containernode()+'>');
            containernode.addClass(M.course.format.get_containerclass());
            var sectionnode = Y.Node.create('<'+ M.course.format.get_sectionwrappernode()+'></'+ M.course.format.get_sectionwrappernode()+'>');
            sectionnode.addClass( M.course.format.get_sectionwrapperclass());
            sectionnode.setStyle('margin', 0);
            sectionnode.setContent(drag.get('node').get('innerHTML'));
            containernode.appendChild(sectionnode);
            drag.get('dragNode').setContent(containernode);
            drag.get('dragNode').addClass(CSS.COURSECONTENT);
        },

        drag_dropmiss : function(e) {
            // Missed the target, but we assume the user intended to drop it
            // on the last last ghost node location, e.drag and e.drop should be
            // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
            this.drop_hit(e);
        },

        drop_hit : function(e) {
            var drag = e.drag;
            // Get a reference to our drag node
            var dragnode = drag.get('node');
            var dropnode = e.drop.get('node');
            // Prepare some variables
            var dragnodeid = Number(this.get_section_id(dragnode));
            var dropnodeid = Number(this.get_section_id(dropnode));

            var loopstart = dragnodeid;
            var loopend = dropnodeid;

            if (this.goingup) {
                loopstart = dropnodeid;
                loopend = dragnodeid;
            }

            // Get the list of nodes
            drag.get('dragNode').removeClass(CSS.COURSECONTENT);
            var sectionlist = Y.Node.all(this.sectionlistselector);

            // Add lightbox if it not there
            var lightbox = M.util.add_lightbox(Y, dragnode);

            var params = {};

            // Handle any variables which we must pass back through to
            var pageparams = this.get('config').pageparams;
            for (varname in pageparams) {
                params[varname] = pageparams[varname];
            }

            // Prepare request parameters
            params.sesskey = M.cfg.sesskey;
            params.courseId = this.get('courseid');
            params['class'] = 'section';
            params.field = 'move';
            params.id = dragnodeid;
            params.value = dropnodeid;

            // Do AJAX request
            var uri = M.cfg.wwwroot + this.get('ajaxurl');

            Y.io(uri, {
                method: 'POST',
                data: params,
                on: {
                    start : function(tid) {
                        lightbox.show();
                    },
                    success: function(tid, response) {
                        // Update section titles, we can't simply swap them as
                        // they might have custom title
                        try {
                            var responsetext = Y.JSON.parse(response.responseText);
                            if (responsetext.error) {
                                new M.core.ajaxException(responsetext);
                            }
                            M.course.format.process_sections(Y, sectionlist, responsetext, loopstart, loopend);
                        } catch (e) {}

                        // Classic bubble sort algorithm is applied to the section
                        // nodes between original drag node location and the new one.
                        do {
                            var swapped = false;
                            for (var i = loopstart; i <= loopend; i++) {
                                if (this.get_section_id(sectionlist.item(i-1)) > this.get_section_id(sectionlist.item(i))) {
                                    // Swap section id
                                    var sectionid = sectionlist.item(i-1).get('id');
                                    sectionlist.item(i-1).set('id', sectionlist.item(i).get('id'));
                                    sectionlist.item(i).set('id', sectionid);
                                    // See what format needs to swap
                                    M.course.format.swap_sections(Y, i-1, i);
                                    // Update flag
                                    swapped = true;
                                }
                            }
                            loopend = loopend - 1;
                        } while (swapped);

                        // Finally, hide the lightbox
                        window.setTimeout(function(e) {
                            lightbox.hide();
                        }, 250);
                    },
                    failure: function(tid, response) {
                        this.ajax_failure(response);
                        lightbox.hide();
                    }
                },
                context:this
            });
        }

    }, {
        NAME : 'course-dragdrop-section',
        ATTRS : {
            courseid : {
                value : null
            },
            ajaxurl : {
                'value' : 0
            },
            config : {
                'value' : 0
            }
        }
    });

    var DRAGRESOURCE = function() {
        DRAGRESOURCE.superclass.constructor.apply(this, arguments);
    };
    Y.extend(DRAGRESOURCE, M.core.dragdrop, {
        initializer : function(params) {
            // Set group for parent class
            this.groups = ['resource'];
            this.samenodeclass = CSS.ACTIVITY;
            this.parentnodeclass = CSS.SECTION;
            this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS);

            // Go through all sections
            var sectionlistselector = M.course.format.get_section_selector(Y);
            if (sectionlistselector) {
                sectionlistselector = '.'+CSS.COURSECONTENT+' '+sectionlistselector;
                this.setup_for_section(sectionlistselector);

                // Initialise drag & drop for all resources/activities
                var nodeselector = sectionlistselector.slice(CSS.COURSECONTENT.length+2)+' li.'+CSS.ACTIVITY;
                var del = new Y.DD.Delegate({
                    container: '.'+CSS.COURSECONTENT,
                    nodes: nodeselector,
                    target: true,
                    handles: ['.' + CSS.EDITINGMOVE],
                    dragConfig: {groups: this.groups}
                });
                del.dd.plug(Y.Plugin.DDProxy, {
                    // Don't move the node at the end of the drag
                    moveOnEnd: false,
                    cloneNode: true
                });
                del.dd.plug(Y.Plugin.DDConstrained, {
                    // Keep it inside the .course-content
                    constrain: '#'+CSS.PAGECONTENT
                });
                del.dd.plug(Y.Plugin.DDWinScroll);

                M.course.coursebase.register_module(this);
                M.course.dragres = this;
            }
        },

         /**
         * Apply dragdrop features to the specified selector or node that refers to section(s)
         *
         * @param baseselector The CSS selector or node to limit scope to
         * @return void
         */
        setup_for_section : function(baseselector) {
            Y.Node.all(baseselector).each(function(sectionnode) {
                var resources = sectionnode.one('.'+CSS.CONTENT+' ul.'+CSS.SECTION);
                // See if resources ul exists, if not create one
                if (!resources) {
                    var resources = Y.Node.create('

');
                    resources.addClass(CSS.SECTION);
                    sectionnode.one('.'+CSS.CONTENT+' div.'+CSS.SUMMARY).insert(resources, 'after');
                }
                resources.setAttribute('data-draggroups', this.groups.join(' '));
                // Define empty ul as droptarget, so that item could be moved to empty list
                var tar = new Y.DD.Drop({
                    node: resources,
                    groups: this.groups,
                    padding: '20 0 20 0'
                });

                // Initialise each resource/activity in this section
                this.setup_for_resource('#'+sectionnode.get('id')+' li.'+CSS.ACTIVITY);
            }, this);
        },
        /**
         * Apply dragdrop features to the specified selector or node that refers to resource(s)
         *
         * @param baseselector The CSS selector or node to limit scope to
         * @return void
         */
        setup_for_resource : function(baseselector) {
            Y.Node.all(baseselector).each(function(resourcesnode) {
                // Replace move icons
                var move = resourcesnode.one('a.'+CSS.EDITINGMOVE);
                if (move) {
                    move.replace(this.resourcedraghandle.cloneNode(true));
                }
            }, this);
        },

        get_section_id : function(node) {
            return Number(node.get('id').replace(/section-/i, ''));
        },

        get_resource_id : function(node) {
            return Number(node.get('id').replace(/module-/i, ''));
        },

        drag_start : function(e) {
            // Get our drag object
            var drag = e.target;
            drag.get('dragNode').setContent(drag.get('node').get('innerHTML'));
            drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline');
        },

        drag_dropmiss : function(e) {
            // Missed the target, but we assume the user intended to drop it
            // on the last last ghost node location, e.drag and e.drop should be
            // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
            this.drop_hit(e);
        },

        drop_hit : function(e) {
            var drag = e.drag;
            // Get a reference to our drag node
            var dragnode = drag.get('node');
            var dropnode = e.drop.get('node');

            // Add spinner if it not there
            var spinner = M.util.add_spinner(Y, dragnode.one(CSS.COMMANDSPAN));

            var params = {};

            // Handle any variables which we must pass back through to
            var pageparams = this.get('config').pageparams;
            for (varname in pageparams) {
                params[varname] = pageparams[varname];
            }

            // Prepare request parameters
            params.sesskey = M.cfg.sesskey;
            params.courseId = this.get('courseid');
            params['class'] = 'resource';
            params.field = 'move';
            params.id = Number(this.get_resource_id(dragnode));
            params.sectionId = this.get_section_id(dropnode.ancestor(M.course.format.get_section_wrapper(Y), true));

            if (dragnode.next()) {
                params.beforeId = Number(this.get_resource_id(dragnode.next()));
            }

            // Do AJAX request
            var uri = M.cfg.wwwroot + this.get('ajaxurl');

            Y.io(uri, {
                method: 'POST',
                data: params,
                on: {
                    start : function(tid) {
                        this.lock_drag_handle(drag, CSS.EDITINGMOVE);
                        spinner.show();
                    },
                    success: function(tid, response) {
                        var responsetext = Y.JSON.parse(response.responseText);
                        var params = {element: dragnode, visible: responsetext.visible};
                        M.course.coursebase.invoke_function('set_visibility_resource_ui', params);
                        this.unlock_drag_handle(drag, CSS.EDITINGMOVE);
                        window.setTimeout(function(e) {
                            spinner.hide();
                        }, 250);
                    },
                    failure: function(tid, response) {
                        this.ajax_failure(response);
                        this.unlock_drag_handle(drag, CSS.SECTIONHANDLE);
                        spinner.hide();
                        // TODO: revert nodes location
                    }
                },
                context:this
            });
        }
    }, {
        NAME : 'course-dragdrop-resource',
        ATTRS : {
            courseid : {
                value : null
            },
            ajaxurl : {
                'value' : 0
            },
            config : {
                'value' : 0
            }
        }
    });

    M.course = M.course || {};
    M.course.init_resource_dragdrop = function(params) {
        new DRAGRESOURCE(params);
    }
    M.course.init_section_dragdrop = function(params) {
        new DRAGSECTION(params);
    }
}, '@VERSION@', {requires:['base', 'node', 'io', 'dom', 'dd', 'dd-scroll', 'moodle-core-dragdrop', 'moodle-core-notification', 'moodle-course-coursebase']});
<|endoftext|>


<commit_before>const { stripIndent, source } = require('common-tags')
const { WHITESPACE } = require('../constants')

function template4({ profile, schools, jobs, projects, skills, awards }) {
  return stripIndent`
    ${generateCommentHeader()}
    \\documentclass[]{deedy-resume-openfont}

    \\begin{document}
    ${generateProfileSection(profile)}
    ${generateEducationSection(schools)}
    ${generateExperienceSection(jobs)}
    ${generateSkillsSection(skills)}
    ${generateProjectsSection(projects)}
    ${generateAwardsSection(awards)}
    ${WHITESPACE}
    \\end{document}
  `
}

function generateProfileSection(profile) {
  if (!profile) {
    return '\\namesection{Your}{Name}{}'
  }

  const { fullName, email, phoneNumber, address, link } = profile

  let nameStart = ''
  let nameEnd = ''

  if (fullName) {
    const names = fullName.split(' ')

    if (names.length === 1) {
      nameStart = names[0]
      nameEnd = ''
    } else {
      nameStart = names[0]
      nameEnd = names.slice(1, names.length).join(' ')
    }
  }

  const info = [email, phoneNumber, address, link].filter(Boolean).join(' | ')

  const sectionHeader = stripIndent`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    %     Profile
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  `

  if (!fullName) {
    return stripIndent`
      ${sectionHeader}
      \\centering{
        \\color{headings}
        \\fontspec[Path = fonts/raleway/]{Raleway-Medium}
        \\fontsize{11pt}{14pt}
        \\selectfont ${info}
      }
    `
  }

  return stripIndent`
    ${sectionHeader}
    \\namesection{${nameStart}}{${nameEnd}}{${info}}
  `
}

function generateEducationSection(schools) {
  if (!schools) {
    return ''
  }

  return source`
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %     Education
  %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  \\section{Education}
  \\raggedright
  ${schools.map(school => {
    const { name, location, degree, major, gpa, graduationDate } = school

    let line1 = ''
    let line2 = ''

    if (name) {
      line1 += `\\runsubsection{${name}}`
    }

    if (degree && major) {
      line1 += `\\descript{| ${degree} ${major}}`
    } else if (degree) {
      line1 += `\\descript{| ${degree}}`
    } else if (major) {
      line1 += `\\descript{| ${major}}`
    }

    const locationAndDate = [location, graduationDate]
      .filter(Boolean)
      .join(' | ')

    if (locationAndDate) {
      line1 += `\\hfill \\location{${locationAndDate}}`
    }

    if (line1) {
      line1 += '\\\\'
    }

    if (gpa) {
      line2 += `GPA: ${gpa}\\\\`
    }

    return `
      ${line1}
      ${line2}
      \\sectionsep
    `
  })}
  `
}

function generateExperienceSection(jobs) {
  if (!jobs) {
    return ''
  }

  return source`
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %     Experience
  %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  \\section{Experience}
  ${jobs.map(job => {
    const { name, title, location, startDate, endDate, duties } = job

    let line1 = ''
    let dateRange = ''
    let dutyLines = ''

    if (name) {
      line1 += `\\runsubsection{${name}}`
    }

    if (title) {
      line1 += `\\descript{| ${title}}`
    }

    if (startDate && endDate) {
      dateRange = `${startDate} – ${endDate}`
    } else if (startDate) {
      dateRange = `${startDate} – Present`
    } else {
      dateRange = endDate
    }

    if (location && dateRange) {
      line1 += `\\hfill \\location{${location} | ${dateRange}}`
    } else if (location) {
      line1 += `\\hfill \\location{${location}}`
    } else if (dateRange) {
      line1 += `\\hfill \\location{${dateRange}}`
    }

    if (duties) {
      dutyLines = source`
        \\begin{tightemize}
          ${duties.map(duty => `\\item ${duty}`)}
        \\end{tightemize}
        `
    }

    return stripIndent`
      ${line1}
      ${dutyLines}
      \\sectionsep
    `
  })}
  `
}

function generateSkillsSection(skills) {
  if (!skills) {
    return ''
  }

  return source`
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %     Skills
  %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  \\section{Skills}
  \\raggedright
  \\begin{tabular}{ l l }
  ${skills.map(
    skill => `\\descript{${skill.name}} & {\\location{${skill.details}}} \\\\`
  )}
  \\end{tabular}
  \\sectionsep
  `
}

function generateProjectsSection(projects) {
  if (!projects) {
    return ''
  }

  return source`
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %     Projects
  %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  \\section{Projects}
  \\raggedright
  ${projects.map(project => {
    const { name, description, technologies, link } = project

    let line1 = ''
    let line2 = ''
    let line3 = ''

    if (name) {
      line1 += `\\runsubsection{\\large{${name}}}`
    }

    if (technologies) {
      line2 += `\\descript{| ${technologies}}`
    }

    if (link) {
      line2 += `\\hfill \\location{${link}}`
    }

    if (line2) {
      line2 += '\\\\'
    }

    if (description) {
      line3 += `${description}\\\\`
    }

    return `
      ${line1}
      ${line2}
      ${line3}
      \\sectionsep
    `
  })}
  `
}

function generateCommentHeader() {
  return stripIndent`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % This is a modified ONE COLUMN version of
    % the following template:
    %
    % Deedy - One Page Two Column Resume
    % LaTeX Template
    % Version 1.1 (30/4/2014)
    %
    % Original author:
    % Debarghya Das (http://debarghyadas.com)
    %
    % Original repository:
    % https://github.com/deedydas/Deedy-Resume
    %
    % IMPORTANT: THIS TEMPLATE NEEDS TO BE COMPILED WITH XeLaTeX
    %
    % This template uses several fonts not included with Windows/Linux by
    % default. If you get compilation errors saying a font is missing, find the line
    % on which the font is used and either change it to a font included with your
    % operating system or comment the line out to use the default font.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    % TODO:
    % 1. Integrate biber/bibtex for article citation under publications.
    % 2. Figure out a smoother way for the document to flow onto the next page.
    % 3. Add styling information for a "Projects/Hacks" section.
    % 4. Add location/address information
    % 5. Merge OpenFont and MacFonts as a single sty with options.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    % CHANGELOG:
    % v1.1:
    % 1. Fixed several compilation bugs with \\renewcommand
    % 2. Got Open-source fonts (Windows/Linux support)
    % 3. Added Last Updated
    % 4. Move Title styling into .sty
    % 5. Commented .sty file.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    % Known Issues:
    % 1. Overflows onto second page if any column's contents are more than the
    % vertical limit
    % 2. Hacky space on the first bullet point on the second column.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  `
}

function generateAwardsSection(awards) {
  if (!awards) {
    return ''
  }

  return source`
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %     Awards
  %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  \\section{Awards}
  ${awards.map(award => {
    const { name, details, date, location } = award
    const info = [date, location].filter(Boolean).join(' | ')

    return stripIndent`
      \\runsubsection{\\large{${name || ''}}} \\descript{${info}} \\\\
      ${details ? `${details}\\\\` : ''}
      \\sectionsep
    `
  })}
  `
}

module.exports = template4
<commit_msg>finish template4<commit_after>/**
 * @flow
 */

import { stripIndent, source } from 'common-tags'
import { WHITESPACE } from '../constants'
import type { SanitizedValues } from '../../../types'

function template4({
  basics,
  education,
  work,
  projects,
  skills,
  awards
}: SanitizedValues) {
  return stripIndent`
    ${generateCommentHeader()}
    \\documentclass[]{deedy-resume-openfont}

    \\begin{document}
    ${generateProfileSection(basics)}
    ${generateEducationSection(education)}
    ${generateExperienceSection(work)}
    ${generateSkillsSection(skills)}
    ${generateProjectsSection(projects)}
    ${generateAwardsSection(awards)}
    ${WHITESPACE}
    \\end{document}
  `
}

function generateProfileSection(profile) {
  if (!profile) {
    return '\\namesection{Your}{Name}{}'
  }

  const { name, email, phone, location = {}, website } = profile

  let nameStart = ''
  let nameEnd = ''

  if (name) {
    const names = name.split(' ')

    if (names.length === 1) {
      nameStart = names[0]
      nameEnd = ''
    } else {
      nameStart = names[0]
      nameEnd = names.slice(1, names.length).join(' ')
    }
  }

  const info = [email, phone, location.address, website]
    .filter(Boolean)
    .join(' | ')

  const sectionHeader = stripIndent`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    %     Profile
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  `

  if (!name) {
    return stripIndent`
      ${sectionHeader}
      \\centering{
        \\color{headings}
        \\fontspec[Path = fonts/raleway/]{Raleway-Medium}
        \\fontsize{11pt}{14pt}
        \\selectfont ${info}
      }
    `
  }

  return stripIndent`
    ${sectionHeader}
    \\namesection{${nameStart}}{${nameEnd}}{${info}}
  `
}

function generateEducationSection(education) {
  if (!education) {
    return ''
  }

  return source`
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %     Education
  %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  \\section{Education}
  \\raggedright
  ${education.map(school => {
    const {
      institution,
      location,
      studyType,
      area,
      gpa,
      startDate,
      endDate
    } = school

    let line1 = ''
    let line2 = ''

    if (institution) {
      line1 += `\\runsubsection{${institution}}`
    }

    if (studyType && area) {
      line1 += `\\descript{| ${studyType} ${area}}`
    } else if (studyType) {
      line1 += `\\descript{| ${studyType}}`
    } else if (area) {
      line1 += `\\descript{| ${area}}`
    }

    let dateRange = ''

    if (startDate && endDate) {
      dateRange = `${startDate} - ${endDate}`
    } else if (startDate) {
      dateRange = `${startDate} - Present`
    } else {
      dateRange = endDate
    }

    const locationAndDate = [location, dateRange].filter(Boolean).join(' | ')

    if (locationAndDate) {
      line1 += `\\hfill \\location{${locationAndDate}}`
    }

    if (line1) {
      line1 += '\\\\'
    }

    if (gpa) {
      line2 += `GPA: ${gpa}\\\\`
    }

    return `
      ${line1}
      ${line2}
      \\sectionsep
    `
  })}
  `
}

function generateExperienceSection(work) {
  if (!work) {
    return ''
  }

  return source`
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %     Experience
  %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  \\section{Experience}
  ${work.map(job => {
    const { company, position, location, startDate, endDate, highlights } = job

    let line1 = ''
    let dateRange = ''
    let highlightLines = ''

    if (company) {
      line1 += `\\runsubsection{${company}}`
    }

    if (position) {
      line1 += `\\descript{| ${position}}`
    }

    if (startDate && endDate) {
      dateRange = `${startDate} – ${endDate}`
    } else if (startDate) {
      dateRange = `${startDate} – Present`
    } else {
      dateRange = endDate
    }

    if (location && dateRange) {
      line1 += `\\hfill \\location{${location} | ${dateRange}}`
    } else if (location) {
      line1 += `\\hfill \\location{${location}}`
    } else if (dateRange) {
      line1 += `\\hfill \\location{${dateRange}}`
    }

    if (highlights) {
      highlightLines = source`
        \\begin{tightemize}
          ${highlights.map(highlight => `\\item ${highlight}`)}
        \\end{tightemize}
        `
    }

    return stripIndent`
      ${line1}
      ${highlightLines}
      \\sectionsep
    `
  })}
  `
}

function generateSkillsSection(skills) {
  if (!skills) {
    return ''
  }

  return source`
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %     Skills
  %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  \\section{Skills}
  \\raggedright
  \\begin{tabular}{ l l }
  ${skills.map(skill => {
    const { name = '', keywords = [] } = skill
    return `\\descript{${name}} & {\\location{${keywords.join(', ')}}} \\\\`
  })}
  \\end{tabular}
  \\sectionsep
  `
}

function generateProjectsSection(projects) {
  if (!projects) {
    return ''
  }

  return source`
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %     Projects
  %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  \\section{Projects}
  \\raggedright
  ${projects.map(project => {
    const { name, description, keywords, url } = project

    let line1 = ''
    let line2 = ''
    let line3 = ''

    if (name) {
      line1 += `\\runsubsection{\\large{${name}}}`
    }

    if (keywords) {
      line2 += `\\descript{| ${keywords.join(', ')}}`
    }

    if (url) {
      line2 += `\\hfill \\location{${url}}`
    }

    if (line2) {
      line2 += '\\\\'
    }

    if (description) {
      line3 += `${description}\\\\`
    }

    return `
      ${line1}
      ${line2}
      ${line3}
      \\sectionsep
    `
  })}
  `
}

function generateAwardsSection(awards) {
  if (!awards) {
    return ''
  }

  return source`
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %     Awards
  %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  \\section{Awards}
  ${awards.map(award => {
    const { title, summary, date, awarder } = award
    const info = [awarder, date].filter(Boolean).join(' | ')

    return stripIndent`
      \\runsubsection{\\large{${title || ''}}} \\descript{${info}} \\\\
      ${summary ? `${summary}\\\\` : ''}
      \\sectionsep
    `
  })}
  `
}

function generateCommentHeader() {
  return stripIndent`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % This is a modified ONE COLUMN version of
    % the following template:
    %
    % Deedy - One Page Two Column Resume
    % LaTeX Template
    % Version 1.1 (30/4/2014)
    %
    % Original author:
    % Debarghya Das (http://debarghyadas.com)
    %
    % Original repository:
    % https://github.com/deedydas/Deedy-Resume
    %
    % IMPORTANT: THIS TEMPLATE NEEDS TO BE COMPILED WITH XeLaTeX
    %
    % This template uses several fonts not included with Windows/Linux by
    % default. If you get compilation errors saying a font is missing, find the line
    % on which the font is used and either change it to a font included with your
    % operating system or comment the line out to use the default font.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    % TODO:
    % 1. Integrate biber/bibtex for article citation under publications.
    % 2. Figure out a smoother way for the document to flow onto the next page.
    % 3. Add styling information for a "Projects/Hacks" section.
    % 4. Add location/address information
    % 5. Merge OpenFont and MacFonts as a single sty with options.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    % CHANGELOG:
    % v1.1:
    % 1. Fixed several compilation bugs with \\renewcommand
    % 2. Got Open-source fonts (Windows/Linux support)
    % 3. Added Last Updated
    % 4. Move Title styling into .sty
    % 5. Commented .sty file.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %
    % Known Issues:
    % 1. Overflows onto second page if any column's contents are more than the
    % vertical limit
    % 2. Hacky space on the first bullet point on the second column.
    %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  `
}

module.exports = template4
<|endoftext|>


<commit_before>const { stripIndent, source } = require('common-tags')

function template7({ profile, schools, jobs, skills, projects }) {
  return stripIndent`
    ${generateHeader()}
    ${generateProfileSection(profile)}
    \\begin{document}
    ${profile ? '\\makecvtitle' : ''}
    ${generateEducationSection(schools)}
    ${generateExperienceSection(jobs)}
    ${generateSkillsSection(skills)}

    \\section{Projects}
    \\cventry{}{LaTeX Resume Generator}{}{\\textit{Node.js, Koa, React, Redux}}{}{A webapp for generating LaTeX resumes from form data (including this one).\\\\https://latexresu.me}

    \\vspace{2mm}

    \\cventry{}{Flow Timer}{}{\\textit{Node.js, Koa, React, Redux}}{}{A modern speedcubing app with a scrambler, timer, and analyzer for cubing statistics.\\\\https://flowtimer.com}

    \\vspace{2mm}

    \\cventry{}{Reddit Image Scraper}{}{\\textit{Ruby, Sinatra}}{}{A web app that lets you view a collage of images/videos from a subreddit.\\\\reddit-image-scraper.herokuapp.com}

    \\end{document}
  `
}

function generateProfileSection(profile = {}) {
  const { fullName, email, phoneNumber, address, link } = profile

  return stripIndent`
    % Profile
    \\name{${fullName || ''}}{}
    \\address{${address || ''}}
    ${phoneNumber ? `\\phone[mobile]{${phoneNumber}}` : ''}
    ${email ? `\\email{${email || ''}}` : ''}
    ${link ? `\\homepage{${link || ''}}` : ''}
  `
}

function generateEducationSection(schools) {
  if (!schools) {
    return ''
  }

  return source`
    \\section{Education}
    ${schools.map((school) => {
      const { name, degree, major, gpa, location, graduationDate } = school

      let degreeLine = ''

      if (degree && major) {
        degreeLine = `${degree} in ${major}`
      } else if (degree || major) {
        degreeLine = degree || major
      }

      return stripIndent`
        \\cventry
          {${graduationDate || ''}}
          {${degreeLine}}
          {${name || ''}}
          {${gpa ? `GPA: ${gpa}` : ''}}
          {\\textit{${location || ''}}}
          {}
      `
    })}
  `
}

function generateExperienceSection(jobs) {
  if (!jobs) {
    return ''
  }

  return source`
    \\section{Experience}
    ${jobs.map((job) => {
      const { name, title, location, startDate, endDate, duties } = job

      let dateRange = ''
      let dutyLines = ''

      if (startDate && endDate) {
        dateRange = `${startDate} -- ${endDate}`
      } else if (startDate) {
        dateRange = `${startDate} -- Present`
      } else {
        dateRange = endDate
      }

      if (duties) {
        dutyLines = source`
          \\begin{itemize}%
            ${duties.map(duty => `\\item ${duty}`)}
          \\end{itemize}
        `
      }

      return stripIndent`
        \\cventry
          {${dateRange}}
          {${title || ''}}
          {${name || ''}}
          {${location || ''}}
          {}
          {${dutyLines}}
      `
    })}
  `
}

function generateSkillsSection(skills) {
  if (!skills) {
    return ''
  }

  return source`
    \\section{Skills}
    ${skills.map(skill => `\\cvitem{${skill.name || ''}}{${skill.details || ''}}`)}
  `
}

function generateHeader() {
  return stripIndent`
    %% start of file 'template.tex'.
    %% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com).
    %
    % This work may be distributed and/or modified under the
    % conditions of the LaTeX Project Public License version 1.3c,
    % available at http://www.latex-project.org/lppl/.

    \\documentclass[11pt,a4paper,sans]{moderncv}        % possible options include font size ('10pt', '11pt' and '12pt'), paper size ('a4paper', 'letterpaper', 'a5paper', 'legalpaper', 'executivepaper' and 'landscape') and font family ('sans' and 'roman')

    % moderncv themes
    \\moderncvstyle{classic}                             % style options are 'casual' (default), 'classic', 'oldstyle' and 'banking'
    \\moderncvcolor{blue}                               % color options 'blue' (default), 'orange', 'green', 'red', 'purple', 'grey' and 'black'
    %\\renewcommand{\\familydefault}{\\sfdefault}         % to set the default font; use '\\sfdefault' for the default sans serif font, '\\rmdefault' for the default roman one, or any tex font name
    %\\nopagenumbers{}                                  % uncomment to suppress automatic page numbering for CVs longer than one page

    % character encoding
    \\usepackage[utf8]{inputenc}                       % if you are not using xelatex ou lualatex, replace by the encoding you are using
    %\\usepackage{CJKutf8}                              % if you need to use CJK to typeset your resume in Chinese, Japanese or Korean

    % adjust the page margins
    \\usepackage[scale=0.75]{geometry}
    %\\setlength{\\hintscolumnwidth}{3cm}                % if you want to change the width of the column with the dates
    %\\setlength{\\makecvtitlenamewidth}{10cm}           % for the 'classic' style, if you want to force the width allocated to your name and avoid line breaks. be careful though, the length is normally calculated to avoid any overlap with your personal info; use this at your own typographical risks...
  `
}

module.exports = template7
<commit_msg>finish template7<commit_after>const { stripIndent, source } = require('common-tags')

function template7({ profile, schools, jobs, skills, projects }) {
  return stripIndent`
    ${generateHeader()}
    ${generateProfileSection(profile)}
    \\begin{document}
    ${profile ? '\\makecvtitle' : ''}
    ${generateEducationSection(schools)}
    ${generateExperienceSection(jobs)}
    ${generateSkillsSection(skills)}
    ${generateProjectsSection(projects)}
    \\end{document}
  `
}

function generateProfileSection(profile = {}) {
  const { fullName, email, phoneNumber, address, link } = profile

  return stripIndent`
    % Profile
    \\name{${fullName || ''}}{}
    \\address{${address || ''}}
    ${phoneNumber ? `\\phone[mobile]{${phoneNumber}}` : ''}
    ${email ? `\\email{${email || ''}}` : ''}
    ${link ? `\\homepage{${link || ''}}` : ''}
  `
}

function generateEducationSection(schools) {
  if (!schools) {
    return ''
  }

  return source`
    \\section{Education}
    ${schools.map((school) => {
      const { name, degree, major, gpa, location, graduationDate } = school

      let degreeLine = ''

      if (degree && major) {
        degreeLine = `${degree} in ${major}`
      } else if (degree || major) {
        degreeLine = degree || major
      }

      return stripIndent`
        \\cventry
          {${graduationDate || ''}}
          {${degreeLine}}
          {${name || ''}}
          {${gpa ? `GPA: ${gpa}` : ''}}
          {\\textit{${location || ''}}}
          {}
      `
    })}
  `
}

function generateExperienceSection(jobs) {
  if (!jobs) {
    return ''
  }

  return source`
    \\section{Experience}
    ${jobs.map((job) => {
      const { name, title, location, startDate, endDate, duties } = job

      let dateRange = ''
      let dutyLines = ''

      if (startDate && endDate) {
        dateRange = `${startDate} -- ${endDate}`
      } else if (startDate) {
        dateRange = `${startDate} -- Present`
      } else {
        dateRange = endDate
      }

      if (duties) {
        dutyLines = source`
          \\begin{itemize}%
            ${duties.map(duty => `\\item ${duty}`)}
          \\end{itemize}
        `
      }

      return stripIndent`
        \\cventry
          {${dateRange}}
          {${title || ''}}
          {${name || ''}}
          {${location || ''}}
          {}
          {${dutyLines}}
      `
    })}
  `
}

function generateSkillsSection(skills) {
  if (!skills) {
    return ''
  }

  return source`
    \\section{Skills}
    ${skills.map(skill => `\\cvitem{${skill.name || ''}}{${skill.details || ''}}`)}
  `
}

function generateProjectsSection(projects) {
  if (!projects) {
    return ''
  }

  return source`
    \\section{Projects}
    ${projects.map((project) => {
      const { name, description, technologies, link } = project

      let detailsLine = ''

      if (description) {
        detailsLine += `${description}\\\\`
      }

      if (link) {
        detailsLine += link
      }

      return stripIndent`
        \\cventry
          {}
          {${name || ''}}
          {}
          {\\textit{${technologies || ''}}}
          {}
          {${detailsLine}}
        \\vspace{1mm}
      `
    })}
  `
}

function generateHeader() {
  return stripIndent`
    %% start of file 'template.tex'.
    %% Copyright 2006-2013 Xavier Danaux (xdanaux@gmail.com).
    %
    % This work may be distributed and/or modified under the
    % conditions of the LaTeX Project Public License version 1.3c,
    % available at http://www.latex-project.org/lppl/.

    \\documentclass[11pt,a4paper,sans]{moderncv}        % possible options include font size ('10pt', '11pt' and '12pt'), paper size ('a4paper', 'letterpaper', 'a5paper', 'legalpaper', 'executivepaper' and 'landscape') and font family ('sans' and 'roman')

    % moderncv themes
    \\moderncvstyle{classic}                             % style options are 'casual' (default), 'classic', 'oldstyle' and 'banking'
    \\moderncvcolor{blue}                               % color options 'blue' (default), 'orange', 'green', 'red', 'purple', 'grey' and 'black'
    %\\renewcommand{\\familydefault}{\\sfdefault}         % to set the default font; use '\\sfdefault' for the default sans serif font, '\\rmdefault' for the default roman one, or any tex font name
    %\\nopagenumbers{}                                  % uncomment to suppress automatic page numbering for CVs longer than one page

    % character encoding
    \\usepackage[utf8]{inputenc}                       % if you are not using xelatex ou lualatex, replace by the encoding you are using
    %\\usepackage{CJKutf8}                              % if you need to use CJK to typeset your resume in Chinese, Japanese or Korean

    % adjust the page margins
    \\usepackage[scale=0.75]{geometry}
    %\\setlength{\\hintscolumnwidth}{3cm}                % if you want to change the width of the column with the dates
    %\\setlength{\\makecvtitlenamewidth}{10cm}           % for the 'classic' style, if you want to force the width allocated to your name and avoid line breaks. be careful though, the length is normally calculated to avoid any overlap with your personal info; use this at your own typographical risks...
  `
}

module.exports = template7
<|endoftext|>


<commit_before>angular.module('stPagination').directive('stPagination', function (StPagination, $parse) {
  'use strict';

  var css3UserSelectAliases = [
    '-webkit-touch-callout',
    '-webkit-user-select',
    '-moz-user-select',
    '-ms-user-select',
    'user-select'
  ];

  var basePagination = '
' +
      '
' +
        '«' +
      '
' +
      '
' +
        '{{ index | displayPaginationIndex }}' +
      '
' +
      '
' +
        '»' +
      '
' +
    '
';

  function extendDefaults(options) {
    return angular.extend({
      divWrapped: false,
      selectedClass: 'active',
      disabledClass: 'disabled'
    }, options);
  }

  var cssConfigsByKey = {
    list: {},
    divWrappedList: {
      divWrapped: true
    },
    bootstrap3: {},
    bootstrap2: {
      divWrapped: true
    },
    zurbFoundation: {
      selectedClass: 'current',
      disabledClass: 'unavailable'
    }
  };

  var allowedValues = '"' + Object.keys(cssConfigsByKey).join('", "') + '"';
  var defaultCssConfig = 'list';

  function parseCssConfig(cssConfig) {
    var configObject = $parse(cssConfig)({});
    if (angular.isObject(configObject)) {
      return extendDefaults(configObject);
    }
    cssConfig = cssConfig || defaultCssConfig;
    configObject = cssConfigsByKey[cssConfig];
    if (configObject !== undefined) {
      return extendDefaults(configObject);
    } else {
      var msg = 'Given css-config attribute "' + cssConfig + '" is not in allowed values ' + allowedValues;
      throw new Error(msg);
    }
  }

  /**
   * @ngdoc directive
   * @name stPagination.directive:stPagination
   * @restrict E
   *
   * @description
   * Directive that display a pagination for a collection that is initialized by the
   * {@link stPagination.filter:stPagination `stPagination` filter}.
   *
   *

   *    <st-pagination collection="users"></st-pagination>
   *

   *
   * ## Configure displayed ranges
   *
   * The automatic folding of indices can be configured.
   * The length of the displayed index ranges are set with the attributes `midRange` and `edgeRange.
   * If the ranges overlap they are summed up to keep a fixed length.
   * The overlapped element a replaced by '...' and a click on that pagination element will jump to the middle
   * of the folded elements.
   *
   *

   *    <st-pagination collection="users" mid-range="2" edge-range="3"></st-pagination>
   *

   *
   * ## Configure structure for css
   *
   * Configure the html structure for you css with the parameter `cssConfig`.
   * Decide between a basic `<ul>` list (`'list'`) or a list wrapped by `<div>` elements (`'divWrappedList'`).
   *
   * Bootstrap 3.x and 2.x are supported by these html structures.
   * Therefore just use the aliases `'bootstrap3'` for `'list'` and `'bootstrap2'` for `'divWrappedList'`.
   *
   * Zurb foundation (3.x-5.x) is also supported and has its own key `'zurbFoundation'`.
   *
   *

   *    <st-pagination css-config="'bootstrap3'"></st-pagination>
   *

   *
   * @param {Array} collection Array that was initialized by the
   *  {@link stPagination.filter:stPagination `stPagination` filter}
   * @param {number=} [midRange=3] Range before and after current index
   * @param {number=} [edgeRange=3]  Range at the start and end of all indices
   * @param {string=} [cssConfig='list'] Key to defined the html structure.
   *   - `'list'`
   *   - `'divWrappedList'`
   *   - `'bootstrap3'` alias for `'list'`
   *   - `'bootstrap2'` alias for `'divWrappedList'`
   *   - `'zurbFoundation'` - custom configuration for zurb
   * @example
   *
     <example module="paginationExample">
       <file name="index.html">

                 Name
                 Email

                 {{ user.name }}
                 {{ user.email  }}

             {{ users | stPageInfo:'startIndex' }} - {{ users | stPageInfo:'stopIndex' }}
             |
             Total {{ users | stPageInfo:'total' }}

           <st-pagination collection="users" mid-range="midRange" edge-range="edgeRange" css-config="bootstrap2">
           </st-pagination>

             midRange:
             <select ng-options="r for r in [1,2,3,4,5]" ng-model="mR" class="input-small"></select>
             edgeRange:
             <select ng-options="r for r in [1,2,3,4,5]" ng-model="eR" class="input-small"></select>

              <st-pagination collection="users" mid-range="{{mR}}" edge-range="{{eR}}"></st-pagination>

       </file>
       <file name="app.js">
         angular.module('paginationExample', ['stPagination', 'exampleData'])
           .controller('UserController', function UserController($scope, exampleUsers) {
               $scope.users = exampleUsers;
               $scope.$watch('users.pagination', function (pagination) {
                 pagination.setPage(14)
               });
            });
       </file>
     </example>
  */
  return {
    restrict: 'E',
    replace: true,
    scope: {
      collection: '=',
      edgeRange: '=',
      midRange: '='
    },
    template: basePagination,
    compile: function ($element, attributes) {
      var cssConfigObject = parseCssConfig(attributes.cssConfig);

      if (cssConfigObject.divWrapped) {
        $element.wrap('

');
      } else {
        $element.addClass('pagination');
      }

      angular.forEach($element.find('li'), function (liElement) {
        var $liElement = angular.element(liElement);
        var ngClass = $liElement.attr('ng-class');
        ngClass = ngClass.replace('%DISABLED_CLASS%', cssConfigObject.disabledClass);
        ngClass = ngClass.replace('%SELECTED_CLASS%', cssConfigObject.selectedClass);
        $liElement.attr('ng-class', ngClass);
      });

    },
    controller: function ($scope, $element, $attrs) {
      // set css to prevent selections
      angular.forEach(css3UserSelectAliases, function (alias) {
        $element.css(alias, 'none');
      });

      var collectionName = $attrs.collection;

      $scope.$watch('collection', function (collection) {
        if (angular.isArray(collection)) {
          if (StPagination.hasPagination(collection)) {
            $scope.pagination = collection.pagination;
          } else {
            var msg = 'Collection "' + collectionName + '" in the pagination directive is not used with a neccessary ' +
              'pagination filter.';
            throw new Error(msg);
          }
        }
      });
    }
  };
});
<commit_msg>added documentation for css config objects (fixes #23)<commit_after>angular.module('stPagination').directive('stPagination', function (StPagination, $parse) {
  'use strict';

  var css3UserSelectAliases = [
    '-webkit-touch-callout',
    '-webkit-user-select',
    '-moz-user-select',
    '-ms-user-select',
    'user-select'
  ];

  var basePagination = '
' +
      '
' +
        '«' +
      '
' +
      '
' +
        '{{ index | displayPaginationIndex }}' +
      '
' +
      '
' +
        '»' +
      '
' +
    '
';

  function extendDefaults(options) {
    return angular.extend({
      divWrapped: false,
      selectedClass: 'active',
      disabledClass: 'disabled'
    }, options);
  }

  var cssConfigsByKey = {
    list: {},
    divWrappedList: {
      divWrapped: true
    },
    bootstrap3: {},
    bootstrap2: {
      divWrapped: true
    },
    zurbFoundation: {
      selectedClass: 'current',
      disabledClass: 'unavailable'
    }
  };

  var allowedValues = '"' + Object.keys(cssConfigsByKey).join('", "') + '"';
  var defaultCssConfig = 'list';

  function parseCssConfig(cssConfig) {
    var configObject = $parse(cssConfig)({});
    if (angular.isObject(configObject)) {
      return extendDefaults(configObject);
    }
    cssConfig = cssConfig || defaultCssConfig;
    configObject = cssConfigsByKey[cssConfig];
    if (configObject !== undefined) {
      return extendDefaults(configObject);
    } else {
      var msg = 'Given css-config attribute "' + cssConfig + '" is not in allowed values ' + allowedValues;
      throw new Error(msg);
    }
  }

  /**
   * @ngdoc directive
   * @name stPagination.directive:stPagination
   * @restrict E
   *
   * @description
   * Directive that display a pagination for a collection that is initialized by the
   * {@link stPagination.filter:stPagination `stPagination` filter}.
   *
   *

   *    <st-pagination collection="users"></st-pagination>
   *

   *
   * ## Configure displayed ranges
   *
   * The automatic folding of indices can be configured.
   * The length of the displayed index ranges are set with the attributes `midRange` and `edgeRange.
   * If the ranges overlap they are summed up to keep a fixed length.
   * The overlapped element a replaced by '...' and a click on that pagination element will jump to the middle
   * of the folded elements.
   *
   *

   *    <st-pagination collection="users" mid-range="2" edge-range="3"></st-pagination>
   *

   *
   * ## Configure structure for css
   *
   * Configure the html structure for you css with the parameter `cssConfig`.
   * As parameter you can pass a **`{string}`** key for configuration presets or an custom configuration
   * as **`{object}`**.
   *
   * The generated default structure of the directive is a simple list with links and a ***pagination*** class.
   * The current page link has an ***active*** class and the previous and next buttons get a ***disabled*** class
   * for the first or last page.
   *
   *

   *

   *
«

   *
1

   *
1

   *
2

   *
3

   *
»

   *

   *

   *
   * The config property `{divWrapped: true}` or the key `'divWrappedList'` wraps the list in a div element.
   *
   *

   *

   *

   *       ...
   *

   *

   *

   *
   * The config properties `selectedClass` and `disabledClass` replace the class attributes for the list elements.
   * For example `{selectedClass: 'current', disabledClass: 'unavailable'}` will generate the following html structure.
   *
   *

   *

   *
«

   *
1

   *
1

   *
2

   *
3

   *
»

   *

   *

   *
   * ### Config keys for css frameworks
   *
   * To simplify the configuration for popular css framework just use a **`{string}`** key for the configuration.
   *
   *   - `'bootstrap3'` generates the default structure
   *   - `'bootstrap2'` generates a `'divWrappedList'`
   *   - `'zurbFoundation'` set class attributes necessary for foundation (works with version 3-5)
   *
   *

   *    <st-pagination css-config="'zurbFoundation'"></st-pagination>
   *

   *
   * @param {Array} collection Array that was initialized by the
   *  {@link stPagination.filter:stPagination `stPagination` filter}
   * @param {number=} [midRange=3] Range before and after current index
   * @param {number=} [edgeRange=3]  Range at the start and end of all indices
   * @param {object|string=} [cssConfig='list'] Custom `{object}` to configure the html structure or `{string}` key
   *   for a predefined configuration.
   *
   *   Config **`{object}`** properties:
   *   - `divWrapped` - `{boolean}`   - if true the pagination list will be wrapped with a div element
   *        *(Default: false)*
   *   - `selectedClass` - `{string}` - set as class attribute for the li element of the selected page
   *        *(Default: 'active')*
   *   - `disabledClass` - `{string}` - set as class attribute to disable previous and next elements
   *        on first and last page *(Default: 'disabled')*
   *
   *  Config **`{string}`** keys:
   *   - `'list'`
   *   - `'divWrappedList'`
   *   - `'bootstrap3'` alias for `'list'`
   *   - `'bootstrap2'` alias for `'divWrappedList'`
   *   - `'zurbFoundation'` - custom configuration for zurb
   *
   * @example
   *
     <example module="paginationExample">
       <file name="index.html">

                 Name
                 Email

                 {{ user.name }}
                 {{ user.email  }}

             {{ users | stPageInfo:'startIndex' }} - {{ users | stPageInfo:'stopIndex' }}
             |
             Total {{ users | stPageInfo:'total' }}

           <st-pagination collection="users" mid-range="midRange" edge-range="edgeRange" css-config="bootstrap2">
           </st-pagination>

             midRange:
             <select ng-options="r for r in [1,2,3,4,5]" ng-model="mR" class="input-small"></select>
             edgeRange:
             <select ng-options="r for r in [1,2,3,4,5]" ng-model="eR" class="input-small"></select>

              <st-pagination collection="users" mid-range="{{mR}}" edge-range="{{eR}}"></st-pagination>

       </file>
       <file name="app.js">
         angular.module('paginationExample', ['stPagination', 'exampleData'])
           .controller('UserController', function UserController($scope, exampleUsers) {
               $scope.users = exampleUsers;
               $scope.$watch('users.pagination', function (pagination) {
                 pagination.setPage(14)
               });
            });
       </file>
     </example>
  */
  return {
    restrict: 'E',
    replace: true,
    scope: {
      collection: '=',
      edgeRange: '=',
      midRange: '='
    },
    template: basePagination,
    compile: function ($element, attributes) {
      var cssConfigObject = parseCssConfig(attributes.cssConfig);

      if (cssConfigObject.divWrapped) {
        $element.wrap('

');
      } else {
        $element.addClass('pagination');
      }

      angular.forEach($element.find('li'), function (liElement) {
        var $liElement = angular.element(liElement);
        var ngClass = $liElement.attr('ng-class');
        ngClass = ngClass.replace('%DISABLED_CLASS%', cssConfigObject.disabledClass);
        ngClass = ngClass.replace('%SELECTED_CLASS%', cssConfigObject.selectedClass);
        $liElement.attr('ng-class', ngClass);
      });

    },
    controller: function ($scope, $element, $attrs) {
      // set css to prevent selections
      angular.forEach(css3UserSelectAliases, function (alias) {
        $element.css(alias, 'none');
      });

      var collectionName = $attrs.collection;

      $scope.$watch('collection', function (collection) {
        if (angular.isArray(collection)) {
          if (StPagination.hasPagination(collection)) {
            $scope.pagination = collection.pagination;
          } else {
            var msg = 'Collection "' + collectionName + '" in the pagination directive is not used with a neccessary ' +
              'pagination filter.';
            throw new Error(msg);
          }
        }
      });
    }
  };
});
<|endoftext|>


<commit_before>'use strict';

angular.module('edge.app.controllers').controller('AppController', function (edgePage, $scope, $timeout, NProgress) {
    var app = this;
    app.edgePage = edgePage;

    $scope.$watch(function() {
        app.showNavbar = edgePage.isNavbarVisible();
        if (app.showNavbar) {
            edgePage.addHtmlClass('has-app-header');
        } else {
            edgePage.removeHtmlClass('has-app-header');
        }

        app.showFooter = edgePage.isFooterVisible();
        if (app.showFooter) {
            edgePage.addHtmlClass('has-app-footer');
        } else {
            edgePage.removeHtmlClass('has-app-footer');
        }
    });

    app.collapseNavbar = true;
    app.toggleNavbar = function () {
        app.collapseNavbar = !app.collapseNavbar;
    };

    var loaderTimeout;
    $scope.$on('$stateChangeStart', function () {
        loaderTimeout = $timeout(function() {
            NProgress.start();
            NProgress.inc();
        }, 250);
    });
    $scope.$on('$stateChangeSuccess', function () {
        $timeout.cancel(loaderTimeout);
        NProgress.done();
    });
    $scope.$on('$stateNotFound', function () {
        $timeout.cancel(loaderTimeout);
        NProgress.done();
    });
});
<commit_msg>fixed nprogress cancel timeout on $stateChangeStart event<commit_after>'use strict';

angular.module('edge.app.controllers').controller('AppController', function (edgePage, $scope, $timeout, NProgress) {
    var app = this;
    app.edgePage = edgePage;

    $scope.$watch(function() {
        app.showNavbar = edgePage.isNavbarVisible();
        if (app.showNavbar) {
            edgePage.addHtmlClass('has-app-header');
        } else {
            edgePage.removeHtmlClass('has-app-header');
        }

        app.showFooter = edgePage.isFooterVisible();
        if (app.showFooter) {
            edgePage.addHtmlClass('has-app-footer');
        } else {
            edgePage.removeHtmlClass('has-app-footer');
        }
    });

    app.collapseNavbar = true;
    app.toggleNavbar = function () {
        app.collapseNavbar = !app.collapseNavbar;
    };

    var loaderTimeout;
    $scope.$on('$stateChangeStart', function () {
        if(loaderTimeout) {
            $timeout.cancel(loaderTimeout);
        }
        loaderTimeout = $timeout(function() {
            NProgress.start();
            NProgress.inc();
        }, 250);
    });
    $scope.$on('$stateChangeSuccess', function () {
        $timeout.cancel(loaderTimeout);
        NProgress.done();
    });
    $scope.$on('$stateNotFound', function () {
        $timeout.cancel(loaderTimeout);
        NProgress.done();
    });
});
<|endoftext|>


<commit_before>describe('Root View Component', function() {
    var $el;

    it('should be mounted in DOM', function() {
        expect($el.length).toEqual(1);
    });
});<commit_msg>identified more component features in root spec<commit_after>describe('Root View Component', function() {
    var $el;

    it('should be mounted in DOM', function() {
        expect($el.length).toEqual(1);
    });

    it('should get Meteor data from Openweather collection', function() {

    });

    it('should render options to choose different cities', function() {

    });
});<|endoftext|>