<commit_before>import React, { Component } from 'react';
import { Link, NavLink } from 'react-router-dom';
import { Badge, DropdownItem, DropdownMenu, DropdownToggle, Nav, NavItem } from 'reactstrap';
import PropTypes from 'prop-types';

import { AppAsideToggler, AppHeaderDropdown, AppNavbarBrand, AppSidebarToggler } from '@coreui/react';
import logo from '../../assets/img/brand/logo.svg'
import sygnet from '../../assets/img/brand/sygnet.svg'

const propTypes = {
  children: PropTypes.node,
};

const defaultProps = {};

class DefaultHeader extends Component {
  render() {

    // eslint-disable-next-line
    const { children, ...attributes } = this.props;

    return (
      <React.Fragment>
        <AppSidebarToggler className="d-lg-none" display="md" mobile />
        <AppNavbarBrand
          full={{ src: logo, width: 89, height: 25, alt: 'CoreUI Logo' }}
          minimized={{ src: sygnet, width: 30, height: 30, alt: 'CoreUI Logo' }}
        />
        <AppSidebarToggler className="d-md-down-none" display="lg" />

          <NavItem className="px-3">
            <NavLink to="/dashboard" className="nav-link" >Dashboard</NavLink>
          </NavItem>
          <NavItem className="px-3">
            <Link to="/users" className="nav-link">Users</Link>
          </NavItem>
          <NavItem className="px-3">
            <NavLink to="#" className="nav-link">Settings</NavLink>
          </NavItem>

          <NavItem className="d-md-down-none">
            <NavLink to="#" className="nav-link"><Badge pill color="danger">5</Badge></NavLink>
          </NavItem>
          <NavItem className="d-md-down-none">
            <NavLink to="#" className="nav-link"></NavLink>
          </NavItem>
          <NavItem className="d-md-down-none">
            <NavLink to="#" className="nav-link"></NavLink>
          </NavItem>
          <AppHeaderDropdown direction="down">
            <DropdownToggle nav>

            </DropdownToggle>
            <DropdownMenu right style={{ right: 'auto' }}>
              <DropdownItem header tag="div" className="text-center">Account</DropdownItem>
              <DropdownItem> Updates<Badge color="info">42</Badge></DropdownItem>
              <DropdownItem> Messages<Badge color="success">42</Badge></DropdownItem>
              <DropdownItem> Tasks<Badge color="danger">42</Badge></DropdownItem>
              <DropdownItem> Comments<Badge color="warning">42</Badge></DropdownItem>
              <DropdownItem header tag="div" className="text-center">Settings</DropdownItem>
              <DropdownItem> Profile</DropdownItem>
              <DropdownItem> Settings</DropdownItem>
              <DropdownItem> Payments<Badge color="secondary">42</Badge></DropdownItem>
              <DropdownItem> Projects<Badge color="primary">42</Badge></DropdownItem>
              <DropdownItem divider />
              <DropdownItem> Lock Account</DropdownItem>
              <DropdownItem onClick={e => this.props.onLogout(e)}> Logout</DropdownItem>
            </DropdownMenu>
          </AppHeaderDropdown>

        <AppAsideToggler className="d-md-down-none" />
        {/*<AppAsideToggler className="d-lg-none" mobile />*/}
      </React.Fragment>
    );
  }
}

DefaultHeader.propTypes = propTypes;
DefaultHeader.defaultProps = defaultProps;

export default DefaultHeader;
<commit_msg>fix(DefaultHeader): replace `AppHeaderDropdown` with `UncontrolledDropdown`<commit_after>import React, { Component } from 'react';
import { Link, NavLink } from 'react-router-dom';
import { Badge, UncontrolledDropdown, DropdownItem, DropdownMenu, DropdownToggle, Nav, NavItem } from 'reactstrap';
import PropTypes from 'prop-types';

import { AppAsideToggler, AppNavbarBrand, AppSidebarToggler } from '@coreui/react';
import logo from '../../assets/img/brand/logo.svg'
import sygnet from '../../assets/img/brand/sygnet.svg'

const propTypes = {
  children: PropTypes.node,
};

const defaultProps = {};

class DefaultHeader extends Component {
  render() {

    // eslint-disable-next-line
    const { children, ...attributes } = this.props;

    return (
      <React.Fragment>
        <AppSidebarToggler className="d-lg-none" display="md" mobile />
        <AppNavbarBrand
          full={{ src: logo, width: 89, height: 25, alt: 'CoreUI Logo' }}
          minimized={{ src: sygnet, width: 30, height: 30, alt: 'CoreUI Logo' }}
        />
        <AppSidebarToggler className="d-md-down-none" display="lg" />

          <NavItem className="px-3">
            <NavLink to="/dashboard" className="nav-link" >Dashboard</NavLink>
          </NavItem>
          <NavItem className="px-3">
            <Link to="/users" className="nav-link">Users</Link>
          </NavItem>
          <NavItem className="px-3">
            <NavLink to="#" className="nav-link">Settings</NavLink>
          </NavItem>

          <NavItem className="d-md-down-none">
            <NavLink to="#" className="nav-link"><Badge pill color="danger">5</Badge></NavLink>
          </NavItem>
          <NavItem className="d-md-down-none">
            <NavLink to="#" className="nav-link"></NavLink>
          </NavItem>
          <NavItem className="d-md-down-none">
            <NavLink to="#" className="nav-link"></NavLink>
          </NavItem>
          <UncontrolledDropdown nav direction="down">
            <DropdownToggle nav>

            </DropdownToggle>
            <DropdownMenu right>
              <DropdownItem header tag="div" className="text-center">Account</DropdownItem>
              <DropdownItem> Updates<Badge color="info">42</Badge></DropdownItem>
              <DropdownItem> Messages<Badge color="success">42</Badge></DropdownItem>
              <DropdownItem> Tasks<Badge color="danger">42</Badge></DropdownItem>
              <DropdownItem> Comments<Badge color="warning">42</Badge></DropdownItem>
              <DropdownItem header tag="div" className="text-center">Settings</DropdownItem>
              <DropdownItem> Profile</DropdownItem>
              <DropdownItem> Settings</DropdownItem>
              <DropdownItem> Payments<Badge color="secondary">42</Badge></DropdownItem>
              <DropdownItem> Projects<Badge color="primary">42</Badge></DropdownItem>
              <DropdownItem divider />
              <DropdownItem> Lock Account</DropdownItem>
              <DropdownItem onClick={e => this.props.onLogout(e)}> Logout</DropdownItem>
            </DropdownMenu>
          </UncontrolledDropdown>

        <AppAsideToggler className="d-md-down-none" />
        {/*<AppAsideToggler className="d-lg-none" mobile />*/}
      </React.Fragment>
    );
  }
}

DefaultHeader.propTypes = propTypes;
DefaultHeader.defaultProps = defaultProps;

export default DefaultHeader;
<|endoftext|>


<commit_before>$('.dropdown-toggle').dropdown();

function countProperties(obj) {
	var count = 0;
	for(var prop in obj) {
		if(obj.hasOwnProperty(prop))
			++count;
	}
	return count;
}

