<commit_before>import React, { Component, Fragment } from 'react'
import { Head } from 'react-static'
import { api } from '../../data'
import {
  ThemeProvider,
  Card,
  Container,
  Box,
  Flex,
  Text,
  Heading,
  Button,
  LargeButton,
  Link as DSLink,
  cx
} from '@hackclub/design-system'
import LeaderInviteForm from '../components/LeaderInviteForm'
import LoadingAnimation from '../components/LoadingAnimation'
import SubmitButton from '../components/SubmitButton'
import Login from '../components/Login'
import ApplyNav from '../components/ApplyNav'
import fetch from 'unfetch'
import { Link } from 'react-static'

LargeButton.link = LargeButton.withComponent(Link)

const A = DSLink.extend`
  :hover {
    text-decoration: underline;
  }
`

const timeSince = time => {
  const seconds = Math.floor((new Date() - new Date(time)) / 1000)
  const intervals = [
    [Math.floor(seconds / (60 * 60 * 24 * 7)), 'weeks'],
    [Math.floor(seconds / (60 * 60 * 24)), 'days'],
    [Math.floor(seconds / (60 * 60)), 'hours'],
    [Math.floor(seconds / 60), 'minutes']
  ]
  for (var i = 0; i < intervals.length; i++) {
    let interval = intervals[i]
    if (interval[0] > 1) {
      return interval.join(' ')
    }
  }
  return 'less than a minute'
}

const Neg = () => <Text.span color="error" bold children="NOT" />

const CustomCard = props => (
  <Card boxShadowSize="md" p={[3, 4]} color="black" bg="snow" {...props} />
)

const ApplicationCard = props => {
  const {
    id,
    applicant_profiles,
    updated_at,
    created_at,
    submitted_at
  } = props.app
  const { authToken, callback, app } = props

  const leaderProfile = applicant_profiles.find(
    profile => profile.applicant.id == props.applicantId
  )
  const coLeaderProfiles = applicant_profiles.filter(
    profile => profile.applicant.id != props.applicantId
  )

  return (
    <Container maxWidth={36} mt={3} p={3}>
      <CustomCard>
        <Text>You only need a team to apply. You can invite them here:</Text>
        <LeaderInviteForm id={id} authToken={authToken} callback={callback} />
        <Text>After you submit your application:</Text>

We’ll get back to you with our decision in 3 days

            If you’re accepted we’ll schedule a call to train you to lead your
            club

            We’ll give you access to our curriculum, a template for your
            meetings, and our online community of club leaders

            Once you start holding meetings we’ll check in with you each week to
            make sure everything is going well

          Contact us at team@hackclub.com{' '}
          if you have any questions while applying.

            This application was{' '}
            {submitted_at !== null
              ? 'submitted'
              : updated_at === created_at ? 'created' : 'updated'}{' '}
            {timeSince(updated_at)} ago

            You have {leaderProfile.completed_at ? null : <Neg />} finished your
            leader profile

          {coLeaderProfiles.map((profile, index) => (

              {profile.applicant.email}
              {profile.completed_at ? null : (

                  has <Neg />{' '}

              )}
              finished their leader profile

          ))}

        <Text color="slate" f={1}>

            * We also accept applications from clubs that have already held
            meetings.

        </Text>
      </CustomCard>
      <Flex
        align="center"
        justify="center"
        flexDirection={['column', 'row']}
        my={3}
        mx={[null, -2]}
      >
        <LargeButton.link
          w={1}
          m={2}
          to={`/apply/club?id=${id}`}
          children="Edit Application"
        />
        <LargeButton.link
          w={1}
          m={2}
          to={`/apply/leader?id=${leaderProfile.id}`}
          children="Edit Leader Profile"
        />
      </Flex>
      <Flex mb={3}>
        <SubmitButton authToken={authToken} application={app} />
      </Flex>
    </Container>
  )
}

class ApplicationIndex extends Component {
  constructor(props) {
    super(props)

    this.state = {
      status: 'loading',
      app: undefined,
      authToken: undefined,
      applicantId: undefined
    }

    this.populateApplications = this.populateApplications.bind(this)
  }

  populateApplications(
    authToken = this.state.authToken,
    applicantId = this.state.applicantId
  ) {
    fetch(`${api}/v1/applicants/${applicantId}/new_club_applications`, {
      headers: { Authorization: `Bearer ${authToken}` }
    })
      .then(res => {
        if (res.ok) {
          return res.json()
        } else {
          throw res
        }
      })
      .then(json => {
        if (json.length === 0) {
          return fetch(
            `${api}/v1/applicants/${applicantId}/new_club_applications`,
            {
              method: 'POST',
              headers: { Authorization: `Bearer ${authToken}` }
            }
          ).then(res => {
            if (res.ok) {
              return res.json()
            } else {
              throw res
            }
          })
        }
        return json.sort((a, b) => {
          return new Date(b.created_at) - new Date(a.created_at)
        })[0]
      })
      .then(app => {
        this.setState({
          status: 'finished',
          app: app
        })
      })
      .catch(e => {
        console.error(e)
        if (e.status === 401) {
          this.setState({ status: 'needsToAuth' })
        }
      })
  }

  componentDidMount() {
    const authToken = window.localStorage.getItem('authToken')
    const applicantId = window.localStorage.getItem('applicantId')
    this.setState({ authToken, applicantId })
    const needsToAuth = authToken === null || applicantId === null

    if (needsToAuth) {
      this.setState({ status: 'needsToAuth' })
    } else {
      this.populateApplications(authToken, applicantId)
    }
  }

  content() {
    const { app, status, authToken, applicantId } = this.state

    switch (status) {
      case 'needsToAuth':
        return <Login />
      case 'loading':
        return <LoadingAnimation />
      case 'finished':
        return (
          <Fragment>
            <ApplyNav breadcrumb={0} />
            <Heading.h1
              bg="primary"
              color="white"
              f={[5, 6]}
              mt={-1}
              py={4}
              px={3}
              align="center"
            >
              Apply to Hack Club
            </Heading.h1>
            <ApplicationCard
              app={app}
              applicantId={applicantId}
              authToken={authToken}
              callback={this.populateApplications}
            />
          </Fragment>
        )
      default:
        return <Text>Something terrible has happened.</Text>
    }
  }

  render() {
    return (
      <ThemeProvider>

          <title>Apply – Hack Club</title>

        {this.content()}
      </ThemeProvider>
    )
  }
}

export default ApplicationIndex
<commit_msg>Add commas<commit_after>import React, { Component, Fragment } from 'react'
import { Head } from 'react-static'
import { api } from '../../data'
import {
  ThemeProvider,
  Card,
  Container,
  Box,
  Flex,
  Text,
  Heading,
  Button,
  LargeButton,
  Link as DSLink,
  cx
} from '@hackclub/design-system'
import LeaderInviteForm from '../components/LeaderInviteForm'
import LoadingAnimation from '../components/LoadingAnimation'
import SubmitButton from '../components/SubmitButton'
import Login from '../components/Login'
import ApplyNav from '../components/ApplyNav'
import fetch from 'unfetch'
import { Link } from 'react-static'

LargeButton.link = LargeButton.withComponent(Link)

const A = DSLink.extend`
  :hover {
    text-decoration: underline;
  }
`

const timeSince = time => {
  const seconds = Math.floor((new Date() - new Date(time)) / 1000)
  const intervals = [
    [Math.floor(seconds / (60 * 60 * 24 * 7)), 'weeks'],
    [Math.floor(seconds / (60 * 60 * 24)), 'days'],
    [Math.floor(seconds / (60 * 60)), 'hours'],
    [Math.floor(seconds / 60), 'minutes']
  ]
  for (var i = 0; i < intervals.length; i++) {
    let interval = intervals[i]
    if (interval[0] > 1) {
      return interval.join(' ')
    }
  }
  return 'less than a minute'
}

const Neg = () => <Text.span color="error" bold children="NOT" />

const CustomCard = props => (
  <Card boxShadowSize="md" p={[3, 4]} color="black" bg="snow" {...props} />
)

const ApplicationCard = props => {
  const {
    id,
    applicant_profiles,
    updated_at,
    created_at,
    submitted_at
  } = props.app
  const { authToken, callback, app } = props

  const leaderProfile = applicant_profiles.find(
    profile => profile.applicant.id == props.applicantId
  )
  const coLeaderProfiles = applicant_profiles.filter(
    profile => profile.applicant.id != props.applicantId
  )

  return (
    <Container maxWidth={36} mt={3} p={3}>
      <CustomCard>
        <Text>You only need a team to apply. Invite them here:</Text>
        <LeaderInviteForm id={id} authToken={authToken} callback={callback} />
        <Text>After you submit your application:</Text>

We’ll get back to you with our decision in 3 days

            If you’re accepted, we’ll schedule a call to train you to lead your
            club

            We’ll give you access to our curriculum, a template for your
            meetings, and our online community of club leaders

            Once you start holding meetings, we’ll check in with you each week to
            make sure everything is going well

          Contact us at team@hackclub.com{' '}
          if you have any questions while applying.

            This application was{' '}
            {submitted_at !== null
              ? 'submitted'
              : updated_at === created_at ? 'created' : 'updated'}{' '}
            {timeSince(updated_at)} ago

            You have {leaderProfile.completed_at ? null : <Neg />} finished your
            leader profile

          {coLeaderProfiles.map((profile, index) => (

              {profile.applicant.email}
              {profile.completed_at ? null : (

                  has <Neg />{' '}

              )}
              finished their leader profile

          ))}

        <Text color="slate" f={1}>

            * We also accept applications from clubs that have already held
            meetings.

        </Text>
      </CustomCard>
      <Flex
        align="center"
        justify="center"
        flexDirection={['column', 'row']}
        my={3}
        mx={[null, -2]}
      >
        <LargeButton.link
          w={1}
          m={2}
          inverted
          to={`/apply/club?id=${id}`}
          children="Edit Application"
        />
        <LargeButton.link
          w={1}
          m={2}
          inverted
          to={`/apply/leader?id=${leaderProfile.id}`}
          children="Edit Leader Profile"
        />
      </Flex>
      <Flex mb={3}>
        <SubmitButton authToken={authToken} application={app} />
      </Flex>
    </Container>
  )
}