function GoreadCtrl($scope, $http) {
	$scope.loading = 0;
	$scope.contents = {};

	$scope.importOpml = function() {
		$scope.shown = 'feeds';
		$scope.loading++;
		$('#import-opml-form').ajaxForm(function() {
			$('#import-opml-form')[0].reset();
			$scope.loaded();
		});
	};

	$scope.loaded = function() {
		$scope.loading--;
	};

	$scope.http = function(method, url, data) {
		return $http({
			method: method,
			url: url,
			data: $.param(data),
			headers: {'Content-Type': 'application/x-www-form-urlencoded'}
		});
	};

	$scope.addSubscription = function() {
		if (!$scope.addFeedUrl) {
			return false;
		}
		$scope.loading++;
		var f = $('#add-subscription-form');
		$scope.http('POST', f.attr('data-url'), {
			url: $scope.addFeedUrl
		}).then(function() {
			$scope.addFeedUrl = '';
			$scope.refresh($scope.loaded);
		}, function(data) {
			alert(data.data);
			$scope.loading--;
		});
	};

	$scope.refresh = function(cb) {
		$scope.loading++;
		$scope.shown = 'feeds';
		delete $scope.currentStory;
		$http.get($('#refresh').attr('data-url-feeds'))
			.success(function(data) {
				$scope.feeds = data;
				$scope.numfeeds = 0;
				$scope.stories = [];
				$scope.unreadStories = {};
				for(var p in $scope.feeds) {
					$scope.numfeeds++;
					var f = $scope.feeds[p];
					if (!f.Stories)
						continue;
					for(var i = 0; i < f.Stories.length; i++) {
						f.Stories[i].feed = f.Feed;
						var d = new Date(f.Stories[i].Date * 1000);
						f.Stories[i].dispdate = d.toDateString();
						f.Stories[i].read = false;
						$scope.stories.push(f.Stories[i]);
						$scope.unreadStories[f.Stories[i].Id] = true;
					}
					$scope.stories.sort(function(a, b) {
						return b.Date - a.Date;
					});
				}
				if (typeof cb === 'function') cb();
				$scope.loaded();
			})
			.error(function() {
				if (typeof cb === 'function') cb();
				$scope.loaded();
			});
	};

	$scope.setCurrent = function(i) {
		$scope.currentStory = i;
		$scope.markRead($scope.stories[i]);
	};
	$scope.prev = function() {
		if ($scope.currentStory > 0) {
			$scope.$apply('setCurrent(currentStory - 1)');
		}
	};
	$scope.next = function() {
		if ($scope.stories && typeof $scope.currentStory === 'undefined') {
			$scope.$apply('setCurrent(0)');
		} else if ($scope.stories && $scope.currentStory < $scope.stories.length - 2) {
			$scope.$apply('setCurrent(currentStory + 1)');
		}
	};

	$scope.unread = function() {
		return countProperties($scope.unreadStories);
	};

	$scope.markRead = function(s) {
		if ($scope.unreadStories[s.Id]) {
			delete $scope.unreadStories[s.Id];
			s.read = true;
			$scope.http('POST', $('#mark-all-read').attr('data-url-read'), {
				feed: s.feed.Url,
				story: s.Id
			});
		}
	};

	$scope.markAllRead = function(s) {
		$scope.unreadStories = {};
		$scope.stories = [];
		$http.post($('#mark-all-read').attr('data-url'));
	};

	$scope.nothing = function() {
		return $scope.loading == 0 && $scope.stories && !$scope.numfeeds;
	};

	$scope.toggleNav = function() {
		$scope.nav = !$scope.nav;
	}
	$scope.navspan = function() {
		return 'span' + ($scope.nav ? '10' : '12');
	};
	$scope.navmargin = function() {
		return $scope.nav ? {} : {'margin-left': '0'};
	};

	$scope.storyClass = function(s) {
		var o = {};
		o['nocontent'] = $scope.contents[s.Id] === undefined;
		return o;
	};

	$scope.getContents = function() {
		var docViewTop = $(window).scrollTop();
		var docViewBottom = docViewTop + $(window).height();

		var stories = $('.nocontent');
		if (stories.length == 0) return;
		var onscreen = [];
		$.each(stories, function(i, v) {
			v = $(v);
			var elemTop = v.offset().top;
			var elemBottom = elemTop + v.height();
			if ((elemTop <= docViewBottom) && (elemBottom >= docViewTop)) {
				var s = $scope.stories[v.attr('data-idx')];
				onscreen.push({
					Feed: s.feed.Url,
					Story: s.Id,
				});
				// mark this as fetched so a subsequent scroll doesn't re fetch
				$scope.contents[s.Id] = '';
			}
		});
		if (onscreen.length == 0) return;
		$http.post($('#mark-all-read').attr('data-url-contents'), onscreen)
			.success(function(data) {
				for (var k in data) {
					$scope.contents[k] = data[k];
				}
			});
	};
	window.onscroll = function() {
		$scope.$apply('getContents()');
	};
	// todo: switch this cheating to a directive
	setTimeout(function() {
		$scope.$apply('getContents()');
	}, 100);

	var shortcuts = $('#shortcuts');
	Mousetrap.bind('?', function() {
		shortcuts.modal('toggle');
	});
	Mousetrap.bind('esc', function() {
		shortcuts.modal('hide');
	});
	Mousetrap.bind('r', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply($scope.refresh());
	});
	Mousetrap.bind('j', $scope.next);
	Mousetrap.bind('k', $scope.prev);
	Mousetrap.bind('v', function() {
		if ($scope.stories[$scope.currentStory]) {
			window.open($scope.stories[$scope.currentStory].Link);
		}
	});
	Mousetrap.bind('shift+a', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply($scope.markAllRead());
	});
	Mousetrap.bind('a', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply("shown = 'add-subscription'");

		// need to wait for the keypress to finish before focusing
		setTimeout(function() {
			$('#add-subscription-form input').focus();
		}, 0);
	});
	Mousetrap.bind('g a', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply("shown = 'feeds'");
	});
	Mousetrap.bind('u', function() {
		$scope.$apply("toggleNav()");
	});
}
<commit_msg>Fetch visible contents after refresh<commit_after>$('.dropdown-toggle').dropdown();

function countProperties(obj) {
	var count = 0;
	for(var prop in obj) {
		if(obj.hasOwnProperty(prop))
			++count;
	}
	return count;
}

function GoreadCtrl($scope, $http) {
	$scope.loading = 0;
	$scope.contents = {};

	$scope.importOpml = function() {
		$scope.shown = 'feeds';
		$scope.loading++;
		$('#import-opml-form').ajaxForm(function() {
			$('#import-opml-form')[0].reset();
			$scope.loaded();
		});
	};

	$scope.loaded = function() {
		$scope.loading--;
	};

	$scope.http = function(method, url, data) {
		return $http({
			method: method,
			url: url,
			data: $.param(data),
			headers: {'Content-Type': 'application/x-www-form-urlencoded'}
		});
	};

	$scope.addSubscription = function() {
		if (!$scope.addFeedUrl) {
			return false;
		}
		$scope.loading++;
		var f = $('#add-subscription-form');
		$scope.http('POST', f.attr('data-url'), {
			url: $scope.addFeedUrl
		}).then(function() {
			$scope.addFeedUrl = '';
			$scope.refresh($scope.loaded);
		}, function(data) {
			alert(data.data);
			$scope.loading--;
		});
	};

	$scope.refresh = function(cb) {
		$scope.loading++;
		$scope.shown = 'feeds';
		delete $scope.currentStory;
		$http.get($('#refresh').attr('data-url-feeds'))
			.success(function(data) {
				$scope.feeds = data;
				$scope.numfeeds = 0;
				$scope.stories = [];
				$scope.unreadStories = {};
				for(var p in $scope.feeds) {
					$scope.numfeeds++;
					var f = $scope.feeds[p];
					if (!f.Stories)
						continue;
					for(var i = 0; i < f.Stories.length; i++) {
						f.Stories[i].feed = f.Feed;
						var d = new Date(f.Stories[i].Date * 1000);
						f.Stories[i].dispdate = d.toDateString();
						f.Stories[i].read = false;
						$scope.stories.push(f.Stories[i]);
						$scope.unreadStories[f.Stories[i].Id] = true;
					}
					$scope.stories.sort(function(a, b) {
						return b.Date - a.Date;
					});
				}
				if (typeof cb === 'function') cb();
				$scope.loaded();
				$scope.getContents();
			})
			.error(function() {
				if (typeof cb === 'function') cb();
				$scope.loaded();
			});
	};

	$scope.setCurrent = function(i) {
		$scope.currentStory = i;
		$scope.markRead($scope.stories[i]);
	};
	$scope.prev = function() {
		if ($scope.currentStory > 0) {
			$scope.$apply('setCurrent(currentStory - 1)');
		}
	};
	$scope.next = function() {
		if ($scope.stories && typeof $scope.currentStory === 'undefined') {
			$scope.$apply('setCurrent(0)');
		} else if ($scope.stories && $scope.currentStory < $scope.stories.length - 2) {
			$scope.$apply('setCurrent(currentStory + 1)');
		}
	};

	$scope.unread = function() {
		return countProperties($scope.unreadStories);
	};

	$scope.markRead = function(s) {
		if ($scope.unreadStories[s.Id]) {
			delete $scope.unreadStories[s.Id];
			s.read = true;
			$scope.http('POST', $('#mark-all-read').attr('data-url-read'), {
				feed: s.feed.Url,
				story: s.Id
			});
		}
	};

	$scope.markAllRead = function(s) {
		$scope.unreadStories = {};
		$scope.stories = [];
		$http.post($('#mark-all-read').attr('data-url'));
	};

	$scope.nothing = function() {
		return $scope.loading == 0 && $scope.stories && !$scope.numfeeds;
	};

	$scope.toggleNav = function() {
		$scope.nav = !$scope.nav;
	}
	$scope.navspan = function() {
		return 'span' + ($scope.nav ? '10' : '12');
	};
	$scope.navmargin = function() {
		return $scope.nav ? {} : {'margin-left': '0'};
	};

	$scope.storyClass = function(s) {
		var o = {};
		o['nocontent'] = $scope.contents[s.Id] === undefined;
		return o;
	};

	$scope.getContents = function() {
		var docViewTop = $(window).scrollTop();
		var docViewBottom = docViewTop + $(window).height();

		var stories = $('.nocontent');
		if (stories.length == 0) return;
		var onscreen = [];
		$.each(stories, function(i, v) {
			v = $(v);
			var elemTop = v.offset().top;
			var elemBottom = elemTop + v.height();
			if ((elemTop <= docViewBottom) && (elemBottom >= docViewTop)) {
				var s = $scope.stories[v.attr('data-idx')];
				onscreen.push({
					Feed: s.feed.Url,
					Story: s.Id,
				});
				// mark this as fetched so a subsequent scroll doesn't re fetch
				$scope.contents[s.Id] = '';
			}
		});
		if (onscreen.length == 0) return;
		$http.post($('#mark-all-read').attr('data-url-contents'), onscreen)
			.success(function(data) {
				for (var k in data) {
					$scope.contents[k] = data[k];
				}
			});
	};
	window.onscroll = function() {
		$scope.$apply('getContents()');
	};
	// todo: switch this cheating to a directive
	setTimeout(function() {
		$scope.$apply('getContents()');
	}, 100);

	var shortcuts = $('#shortcuts');
	Mousetrap.bind('?', function() {
		shortcuts.modal('toggle');
	});
	Mousetrap.bind('esc', function() {
		shortcuts.modal('hide');
	});
	Mousetrap.bind('r', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply($scope.refresh());
	});
	Mousetrap.bind('j', $scope.next);
	Mousetrap.bind('k', $scope.prev);
	Mousetrap.bind('v', function() {
		if ($scope.stories[$scope.currentStory]) {
			window.open($scope.stories[$scope.currentStory].Link);
		}
	});
	Mousetrap.bind('shift+a', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply($scope.markAllRead());
	});
	Mousetrap.bind('a', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply("shown = 'add-subscription'");

		// need to wait for the keypress to finish before focusing
		setTimeout(function() {
			$('#add-subscription-form input').focus();
		}, 0);
	});
	Mousetrap.bind('g a', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply("shown = 'feeds'");
	});
	Mousetrap.bind('u', function() {
		$scope.$apply("toggleNav()");
	});
}
<|endoftext|>