class ApplicationIndex extends Component {
  constructor(props) {
    super(props)

    this.state = {
      status: 'loading',
      app: undefined,
      authToken: undefined,
      applicantId: undefined
    }

    this.populateApplications = this.populateApplications.bind(this)
  }

  populateApplications(
    authToken = this.state.authToken,
    applicantId = this.state.applicantId
  ) {
    fetch(`${api}/v1/applicants/${applicantId}/new_club_applications`, {
      headers: { Authorization: `Bearer ${authToken}` }
    })
      .then(res => {
        if (res.ok) {
          return res.json()
        } else {
          throw res
        }
      })
      .then(json => {
        if (json.length === 0) {
          return fetch(
            `${api}/v1/applicants/${applicantId}/new_club_applications`,
            {
              method: 'POST',
              headers: { Authorization: `Bearer ${authToken}` }
            }
          ).then(res => {
            if (res.ok) {
              return res.json()
            } else {
              throw res
            }
          })
        }
        return json.sort((a, b) => {
          return new Date(b.created_at) - new Date(a.created_at)
        })[0]
      })
      .then(app => {
        this.setState({
          status: 'finished',
          app: app
        })
      })
      .catch(e => {
        console.error(e)
        if (e.status === 401) {
          this.setState({ status: 'needsToAuth' })
        }
      })
  }

  componentDidMount() {
    const authToken = window.localStorage.getItem('authToken')
    const applicantId = window.localStorage.getItem('applicantId')
    this.setState({ authToken, applicantId })
    const needsToAuth = authToken === null || applicantId === null

    if (needsToAuth) {
      this.setState({ status: 'needsToAuth' })
    } else {
      this.populateApplications(authToken, applicantId)
    }
  }

  content() {
    const { app, status, authToken, applicantId } = this.state

    switch (status) {
      case 'needsToAuth':
        return <Login />
      case 'loading':
        return <LoadingAnimation />
      case 'finished':
        return (
          <Fragment>
            <ApplyNav breadcrumb={0} />
            <Heading.h1
              bg="primary"
              color="white"
              f={[5, 6]}
              mt={-1}
              py={4}
              px={3}
              align="center"
            >
              Apply to Hack Club
            </Heading.h1>
            <ApplicationCard
              app={app}
              applicantId={applicantId}
              authToken={authToken}
              callback={this.populateApplications}
            />
          </Fragment>
        )
      default:
        return <Text>Something terrible has happened.</Text>
    }
  }

  render() {
    return (
      <ThemeProvider>

          <title>Apply – Hack Club</title>

        {this.content()}
      </ThemeProvider>
    )
  }
}

export default ApplicationIndex
<|endoftext|>


<commit_before>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This is a fork/based on Small Tabs, written by ロシア,
 * https://addons.mozilla.org/en-US/firefox/addon/small-tabs/
 */

"use strict";

var EXPORTED_SYMBOLS = [ "ThinTabs" ];

Components.utils.import("resource://gre/modules/Services.jsm");

Components.utils.import("chrome://thintabs/content/javascript/DynamicStyleSheets.js");