<commit_before>function play(selected){
    // Fetch how many games player wants to play.
    repeat = parseInt(document.getElementById('repeat').value);

    if(repeat < 1){
        return;
    }

    var temp_increases = [
      0,
      0,
      0,
    ];

    // Loop through the games.
    var loop_counter = repeat - 1;
    do{
        // Generate a random number (0, 1, or 2).
        opponent_choice = Math.floor(Math.random() * 3);

        // Determine the result of the game.
        if(selected == opponent_choice){
            // Result is a tie.
            result = 2;

        }else if((selected == 0 && opponent_choice == 2)
          || (selected == 1 && opponent_choice == 0)
          || (selected == 2 && opponent_choice == 1)){
            // Result is a win.
            result = 1;

        }else{
            // Result is a loss.
            result = 0;
        }

        // Update loss/tie/win values and store them in a temporary array.
        temp_increases[result] += 1;
    }while(loop_counter--);

    // Update losses innerHTML.
    document.getElementById('losses').innerHTML =
      parseInt(document.getElementById('losses').innerHTML)
      + temp_increases[0];

    // Update ties innerHTML.
    document.getElementById('ties').innerHTML =
      parseInt(document.getElementById('ties').innerHTML)
      + temp_increases[2];

    // Update wins innerHTML.
    document.getElementById('wins').innerHTML =
      parseInt(document.getElementById('wins').innerHTML)
      + temp_increases[1];

    // display game information, limiting information for multiple games played
    document.getElementById('result').innerHTML = 'You played '
      + ['rock', 'paper', 'scissors',][selected]
      + ' ' + repeat + ' times
'
      + 'Your opponent played '
      + (repeat > 1
        ? 'lots of stuff'
        : ['rock', 'paper', 'scissors',][opponent_choice]
      )
      + '
'
      + (repeat > 1
        ? 'You probably won some of them!'
        : 'YOU ' + ['LOSE', 'WIN', 'TIE',][result] + '!'
      );
}

function reset(){
    if(!confirm('Reset scores?')){
        return;
    }

    document.getElementById('losses').innerHTML = 0;
    document.getElementById('ties').innerHTML = 0;
    document.getElementById('wins').innerHTML = 0;

    document.getElementById('result').innerHTML = '';
}

var key = 0;
var opponent_choice = 0;
var repeat = 0;
var result = 0;
var selected = 0;

window.onkeydown = function(e){
    key = window.event ? event : e;
    key = key.charCode ? key.charCode : key.keyCode;

    // R: play rock.
    if(key == 82){
        play(0);

    // P: play paper.
    }else if(key == 80){
        play(1);

    // S: play scissors.
    }else if(key == 83){
        play(2);
    }
};
<commit_msg>improved key code fetch<commit_after>function play(selected){
    // Fetch how many games player wants to play.
    repeat = parseInt(document.getElementById('repeat').value);

    if(repeat < 1){
        return;
    }

    var temp_increases = [
      0,
      0,
      0,
    ];

    // Loop through the games.
    var loop_counter = repeat - 1;
    do{
        // Generate a random number (0, 1, or 2).
        opponent_choice = Math.floor(Math.random() * 3);

        // Determine the result of the game.
        if(selected == opponent_choice){
            // Result is a tie.
            result = 2;

        }else if((selected == 0 && opponent_choice == 2)
          || (selected == 1 && opponent_choice == 0)
          || (selected == 2 && opponent_choice == 1)){
            // Result is a win.
            result = 1;

        }else{
            // Result is a loss.
            result = 0;
        }

        // Update loss/tie/win values and store them in a temporary array.
        temp_increases[result] += 1;
    }while(loop_counter--);

    // Update losses innerHTML.
    document.getElementById('losses').innerHTML =
      parseInt(document.getElementById('losses').innerHTML)
      + temp_increases[0];

    // Update ties innerHTML.
    document.getElementById('ties').innerHTML =
      parseInt(document.getElementById('ties').innerHTML)
      + temp_increases[2];

    // Update wins innerHTML.
    document.getElementById('wins').innerHTML =
      parseInt(document.getElementById('wins').innerHTML)
      + temp_increases[1];

    // display game information, limiting information for multiple games played
    document.getElementById('result').innerHTML = 'You played '
      + ['rock', 'paper', 'scissors',][selected]
      + ' ' + repeat + ' times
'
      + 'Your opponent played '
      + (repeat > 1
        ? 'lots of stuff'
        : ['rock', 'paper', 'scissors',][opponent_choice]
      )
      + '
'
      + (repeat > 1
        ? 'You probably won some of them!'
        : 'YOU ' + ['LOSE', 'WIN', 'TIE',][result] + '!'
      );
}

function reset(){
    if(!confirm('Reset scores?')){
        return;
    }

    document.getElementById('losses').innerHTML = 0;
    document.getElementById('ties').innerHTML = 0;
    document.getElementById('wins').innerHTML = 0;

    document.getElementById('result').innerHTML = '';
}

var opponent_choice = 0;
var repeat = 0;
var result = 0;
var selected = 0;

window.onkeydown = function(e){
    var key = e.keyCode || e.which;

    // R: play rock.
    if(key == 82){
        play(0);

    // P: play paper.
    }else if(key == 80){
        play(1);

    // S: play scissors.
    }else if(key == 83){
        play(2);
    }
};
<|endoftext|>


<commit_before>#!/usr/bin/env nodemon

var fs = require('fs');
var express = require('express');
var crypto = require('crypto');
var url = require('url');
var _ = require('underscore');

var app = express();
var serverPort = 3000;

var ROOM_TYPES = {
    'shirt-sizes': [{
            value: 1,
            label: 'XS'
        }, {
            value: 2,
            label: 'S'
        }, {
            value: 3,
            label: 'M'
        }, {
            value: 4,
            label: 'L'
        }, {
            value: 5,
            label: 'XL'
        }],
    'sequence': [{
            value: 0,
            label: '0'
        }, {
            value: 1,
            label: '1'
        }, {
            value: 2,
            label: '2'
        }, {
            value: 3,
            label: '3'
        }, {
            value: 4,
            label: '4'
        }, {
            value: 5,
            label: '5'
        }, {
            value: 6,
            label: '6'
        }, {
            value: 7,
            label: '7'
        }, {
            value: 8,
            label: '8'
        }, {
            value: 9,
            label: '9'
        }, {
            value: 10,
            label: '10'
        }],
    'modified-fibonacci': [{
            value: 0,
            label: '0'
        }, {
            value: 0.5,
            label: "\u00BD"
        }, {
            value: 1,
            label: '1'
        }, {
            value: 2,
            label: '2'
        }, {
            value: 3,
            label: '3'
        }, {
            value: 5,
            label: '5'
        }, {
            value: 8,
            label: '8'
        }, {
            value: 13,
            label: '13'
        }, {
            value: 20,
            label: '20'
        }, {
            value: 40,
            label: '40'
        }, {
            value: 100,
            label: '100'
        }]
};

app.set('view engine', 'jade');
app.set('views', __dirname + '/views');

app.use(express.bodyParser());
app.use(app.router);

// TODO replace this with a real data store
var rooms = {};

function createUrl (request) {
    return url.format({
        protocol: request.protocol,
        hostname: request.host,
        port: serverPort,
        pathname: _.toArray(arguments).slice(1).join('/').replace('//', '/')
    });
}

function redirect (url) {
    return function (request, response) {
        response.redirect(301, url);
    };
}

function index (request, response) {
    response.render('index', {
        assetsUrl: app.get('assetsUrl'),
        apiUrl: app.get('apiUrl'),
        appRoot: app.get('appRoot')
    });
}

function normalizeCards (cards) {
    var prevValue = null;

    for (var i = 0; i < cards.length; i += 1) {
        var card = cards[i];
        var value;
        var label;

        if (card) {
            if (_.isString(card)) {
                if (prevValue === null) {
                    prevValue = i + 1;
                } else {
                    prevValue += 1;
                }

                value = prevValue;
                label = card;
            } else if (_.isNumber(card)) {
                prevValue = card;
                value = card;
                label = card.toString(10);
            } else {
                if (_.isNumber(card.value)) {
                    prevValue = card.value;
                    value = card.value;
                } else {
                    if (prevValue === null) {
                        prevValue = i + 1;
                    } else {
                        prevValue += 1;
                    }

                    value = prevValue;
                }

                if (_.isString(card.label)) {
                    label = card.label;
                } else {
                    label = value.toString(10);
                }
            }
        }

        cards[i] = {
            value: value,
            label: label
        };
    }
}

function create (request, response) {
    var roomType = request.body.type;
    var cards = request.body.cards;

    if (_.isArray(cards) && cards.length > 0) {
        roomType = 'custom';
        normalizeCards(cards);
    } else {
        if (! _.isString(roomType) || roomType === '') {
            roomType = 'modified-fibonacci';
        }

        cards = ROOM_TYPES[roomType];

        if (! cards) {
            response.send(400, 'Invalid room type ' + roomType);
            return;
        }
    }

    crypto.randomBytes(7, function (ex, buf) {
        if (ex) {
            response.send(500);
            console.log('Error generating random room id: ', ex);
            return;
        }

        for (var i = 0; i < buf.length; i += 1) {
            var val = buf[i] % 36;

            if (val === 0) {
                // Map 0 to o to reduce typos since they look similar
                buf[i] = 111;
            } else if (val === 1) {
                // Map 1 to l to reduce typos since they look similar
                buf[i] = 108;
            } else if (val <= 9) {
                buf[i] = val + 48;
            } else {
                buf[i] = val + 97 - 10;
            }
        }

        var room_id = buf.toString();
        var created = new Date();
        var room = {
            id: room_id,
            type: roomType,
            cards: cards,
            created: created,
            access: created,
            url: createUrl(request, request.app.get('appRoot'), room_id)
        };

        rooms[room_id] = room;
        response.send(200, room);
    });
}

if (app.get('env') === 'production') {
    // TODO
} else {
    app.set('assetsUrl', '/assets');
    app.set('appRoot', '/app/');
    app.set('apiUrl', '/api');

    app.get('/', redirect('/app/'));
    app.get('/app/', index);
    app.get('/app', redirect('/app/'));
    app.use('/assets', express['static'](__dirname + '/assets'));
    app.post('/api/create', create);
}

app.listen(serverPort);
console.log('Server is running at http://localhost:' + serverPort + ' (nodejs ' + process.version + ', ' + app.get('env') + ')');

<commit_msg>Added route for room<commit_after>#!/usr/bin/env nodemon

var fs = require('fs');
var express = require('express');
var crypto = require('crypto');
var url = require('url');
var _ = require('underscore');

var app = express();
var serverPort = 3000;

var ROOM_TYPES = {
    'shirt-sizes': [{
            value: 1,
            label: 'XS'
        }, {
            value: 2,
            label: 'S'
        }, {
            value: 3,
            label: 'M'
        }, {
            value: 4,
            label: 'L'
        }, {
            value: 5,
            label: 'XL'
        }],
    'sequence': [{
            value: 0,
            label: '0'
        }, {
            value: 1,
            label: '1'
        }, {
            value: 2,
            label: '2'
        }, {
            value: 3,
            label: '3'
        }, {
            value: 4,
            label: '4'
        }, {
            value: 5,
            label: '5'
        }, {
            value: 6,
            label: '6'
        }, {
            value: 7,
            label: '7'
        }, {
            value: 8,
            label: '8'
        }, {
            value: 9,
            label: '9'
        }, {
            value: 10,
            label: '10'
        }],
    'modified-fibonacci': [{
            value: 0,
            label: '0'
        }, {
            value: 0.5,
            label: "\u00BD"
        }, {
            value: 1,
            label: '1'
        }, {
            value: 2,
            label: '2'
        }, {
            value: 3,
            label: '3'
        }, {
            value: 5,
            label: '5'
        }, {
            value: 8,
            label: '8'
        }, {
            value: 13,
            label: '13'
        }, {
            value: 20,
            label: '20'
        }, {
            value: 40,
            label: '40'
        }, {
            value: 100,
            label: '100'
        }]
};

app.set('view engine', 'jade');
app.set('views', __dirname + '/views');

app.use(express.bodyParser());
app.use(app.router);

// TODO replace this with a real data store
var rooms = {};

function createUrl (request) {
    return url.format({
        protocol: request.protocol,
        hostname: request.host,
        port: serverPort,
        pathname: _.toArray(arguments).slice(1).join('/').replace('//', '/')
    });
}

function redirect (url) {
    return function (request, response) {
        response.redirect(301, url);
    };
}

function index (request, response) {
    response.render('index', {
        assetsUrl: app.get('assetsUrl'),
        apiUrl: app.get('apiUrl'),
        appRoot: app.get('appRoot')
    });
}

function normalizeCards (cards) {
    var prevValue = null;

    for (var i = 0; i < cards.length; i += 1) {
        var card = cards[i];
        var value;
        var label;

        if (card) {
            if (_.isString(card)) {
                if (prevValue === null) {
                    prevValue = i + 1;
                } else {
                    prevValue += 1;
                }

                value = prevValue;
                label = card;
            } else if (_.isNumber(card)) {
                prevValue = card;
                value = card;
                label = card.toString(10);
            } else {
                if (_.isNumber(card.value)) {
                    prevValue = card.value;
                    value = card.value;
                } else {
                    if (prevValue === null) {
                        prevValue = i + 1;
                    } else {
                        prevValue += 1;
                    }

                    value = prevValue;
                }

                if (_.isString(card.label)) {
                    label = card.label;
                } else {
                    label = value.toString(10);
                }
            }
        }

        cards[i] = {
            value: value,
            label: label
        };
    }
}

function create (request, response) {
    var roomType = request.body.type;
    var cards = request.body.cards;

    if (_.isArray(cards) && cards.length > 0) {
        roomType = 'custom';
        normalizeCards(cards);
    } else {
        if (! _.isString(roomType) || roomType === '') {
            roomType = 'modified-fibonacci';
        }

        cards = ROOM_TYPES[roomType];

        if (! cards) {
            response.send(400, 'Invalid room type ' + roomType);
            return;
        }
    }

    crypto.randomBytes(7, function (ex, buf) {
        if (ex) {
            response.send(500);
            console.log('Error generating random room id: ', ex);
            return;
        }

        for (var i = 0; i < buf.length; i += 1) {
            var val = buf[i] % 36;

            if (val === 0) {
                // Map 0 to o to reduce typos since they look similar
                buf[i] = 111;
            } else if (val === 1) {
                // Map 1 to l to reduce typos since they look similar
                buf[i] = 108;
            } else if (val <= 9) {
                buf[i] = val + 48;
            } else {
                buf[i] = val + 97 - 10;
            }
        }

        var room_id = buf.toString();
        var created = new Date();
        var room = {
            id: room_id,
            type: roomType,
            cards: cards,
            created: created,
            access: created,
            url: createUrl(request, request.app.get('appRoot'), room_id)
        };

        rooms[room_id] = room;
        response.send(200, room);
    });
}

if (app.get('env') === 'production') {
    // TODO
} else {
    app.set('assetsUrl', '/assets');
    app.set('appRoot', '/app/');
    app.set('apiUrl', '/api');

    app.get('/', redirect('/app/'));
    app.get('/app/', index);
    app.get('/app/:room_id', index);
    app.get('/app', redirect('/app/'));
    app.use('/assets', express['static'](__dirname + '/assets'));
    app.post('/api/create', create);
}