var ThinTabs = {
	preferences : null,
	styleSheet : null,
	styleSheetService : null,

	getWindows : function() {
		var windows = [];

		var browserWindows = Services.wm.getEnumerator("navigator:browser");

		while (browserWindows.hasMoreElements()) {
			var browserWindow = browserWindows.getNext();
			browserWindow.QueryInterface(Components.interfaces.nsIDOMWindow);
			windows.push(browserWindow);
		}

		return windows;
	},

	handleEvent : function(event) {
		var document = event.target;
		var window = document.defaultView;

		window.removeEventListener("load", this, true);

		if (document.documentElement.getAttribute("windowtype") != "navigator:browser") {
			return;
		}

		this.loadScript(window);
	},

	loadStyle : function() {
		if (!this.styleSheetService.sheetRegistered(this.styleSheet, this.styleSheetService.USER_SHEET)) {
			this.styleSheetService.loadAndRegisterSheet(this.styleSheet, this.styleSheetService.USER_SHEET);
		}
	},

	unloadStyle : function() {
		if (this.styleSheetService.sheetRegistered(this.styleSheet, this.styleSheetService.USER_SHEET)) {
			this.styleSheetService.unregisterSheet(this.styleSheet, this.styleSheetService.USER_SHEET);
		}
	},

	loadScript : function(window) {
	},

	unloadScript : function(window) {
	},

	onOpenWindow : function(window) {
		var domWindow = window.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(
				Components.interfaces.nsIDOMWindow);
		domWindow.addEventListener("load", this, true);
	},

	onCloseWindow : function(window) {
	},

	onWindowTitleChange : function(window, title) {
	},

	observe : function(subject, topic, data) {
		if (topic != "nsPref:changed") {
			return;
		}

		this.refreshPreference(data);
	},

	refreshPreference : function(name) {
		switch (name) {
			case "close.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets.register(name, ".tab-close-button:not([pinned]) { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "icon.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets.register(name, ".tab-icon-image:not([pinned]) { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "tabs.height":
				var tabsHeight = this.preferences.getIntPref(name);

				DynamicStyleSheets
						.register(
								name,
								".tab-background-start[selected=true]::after,\
								.tab-background-start[selected=true]::before,\
								.tab-background-start,\
								.tab-background-end,\
								.tab-background-end[selected=true]::after,\
								.tab-background-end[selected=true]::before,\
								.tabbrowser-tabs {\
								height: "
										+ tabsHeight
										+ "px !important;\
								min-height: "
										+ tabsHeight
										+ "px !important;\
								}");
				break;

			case "tabs.padding.end":
				var tabsPaddingEnd = this.preferences.getIntPref(name);

				if (tabsPaddingEnd >= 0) {
					DynamicStyleSheets.register(name, ".tab-content:not([pinned]) { -moz-padding-end: "
							+ tabsPaddingEnd + "px !important; }");
				} else {
					DynamicStyleSheets.register(name,
							".tab-content:not([pinned]) { -moz-padding-end: 0px !important; -moz-margin-end: "
									+ tabsPaddingEnd + "px !important; }");
				}
				break;

			case "tabs.padding.start":
				var tabsPaddingStart = this.preferences.getIntPref(name);

				if (tabsPaddingStart >= 0) {
					DynamicStyleSheets.register(name, ".tab-content:not([pinned]) { -moz-padding-start: "
							+ tabsPaddingStart + "px !important; }");
				} else {
					DynamicStyleSheets.register(name,
							".tab-content:not([pinned]) {  -moz-padding-start: 0px !important; -moz-margin-start: "
									+ tabsPaddingStart + "px !important; }");
				}
				break;

			case "tabs.padding.top":
				var tabsPaddingTop = this.preferences.getIntPref(name);

				if (tabsPaddingTop >= 0) {
					DynamicStyleSheets.register(name, ".tab-content > * { margin-top: " + tabsPaddingTop
							+ "px !important; }");
				} else {
					DynamicStyleSheets.register(name, ".tab-content > * { margin-top: " + tabsPaddingTop
							+ "px !important; }");
				}
				break;

			case "text.font.family":
			case "text.font.family.override":
				if (this.preferences.getBoolPref("text.font.family.override")) {
					DynamicStyleSheets.register("text.font.family", ".tab-text.tab-label:not([pinned]) { font-family: "
							+ this.preferences.getCharPref("text.font.family") + " !important; }");
				} else {
					DynamicStyleSheets.unregister("text.font.family");
				}
				break;

			case "text.font.size":
			case "text.font.size.override":
				if (this.preferences.getBoolPref("text.font.size.override")) {
					DynamicStyleSheets.register("text.font.size", ".tab-text.tab-label:not([pinned]) { font-size: "
							+ this.preferences.getIntPref("text.font.size") + "px !important; }");
				} else {
					DynamicStyleSheets.unregister("text.font.size");
				}
				break;

			case "text.padding.top":
				var textPaddingTop = this.preferences.getIntPref(name);

				if (textPaddingTop >= 0) {
					DynamicStyleSheets.register(name, ".tab-text.tab-label:not([pinned]) { padding-top: "
							+ textPaddingTop + "px !important; }");
				} else {
					DynamicStyleSheets.register(name, ".tab-text.tab-label:not([pinned]) { margin-top: "
							+ textPaddingTop + "px !important; }");
				}
				break;

			case "throbber.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets.register(name, ".tab-throbber:not([pinned]) { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;
		}
	},

	setDefaultPreferences : function() {
		var defaultPreferences = Components.classes["@mozilla.org/preferences-service;1"].getService(
				Components.interfaces.nsIPrefService).getDefaultBranch("extensions.org.bonsaimind.thintabs.");

		defaultPreferences.setBoolPref("close.hide", false);
		this.refreshPreference("close.hide");
		defaultPreferences.setBoolPref("icon.hide", false);
		this.refreshPreference("icon.hide");
		defaultPreferences.setIntPref("tabs.height", 19);
		this.refreshPreference("tabs.height");
		defaultPreferences.setIntPref("tabs.padding.end", 7);
		this.refreshPreference("tabs.padding.end");
		defaultPreferences.setIntPref("tabs.padding.start", 1);
		this.refreshPreference("tabs.padding.start");
		defaultPreferences.setIntPref("tabs.padding.top", 0);
		this.refreshPreference("tabs.padding.top");
		defaultPreferences.setCharPref("text.font.family", "monospace");
		defaultPreferences.setBoolPref("text.font.family.override", false);
		this.refreshPreference("text.font.family.override");
		defaultPreferences.setIntPref("text.font.size", 8);
		defaultPreferences.setBoolPref("text.font.size.override", false);
		this.refreshPreference("text.font.size.override");
		defaultPreferences.setIntPref("text.padding.top", 1);
		this.refreshPreference("text.padding.top");
		defaultPreferences.setBoolPref("throbber.hide", false);
		this.refreshPreference("throbber.hide");
	},

	init : function() {
		DynamicStyleSheets.prefix = "extensions.org.bonsaimind.thintabs.";

		this.preferences = Components.classes["@mozilla.org/preferences-service;1"].getService(
				Components.interfaces.nsIPrefService).getBranch("extensions.org.bonsaimind.thintabs.");
		this.preferences.QueryInterface(Components.interfaces.nsIPrefBranch2);
		this.preferences.addObserver("", this, false);
		this.styleSheet = Services.io.newURI("resource://thintabs/thintabs.css", null, null);
		this.styleSheetService = Components.classes["@mozilla.org/content/style-sheet-service;1"]
				.getService(Components.interfaces.nsIStyleSheetService);

		this.setDefaultPreferences();
		this.loadStyle();

		Services.wm.addListener(this);

		this.getWindows().forEach(function(window) {
			this.loadScript(window);
		}, this);
	},

	uninit : function() {
		this.unloadStyle();

		DynamicStyleSheets.unregisterAll();

		Services.wm.removeListener(this);

		this.getWindows().forEach(function(window) {
			this.unloadScript(window);
		}, this);
	}
};
<commit_msg>Simplified.<commit_after>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This is a fork/based on Small Tabs, written by ロシア,
 * https://addons.mozilla.org/en-US/firefox/addon/small-tabs/
 */

"use strict";

var EXPORTED_SYMBOLS = [ "ThinTabs" ];

Components.utils.import("resource://gre/modules/Services.jsm");

Components.utils.import("chrome://thintabs/content/javascript/DynamicStyleSheets.js");

var ThinTabs = {
	preferences : null,
	styleSheet : null,
	styleSheetService : null,

	getWindows : function() {
		var windows = [];

		var browserWindows = Services.wm.getEnumerator("navigator:browser");

		while (browserWindows.hasMoreElements()) {
			var browserWindow = browserWindows.getNext();
			browserWindow.QueryInterface(Components.interfaces.nsIDOMWindow);
			windows.push(browserWindow);
		}

		return windows;
	},

	handleEvent : function(event) {
		var document = event.target;
		var window = document.defaultView;

		window.removeEventListener("load", this, true);

		if (document.documentElement.getAttribute("windowtype") != "navigator:browser") {
			return;
		}

		this.loadScript(window);
	},

	loadStyle : function() {
		if (!this.styleSheetService.sheetRegistered(this.styleSheet, this.styleSheetService.USER_SHEET)) {
			this.styleSheetService.loadAndRegisterSheet(this.styleSheet, this.styleSheetService.USER_SHEET);
		}
	},

	unloadStyle : function() {
		if (this.styleSheetService.sheetRegistered(this.styleSheet, this.styleSheetService.USER_SHEET)) {
			this.styleSheetService.unregisterSheet(this.styleSheet, this.styleSheetService.USER_SHEET);
		}
	},

	loadScript : function(window) {
	},

	unloadScript : function(window) {
	},

	onOpenWindow : function(window) {
		var domWindow = window.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(
				Components.interfaces.nsIDOMWindow);
		domWindow.addEventListener("load", this, true);
	},

	onCloseWindow : function(window) {
	},

	onWindowTitleChange : function(window, title) {
	},

	observe : function(subject, topic, data) {
		if (topic != "nsPref:changed") {
			return;
		}

		this.refreshPreference(data);
	},

	refreshPreference : function(name) {
		switch (name) {
			case "close.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets.register(name, ".tab-close-button:not([pinned]) { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "icon.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets.register(name, ".tab-icon-image:not([pinned]) { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "tabs.height":
				var tabsHeight = this.preferences.getIntPref(name);

				DynamicStyleSheets
						.register(
								name,
								".tab-background-start[selected=true]::after,\
								.tab-background-start[selected=true]::before,\
								.tab-background-start,\
								.tab-background-end,\
								.tab-background-end[selected=true]::after,\
								.tab-background-end[selected=true]::before,\
								.tabbrowser-tabs {\
								height: "
										+ tabsHeight
										+ "px !important;\
								min-height: "
										+ tabsHeight
										+ "px !important;\
								}");
				break;

			case "tabs.padding.end":
				var tabsPaddingEnd = this.preferences.getIntPref(name);

				if (tabsPaddingEnd >= 0) {
					DynamicStyleSheets.register(name, ".tab-content:not([pinned]) { -moz-padding-end: "
							+ tabsPaddingEnd + "px !important; }");
				} else {
					DynamicStyleSheets.register(name,
							".tab-content:not([pinned]) { -moz-padding-end: 0px !important; -moz-margin-end: "
									+ tabsPaddingEnd + "px !important; }");
				}
				break;

			case "tabs.padding.start":
				var tabsPaddingStart = this.preferences.getIntPref(name);

				if (tabsPaddingStart >= 0) {
					DynamicStyleSheets.register(name, ".tab-content:not([pinned]) { -moz-padding-start: "
							+ tabsPaddingStart + "px !important; }");
				} else {
					DynamicStyleSheets.register(name,
							".tab-content:not([pinned]) {  -moz-padding-start: 0px !important; -moz-margin-start: "
									+ tabsPaddingStart + "px !important; }");
				}
				break;

			case "tabs.padding.top":
				var tabsPaddingTop = this.preferences.getIntPref(name);

				DynamicStyleSheets.register(name, ".tab-content > * { margin-top: " + tabsPaddingTop
						+ "px !important; }");
				break;

			case "text.font.family":
			case "text.font.family.override":
				if (this.preferences.getBoolPref("text.font.family.override")) {
					DynamicStyleSheets.register("text.font.family", ".tab-text.tab-label:not([pinned]) { font-family: "
							+ this.preferences.getCharPref("text.font.family") + " !important; }");
				} else {
					DynamicStyleSheets.unregister("text.font.family");
				}
				break;

			case "text.font.size":
			case "text.font.size.override":
				if (this.preferences.getBoolPref("text.font.size.override")) {
					DynamicStyleSheets.register("text.font.size", ".tab-text.tab-label:not([pinned]) { font-size: "
							+ this.preferences.getIntPref("text.font.size") + "px !important; }");
				} else {
					DynamicStyleSheets.unregister("text.font.size");
				}
				break;

			case "text.padding.top":
				var textPaddingTop = this.preferences.getIntPref(name);

				if (textPaddingTop >= 0) {
					DynamicStyleSheets.register(name, ".tab-text.tab-label:not([pinned]) { padding-top: "
							+ textPaddingTop + "px !important; }");
				} else {
					DynamicStyleSheets.register(name, ".tab-text.tab-label:not([pinned]) { margin-top: "
							+ textPaddingTop + "px !important; }");
				}
				break;

			case "throbber.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets.register(name, ".tab-throbber:not([pinned]) { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;
		}
	},

	setDefaultPreferences : function() {
		var defaultPreferences = Components.classes["@mozilla.org/preferences-service;1"].getService(
				Components.interfaces.nsIPrefService).getDefaultBranch("extensions.org.bonsaimind.thintabs.");

		defaultPreferences.setBoolPref("close.hide", false);
		this.refreshPreference("close.hide");
		defaultPreferences.setBoolPref("icon.hide", false);
		this.refreshPreference("icon.hide");
		defaultPreferences.setIntPref("tabs.height", 19);
		this.refreshPreference("tabs.height");
		defaultPreferences.setIntPref("tabs.padding.end", 7);
		this.refreshPreference("tabs.padding.end");
		defaultPreferences.setIntPref("tabs.padding.start", 1);
		this.refreshPreference("tabs.padding.start");
		defaultPreferences.setIntPref("tabs.padding.top", 0);
		this.refreshPreference("tabs.padding.top");
		defaultPreferences.setCharPref("text.font.family", "monospace");
		defaultPreferences.setBoolPref("text.font.family.override", false);
		this.refreshPreference("text.font.family.override");
		defaultPreferences.setIntPref("text.font.size", 8);
		defaultPreferences.setBoolPref("text.font.size.override", false);
		this.refreshPreference("text.font.size.override");
		defaultPreferences.setIntPref("text.padding.top", 1);
		this.refreshPreference("text.padding.top");
		defaultPreferences.setBoolPref("throbber.hide", false);
		this.refreshPreference("throbber.hide");
	},

	init : function() {
		DynamicStyleSheets.prefix = "extensions.org.bonsaimind.thintabs.";

		this.preferences = Components.classes["@mozilla.org/preferences-service;1"].getService(
				Components.interfaces.nsIPrefService).getBranch("extensions.org.bonsaimind.thintabs.");
		this.preferences.QueryInterface(Components.interfaces.nsIPrefBranch2);
		this.preferences.addObserver("", this, false);
		this.styleSheet = Services.io.newURI("resource://thintabs/thintabs.css", null, null);
		this.styleSheetService = Components.classes["@mozilla.org/content/style-sheet-service;1"]
				.getService(Components.interfaces.nsIStyleSheetService);

		this.setDefaultPreferences();
		this.loadStyle();

		Services.wm.addListener(this);

		this.getWindows().forEach(function(window) {
			this.loadScript(window);
		}, this);
	},

	uninit : function() {
		this.unloadStyle();

		DynamicStyleSheets.unregisterAll();

		Services.wm.removeListener(this);

		this.getWindows().forEach(function(window) {
			this.unloadScript(window);
		}, this);
	}
};
<|endoftext|>


<commit_before>import $ from 'dom7';
import { window, document } from 'ssr-window';
import Utils from '../../utils/utils';
import Device from '../../utils/device';

const Input = {
  ignoreTypes: ['checkbox', 'button', 'submit', 'range', 'radio', 'image'],
  createTextareaResizableShadow() {
    const $shadowEl = $(document.createElement('textarea'));
    $shadowEl.addClass('textarea-resizable-shadow');
    $shadowEl.prop({
      disabled: true,
      readonly: true,
    });
    Input.textareaResizableShadow = $shadowEl;
  },
  textareaResizableShadow: undefined,
  resizeTextarea(textareaEl) {
    const app = this;
    const $textareaEl = $(textareaEl);
    if (!Input.textareaResizableShadow) {
      Input.createTextareaResizableShadow();
    }
    const $shadowEl = Input.textareaResizableShadow;
    if (!$textareaEl.length) return;
    if (!$textareaEl.hasClass('resizable')) return;
    if (Input.textareaResizableShadow.parents().length === 0) {
      app.root.append($shadowEl);
    }

    const styles = window.getComputedStyle($textareaEl[0]);
    ('padding-top padding-bottom padding-left padding-right margin-left margin-right margin-top margin-bottom width font-size font-family font-style font-weight line-height font-variant text-transform letter-spacing border box-sizing display').split(' ').forEach((style) => {
      let styleValue = styles[style];
      if (('font-size line-height letter-spacing width').split(' ').indexOf(style) >= 0) {
        styleValue = styleValue.replace(',', '.');
      }
      $shadowEl.css(style, styleValue);
    });
    const currentHeight = $textareaEl[0].clientHeight;

    $shadowEl.val('');
    const initialHeight = $shadowEl[0].scrollHeight;

    $shadowEl.val($textareaEl.val());
    $shadowEl.css('height', 0);
    const scrollHeight = $shadowEl[0].scrollHeight;

    if (currentHeight !== scrollHeight) {
      if (scrollHeight > initialHeight) {
        $textareaEl.css('height', `${scrollHeight}px`);
        $textareaEl.trigger('textarea:resize', { initialHeight, currentHeight, scrollHeight });
      } else if (scrollHeight < currentHeight) {
        $textareaEl.css('height', '');
        $textareaEl.trigger('textarea:resize', { initialHeight, currentHeight, scrollHeight });
      }
    }
  },
  validate(inputEl) {
    const $inputEl = $(inputEl);
    if (!$inputEl.length) return;
    const $itemInputEl = $inputEl.parents('.item-input');
    const $inputWrapEl = $inputEl.parents('.input');
    const validity = $inputEl[0].validity;
    const validationMessage = $inputEl.dataset().errorMessage || $inputEl[0].validationMessage || '';
    if (!validity) return;
    if (!validity.valid) {
      let $errorEl = $inputEl.nextAll('.item-input-error-message, .input-error-message');
      if (validationMessage) {
        if ($errorEl.length === 0) {
          $errorEl = $(`<div class="${$inputWrapEl.length ? 'input-error-message' : 'item-input-error-message'}"></div>`);
          $errorEl.insertAfter($inputEl);
        }
        $errorEl.text(validationMessage);
      }
      if ($errorEl.length > 0) {
        $itemInputEl.addClass('item-input-with-error-message');
        $inputWrapEl.addClass('input-with-error-message');
      }
      $itemInputEl.addClass('item-input-invalid');
      $inputWrapEl.addClass('input-invalid');
      $inputEl.addClass('input-invalid');
    } else {
      $itemInputEl.removeClass('item-input-invalid item-input-with-error-message');
      $inputWrapEl.removeClass('input-invalid input-with-error-message');
      $inputEl.removeClass('input-invalid');
    }
  },
  validateInputs(el) {
    const app = this;
    $(el).find('input, textarea, select').each((index, inputEl) => {
      app.input.validate(inputEl);
    });
  },
  focus(inputEl) {
    const $inputEl = $(inputEl);
    const type = $inputEl.attr('type');
    if (Input.ignoreTypes.indexOf(type) >= 0) return;
    $inputEl.parents('.item-input').addClass('item-input-focused');
    $inputEl.parents('.input').addClass('input-focused');
    $inputEl.addClass('input-focused');
  },
  blur(inputEl) {
    const $inputEl = $(inputEl);
    $inputEl.parents('.item-input').removeClass('item-input-focused');
    $inputEl.parents('.input').removeClass('input-focused');
    $inputEl.removeClass('input-focused');
  },
  checkEmptyState(inputEl) {
    let $inputEl = $(inputEl);
    if (!$inputEl.is('input, select, textarea')) {
      $inputEl = $inputEl.find('input, select, textarea').eq(0);
    }
    if (!$inputEl.length) return;

    const value = $inputEl.val();
    const $itemInputEl = $inputEl.parents('.item-input');
    const $inputWrapEl = $inputEl.parents('.input');
    if ((value && (typeof value === 'string' && value.trim() !== '')) || (Array.isArray(value) && value.length > 0)) {
      $itemInputEl.addClass('item-input-with-value');
      $inputWrapEl.addClass('input-with-value');
      $inputEl.addClass('input-with-value');
      $inputEl.trigger('input:notempty');
    } else {
      $itemInputEl.removeClass('item-input-with-value');
      $inputWrapEl.removeClass('input-with-value');
      $inputEl.removeClass('input-with-value');
      $inputEl.trigger('input:empty');
    }
  },
  scrollIntoView(inputEl, duration = 0, centered, force) {
    const $inputEl = $(inputEl);
    const $scrollableEl = $inputEl.parents('.page-content, .panel').eq(0);
    if (!$scrollableEl.length) {
      return false;
    }
    const contentHeight = $scrollableEl[0].offsetHeight;
    const contentScrollTop = $scrollableEl[0].scrollTop;
    const contentPaddingTop = parseInt($scrollableEl.css('padding-top'), 10);
    const contentPaddingBottom = parseInt($scrollableEl.css('padding-bottom'), 10);
    const contentOffsetTop = $scrollableEl.offset().top - contentScrollTop;

    const inputOffsetTop = $inputEl.offset().top - contentOffsetTop;
    const inputHeight = $inputEl[0].offsetHeight;

    const min = (inputOffsetTop + contentScrollTop) - contentPaddingTop;
    const max = ((inputOffsetTop + contentScrollTop) - contentHeight) + contentPaddingBottom + inputHeight;
    const centeredPosition = min + ((max - min) / 2);

    if (contentScrollTop > min) {
      $scrollableEl.scrollTop(centered ? centeredPosition : min, duration);
      return true;
    }
    if (contentScrollTop < max) {
      $scrollableEl.scrollTop(centered ? centeredPosition : max, duration);
      return true;
    }
    if (force) {
      $scrollableEl.scrollTop(centered ? centeredPosition : max, duration);
    }
    return false;
  },
  init() {
    const app = this;
    Input.createTextareaResizableShadow();
    function onFocus() {
      const inputEl = this;
      if (app.params.input.scrollIntoViewOnFocus) {
        if (Device.android) {
          $(window).once('resize', () => {
            if (document && document.activeElement === inputEl) {
              app.input.scrollIntoView(inputEl, app.params.input.scrollIntoViewDuration, app.params.input.scrollIntoViewCentered, app.params.input.scrollIntoViewAlways);
            }
          });
        } else {
          app.input.scrollIntoView(inputEl, app.params.input.scrollIntoViewDuration, app.params.input.scrollIntoViewCentered, app.params.input.scrollIntoViewAlways);
        }
      }
      app.input.focus(inputEl);
    }
    function onBlur() {
      const $inputEl = $(this);
      const tag = $inputEl[0].nodeName.toLowerCase();
      app.input.blur($inputEl);
      if ($inputEl.dataset().validate || $inputEl.attr('validate') !== null || $inputEl.attr('data-validate-on-blur') !== null) {
        app.input.validate($inputEl);
      }
      // Resize textarea
      if (tag === 'textarea' && $inputEl.hasClass('resizable')) {
        if (Input.textareaResizableShadow) Input.textareaResizableShadow.remove();
      }
    }
    function onChange() {
      const $inputEl = $(this);
      const type = $inputEl.attr('type');
      const tag = $inputEl[0].nodeName.toLowerCase();
      if (Input.ignoreTypes.indexOf(type) >= 0) return;

      // Check Empty State
      app.input.checkEmptyState($inputEl);

      // Check validation
      if ($inputEl.attr('data-validate-on-blur') === null && ($inputEl.dataset().validate || $inputEl.attr('validate') !== null)) {
        app.input.validate($inputEl);
      }

      // Resize textarea
      if (tag === 'textarea' && $inputEl.hasClass('resizable')) {
        app.input.resizeTextarea($inputEl);
      }
    }
    function onInvalid(e) {
      const $inputEl = $(this);
      if ($inputEl.attr('data-validate-on-blur') === null && ($inputEl.dataset().validate || $inputEl.attr('validate') !== null)) {
        e.preventDefault();
        app.input.validate($inputEl);
      }
    }
    function clearInput() {
      const $clicked = $(this);
      const $inputEl = $clicked.siblings('input, textarea').eq(0);
      const previousValue = $inputEl.val();
      $inputEl
        .val('')
        .trigger('input change')
        .focus()
        .trigger('input:clear', previousValue);
    }
    $(document).on('click', '.input-clear-button', clearInput);
    $(document).on('change input', 'input, textarea, select', onChange, true);
    $(document).on('focus', 'input, textarea, select', onFocus, true);
    $(document).on('blur', 'input, textarea, select', onBlur, true);
    $(document).on('invalid', 'input, textarea, select', onInvalid, true);
  },
};

export default {
  name: 'input',
  params: {
    input: {
      scrollIntoViewOnFocus: Device.android,
      scrollIntoViewCentered: false,
      scrollIntoViewDuration: 0,
      scrollIntoViewAlways: false,
    },
  },
  create() {
    const app = this;
    Utils.extend(app, {
      input: {
        scrollIntoView: Input.scrollIntoView.bind(app),
        focus: Input.focus.bind(app),
        blur: Input.blur.bind(app),
        validate: Input.validate.bind(app),
        validateInputs: Input.validateInputs.bind(app),
        checkEmptyState: Input.checkEmptyState.bind(app),
        resizeTextarea: Input.resizeTextarea.bind(app),
        init: Input.init.bind(app),
      },
    });
  },
  on: {
    init() {
      const app = this;
      app.input.init();
    },
    tabMounted(tabEl) {
      const app = this;
      const $tabEl = $(tabEl);
      $tabEl.find('.item-input, .input').each((itemInputIndex, itemInputEl) => {
        const $itemInputEl = $(itemInputEl);
        $itemInputEl.find('input, select, textarea').each((inputIndex, inputEl) => {
          const $inputEl = $(inputEl);
          if (Input.ignoreTypes.indexOf($inputEl.attr('type')) >= 0) return;
          app.input.checkEmptyState($inputEl);
        });
      });
      $tabEl.find('textarea.resizable').each((textareaIndex, textareaEl) => {
        app.input.resizeTextarea(textareaEl);
      });
    },
    pageInit(page) {
      const app = this;
      const $pageEl = page.$el;
      $pageEl.find('.item-input, .input').each((itemInputIndex, itemInputEl) => {
        const $itemInputEl = $(itemInputEl);
        $itemInputEl.find('input, select, textarea').each((inputIndex, inputEl) => {
          const $inputEl = $(inputEl);
          if (Input.ignoreTypes.indexOf($inputEl.attr('type')) >= 0) return;
          app.input.checkEmptyState($inputEl);
        });
      });
      $pageEl.find('textarea.resizable').each((textareaIndex, textareaEl) => {
        app.input.resizeTextarea(textareaEl);
      });
    },
  },
};
<commit_msg>v5: Core Input - add app Input events<commit_after>import $ from 'dom7';
import { window, document } from 'ssr-window';
import Utils from '../../utils/utils';
import Device from '../../utils/device';

const Input = {
  ignoreTypes: ['checkbox', 'button', 'submit', 'range', 'radio', 'image'],
  createTextareaResizableShadow() {
    const $shadowEl = $(document.createElement('textarea'));
    $shadowEl.addClass('textarea-resizable-shadow');
    $shadowEl.prop({
      disabled: true,
      readonly: true,
    });
    Input.textareaResizableShadow = $shadowEl;
  },
  textareaResizableShadow: undefined,
  resizeTextarea(textareaEl) {
    const app = this;
    const $textareaEl = $(textareaEl);
    if (!Input.textareaResizableShadow) {
      Input.createTextareaResizableShadow();
    }
    const $shadowEl = Input.textareaResizableShadow;
    if (!$textareaEl.length) return;
    if (!$textareaEl.hasClass('resizable')) return;
    if (Input.textareaResizableShadow.parents().length === 0) {
      app.root.append($shadowEl);
    }

    const styles = window.getComputedStyle($textareaEl[0]);
    ('padding-top padding-bottom padding-left padding-right margin-left margin-right margin-top margin-bottom width font-size font-family font-style font-weight line-height font-variant text-transform letter-spacing border box-sizing display').split(' ').forEach((style) => {
      let styleValue = styles[style];
      if (('font-size line-height letter-spacing width').split(' ').indexOf(style) >= 0) {
        styleValue = styleValue.replace(',', '.');
      }
      $shadowEl.css(style, styleValue);
    });
    const currentHeight = $textareaEl[0].clientHeight;

    $shadowEl.val('');
    const initialHeight = $shadowEl[0].scrollHeight;

    $shadowEl.val($textareaEl.val());
    $shadowEl.css('height', 0);
    const scrollHeight = $shadowEl[0].scrollHeight;

    if (currentHeight !== scrollHeight) {
      if (scrollHeight > initialHeight) {
        $textareaEl.css('height', `${scrollHeight}px`);
      } else if (scrollHeight < currentHeight) {
        $textareaEl.css('height', '');
      }
      if (scrollHeight > initialHeight || scrollHeight < currentHeight) {
        $textareaEl.trigger('textarea:resize', { initialHeight, currentHeight, scrollHeight });
        app.emit('textareaResize', { initialHeight, currentHeight, scrollHeight });
      }
    }
  },
  validate(inputEl) {
    const $inputEl = $(inputEl);
    if (!$inputEl.length) return;
    const $itemInputEl = $inputEl.parents('.item-input');
    const $inputWrapEl = $inputEl.parents('.input');
    const validity = $inputEl[0].validity;
    const validationMessage = $inputEl.dataset().errorMessage || $inputEl[0].validationMessage || '';
    if (!validity) return;
    if (!validity.valid) {
      let $errorEl = $inputEl.nextAll('.item-input-error-message, .input-error-message');
      if (validationMessage) {
        if ($errorEl.length === 0) {
          $errorEl = $(`<div class="${$inputWrapEl.length ? 'input-error-message' : 'item-input-error-message'}"></div>`);
          $errorEl.insertAfter($inputEl);
        }
        $errorEl.text(validationMessage);
      }
      if ($errorEl.length > 0) {
        $itemInputEl.addClass('item-input-with-error-message');
        $inputWrapEl.addClass('input-with-error-message');
      }
      $itemInputEl.addClass('item-input-invalid');
      $inputWrapEl.addClass('input-invalid');
      $inputEl.addClass('input-invalid');
    } else {
      $itemInputEl.removeClass('item-input-invalid item-input-with-error-message');
      $inputWrapEl.removeClass('input-invalid input-with-error-message');
      $inputEl.removeClass('input-invalid');
    }
  },
  validateInputs(el) {
    const app = this;
    $(el).find('input, textarea, select').each((index, inputEl) => {
      app.input.validate(inputEl);
    });
  },
  focus(inputEl) {
    const $inputEl = $(inputEl);
    const type = $inputEl.attr('type');
    if (Input.ignoreTypes.indexOf(type) >= 0) return;
    $inputEl.parents('.item-input').addClass('item-input-focused');
    $inputEl.parents('.input').addClass('input-focused');
    $inputEl.addClass('input-focused');
  },
  blur(inputEl) {
    const $inputEl = $(inputEl);
    $inputEl.parents('.item-input').removeClass('item-input-focused');
    $inputEl.parents('.input').removeClass('input-focused');
    $inputEl.removeClass('input-focused');
  },
  checkEmptyState(inputEl) {
    const app = this;
    let $inputEl = $(inputEl);
    if (!$inputEl.is('input, select, textarea')) {
      $inputEl = $inputEl.find('input, select, textarea').eq(0);
    }
    if (!$inputEl.length) return;

    const value = $inputEl.val();
    const $itemInputEl = $inputEl.parents('.item-input');
    const $inputWrapEl = $inputEl.parents('.input');
    if ((value && (typeof value === 'string' && value.trim() !== '')) || (Array.isArray(value) && value.length > 0)) {
      $itemInputEl.addClass('item-input-with-value');
      $inputWrapEl.addClass('input-with-value');
      $inputEl.addClass('input-with-value');
      $inputEl.trigger('input:notempty');
      app.emit('inputNotEmpty', $inputEl[0]);
    } else {
      $itemInputEl.removeClass('item-input-with-value');
      $inputWrapEl.removeClass('input-with-value');
      $inputEl.removeClass('input-with-value');
      $inputEl.trigger('input:empty');
      app.emit('inputEmpty', $inputEl[0]);
    }
  },
  scrollIntoView(inputEl, duration = 0, centered, force) {
    const $inputEl = $(inputEl);
    const $scrollableEl = $inputEl.parents('.page-content, .panel').eq(0);
    if (!$scrollableEl.length) {
      return false;
    }
    const contentHeight = $scrollableEl[0].offsetHeight;
    const contentScrollTop = $scrollableEl[0].scrollTop;
    const contentPaddingTop = parseInt($scrollableEl.css('padding-top'), 10);
    const contentPaddingBottom = parseInt($scrollableEl.css('padding-bottom'), 10);
    const contentOffsetTop = $scrollableEl.offset().top - contentScrollTop;

    const inputOffsetTop = $inputEl.offset().top - contentOffsetTop;
    const inputHeight = $inputEl[0].offsetHeight;

    const min = (inputOffsetTop + contentScrollTop) - contentPaddingTop;
    const max = ((inputOffsetTop + contentScrollTop) - contentHeight) + contentPaddingBottom + inputHeight;
    const centeredPosition = min + ((max - min) / 2);

    if (contentScrollTop > min) {
      $scrollableEl.scrollTop(centered ? centeredPosition : min, duration);
      return true;
    }
    if (contentScrollTop < max) {
      $scrollableEl.scrollTop(centered ? centeredPosition : max, duration);
      return true;
    }
    if (force) {
      $scrollableEl.scrollTop(centered ? centeredPosition : max, duration);
    }
    return false;
  },
  init() {
    const app = this;
    Input.createTextareaResizableShadow();
    function onFocus() {
      const inputEl = this;
      if (app.params.input.scrollIntoViewOnFocus) {
        if (Device.android) {
          $(window).once('resize', () => {
            if (document && document.activeElement === inputEl) {
              app.input.scrollIntoView(inputEl, app.params.input.scrollIntoViewDuration, app.params.input.scrollIntoViewCentered, app.params.input.scrollIntoViewAlways);
            }
          });
        } else {
          app.input.scrollIntoView(inputEl, app.params.input.scrollIntoViewDuration, app.params.input.scrollIntoViewCentered, app.params.input.scrollIntoViewAlways);
        }
      }
      app.input.focus(inputEl);
    }
    function onBlur() {
      const $inputEl = $(this);
      const tag = $inputEl[0].nodeName.toLowerCase();
      app.input.blur($inputEl);
      if ($inputEl.dataset().validate || $inputEl.attr('validate') !== null || $inputEl.attr('data-validate-on-blur') !== null) {
        app.input.validate($inputEl);
      }
      // Resize textarea
      if (tag === 'textarea' && $inputEl.hasClass('resizable')) {
        if (Input.textareaResizableShadow) Input.textareaResizableShadow.remove();
      }
    }
    function onChange() {
      const $inputEl = $(this);
      const type = $inputEl.attr('type');
      const tag = $inputEl[0].nodeName.toLowerCase();
      if (Input.ignoreTypes.indexOf(type) >= 0) return;

      // Check Empty State
      app.input.checkEmptyState($inputEl);

      // Check validation
      if ($inputEl.attr('data-validate-on-blur') === null && ($inputEl.dataset().validate || $inputEl.attr('validate') !== null)) {
        app.input.validate($inputEl);
      }

      // Resize textarea
      if (tag === 'textarea' && $inputEl.hasClass('resizable')) {
        app.input.resizeTextarea($inputEl);
      }
    }
    function onInvalid(e) {
      const $inputEl = $(this);
      if ($inputEl.attr('data-validate-on-blur') === null && ($inputEl.dataset().validate || $inputEl.attr('validate') !== null)) {
        e.preventDefault();
        app.input.validate($inputEl);
      }
    }
    function clearInput() {
      const $clicked = $(this);
      const $inputEl = $clicked.siblings('input, textarea').eq(0);
      const previousValue = $inputEl.val();
      $inputEl
        .val('')
        .trigger('input change')
        .focus()
        .trigger('input:clear', previousValue);
      app.emit('inputClear', previousValue);
    }
    $(document).on('click', '.input-clear-button', clearInput);
    $(document).on('change input', 'input, textarea, select', onChange, true);
    $(document).on('focus', 'input, textarea, select', onFocus, true);
    $(document).on('blur', 'input, textarea, select', onBlur, true);
    $(document).on('invalid', 'input, textarea, select', onInvalid, true);
  },
};

export default {
  name: 'input',
  params: {
    input: {
      scrollIntoViewOnFocus: Device.android,
      scrollIntoViewCentered: false,
      scrollIntoViewDuration: 0,
      scrollIntoViewAlways: false,
    },
  },
  create() {
    const app = this;
    Utils.extend(app, {
      input: {
        scrollIntoView: Input.scrollIntoView.bind(app),
        focus: Input.focus.bind(app),
        blur: Input.blur.bind(app),
        validate: Input.validate.bind(app),
        validateInputs: Input.validateInputs.bind(app),
        checkEmptyState: Input.checkEmptyState.bind(app),
        resizeTextarea: Input.resizeTextarea.bind(app),
        init: Input.init.bind(app),
      },
    });
  },
  on: {
    init() {
      const app = this;
      app.input.init();
    },
    tabMounted(tabEl) {
      const app = this;
      const $tabEl = $(tabEl);
      $tabEl.find('.item-input, .input').each((itemInputIndex, itemInputEl) => {
        const $itemInputEl = $(itemInputEl);
        $itemInputEl.find('input, select, textarea').each((inputIndex, inputEl) => {
          const $inputEl = $(inputEl);
          if (Input.ignoreTypes.indexOf($inputEl.attr('type')) >= 0) return;
          app.input.checkEmptyState($inputEl);
        });
      });
      $tabEl.find('textarea.resizable').each((textareaIndex, textareaEl) => {
        app.input.resizeTextarea(textareaEl);
      });
    },
    pageInit(page) {
      const app = this;
      const $pageEl = page.$el;
      $pageEl.find('.item-input, .input').each((itemInputIndex, itemInputEl) => {
        const $itemInputEl = $(itemInputEl);
        $itemInputEl.find('input, select, textarea').each((inputIndex, inputEl) => {
          const $inputEl = $(inputEl);
          if (Input.ignoreTypes.indexOf($inputEl.attr('type')) >= 0) return;
          app.input.checkEmptyState($inputEl);
        });
      });
      $pageEl.find('textarea.resizable').each((textareaIndex, textareaEl) => {
        app.input.resizeTextarea(textareaEl);
      });
    },
  },
};
<|endoftext|>


<commit_before>"use strict";

var _ = require('lodash'),
	PluginError = require('gulp-util').PluginError;

function verify(schema, config) {
	var title;

	if (schema && config) {
		title = schema.title;
		_entry(title, schema, config)
	}

	function _entry(name, schema, value) {
		_object() || _array();

		function _array() {
			if (_strict('array') || _loose('array', Array.isArray)) {
				if (!Array.isArray(value)) {
					throw new PluginError(title, 'configuration property "' + name + '" should be an array');
				}

				if (schema.minItems && value.length < schema.minItems) {
					throw new PluginError(title, 'configuration property "' + name + '" should at least contains ' + schema.minItems + (schema.minItems > 1 ? ' items' : ' item'));
				}

				if (schema.maxItems && schema.maxItems < value.length) {
					throw new PluginError(title, 'configuration property "' + name + '" should at most contains ' + schema.minItems + (schema.minItems > 1 ? ' items' : ' item'));
				}

				if (schema.items) {
					value.forEach(function (item) {
						_entry('item', schema.items, item);
					});
				}

				return true;
			}
		}

		function _object() {
			if (_strict('object') || _loose('object', _.isPlainObject) || _implicit()) {
				if (!_.isPlainObject(value)) {
					throw new PluginError(title, 'configuration property "' + name + '" should be an object');
				}

				if (schema.required) {
					schema.required.forEach(function (property) {
						if (!value.hasOwnProperty(property)) {
							throw new PluginError(title, 'configuration property "' + property + '" is required');
						}
					});
				}

				if (schema.properties) {
					_.forEach(value, function (item, property) {
						_entry(property, schema.properties[property], item);
					});
				}

				return true;
			}
		}

		function _strict(type) {
			return schema.type === type;
		}

		function _loose(type, fn) {
			return _.includes(schema.type, type) && fn(value);
		}

		function _implicit() {
			return _.size(schema.properties) > 0;
		}
	}
}

module.exports = verify;
<commit_msg>remove configuration_verifier<commit_after><|endoftext|>


<commit_before>/**
 * @fileoverview Retrieve files via XMLHttpRequest.
 */

goog.provide('treesaver.network');

goog.require('treesaver.array'); // forEach
goog.require('treesaver.capabilities'); // delay
goog.require('treesaver.constants');
goog.require('treesaver.debug');
goog.require('treesaver.scheduler');

/**
 * @private
 * @const
 * @type {number}
 */
treesaver.network.DEFAULT_TIMEOUT = 10000; // 10 seconds

/**
 * Network events fired
 * @const
 * @type {Object.<string, string>}
 */
treesaver.network.events = {
  ONLINE: 'treesaver.online',
  OFFLINE: 'treesaver.offline'
};

/**
 * Browser events watched
 * @private
 * @const
 * @type {Array.<string>}
 */
treesaver.network.watchedEvents_ = [
  'offline',
  'online'
];

/**
 * Cache events watched (on document, not window)
 * @private
 * @const
 * @type {Array.<string>}
 */
treesaver.network.watchedCacheEvents_ = [
  'uncached',
  'idle',
  'checking',
  'downloading',
  'updateready',
  'obsolete'
];

/**
 * Whether the network library is loaded
 * @private
 * @type {boolean}
 */
treesaver.network.isLoaded_ = false;

/**
 * Initialize the network module, hook up event handlers, etc
 */
treesaver.network.load = function() {
  if (!treesaver.network.isLoaded_) {
    treesaver.network.isLoaded_ = true;

    // Hook up event handlers
    treesaver.network.watchedEvents_.forEach(function(evt) {
      treesaver.events.addListener(document, evt, treesaver.network);
    });

    if (treesaver.capabilities.SUPPORTS_APPLICATIONCACHE &&
        // FF3.5 gets nasty if you try to add event handlers to an uncached page
        // (specifically, it won't let you add event handlers to the cache obj)
        treesaver.network.loadedFromCache_) {
      treesaver.network.watchedCacheEvents_.forEach(function(evt) {
        treesaver.events.addListener(window.applicationCache, evt, treesaver.network);
      });
    }
  }
};

/**
 * Unload handlers and cleanup
 */
treesaver.network.unload = function() {
  if (treesaver.network.isLoaded_) {
    treesaver.network.isLoaded_ = false;

    // Unhook event handlers
    treesaver.network.watchedEvents_.forEach(function(evt) {
      treesaver.events.removeListener(window, evt, treesaver.network);
    });
    // Unhook cache handlers only if they were set (avoid FF3.5 bug from above)
    if (treesaver.capabilities.SUPPORTS_APPLICATIONCACHE &&
        treesaver.network.loadedFromCache_) {
      treesaver.network.watchedCacheEvents_.forEach(function(evt) {
        treesaver.events.removeListener(window.applicationCache, evt, treesaver.network);
      });
    }

    // TODO: Cancel outstanding requests
  }
};

/**
 * @return {boolean} True if browser has an internet connection.
 */
treesaver.network.isOnline = function() {
  if ('onLine' in window.navigator) {
    return window.navigator.onLine;
  }

  // TODO: What's a good option here? IE8, and recent FF/WebKit support
  // navigator.onLine, so perhaps we just don't worry about this too much
  return true;
};

/**
 * @private
 * @type {boolean}
 */
treesaver.network.loadedFromCache_ =
  treesaver.capabilities.SUPPORTS_APPLICATIONCACHE &&
  // 0 = UNCACHED, anything else means page was cached on load
  !!window.applicationCache.status;

/**
 * @return {boolean} True if the browser cache was active during boot.
 */
treesaver.network.loadedFromCache = function() {
  return treesaver.network.loadedFromCache_;
};

/**
 * Handle events
 * @param {Event} e
 */
treesaver.network['handleEvent'] = function(e) {
  treesaver.debug.info('Network event recieved: ' + e);

  switch (e.type) {
  case 'online':
    treesaver.debug.info('Application online');

    // TODO: Refactor this and create an event handler in capabilities
    treesaver.capabilities.updateClasses();

    treesaver.events.fireEvent(window, treesaver.network.events.ONLINE);
    return;

  case 'offline':
    treesaver.debug.info('Application offline');

    // TODO: Refactor this and create an event handler in capabilities
    treesaver.capabilities.updateClasses();

    treesaver.events.fireEvent(window, treesaver.network.events.OFFLINE);
    return;

  case 'updateready':
    treesaver.debug.info('Updating application cache');

    // New version of cached element is ready, hot swap
    window.applicationCache.swapCache();

    // Force reload of app in order to get new JS and content?

    return;

  case 'error':
    treesaver.debug.warn('Application Cache Error: ' + e);

    // TODO: ???
    return;
  }
};

/**
 * @param {!string} url
 * @return {!string} path.
 */
treesaver.network.urlToPath = function(url) {
  var a = document.createElement('a'),
      div,
      path;

  if (SUPPORT_IE && treesaver.capabilities.IS_LEGACY) {
    // IE7 requires this to be in the tree in order to get an accurate value
    div = document.createElement('div');
    document.body.appendChild(div);
    div.appendChild(a);
  }

  a.href = url;
  // TODO: Verify that pathname is supported everywhere
  path = a['pathname'];

  if (SUPPORT_IE && treesaver.capabilities.IS_LEGACY) {
    // Compiler's not smart enough to know that div will be set here
    document.body.removeChild(/** @type {!Element} */ (div));
    div.removeChild(a);
  }

  // IE & Opera sometimes don't prefix the path with '/'
  if (path.charAt(0) !== '/') {
    path = '/' + path;
  }

  return path;
};

/**
 * @param {!string} url
 * @return {!string} The url without the hash.
 */
treesaver.network.stripHash = function(url) {
  var hash_index = url.indexOf('#');

  if (hash_index === -1) {
    return url;
  }
  else {
    return url.substr(0, hash_index);
  }
};

/**
 * @private
 * @const
 * @type {!RegExp}
 */
treesaver.network.protocolRegex_ = /^https?:\/\//i;

/**
 * @param {!string} rel_path
 * @return {!string} An absolute URL.
 */
treesaver.network.absoluteURL = function(rel_path) {
  // Shortcut anything that starts with slash
  if (rel_path[0] === '/' || treesaver.network.protocolRegex_.test(rel_path)) {
    return rel_path;
  }

  var a = document.createElement('a'),
      div,
      url;

  // IE7 doesn't properly compute the pathname if the link
  // is not in the tree
  if (SUPPORT_IE && treesaver.capabilities.IS_LEGACY) {
    div = document.createElement('div');
    document.body.appendChild(div);
    div.appendChild(a);
  }

  a.href = rel_path;
  url = a.href;

  // Remove element from tree
  if (SUPPORT_IE && treesaver.capabilities.IS_LEGACY) {
    document.body.removeChild(/** @type {!Element} */ (div));
    div.removeChild(a);
  }

  return url;
};

/**
 * @param {!string} url
 * @param {?function()} callback
 * @param {number=} timeout
 */
treesaver.network.get = function get(url, callback, timeout) {
  treesaver.debug.info('XHR request to: ' + url);

  var request = {
    xhr: new XMLHttpRequest(),
    url: url,
    callback: callback
  };

  treesaver.scheduler.delay(
    function() {
      treesaver.network.requestTimeout_(request);
    },
    timeout || treesaver.network.DEFAULT_TIMEOUT,
    [],
    treesaver.network.makeRequestId_(request)
  );

  // Setup timeout
  request.xhr.onreadystatechange = treesaver.network.createHandler_(request);

  try {
    // IE will throw if you try X-domain
    request.xhr.open('GET', request.url, true);
    request.xhr.send(null);
  }
  catch (e) {
    treesaver.debug.warn('XHR Request exception: ' + e);

    treesaver.network.requestError_(request);
  }
};

/**
 * @private
 */
treesaver.network.makeRequestId_ = function(request) {
  // TODO: Make unique across repeated requests?
  return 'fetch:' + request.url;
};

/**
 * @private
 */
treesaver.network.createHandler_ = function createHandler_(request) {
  return function() {
    if (request.xhr.readyState === 4) {
      // The hosted UIWebView gives us a status of 0 for some reason
      if ((WITHIN_IOS_WRAPPER && request.xhr.status === 0) ||
          (request.xhr.status === 200 || request.xhr.status === 304)) {
        treesaver.debug.info('XHR response from: ' + request.url);
        request.callback(request.xhr.responseText, request.url);
        treesaver.network.cleanupRequest_(request);
      }
      else {
        treesaver.debug.warn('XHR request failed for: ' + request.url);

        treesaver.network.requestError_(request);
      }
    }
  };
};

/**
 * @private
 */
treesaver.network.cleanupRequest_ = function cleanupRequest_(request) {
  // Remove timeout
  treesaver.scheduler.clear(treesaver.network.makeRequestId_(request));
  // Clear reference
  request.xhr.onreadystatechange = null;
};

/**
 * @private
 */
treesaver.network.requestError_ = function requestError_(request) {
  // Failed for some reason; TODO: Error handling / event?
  request.callback(null, request.url);
  treesaver.network.cleanupRequest_(request);
};

/**
 * @private
 */
treesaver.network.requestTimeout_ = function requestTimeout_(request) {
  request.xhr.abort();
  treesaver.network.requestError_(request);
};
<commit_msg>IE7 doesn't know how to compute absolute URLs without innerHTML. Closes #90<commit_after>/**
 * @fileoverview Retrieve files via XMLHttpRequest.
 */

goog.provide('treesaver.network');

goog.require('treesaver.array'); // forEach
goog.require('treesaver.capabilities'); // delay
goog.require('treesaver.constants');
goog.require('treesaver.debug');
goog.require('treesaver.scheduler');

/**
 * @private
 * @const
 * @type {number}
 */
treesaver.network.DEFAULT_TIMEOUT = 10000; // 10 seconds

/**
 * Network events fired
 * @const
 * @type {Object.<string, string>}
 */
treesaver.network.events = {
  ONLINE: 'treesaver.online',
  OFFLINE: 'treesaver.offline'
};

/**
 * Browser events watched
 * @private
 * @const
 * @type {Array.<string>}
 */
treesaver.network.watchedEvents_ = [
  'offline',
  'online'
];

/**
 * Cache events watched (on document, not window)
 * @private
 * @const
 * @type {Array.<string>}
 */
treesaver.network.watchedCacheEvents_ = [
  'uncached',
  'idle',
  'checking',
  'downloading',
  'updateready',
  'obsolete'
];

/**
 * Whether the network library is loaded
 * @private
 * @type {boolean}
 */
treesaver.network.isLoaded_ = false;

/**
 * Initialize the network module, hook up event handlers, etc
 */
treesaver.network.load = function() {
  if (!treesaver.network.isLoaded_) {
    treesaver.network.isLoaded_ = true;

    // Hook up event handlers
    treesaver.network.watchedEvents_.forEach(function(evt) {
      treesaver.events.addListener(document, evt, treesaver.network);
    });

    if (treesaver.capabilities.SUPPORTS_APPLICATIONCACHE &&
        // FF3.5 gets nasty if you try to add event handlers to an uncached page
        // (specifically, it won't let you add event handlers to the cache obj)
        treesaver.network.loadedFromCache_) {
      treesaver.network.watchedCacheEvents_.forEach(function(evt) {
        treesaver.events.addListener(window.applicationCache, evt, treesaver.network);
      });
    }
  }
};

/**
 * Unload handlers and cleanup
 */
treesaver.network.unload = function() {
  if (treesaver.network.isLoaded_) {
    treesaver.network.isLoaded_ = false;

    // Unhook event handlers
    treesaver.network.watchedEvents_.forEach(function(evt) {
      treesaver.events.removeListener(window, evt, treesaver.network);
    });
    // Unhook cache handlers only if they were set (avoid FF3.5 bug from above)
    if (treesaver.capabilities.SUPPORTS_APPLICATIONCACHE &&
        treesaver.network.loadedFromCache_) {
      treesaver.network.watchedCacheEvents_.forEach(function(evt) {
        treesaver.events.removeListener(window.applicationCache, evt, treesaver.network);
      });
    }

    // TODO: Cancel outstanding requests
  }
};

/**
 * @return {boolean} True if browser has an internet connection.
 */
treesaver.network.isOnline = function() {
  if ('onLine' in window.navigator) {
    return window.navigator.onLine;
  }

  // TODO: What's a good option here? IE8, and recent FF/WebKit support
  // navigator.onLine, so perhaps we just don't worry about this too much
  return true;
};

/**
 * @private
 * @type {boolean}
 */
treesaver.network.loadedFromCache_ =
  treesaver.capabilities.SUPPORTS_APPLICATIONCACHE &&
  // 0 = UNCACHED, anything else means page was cached on load
  !!window.applicationCache.status;

/**
 * @return {boolean} True if the browser cache was active during boot.
 */
treesaver.network.loadedFromCache = function() {
  return treesaver.network.loadedFromCache_;
};

/**
 * Handle events
 * @param {Event} e
 */
treesaver.network['handleEvent'] = function(e) {
  treesaver.debug.info('Network event recieved: ' + e);

  switch (e.type) {
  case 'online':
    treesaver.debug.info('Application online');

    // TODO: Refactor this and create an event handler in capabilities
    treesaver.capabilities.updateClasses();

    treesaver.events.fireEvent(window, treesaver.network.events.ONLINE);
    return;

  case 'offline':
    treesaver.debug.info('Application offline');

    // TODO: Refactor this and create an event handler in capabilities
    treesaver.capabilities.updateClasses();

    treesaver.events.fireEvent(window, treesaver.network.events.OFFLINE);
    return;

  case 'updateready':
    treesaver.debug.info('Updating application cache');

    // New version of cached element is ready, hot swap
    window.applicationCache.swapCache();

    // Force reload of app in order to get new JS and content?

    return;

  case 'error':
    treesaver.debug.warn('Application Cache Error: ' + e);

    // TODO: ???
    return;
  }
};

/**
 * @param {!string} url
 * @return {!string} path.
 */
treesaver.network.urlToPath = function(url) {
  var a,
      div,
      path;

  if (SUPPORT_IE && treesaver.capabilities.IS_LEGACY) {
    // IE7 has buggy behavior here if you set the href property,
    // so we have to use innerHTML to get the real absolute URL
    div = document.createElement('div');
    div.style.display = 'none';
    document.body.appendChild(div);
    div.innerHTML = '';
    a = /** @type {!Element} */ (div.firstChild);
  }
  else {
    a = document.createElement('a');
    a.href = url;
  }

  // TODO: Verify that pathname is supported everywhere
  path = a['pathname'];

  if (SUPPORT_IE && treesaver.capabilities.IS_LEGACY) {
    // Compiler's not smart enough to know that div will be set here
    document.body.removeChild(/** @type {!Element} */ (div));
    div.removeChild(a);
  }

  // IE & Opera sometimes don't prefix the path with '/'
  if (path.charAt(0) !== '/') {
    path = '/' + path;
  }

  return path;
};

/**
 * @param {!string} url
 * @return {!string} The url without the hash.
 */
treesaver.network.stripHash = function(url) {
  var hash_index = url.indexOf('#');

  if (hash_index === -1) {
    return url;
  }
  else {
    return url.substr(0, hash_index);
  }
};

/**
 * @private
 * @const
 * @type {!RegExp}
 */
treesaver.network.protocolRegex_ = /^https?:\/\//i;

/**
 * @param {!string} rel_path
 * @return {!string} An absolute URL.
 */
treesaver.network.absoluteURL = function(rel_path) {
  // Shortcut anything that starts with slash
  if (rel_path[0] === '/' || treesaver.network.protocolRegex_.test(rel_path)) {
    return rel_path;
  }

  var a = document.createElement('a'),
      div,
      url;

  // IE7 doesn't properly compute the pathname if the link
  // is not in the tree
  if (SUPPORT_IE && treesaver.capabilities.IS_LEGACY) {
    div = document.createElement('div');
    document.body.appendChild(div);
    div.appendChild(a);
  }

  a.href = rel_path;
  url = a.href;

  // Remove element from tree
  if (SUPPORT_IE && treesaver.capabilities.IS_LEGACY) {
    document.body.removeChild(/** @type {!Element} */ (div));
    div.removeChild(a);
  }

  return url;
};

/**
 * @param {!string} url
 * @param {?function()} callback
 * @param {number=} timeout
 */
treesaver.network.get = function get(url, callback, timeout) {
  treesaver.debug.info('XHR request to: ' + url);

  var request = {
    xhr: new XMLHttpRequest(),
    url: url,
    callback: callback
  };

  treesaver.scheduler.delay(
    function() {
      treesaver.network.requestTimeout_(request);
    },
    timeout || treesaver.network.DEFAULT_TIMEOUT,
    [],
    treesaver.network.makeRequestId_(request)
  );

  // Setup timeout
  request.xhr.onreadystatechange = treesaver.network.createHandler_(request);

  try {
    // IE will throw if you try X-domain
    request.xhr.open('GET', request.url, true);
    request.xhr.send(null);
  }
  catch (e) {
    treesaver.debug.warn('XHR Request exception: ' + e);

    treesaver.network.requestError_(request);
  }
};

/**
 * @private
 */
treesaver.network.makeRequestId_ = function(request) {
  // TODO: Make unique across repeated requests?
  return 'fetch:' + request.url;
};

/**
 * @private
 */
treesaver.network.createHandler_ = function createHandler_(request) {
  return function() {
    if (request.xhr.readyState === 4) {
      // The hosted UIWebView gives us a status of 0 for some reason
      if ((WITHIN_IOS_WRAPPER && request.xhr.status === 0) ||
          (request.xhr.status === 200 || request.xhr.status === 304)) {
        treesaver.debug.info('XHR response from: ' + request.url);
        request.callback(request.xhr.responseText, request.url);
        treesaver.network.cleanupRequest_(request);
      }
      else {
        treesaver.debug.warn('XHR request failed for: ' + request.url);

        treesaver.network.requestError_(request);
      }
    }
  };
};

/**
 * @private
 */
treesaver.network.cleanupRequest_ = function cleanupRequest_(request) {
  // Remove timeout
  treesaver.scheduler.clear(treesaver.network.makeRequestId_(request));
  // Clear reference
  request.xhr.onreadystatechange = null;
};

/**
 * @private
 */
treesaver.network.requestError_ = function requestError_(request) {
  // Failed for some reason; TODO: Error handling / event?
  request.callback(null, request.url);
  treesaver.network.cleanupRequest_(request);
};

/**
 * @private
 */
treesaver.network.requestTimeout_ = function requestTimeout_(request) {
  request.xhr.abort();
  treesaver.network.requestError_(request);
};
<|endoftext|>


<commit_before>
var winston = require('winston');
var expressWinston = require('express-winston');

module.exports = function(err, req, res, next) {
	// Don't handle ObsoleteId, for it's sign of a 404.
	if (err.type === 'ObsoleteId' || err.type === 'InvalidId') {
		// TODO: find way to detect while model type we couldn't find and customize 404 message.
		return res.render404(); // "Esse usuário não existe.");
	}

	// Set status.
	if (err.status)
		res.status(err.status);
	else if (err.permission)
		res.status(401);
	else if (res.statusCode < 400)
		res.status(500);

	if (err.permission === 'not_on_list') {
		req.res.render404({msg: "Você não está autorizado a continuar."})
		return;
	}
	if (err.name === 'InternalOAuthError') {
		req.res.render404({msg: "Não conseguimos te autenciar. Tente novamente."})
		return;
	}

	var accept = req.headers.accept || '';
	// Test permissions like login and don't trace/log them.
	// Ideally this could be implemented as a map object of permissionError to callbacks implementing
	// their responses.
	if (err.permission && err.permission === 'login') {
		jobber.debug('IP '+req.connection.remoteAddress+' can\'t '+req.method+' path '+req.url);
		if (~accept.indexOf('html')) {
			res.redirect('/');
		} else {
			// Don't use middleware.
			res.endJSON({ error: true, message: 'Unauthenticated user.' });
		}
		return;
	} else if (err.permission) {
		err.msg = "Proibido de continuar.";
	}

	// hack to use middleware conditionally
	// require('express-bunyan-logger').errorLogger({
	// 	format: ':remote-address - - :method :url',
	// })(err, res, res, function(){});
	// app.use(require('express-bunyan-logger')({
	// 	format: ":remote-address - :user-agent[major] custom logger"
	// }));
	// expressWinston.errorLogger({
	// 	transports: [ new winston.transports.Console({ json: true, colorize: true }) ],
	// })(err, res, res, function () {});
	req.logger.error(err);

	if (req.app.get('env') === 'production') {
		try {
			var newrelic = require('newrelic');
			newrelic.noticeError(err);
		} catch (e) {
			req.logger.warn("Failed to call newrelic.noticeError.", e);
		}
	}

	req.logger.error('Error stack:', err, err.args && JSON.stringify(err.args.err && err.args.err.errors));
	console.trace();

	if (~accept.indexOf('html') && !req.isAPICall) {
		if (req.app.get('env') === 'development') {
			res.render('app/500', {
				user: req.user,
				error_code: res.statusCode,
				error_msg: err,
				error_stack: (err.stack || '').split('\n').slice(1).join('
'),
			});
		} else {
			res.render('app/500', {
				user: req.user,
				message: err.human_message
			});
		}
	} else {
		var error = { message: err.message };
		for (var prop in err) error[prop] = err[prop];
		return res
			.set('Content-Type', 'application/json')
			// .end(JSON.stringify({ error: message, message: err.msg || 'Erro.' }));
			.end(JSON.stringify({ error: true, message: err.human_message || 'Erro.' }));
	}
}<commit_msg>Logging errors once.<commit_after>
var winston = require('winston');
var expressWinston = require('express-winston');

module.exports = function(err, req, res, next) {
	// Don't handle ObsoleteId, for it's sign of a 404.
	if (err.type === 'ObsoleteId' || err.type === 'InvalidId') {
		// TODO: find way to detect while model type we couldn't find and customize 404 message.
		return res.render404(); // "Esse usuário não existe.");
	}

	// Set status.
	if (err.status)
		res.status(err.status);
	else if (err.permission)
		res.status(401);
	else if (res.statusCode < 400)
		res.status(500);

	if (err.permission === 'not_on_list') {
		req.res.render404({msg: "Você não está autorizado a continuar."})
		return;
	}
	if (err.name === 'InternalOAuthError') {
		req.res.render404({msg: "Não conseguimos te autenciar. Tente novamente."})
		return;
	}

	var accept = req.headers.accept || '';
	// Test permissions like login and don't trace/log them.
	// Ideally this could be implemented as a map object of permissionError to callbacks implementing
	// their responses.
	if (err.permission && err.permission === 'login') {
		jobber.debug('IP '+req.connection.remoteAddress+' can\'t '+req.method+' path '+req.url);
		if (~accept.indexOf('html')) {
			res.redirect('/');
		} else {
			// Don't use middleware.
			res.endJSON({ error: true, message: 'Unauthenticated user.' });
		}
		return;
	} else if (err.permission) {
		err.msg = "Proibido de continuar.";
	}

	// hack to use middleware conditionally
	// require('express-bunyan-logger').errorLogger({
	// 	format: ':remote-address - - :method :url',
	// })(err, res, res, function(){});
	// app.use(require('express-bunyan-logger')({
	// 	format: ":remote-address - :user-agent[major] custom logger"
	// }));
	// expressWinston.errorLogger({
	// 	transports: [ new winston.transports.Console({ json: true, colorize: true }) ],
	// })(err, res, res, function () {});

	if (req.app.get('env') === 'production') {
		try {
			var newrelic = require('newrelic');
			newrelic.noticeError(err);
		} catch (e) {
			req.logger.warn("Failed to call newrelic.noticeError.", e);
		}
	}

	req.logger.error('Error detected:', err, err.args && JSON.stringify(err.args.err && err.args.err.errors));
	console.trace();

	if (~accept.indexOf('html') && !req.isAPICall) {
		if (req.app.get('env') === 'development') {
			res.render('app/500', {
				user: req.user,
				error_code: res.statusCode,
				error_msg: err,
				error_stack: (err.stack || '').split('\n').slice(1).join('
'),
			});
		} else {
			res.render('app/500', {
				user: req.user,
				message: err.human_message
			});
		}
	} else {
		var error = { message: err.message };
		for (var prop in err) error[prop] = err[prop];
		return res
			.set('Content-Type', 'application/json')
			// .end(JSON.stringify({ error: message, message: err.msg || 'Erro.' }));
			.end(JSON.stringify({ error: true, message: err.human_message || 'Erro.' }));
	}
}<|endoftext|>