app.listen(serverPort);
console.log('Server is running at http://localhost:' + serverPort + ' (nodejs ' + process.version + ', ' + app.get('env') + ')');

<|endoftext|>


<commit_before>"use strict";
 /*
  * Google drive storage for ghost blog
  * @author : Robin C Samuel <hi@robinz.in> http://robinz.in
  * @updated : 10th April 2022
  *
*/

const StorageBase = require("ghost-storage-base");
const fs = require("fs");
const { google } = require("googleapis");
const drive = google.drive("v3");

var auth = config => {
  var jwtClient = new google.auth.JWT(
    config.key.client_email,
    null,
    config.key.private_key,
    ['https://www.googleapis.com/auth/drive'],
    null
  );

  return new Promise((resolve, reject) => {
    jwtClient.authorize(err => {
      if (err) {
        reject(err);
      }

      resolve(jwtClient);
    });
  });
}

var upload = (client, file) => {
  return new Promise((resolve, reject) => {
    drive.files.create({
      auth: client,
      resource: {
        name: file.name,
        mimeType: file.type
      },
      media: {
        mimeType: file.type,
        body: fs.createReadStream(file.path)
      },
      fields: 'id, fileExtension'
    }, (err, uploadedFile) => {
      if (err) {
        reject(err);
      }

      // Promise is resolved with the result of create call
      resolve(uploadedFile);
    });
  });
}

var setPermissions = (client, data) => {
  drive.permissions.create({
    auth: client,
    fileId: data.id,
    supportsAllDrives: true,
    supportsTeamDrives: true,
    resource: {
      'type': 'anyone',
      'role': 'reader',
    },
    fields: 'id',
  }, function (err, res) {
    if (err) {
      console.error(err);
    } else {
      console.log('Permission ID: ', res.id)
    }
  });
}

var get = (client, fileId, callback) => {
  drive.files.get({
    auth: client,
    fileId: fileId,
    alt: 'media'
  }, (error, response) => {
    callback(error, response);
  });
}

var remove = (client, file) => {
  return new Promise(function (resolve, reject) {
    drive.files.delete({
      auth: client,
      fileId: file
    },
    function (err, data) {
      if (err) {
        console.log(err);
        return reject(err);
      }
      resolve();
    });
  });
}

class ghostGoogleDrive extends StorageBase {
  constructor(config) {
    super();
    this.config = config;
  }

  /**
   * Saves the image to storage (the file system)
   * - image is the express image object
   * - returns a promise which ultimately returns the full url to the uploaded image
   *
   * @param image
   * @param targetDir
   * @returns {*}
   */
  save(file, targetDir) {
    return new Promise((resolve, reject) => {
      auth(this.config)
        .then(client => {
          upload(client, file)
            .then(data => {
              console.log(data);
              resolve('/content/images/' + data.id + '.' + data.fileExtension);
              setPermissions(client, data);
            });
        });
    });
  }

  /**
   * checks for existance of file (handle 404's proper)
   * @param {*} fileName
   * @param {*} targetDir
   * @returns Promise.<*>
   */
  exists(fileName, targetDir) {
    return new Promise((resolve, reject) => {
      auth(this.config)
        .then(client => {
          get(client, fileName, (error, response) => {
            if (error) {
              console.error(fileName, " not found");
              resolve(false);
            }

            resolve(true);
          })
        });
    });
  }

  /**
   * TODO: implement 404 functionality
   * @returns {serveStaticContent}
   */
  serve() {
    let _this = this;
    return function serveContent(req, res, next) {
      // get the file id from url
      var fileId = req.path.replace('/', '').split('.')[0];
      _this.exists(fileId).then(() => {
        auth(_this.config).then(client => {
          drive.files.get({ auth: client, fileId: fileId, alt: "media" }, { responseType: "stream" },
            function (err, resp) {
              if (err) {
                console.error("fileId: " + fileId, "err: " + err);
                next();
                return;
              }
              resp.data.on("end", () => {
                console.log("Done");
              }).on("error", err => {
                console.log("Error", err);
              })
              .pipe(res);
          });
        });
      });
    }
  }

  /**
   * @returns {Promise.<*>}
   */
  delete() {
    return function deleteContent(req, res, next) {
      var fileId = req.path.replace('/', '').split('.')[0];
      auth(this.config)
        .then(client => {
          remove(client, fileId)
        });
    };
  }

  /**
   * Reads bytes from disk for a target image
   * - path of target image (without content path!)
   *
   * @param options
   */
  read(options) {
    var fileId = options.path.replace('/', '').split('.')[0];
    console.log(fileId,options);
    return new Promise((resolve, reject) => {
      auth(this.config)
        .then(client => {
          get(client, fileId, (error, response) => {
            if (error) {
              reject(error);
            }

            resolve(response);
          });
        });
    });
  }
}

module.exports = ghostGoogleDrive;
<commit_msg>data => resp.data<commit_after>"use strict";
 /*
  * Google drive storage for ghost blog
  * @author : Robin C Samuel <hi@robinz.in> http://robinz.in
  * @updated : 10th April 2022
  *
*/

const StorageBase = require("ghost-storage-base");
const fs = require("fs");
const { google } = require("googleapis");
const drive = google.drive("v3");

var auth = config => {
  var jwtClient = new google.auth.JWT(
    config.key.client_email,
    null,
    config.key.private_key,
    ['https://www.googleapis.com/auth/drive'],
    null
  );

  return new Promise((resolve, reject) => {
    jwtClient.authorize(err => {
      if (err) {
        reject(err);
      }

      resolve(jwtClient);
    });
  });
}

var upload = (client, file) => {
  return new Promise((resolve, reject) => {
    drive.files.create({
      auth: client,
      resource: {
        name: file.name,
        mimeType: file.type
      },
      media: {
        mimeType: file.type,
        body: fs.createReadStream(file.path)
      },
      fields: 'id, fileExtension'
    }, (err, uploadedFile) => {
      if (err) {
        reject(err);
      }

      // Promise is resolved with the result of create call
      resolve(uploadedFile);
    });
  });
}

var setPermissions = (client, data) => {
  drive.permissions.create({
    auth: client,
    fileId: data.id,
    supportsAllDrives: true,
    supportsTeamDrives: true,
    resource: {
      'type': 'anyone',
      'role': 'reader',
    },
    fields: 'id',
  }, function (err, res) {
    if (err) {
      console.error(err);
    } else {
      console.log('Permission ID: ', res.id)
    }
  });
}

var get = (client, fileId, callback) => {
  drive.files.get({
    auth: client,
    fileId: fileId,
    alt: 'media'
  }, (error, response) => {
    callback(error, response);
  });
}

var remove = (client, file) => {
  return new Promise(function (resolve, reject) {
    drive.files.delete({
      auth: client,
      fileId: file
    },
    function (err, data) {
      if (err) {
        console.log(err);
        return reject(err);
      }
      resolve();
    });
  });
}

class ghostGoogleDrive extends StorageBase {
  constructor(config) {
    super();
    this.config = config;
  }

  /**
   * Saves the image to storage (the file system)
   * - image is the express image object
   * - returns a promise which ultimately returns the full url to the uploaded image
   *
   * @param image
   * @param targetDir
   * @returns {*}
   */
  save(file, targetDir) {
    return new Promise((resolve, reject) => {
      auth(this.config)
        .then(client => {
          upload(client, file)
            .then(resp => {
              console.log(resp);
              resolve('/content/images/' + resp.data.id + '.' + resp.data.fileExtension);
              setPermissions(client, resp.data);
            });
        });
    });
  }

  /**
   * checks for existance of file (handle 404's proper)
   * @param {*} fileName
   * @param {*} targetDir
   * @returns Promise.<*>
   */
  exists(fileName, targetDir) {
    return new Promise((resolve, reject) => {
      auth(this.config)
        .then(client => {
          get(client, fileName, (error, response) => {
            if (error) {
              console.error(fileName, " not found");
              resolve(false);
            }

            resolve(true);
          })
        });
    });
  }

  /**
   * TODO: implement 404 functionality
   * @returns {serveStaticContent}
   */
  serve() {
    let _this = this;
    return function serveContent(req, res, next) {
      // get the file id from url
      var fileId = req.path.replace('/', '').split('.')[0];
      _this.exists(fileId).then(() => {
        auth(_this.config).then(client => {
          drive.files.get({ auth: client, fileId: fileId, alt: "media" }, { responseType: "stream" },
            function (err, resp) {
              if (err) {
                console.error("fileId: " + fileId, "err: " + err);
                next();
                return;
              }
              resp.data.on("end", () => {
                console.log("Done");
              }).on("error", err => {
                console.log("Error", err);
              })
              .pipe(res);
          });
        });
      });
    }
  }

  /**
   * @returns {Promise.<*>}
   */
  delete() {
    return function deleteContent(req, res, next) {
      var fileId = req.path.replace('/', '').split('.')[0];
      auth(this.config)
        .then(client => {
          remove(client, fileId)
        });
    };
  }

  /**
   * Reads bytes from disk for a target image
   * - path of target image (without content path!)
   *
   * @param options
   */
  read(options) {
    var fileId = options.path.replace('/', '').split('.')[0];
    console.log(fileId,options);
    return new Promise((resolve, reject) => {
      auth(this.config)
        .then(client => {
          get(client, fileId, (error, response) => {
            if (error) {
              reject(error);
            }

            resolve(response);
          });
        });
    });
  }
}

module.exports = ghostGoogleDrive;
<|endoftext|>


<commit_before>/*
 *   兼容所有的浏览器支持表单的html5属性，表现形式与谷歌和火狐一致，目前支持：
 *       autofocus、placeholder、required、pattern、email
 *   如果您对此进行了改进，请发一份到我的邮箱里，谢谢！
 *   @author Silva Zhou
 *   @email zhoulinspq2006@126.com
 *   @qq 294692513
 *   @site https://github.com/silvajs/jquery-webform
 */
;
(function($) {

    //class
    var css = {
        '.webform-placeholderWraper': 'position:relative;line-height:normal;overflow:hidden;',
        '.webform-placeholderLabel': 'visibility:visible;cursor:text;position:absolute;left:0;top:0;color:#aa9f9f;display:block;line-height:16px;font-size:14px;white-space:nowrap;font-weight:normal;margin:0;',
        '.webform-alert': ' position:absolute;box-shadow:2px 2px 5px rgba(0,0,0,0.2),-2px 0 5px rgba(0,0,0,0.2)',
        '.webform-alert-filter': 'background:#F0F0F0; position:absolute;top:0;left:0;bottom:0;right:0;background-repeat: repeat-x;',
        '.webform-alert-content': ' padding:12px 15px;border:1px solid #bab3b3;border-top-color:#c4bbbb; border-radius:3px;font-size:12px;font-family:"微软雅黑";zoom:1;position:relative;',
        '.webform-alert-icon': ' position:absolute;width:0;height:0;border:6px solid #c4bbbb;border-top-color:transparent;border-left-color:transparent;border-right-color:transparent;top:-12px;left:10px;z-index:2;',
        '.webform-alert-icon2': ' border-bottom-color:#fff;top:-11px;'
    };

    var gradient = [
        'background-image: linear-gradient(top, #FFFFFF, #F0F0F0);background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#FFFFFF), to(#F0F0F0))',
        'filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#FFFFFF", endColorstr="#F0F0F0")',
        'background-image: -o-linear-gradient(top, #FFFFFF 0%, #F0F0F0 100%)',
        'background-image: linear-gradient(to bottom, #FFFFFF 0%, #F0F0F0 100%)'
    ];

    css['.webform-alert-filter'] += gradient.join(';');

    var inputElem = document.createElement('input');

    var html5Attrs = 'autocomplete autofocus list placeholder max min multiple pattern required step maxlength minlength'.split(' ');
    var methods = ['required', 'pattern', 'email', 'minlength'];

    function getTitle(input) {
        var $input = $(input);
        return $input.attr('title') ? '：' + $input.attr('title') : '';
    }

    function format(template) {
        var params = Array.prototype.slice.call(arguments, 1);
        $.each(params, function(i, n) {
            template = template.replace(new RegExp("\\{" + i + "\\}", "g"), function() {
                return n;
            });
        });
        return template;
    }

    //添加样式
    ;
    (function(styles) {
        var styleSheet,
            i = 0;
        if (document.createStyleSheet) {
            styleSheet = document.createStyleSheet();
        } else {
            var head = document.getElementsByTagName('head')[0];
            head.appendChild(document.createElement('style'));
            styleSheet = document.styleSheets[document.styleSheets.length - 1];
        }
        //传入的是css对象
        for (var selector in styles) {
            if (styleSheet.insertRule) {
                styleSheet.insertRule(selector + '{' + styles[selector] + '}', i++);
            } else {
                styleSheet.addRule(selector, styles[selector], i++);
            }
        }
    })(css);

    $.fn.textFocus = function(v) {
        var v = typeof v === 'undefined' ? 'all' : parseInt(v);
        this.each(function() {
            var range, len;
            try {
                if (this.createTextRange) {
                    range = this.createTextRange();
                    v === 'all' ? range.collapse(false) : range.move("character", v);
                    range.select();
                } else {
                    len = this.value.length;
                    v === 'all' ? this.setSelectionRange(len, len) : this.setSelectionRange(v, v);
                }
            } catch (e) {
                if (console && console.log) {
                    console.log(e.message);
                }
            }
            this.focus();
        });
        return this;
    };

    var Webform = function(form, options) {
        var me = this;
        this.options = $.extend({}, defaults, options || {});
        this.init(form);
    };

    Webform.prototype.init = function(form) {
        this.$form = $(form);
        if (this.options.forceSimulate) {
            this.$form.attr('novalidate', 'novalidate');
        }
        this.setInput(html5Attrs);
        this.runMethods();
        this.registeEvents();
    };

    Webform.prototype.setInput = function(props) {
        this.input = {};
        for (var i = 0, len = props.length; i < len; i++) {
            if (this.options.forceSimulate) {
                this.input[props[i]] = false;
            } else {
                this.input[props[i]] = !!(props[i] in inputElem);
            }
        }
        if (this.input.list) {
            this.input.list = !!(document.createElement('datalist') && window.HTMLDataListElement);
        }
    };

    Webform.prototype.runMethods = function() {
        this.placeholder();
        this.autofocus();
    };

    Webform.prototype.registeEvents = function() {
        var me = this;
        this.$form.on('submit', function(e) {
            return me.runValidators();
        });
        this.$form.on('click', '[type="submit"]', function(e) {
            e.stopPropagation();
        });
        $(document).click(function() {
            me.removeAlert();
        });
    };

    Webform.prototype.runValidators = function() {
        var form = this.$form.get(0);
        var inputs = $.grep(form.elements, function(elem, i) {
            return !$(elem).is(":disabled") && /^(?:input|textarea)/i.test(elem.nodeName);
        });
        for (var i = 0; i < inputs.length; i++) {
            var elem = inputs[i];
            if (!this.runValidator(elem)) {
                return false;
            }
        }
        return true;
    };

    Webform.prototype.runValidator = function(elem) {
        for (var j = 0; j < methods.length; j++) {
            var method = methods[j];
            if (this[method] && !this[method](elem)) {
                return false;
            }
        }
        return true;
    };

    //模拟placeholder效果
    Webform.prototype.placeholder = function() {
        //支持placeholder，无需模拟
        if (this.input.placeholder || !this.options.placeholder) return;

        //给input增加模拟的html标签
        $('[placeholder]').each(function(i, dom) {
            var input = $(dom);
            var text = input.attr('placeholder');
            input.removeAttr('placeholder');
            var id = dom.id || Math.random(),
                paddingLeft = input.css('padding-left'),
                paddingTop = input.css('padding-top'),
                paddingBottom = input.css('padding-bottom'),
                height = input.css('height'),
                borderLeftWidth = input.css('border-left-width'),
                borderTopWidth = input.css('border-top-width'),
                borderBottomWidth = input.css('border-bottom-width'),
                boxSizing = input.css('box-sizing'),
                lineHeight = input.height() + 'px';
            var style = [
                'padding-left:' + paddingLeft,
                'padding-top:' + paddingTop,
                'padding-bottom:' + paddingBottom,
                'height:' + height,
                'line-height:' + lineHeight,
                'border-left-width:' + borderLeftWidth,
                'border-top-width:' + borderTopWidth,
                'border-bottom-width:' + borderBottomWidth,
                'border-style:solid',
                'border-color:transparent',
                'box-sizing:' + boxSizing
            ];
            if (input.val().length > 0) {
                style.push('visibility:hidden');
            }
            dom.id = id;
            input.wrap('

').after('' + text + '');

        }).on('keydown.placeholder', function() {
            var me = this;
            var placeholderLabel = $(this).next('.webform-placeholderLabel');
            setTimeout(function() {
                if (me.value !== '') {
                    placeholderLabel.css('visibility', 'hidden');
                } else {
                    placeholderLabel.css('visibility', 'visible');
                }
            }, 0);

        });

    };

    //模拟自动聚焦
    Webform.prototype.autofocus = function() {
        if (this.input.autofocus) return;

        setTimeout(function() {
            $('[autofocus]').last().focus();
        }, 0);
    };

    //文本框必须输入
    Webform.prototype.required = function(elem) {
        if (this.input.required) return true;

        var input = $(elem);
        if (input.attr('required') && input.val().length === 0) {
            var text = this.options.messages.required + getTitle(input);
            this.alert(text, input);
            return false;
        }
        return true;
    };

    //pattern验证输入
    Webform.prototype.pattern = function(elem) {
        if (this.input.pattern) return true;

        var input = $(elem);
        if (input.attr('pattern')) {
            var reg = new RegExp(input.attr('pattern'));
            if (input.val().length > 0 && !reg.test(input.val())) {
                var text = this.options.messages.pattern + getTitle(input);
                this.alert(text, input);
                return false;
            }
        }
        return true;
    };

    Webform.prototype.email = function(elem) {
        if ($(elem).attr('type') !== 'email') {
            return true;
        }
        var value = elem.value;
        if (!/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(value)) {
            var text = this.options.messages.email + getTitle(elem);
            this.alert(text, elem);
            return false;
        }
        return true;
    };

    Webform.prototype.minlength = function(elem) {
        var $el = $(elem);
        if (!$el.attr('minlength')) {
            return true;
        }
        var minlength = parseInt($el.attr('minlength'));
        var value = $el.val();
        if (value.length < minlength) {
            var text = format(this.options.messages.minlength, minlength);
            this.alert(text, $el);
            return false;
        }
        return true;
    };

    Webform.prototype.minlength = function(elem) {
        var $el = $(elem);
        if (!$el.attr('maxlength')) {
            return true;
        }
        var maxlength = parseInt($el.attr('maxlength'));
        var value = $el.val();
        if (value.length > maxlength) {
            var text = format(this.options.messages.maxlength, maxlength);
            this.alert(text, $el);
            return false;
        }
        return true;
    };

    Webform.prototype.alert = function(text, input) {
        if (this.alertDialog) {
            this.alertDialog.remove();
        }
        $(input).attr('autocomplete', this.options.autocomplete);
        this.alertDialog = new Alert(text, input);
        input.focus();
    };

    Webform.prototype.removeAlert = function() {
        if (this.alertDialog) {
            this.alertDialog.remove();
            this.alertDialog = null;
        }
    };

    //弹出提示信息
    var Alert = function(text, input) {
        this.init(text, input);
    };

    Alert.prototype = {
        timeout: 500000,

        init: function(text, input) {
            if (this.dialog) return;

            //显示提示信息
            this.$input = $(input);
            var offset = this.$input.offset(),
                x = offset.left + 5,
                y = offset.top + this.$input.outerHeight() + 6;
            this.add(text, x, y);
            this.setTimer();
        },

        add: function(text, x, y) {
            var me = this;
            var html = ['
',
                '

',
                '

',
                '

',
                '
',
                text,
                '
',
                '
'
            ].join('');
            this.dialog = $(html);
            this.dialog.css({
                left: (x || 0) + 'px',
                top: (y || 0) + 'px'
            });
            $(document.body).append(this.dialog);
            this.registerEvents();
            return this;
        },

        remove: function() {
            this.dialog && this.dialog.remove();
            this.dialog = null;
            this.timer && clearTimeout(this.timer);
            this.off();
        },

        registerEvents: function() {
            var me = this;
            this.$input.on('keydown.webform', function() {
                var input = this;
                setTimeout(function() {
                    var webform = $(input.form).data('webform');
                    var pass = webform.runValidator(input);
                    if (pass) {
                        me.remove();
                    }
                }, 0);
            });
            return this;
        },

        off: function() {
            this.$input.off('keydown.webform');
        },

        setTimer: function() {
            var me = this;
            this.timer = setTimeout(function() {
                me.remove();
                me.timer = null;
            }, this.timeout);
        },

        resetTimer: function() {
            this.timer && clearTimeout(this.timer);
            this.setTimer();
        },

        content: function(text) {
            if (!this.dialog) return;
            this.dialog.find('.webform-alert-content').html(text);
        }
    };

    $.fn.webform = function(options) {
        this.each(function() {
            var data = $(this).data('webform');
            if (!data) {
                options = options || {};
                var messages = $.extend({}, $.fn.webform.messages, options.messages || {});
                options.messages = messages;
                $(this).data('webform', new Webform(this, options));
            }
        });
    };

    var defaults = {
        forceSimulate: true,
        placeholder: true,
        autocomplete: 'off',
        messages: null
    };

    $.fn.webform.messages = {
        required: '请填写此字段',
        pattern: '请匹配要求的格式',
        email: '请输入有效的邮箱地址',
        minlength: '请至少输入{0}个字符',
        maxlength: '最多输入{0}个字符'
    };

    $.fn.webform.addMethod = function(method, fn) {
        if (Webform.prototype[method]) {
            throw 'this method ' + method + ' has been existed';
        }
        methods.push(method);
        Webform.prototype[method] = fn;
    };

})(jQuery);<commit_msg>maxlength<commit_after>/*
 *   兼容所有的浏览器支持表单的html5属性，表现形式与谷歌和火狐一致，目前支持：
 *       autofocus、placeholder、required、pattern、email
 *   如果您对此进行了改进，请发一份到我的邮箱里，谢谢！
 *   @author Silva Zhou
 *   @email zhoulinspq2006@126.com
 *   @qq 294692513
 *   @site https://github.com/silvajs/jquery-webform
 */
;
(function($) {

    //class
    var css = {
        '.webform-placeholderWraper': 'position:relative;line-height:normal;overflow:hidden;',
        '.webform-placeholderLabel': 'visibility:visible;cursor:text;position:absolute;left:0;top:0;color:#aa9f9f;display:block;line-height:16px;font-size:14px;white-space:nowrap;font-weight:normal;margin:0;',
        '.webform-alert': ' position:absolute;box-shadow:2px 2px 5px rgba(0,0,0,0.2),-2px 0 5px rgba(0,0,0,0.2)',
        '.webform-alert-filter': 'background:#F0F0F0; position:absolute;top:0;left:0;bottom:0;right:0;background-repeat: repeat-x;',
        '.webform-alert-content': ' padding:12px 15px;border:1px solid #bab3b3;border-top-color:#c4bbbb; border-radius:3px;font-size:12px;font-family:"微软雅黑";zoom:1;position:relative;',
        '.webform-alert-icon': ' position:absolute;width:0;height:0;border:6px solid #c4bbbb;border-top-color:transparent;border-left-color:transparent;border-right-color:transparent;top:-12px;left:10px;z-index:2;',
        '.webform-alert-icon2': ' border-bottom-color:#fff;top:-11px;'
    };

    var gradient = [
        'background-image: linear-gradient(top, #FFFFFF, #F0F0F0);background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#FFFFFF), to(#F0F0F0))',
        'filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#FFFFFF", endColorstr="#F0F0F0")',
        'background-image: -o-linear-gradient(top, #FFFFFF 0%, #F0F0F0 100%)',
        'background-image: linear-gradient(to bottom, #FFFFFF 0%, #F0F0F0 100%)'
    ];

    css['.webform-alert-filter'] += gradient.join(';');

    var inputElem = document.createElement('input');

    var html5Attrs = 'autocomplete autofocus list placeholder max min multiple pattern required step maxlength minlength'.split(' ');
    var methods = ['required', 'pattern', 'email', 'minlength'];

    function getTitle(input) {
        var $input = $(input);
        return $input.attr('title') ? '：' + $input.attr('title') : '';
    }

    function format(template) {
        var params = Array.prototype.slice.call(arguments, 1);
        $.each(params, function(i, n) {
            template = template.replace(new RegExp("\\{" + i + "\\}", "g"), function() {
                return n;
            });
        });
        return template;
    }

    //添加样式
    ;
    (function(styles) {
        var styleSheet,
            i = 0;
        if (document.createStyleSheet) {
            styleSheet = document.createStyleSheet();
        } else {
            var head = document.getElementsByTagName('head')[0];
            head.appendChild(document.createElement('style'));
            styleSheet = document.styleSheets[document.styleSheets.length - 1];
        }
        //传入的是css对象
        for (var selector in styles) {
            if (styleSheet.insertRule) {
                styleSheet.insertRule(selector + '{' + styles[selector] + '}', i++);
            } else {
                styleSheet.addRule(selector, styles[selector], i++);
            }
        }
    })(css);

    $.fn.textFocus = function(v) {
        var v = typeof v === 'undefined' ? 'all' : parseInt(v);
        this.each(function() {
            var range, len;
            try {
                if (this.createTextRange) {
                    range = this.createTextRange();
                    v === 'all' ? range.collapse(false) : range.move("character", v);
                    range.select();
                } else {
                    len = this.value.length;
                    v === 'all' ? this.setSelectionRange(len, len) : this.setSelectionRange(v, v);
                }
            } catch (e) {
                if (console && console.log) {
                    console.log(e.message);
                }
            }
            this.focus();
        });
        return this;
    };

    var Webform = function(form, options) {
        var me = this;
        this.options = $.extend({}, defaults, options || {});
        this.init(form);
    };

    Webform.prototype.init = function(form) {
        this.$form = $(form);
        if (this.options.forceSimulate) {
            this.$form.attr('novalidate', 'novalidate');
        }
        this.setInput(html5Attrs);
        this.runMethods();
        this.registeEvents();
    };

    Webform.prototype.setInput = function(props) {
        this.input = {};
        for (var i = 0, len = props.length; i < len; i++) {
            if (this.options.forceSimulate) {
                this.input[props[i]] = false;
            } else {
                this.input[props[i]] = !!(props[i] in inputElem);
            }
        }
        if (this.input.list) {
            this.input.list = !!(document.createElement('datalist') && window.HTMLDataListElement);
        }
    };

    Webform.prototype.runMethods = function() {
        this.placeholder();
        this.autofocus();
    };

    Webform.prototype.registeEvents = function() {
        var me = this;
        this.$form.on('submit', function(e) {
            return me.runValidators();
        });
        this.$form.on('click', '[type="submit"]', function(e) {
            e.stopPropagation();
        });
        $(document).click(function() {
            me.removeAlert();
        });
    };

    Webform.prototype.runValidators = function() {
        var form = this.$form.get(0);
        var inputs = $.grep(form.elements, function(elem, i) {
            return !$(elem).is(":disabled") && /^(?:input|textarea)/i.test(elem.nodeName);
        });
        for (var i = 0; i < inputs.length; i++) {
            var elem = inputs[i];
            if (!this.runValidator(elem)) {
                return false;
            }
        }
        return true;
    };

    Webform.prototype.runValidator = function(elem) {
        for (var j = 0; j < methods.length; j++) {
            var method = methods[j];
            if (this[method] && !this[method](elem)) {
                return false;
            }
        }
        return true;
    };

    //模拟placeholder效果
    Webform.prototype.placeholder = function() {
        //支持placeholder，无需模拟
        if (this.input.placeholder || !this.options.placeholder) return;

        //给input增加模拟的html标签
        $('[placeholder]').each(function(i, dom) {
            var input = $(dom);
            var text = input.attr('placeholder');
            input.removeAttr('placeholder');
            var id = dom.id || Math.random(),
                paddingLeft = input.css('padding-left'),
                paddingTop = input.css('padding-top'),
                paddingBottom = input.css('padding-bottom'),
                height = input.css('height'),
                borderLeftWidth = input.css('border-left-width'),
                borderTopWidth = input.css('border-top-width'),
                borderBottomWidth = input.css('border-bottom-width'),
                boxSizing = input.css('box-sizing'),
                lineHeight = input.height() + 'px';
            var style = [
                'padding-left:' + paddingLeft,
                'padding-top:' + paddingTop,
                'padding-bottom:' + paddingBottom,
                'height:' + height,
                'line-height:' + lineHeight,
                'border-left-width:' + borderLeftWidth,
                'border-top-width:' + borderTopWidth,
                'border-bottom-width:' + borderBottomWidth,
                'border-style:solid',
                'border-color:transparent',
                'box-sizing:' + boxSizing
            ];
            if (input.val().length > 0) {
                style.push('visibility:hidden');
            }
            dom.id = id;
            input.wrap('

').after('' + text + '');

        }).on('keydown.placeholder', function() {
            var me = this;
            var placeholderLabel = $(this).next('.webform-placeholderLabel');
            setTimeout(function() {
                if (me.value !== '') {
                    placeholderLabel.css('visibility', 'hidden');
                } else {
                    placeholderLabel.css('visibility', 'visible');
                }
            }, 0);

        });

    };

    //模拟自动聚焦
    Webform.prototype.autofocus = function() {
        if (this.input.autofocus) return;

        setTimeout(function() {
            $('[autofocus]').last().focus();
        }, 0);
    };

    //文本框必须输入
    Webform.prototype.required = function(elem) {
        if (this.input.required) return true;

        var input = $(elem);
        if (input.attr('required') && input.val().length === 0) {
            var text = this.options.messages.required + getTitle(input);
            this.alert(text, input);
            return false;
        }
        return true;
    };

    //pattern验证输入
    Webform.prototype.pattern = function(elem) {
        if (this.input.pattern) return true;

        var input = $(elem);
        if (input.attr('pattern')) {
            var reg = new RegExp(input.attr('pattern'));
            if (input.val().length > 0 && !reg.test(input.val())) {
                var text = this.options.messages.pattern + getTitle(input);
                this.alert(text, input);
                return false;
            }
        }
        return true;
    };

    Webform.prototype.email = function(elem) {
        if ($(elem).attr('type') !== 'email') {
            return true;
        }
        var value = elem.value;
        if (!/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(value)) {
            var text = this.options.messages.email + getTitle(elem);
            this.alert(text, elem);
            return false;
        }
        return true;
    };

    Webform.prototype.minlength = function(elem) {
        var $el = $(elem);
        if (!$el.attr('minlength')) {
            return true;
        }
        var minlength = parseInt($el.attr('minlength'));
        var value = $el.val();
        if (value.length < minlength) {
            var text = format(this.options.messages.minlength, minlength);
            this.alert(text, $el);
            return false;
        }
        return true;
    };

    Webform.prototype.maxlength = function(elem) {
        var $el = $(elem);
        if (!$el.attr('maxlength')) {
            return true;
        }
        var maxlength = parseInt($el.attr('maxlength'));
        var value = $el.val();
        if (value.length > maxlength) {
            var text = format(this.options.messages.maxlength, maxlength);
            this.alert(text, $el);
            return false;
        }
        return true;
    };

    Webform.prototype.alert = function(text, input) {
        if (this.alertDialog) {
            this.alertDialog.remove();
        }
        $(input).attr('autocomplete', this.options.autocomplete);
        this.alertDialog = new Alert(text, input);
        input.focus();
    };

    Webform.prototype.removeAlert = function() {
        if (this.alertDialog) {
            this.alertDialog.remove();
            this.alertDialog = null;
        }
    };

    //弹出提示信息
    var Alert = function(text, input) {
        this.init(text, input);
    };

    Alert.prototype = {
        timeout: 500000,

        init: function(text, input) {
            if (this.dialog) return;

            //显示提示信息
            this.$input = $(input);
            var offset = this.$input.offset(),
                x = offset.left + 5,
                y = offset.top + this.$input.outerHeight() + 6;
            this.add(text, x, y);
            this.setTimer();
        },

        add: function(text, x, y) {
            var me = this;
            var html = ['
',
                '

',
                '

',
                '

',
                '
',
                text,
                '
',
                '
'
            ].join('');
            this.dialog = $(html);
            this.dialog.css({
                left: (x || 0) + 'px',
                top: (y || 0) + 'px'
            });
            $(document.body).append(this.dialog);
            this.registerEvents();
            return this;
        },

        remove: function() {
            this.dialog && this.dialog.remove();
            this.dialog = null;
            this.timer && clearTimeout(this.timer);
            this.off();
        },

        registerEvents: function() {
            var me = this;
            this.$input.on('keydown.webform', function() {
                var input = this;
                setTimeout(function() {
                    var webform = $(input.form).data('webform');
                    var pass = webform.runValidator(input);
                    if (pass) {
                        me.remove();
                    }
                }, 0);
            });
            return this;
        },

        off: function() {
            this.$input.off('keydown.webform');
        },

        setTimer: function() {
            var me = this;
            this.timer = setTimeout(function() {
                me.remove();
                me.timer = null;
            }, this.timeout);
        },

        resetTimer: function() {
            this.timer && clearTimeout(this.timer);
            this.setTimer();
        },

        content: function(text) {
            if (!this.dialog) return;
            this.dialog.find('.webform-alert-content').html(text);
        }
    };

    $.fn.webform = function(options) {
        this.each(function() {
            var data = $(this).data('webform');
            if (!data) {
                options = options || {};
                var messages = $.extend({}, $.fn.webform.messages, options.messages || {});
                options.messages = messages;
                $(this).data('webform', new Webform(this, options));
            }
        });
    };

    var defaults = {
        forceSimulate: true,
        placeholder: true,
        autocomplete: 'off',
        messages: null
    };

    $.fn.webform.messages = {
        required: '请填写此字段',
        pattern: '请匹配要求的格式',
        email: '请输入有效的邮箱地址',
        minlength: '请至少输入{0}个字符',
        maxlength: '最多输入{0}个字符'
    };

    $.fn.webform.addMethod = function(method, fn) {
        if (Webform.prototype[method]) {
            throw 'this method ' + method + ' has been existed';
        }
        methods.push(method);
        Webform.prototype[method] = fn;
    };

})(jQuery);<|endoftext|>