diff --git a/.eslintignore b/.eslintignore index fc0d50a25..cd0c80673 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,7 +1,13 @@ dist +docs client/lib **/*.html plugins/* !plugins/coral-plugin-facebook-auth +!plugins/coral-plugin-auth !plugins/coral-plugin-respect +!plugins/coral-plugin-offtopic +!plugins/coral-plugin-like +!plugins/coral-plugin-mod +!plugins/coral-plugin-love node_modules diff --git a/.gitignore b/.gitignore index f0c3b2a4b..11e738d57 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ coverage/ plugins.json plugins/* !plugins/coral-plugin-facebook-auth +!plugins/coral-plugin-auth !plugins/coral-plugin-respect !plugins/coral-plugin-offtopic !plugins/coral-plugin-like diff --git a/README.md b/README.md index 718847d9e..48055e326 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ To set up a development environment or build from source, see [INSTALL.md](https To launch a Talk server of your own from your browser without any need to muck about in a terminal or think about engineering concepts, stay tuned. We will launch [our installer](https://github.com/coralproject/talk-install) shortly! - ### Configuration The Talk application looks for the following configuration values either as environment variables: @@ -45,6 +44,12 @@ sign and verify tokens via a `HS256` algorithm. Refer to the wiki page on [Configuration Loading](https://github.com/coralproject/talk/wiki/Configuration-Loading) for alternative methods of loading configuration during development. +### Using Trust + +Talk ships with core components we call "Trust". This allows Talk to automate certain actions based on previous user behavior. + +Our first feature is the notion of Karma. Talk will automatically pre-moderate comments of users who have a negative karma score. You can [see more how karma works here](/services/karma.js). + ## Supported Browsers ### Web @@ -58,6 +63,8 @@ alternative methods of loading configuration during development. - iPad - iPad Pro +- iPhone 7 Plus +- iPhone 7 - iPhone 6 Plus - iPhone 6 - iPhone 5 diff --git a/app.js b/app.js index 16840f965..d0f0f316b 100644 --- a/app.js +++ b/app.js @@ -11,6 +11,8 @@ const errors = require('./errors'); const {createGraphOptions} = require('./graph'); const apollo = require('graphql-server-express'); const accepts = require('accepts'); +const compression = require('compression'); +const cookieParser = require('cookie-parser'); const app = express(); @@ -31,6 +33,8 @@ app.set('trust proxy', 1); app.use(helmet({ frameguard: false })); +app.use(compression()); +app.use(cookieParser()); app.use(bodyParser.json()); //============================================================================== diff --git a/client/coral-admin/config.json b/client/coral-admin/config.json deleted file mode 100644 index 156740de5..000000000 --- a/client/coral-admin/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "basePath": "admin" -} diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index 989316dd9..3bde5f931 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -1,25 +1,20 @@ import React from 'react'; import {Router, Route, IndexRedirect, browserHistory} from 'react-router'; -import Stories from 'containers/Stories/Stories'; -import Configure from 'containers/Configure/Configure'; -import LayoutContainer from 'containers/LayoutContainer'; -import InstallContainer from 'containers/Install/InstallContainer'; +import Configure from 'routes/Configure'; +import Dashboard from 'routes/Dashboard'; +import Install from 'routes/Install'; +import Stories from 'routes/Stories'; +import {CommunityLayout, Community} from 'routes/Community'; +import {ModerationLayout, Moderation} from 'routes/Moderation'; -import CommunityLayout from 'containers/Community/CommunityLayout'; -import CommunityContainer from 'containers/Community/CommunityContainer'; - -import ModerationLayout from 'containers/ModerationQueue/ModerationLayout'; -import ModerationContainer from 'containers/ModerationQueue/ModerationContainer'; - -import Dashboard from 'containers/Dashboard/Dashboard'; +import Layout from 'containers/Layout'; const routes = (
- - + + - @@ -27,11 +22,11 @@ const routes = ( {/* Community Routes */} - - + + - - + + @@ -39,22 +34,22 @@ const routes = ( {/* Moderation Routes */} - - + + - - + + - - + + - - + + - - + + - + diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js index f5c2f848a..8a18a8e3f 100644 --- a/client/coral-admin/src/actions/assets.js +++ b/client/coral-admin/src/actions/assets.js @@ -8,7 +8,7 @@ import { UPDATE_ASSETS } from '../constants/assets'; -import coralApi from '../../../coral-framework/helpers/response'; +import coralApi from '../../../coral-framework/helpers/request'; /** * Action disptacher related to assets diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js index 8dbaf7554..ca48764f1 100644 --- a/client/coral-admin/src/actions/auth.js +++ b/client/coral-admin/src/actions/auth.js @@ -1,6 +1,7 @@ +import bowser from 'bowser'; import * as actions from '../constants/auth'; +import coralApi from 'coral-framework/helpers/request'; import * as Storage from 'coral-framework/helpers/storage'; -import coralApi from 'coral-framework/helpers/response'; import {handleAuthToken} from 'coral-framework/actions/auth'; //============================================================================== @@ -9,21 +10,36 @@ import {handleAuthToken} from 'coral-framework/actions/auth'; export const handleLogin = (email, password, recaptchaResponse) => (dispatch) => { dispatch({type: actions.LOGIN_REQUEST}); - const params = {method: 'POST', body: {email, password}}; + + const params = { + method: 'POST', + body: { + email, + password + } + }; + if (recaptchaResponse) { - params.headers = {'X-Recaptcha-Response': recaptchaResponse}; + params.headers = { + 'X-Recaptcha-Response': recaptchaResponse + }; } + return coralApi('/auth/local', params) .then(({user, token}) => { + if (!user) { - Storage.removeItem('token'); + if (!bowser.safari && !bowser.ios) { + Storage.removeItem('token'); + } return dispatch(checkLoginFailure('not logged in')); } + dispatch(handleAuthToken(token)); dispatch(checkLoginSuccess(user)); }) .catch((error) => { - if (error.translation_key === 'login_maximum_exceeded') { + if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') { dispatch({ type: actions.LOGIN_MAXIMUM_EXCEEDED, message: error.translation_key @@ -52,7 +68,9 @@ const forgotPassowordFailure = () => ({ export const requestPasswordReset = (email) => (dispatch) => { dispatch(forgotPassowordRequest(email)); - return coralApi('/account/password/reset', {method: 'POST', body: {email}}) + const redirectUri = location.href; + + return coralApi('/account/password/reset', {method: 'POST', body: {email, loc: redirectUri}}) .then(() => dispatch(forgotPassowordSuccess())) .catch((error) => dispatch(forgotPassowordFailure(error))); }; @@ -81,7 +99,9 @@ export const checkLogin = () => (dispatch) => { return coralApi('/auth') .then(({user}) => { if (!user) { - Storage.removeItem('token'); + if (!bowser.safari && !bowser.ios) { + Storage.removeItem('token'); + } return dispatch(checkLoginFailure('not logged in')); } diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index 9a8743762..36b9ffb0a 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -14,7 +14,7 @@ import { HIDE_SUSPENDUSER_DIALOG } from '../constants/community'; -import coralApi from '../../../coral-framework/helpers/response'; +import coralApi from '../../../coral-framework/helpers/request'; export const fetchAccounts = (query = {}) => (dispatch) => { diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js index 21ff58de1..15413e220 100644 --- a/client/coral-admin/src/actions/install.js +++ b/client/coral-admin/src/actions/install.js @@ -1,4 +1,4 @@ -import coralApi from 'coral-framework/helpers/response'; +import coralApi from 'coral-framework/helpers/request'; import * as actions from '../constants/install'; import validate from 'coral-framework/helpers/validate'; import errorMsj from 'coral-framework/helpers/error'; diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js index c5e6fc620..4b0b0538a 100644 --- a/client/coral-admin/src/actions/moderation.js +++ b/client/coral-admin/src/actions/moderation.js @@ -5,7 +5,7 @@ export const singleView = () => ({type: actions.SINGLE_VIEW}); // Ban User Dialog export const showBanUserDialog = (user, commentId, commentStatus, showRejectedNote) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId, commentStatus, showRejectedNote}); -export const hideBanUserDialog = (showDialog) => ({type: actions.HIDE_BANUSER_DIALOG, showDialog}); +export const hideBanUserDialog = () => ({type: actions.HIDE_BANUSER_DIALOG}); // Suspend User Dialog export const showSuspendUserDialog = (userId, username, commentId, commentStatus) => @@ -27,3 +27,9 @@ export const hideShortcutsNote = () => { export const viewUserDetail = (userId) => ({type: actions.VIEW_USER_DETAIL, userId}); export const hideUserDetail = () => ({type: actions.HIDE_USER_DETAIL}); + +export const setSortOrder = (order) => ({ + type: actions.SET_SORT_ORDER, + order +}); + diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js index b0530e77e..65c200420 100644 --- a/client/coral-admin/src/actions/settings.js +++ b/client/coral-admin/src/actions/settings.js @@ -1,4 +1,4 @@ -import coralApi from '../../../coral-framework/helpers/response'; +import coralApi from '../../../coral-framework/helpers/request'; export const SETTINGS_LOADING = 'SETTINGS_LOADING'; export const SETTINGS_RECEIVED = 'SETTINGS_RECEIVED'; diff --git a/client/coral-admin/src/actions/users.js b/client/coral-admin/src/actions/users.js index 2642db90d..e888d5b74 100644 --- a/client/coral-admin/src/actions/users.js +++ b/client/coral-admin/src/actions/users.js @@ -1,4 +1,4 @@ -import coralApi from '../../../coral-framework/helpers/response'; +import coralApi from '../../../coral-framework/helpers/request'; import * as userTypes from '../constants/users'; /** diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index cfd7eb2da..4eda2973e 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -1,7 +1,7 @@ import React, {PropTypes} from 'react'; import styles from './ModerationList.css'; import {Button} from 'coral-ui'; -import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQueueActionsMap'; +import {menuActionsMap} from '../routes/Moderation/helpers/moderationQueueActionsMap'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-admin/src/components/AdminLogin.js b/client/coral-admin/src/components/AdminLogin.js index f63a0014d..9deee53f7 100644 --- a/client/coral-admin/src/components/AdminLogin.js +++ b/client/coral-admin/src/components/AdminLogin.js @@ -35,7 +35,7 @@ class AdminLogin extends React.Component { const {errorMessage, loginMaxExceeded, recaptchaPublic} = this.props; const signInForm = (
- {errorMessage && {t(`errors.${errorMessage}`)}} + {errorMessage && {t(`error.${errorMessage}`)}} ( -

{t('loading_results')}

-); - -export default Loading; diff --git a/client/coral-admin/src/containers/Community/Table.js b/client/coral-admin/src/containers/Community/Table.js deleted file mode 100644 index e44924bff..000000000 --- a/client/coral-admin/src/containers/Community/Table.js +++ /dev/null @@ -1,79 +0,0 @@ -import React, {Component} from 'react'; -import {connect} from 'react-redux'; -import {SelectField, Option} from 'react-mdl-selectfield'; -import styles from './Community.css'; -import {setRole, setCommenterStatus} from '../../actions/community'; - -import t from 'coral-framework/services/i18n'; - -class Table extends Component { - - constructor (props) { - super(props); - this.onRoleChange = this.onRoleChange.bind(this); - } - - onRoleChange (id, role) { - this.props.dispatch(setRole(id, role)); - } - - onCommenterStatusChange (id, status) { - this.props.dispatch(setCommenterStatus(id, status)); - } - - render () { - const {headers, commenters, onHeaderClickHandler} = this.props; - - return ( - - - - {headers.map((header, i) =>( - - ))} - - - - {commenters.map((row, i)=> ( - - - - - - - ))} - -
onHeaderClickHandler({field: header.field})}> - {header.title} -
- {row.username} - {row.profiles.map(({id}) => id)} - - {row.created_at} - - this.onCommenterStatusChange(row.id, status)}> - - - - - this.onRoleChange(row.id, role)}> - - - - - -
- ); - } -} - -export default connect((state) => ({commenters: state.community.get('accounts')}))(Table); diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.js b/client/coral-admin/src/containers/Dashboard/Dashboard.js deleted file mode 100644 index e764bf596..000000000 --- a/client/coral-admin/src/containers/Dashboard/Dashboard.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import styles from './Dashboard.css'; -import {compose} from 'react-apollo'; -import {connect} from 'react-redux'; -import {getMetrics} from 'coral-admin/src/graphql/queries'; -import FlagWidget from './FlagWidget'; -import ActivityWidget from './ActivityWidget'; -import CountdownTimer from 'coral-admin/src/components/CountdownTimer'; - -import {Spinner} from 'coral-ui'; - -class Dashboard extends React.Component { - - reloadData = () => { - this.props.data.refetch(); - } - - render () { - - if (this.props.data && this.props.data.loading) { - return ; - } - - const {data: {assetsByActivity, assetsByFlag}} = this.props; - - return ( -
- -
- - -
-
- ); - } -} - -const mapStateToProps = (state) => { - return { - settings: state.settings.toJS(), - moderation: state.moderation.toJS() - }; -}; - -export default compose( - connect(mapStateToProps), - getMetrics -)(Dashboard); diff --git a/client/coral-admin/src/containers/Install/InstallContainer.js b/client/coral-admin/src/containers/Install/InstallContainer.js deleted file mode 100644 index 67f266123..000000000 --- a/client/coral-admin/src/containers/Install/InstallContainer.js +++ /dev/null @@ -1,101 +0,0 @@ -import React, {Component} from 'react'; -import {connect} from 'react-redux'; -import styles from './style.css'; -import {Wizard, WizardNav} from 'coral-ui'; -import Layout from 'coral-admin/src/components/ui/Layout'; - -import { - goToStep, - nextStep, - submitUser, - checkInstall, - previousStep, - finishInstall, - submitSettings, - updateUserFormData, - updateSettingsFormData, - updatePermittedDomains -} from '../../actions/install'; - -import InitialStep from './components/Steps/InitialStep'; -import AddOrganizationName from './components/Steps/AddOrganizationName'; -import CreateYourAccount from './components/Steps/CreateYourAccount'; -import PermittedDomainsStep from './components/Steps/PermittedDomainsStep'; -import FinalStep from './components/Steps/FinalStep'; - -class InstallContainer extends Component { - componentDidMount() { - const {checkInstall} = this.props; - checkInstall(() => { - this.context.router.push('/admin'); - }); - } - - render() { - const {install} = this.props; - - return ( - -
- { - !install.alreadyInstalled ? ( -
-

Welcome to the Coral Project

- { install.step !== 0 ? : null } - - - - - - - -
- ) : ( -
Talk is already installed
- ) - } -
-
- ); - } -} - -InstallContainer.contextTypes = { - router: React.PropTypes.object -}; - -const mapStateToProps = (state) => ({ - install: state.install.toJS() -}); - -const mapDispatchToProps = (dispatch) => ({ - nextStep: () => dispatch(nextStep()), - goToStep: (step) => dispatch(goToStep(step)), - previousStep: () => dispatch(previousStep()), - finishInstall: () => dispatch(finishInstall()), - checkInstall: (next) => dispatch(checkInstall(next)), - handleDomainsChange: (value) => { - dispatch(updatePermittedDomains(value)); - }, - handleSettingsChange: (e) => { - const {name, value} = e.currentTarget; - dispatch(updateSettingsFormData(name, value)); - }, - handleUserChange: (e) => { - const {name, value} = e.currentTarget; - dispatch(updateUserFormData(name, value)); - }, - handleSettingsSubmit: (e) => { - e.preventDefault(); - dispatch(submitSettings()); - }, - handleUserSubmit: (e) => { - e.preventDefault(); - dispatch(submitUser()); - } -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(InstallContainer); diff --git a/client/coral-admin/src/containers/LayoutContainer.js b/client/coral-admin/src/containers/Layout.js similarity index 100% rename from client/coral-admin/src/containers/LayoutContainer.js rename to client/coral-admin/src/containers/Layout.js diff --git a/client/coral-admin/src/containers/Streams/Stories.js b/client/coral-admin/src/containers/Streams/Stories.js deleted file mode 100644 index 86db633b2..000000000 --- a/client/coral-admin/src/containers/Streams/Stories.js +++ /dev/null @@ -1,185 +0,0 @@ -import React, {Component} from 'react'; -import styles from './Stories.css'; -import {connect} from 'react-redux'; -import t from 'coral-framework/services/i18n'; -import {fetchAssets, updateAssetState} from '../../actions/assets'; - -import {Link} from 'react-router'; - -import {Pager, Icon} from 'coral-ui'; -import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl'; -import EmptyCard from 'coral-admin/src/components/EmptyCard'; - -const limit = 25; - -class Stories extends Component { - - state = { - search: '', - sort: 'desc', - filter: 'all', - statusMenus: {}, - timer: null, - page: 0 - } - - componentDidMount () { - this.props.fetchAssets(0, limit, '', this.state.sortBy); - } - - onSettingChange = (setting) => (e) => { - let options = this.state; - this.setState({[setting]: e.target.value}); - options[setting] = e.target.value; - this.props.fetchAssets(0, limit, options.search, options.sort, options.filter); - } - - onSearchChange = (e) => { - const search = e.target.value; - this.setState((prevState) => { - prevState.search = search; - clearTimeout(prevState.timer); - const fetchAssets = this.props.fetchAssets; - prevState.timer = setTimeout(() => { - fetchAssets(0, limit, search, this.state.sort, this.state.filter); - }, 350); - return prevState; - }); - } - - renderDate = (date) => { - const d = new Date(date); - return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`; - } - - onStatusClick = (closeStream, id, statusMenuOpen) => () => { - if (statusMenuOpen) { - this.setState((prev) => { - prev.statusMenus[id] = false; - return prev; - }); - this.props.updateAssetState(id, closeStream ? Date.now() : null) - .then(() => { - const {search, sort, filter, page} = this.state; - this.props.fetchAssets(page, limit, search, sort, filter); - }); - } else { - this.setState((prev) => { - prev.statusMenus[id] = true; - return prev; - }); - } - } - - renderTitle = (title, {id}) => {title} - - renderStatus = (closedAt, {id}) => { - const closed = closedAt && new Date(closedAt).getTime() < Date.now(); - const statusMenuOpen = this.state.statusMenus[id]; - return
-
- {!statusMenuOpen && } - {closed ? t('streams.closed') : t('streams.open')} -
- { - statusMenuOpen && -
- {!closed ? t('streams.closed') : t('streams.open')} -
- } -
; - } - - onPageClick = (page) => { - this.setState({page}); - const {search, sort, filter} = this.state; - this.props.fetchAssets((page - 1) * limit, limit, search, sort, filter); - } - - render () { - const {search, sort, filter} = this.state; - const {assets} = this.props; - - const assetsIds = assets.ids.map((id) => assets.byId[id]); - - return ( -
-
-
- - -
-
{t('streams.filter_streams')}
-
{t('streams.stream_status')}
- - {t('streams.all')} - {t('streams.open')} - {t('streams.closed')} - -
{t('streams.sort_by')}
- - {t('streams.newest')} - {t('streams.oldest')} - -
- { - assetsIds.length - ?
- - {t('streams.article')} - - {t('streams.pubdate')} - - - {t('streams.status')} - - - -
- : {t('streams.empty_result')} - } -
- ); - } -} - -const mapStateToProps = ({assets}) => { - return { - assets: assets.toJS() - }; -}; - -const mapDispatchToProps = (dispatch) => { - return { - fetchAssets: (...args) => { - dispatch(fetchAssets.apply(this, args)); - }, - updateAssetState: (...args) => dispatch(updateAssetState.apply(this, args)) - }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(Stories); diff --git a/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql b/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql deleted file mode 100644 index 726194143..000000000 --- a/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql +++ /dev/null @@ -1,12 +0,0 @@ -fragment metrics on Asset { - id - title - url - author - created_at - commentCount - action_summaries { - actionCount - actionableItemCount - } -} diff --git a/client/coral-admin/src/graphql/fragments/commentView.graphql b/client/coral-admin/src/graphql/fragments/commentView.graphql deleted file mode 100644 index 1f3b8b1d3..000000000 --- a/client/coral-admin/src/graphql/fragments/commentView.graphql +++ /dev/null @@ -1,31 +0,0 @@ -fragment commentView on Comment { - id - body - created_at - status - user { - id - name: username - status - } - asset { - id - title - url - } - action_summaries { - count - ... on FlagActionSummary { - reason - } - } - actions { - ... on FlagAction { - reason - message - user { - username - } - } - } -} diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js new file mode 100644 index 000000000..740af20a1 --- /dev/null +++ b/client/coral-admin/src/graphql/index.js @@ -0,0 +1,68 @@ +import {add} from 'coral-framework/services/graphqlRegistry'; +const queues = ['all', 'premod', 'flagged', 'accepted', 'rejected']; + +const extension = { + mutations: { + SetUserStatus: () => ({ + refetchQueries: ['CoralAdmin_Community'], + }), + RejectUsername: () => ({ + refetchQueries: ['CoralAdmin_Community'], + }), + SetCommentStatus: ({variables: {commentId, status}}) => ({ + updateQueries: { + CoralAdmin_Moderation: (oldData) => { + const comment = queues.reduce((comment, queue) => { + return comment ? comment : oldData[queue].find((c) => c.id === commentId); + }, null); + + let accepted = oldData.accepted; + let acceptedCount = oldData.acceptedCount; + let rejected = oldData.rejected; + let rejectedCount = oldData.rejectedCount; + + if (status !== comment.status) { + if (status === 'ACCEPTED') { + comment.status = 'ACCEPTED'; + acceptedCount++; + accepted = [comment, ...accepted]; + } + else if (status === 'REJECTED') { + comment.status = 'REJECTED'; + rejectedCount++; + rejected = [comment, ...rejected]; + } + } + + const premod = oldData.premod.filter((c) => c.id !== commentId); + const flagged = oldData.flagged.filter((c) => c.id !== commentId); + const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount; + const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount; + + if (status === 'REJECTED') { + accepted = oldData.accepted.filter((c) => c.id !== commentId); + acceptedCount = accepted.length < oldData.accepted.length ? oldData.acceptedCount - 1 : oldData.acceptedCount; + } + else if (status === 'ACCEPTED') { + rejected = oldData.rejected.filter((c) => c.id !== commentId); + rejectedCount = rejected.length < oldData.rejected.length ? oldData.rejectedCount - 1 : oldData.rejectedCount; + } + + return { + ...oldData, + premodCount: Math.max(0, premodCount), + flaggedCount: Math.max(0, flaggedCount), + acceptedCount: Math.max(0, acceptedCount), + rejectedCount: Math.max(0, rejectedCount), + premod, + flagged, + accepted, + rejected, + }; + } + } + }), + }, +}; + +add(extension); diff --git a/client/coral-admin/src/graphql/mutations/index.js b/client/coral-admin/src/graphql/mutations/index.js deleted file mode 100644 index 3aef68ebc..000000000 --- a/client/coral-admin/src/graphql/mutations/index.js +++ /dev/null @@ -1,153 +0,0 @@ -import {graphql} from 'react-apollo'; -import SET_USER_STATUS from './setUserStatus.graphql'; -import SET_COMMENT_STATUS from './setCommentStatus.graphql'; -import SUSPEND_USER from './suspendUser.graphql'; -import REJECT_USERNAME from './rejectUsername.graphql'; - -export const banUser = graphql(SET_USER_STATUS, { - props: ({mutate}) => ({ - banUser: ({userId}) => { - return mutate({ - variables: { - userId, - status: 'BANNED' - }, - refetchQueries: ['Users'] - }); - }}), -}); - -export const setUserStatus = graphql(SET_USER_STATUS, { - props: ({mutate}) => ({ - approveUser: ({userId}) => { - return mutate({ - variables: { - userId, - status: 'APPROVED' - }, - refetchQueries: ['Users'] - }); - } - }) -}); - -export const suspendUser = graphql(SUSPEND_USER, { - props: ({mutate}) => ({ - suspendUser: (input) => { - return mutate({ - variables: { - input, - }, - }); - } - }) -}); - -export const rejectUsername = graphql(REJECT_USERNAME, { - props: ({mutate}) => ({ - rejectUsername: (input) => { - return mutate({ - variables: { - input, - }, - refetchQueries: ['Users'] - }); - } - }) -}); - -const views = ['all', 'premod', 'flagged', 'accepted', 'rejected']; -export const setCommentStatus = graphql(SET_COMMENT_STATUS, { - props: ({mutate}) => ({ - acceptComment: ({commentId}) => { - return mutate({ - variables: { - commentId, - status: 'ACCEPTED' - }, - updateQueries: { - ModQueue: (oldData) => { - const comment = views.reduce((comment, view) => { - return comment ? comment : oldData[view].find((c) => c.id === commentId); - }, null); - let accepted; - let acceptedCount = oldData.acceptedCount; - - // if the comment was already in the Approved queue, don't re-add it - if (comment.status === 'ACCEPTED') { - accepted = [...oldData.accepted]; - } else { - comment.status = 'ACCEPTED'; - acceptedCount++; - accepted = [comment, ...oldData.accepted]; - } - - const premod = oldData.premod.filter((c) => c.id !== commentId); - const flagged = oldData.flagged.filter((c) => c.id !== commentId); - const rejected = oldData.rejected.filter((c) => c.id !== commentId); - const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount; - const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount; - const rejectedCount = rejected.length < oldData.rejected.length ? oldData.rejectedCount - 1 : oldData.rejectedCount; - - return { - ...oldData, - premodCount: Math.max(0, premodCount), - flaggedCount: Math.max(0, flaggedCount), - acceptedCount: Math.max(0, acceptedCount), - rejectedCount: Math.max(0, rejectedCount), - premod, - flagged, - accepted, - rejected, - }; - } - } - }); - }, - rejectComment: ({commentId}) => { - return mutate({ - variables: { - commentId, - status: 'REJECTED' - }, - updateQueries: { - ModQueue: (oldData) => { - const comment = views.reduce((comment, view) => { - return comment ? comment : oldData[view].find((c) => c.id === commentId); - }, null); - let rejected; - let rejectedCount = oldData.rejectedCount; - - // if the item was already in the Rejected queue, don't put it in again - if (comment.status === 'REJECTED') { - rejected = oldData.rejected; - } else { - comment.status = 'REJECTED'; - rejectedCount++; - rejected = [comment, ...oldData.rejected]; - } - - const premod = oldData.premod.filter((c) => c.id !== commentId); - const flagged = oldData.flagged.filter((c) => c.id !== commentId); - const accepted = oldData.accepted.filter((c) => c.id !== commentId); - const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount; - const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount; - const acceptedCount = accepted.length < oldData.accepted.length ? oldData.acceptedCount - 1 : oldData.acceptedCount; - - return { - ...oldData, - premodCount: Math.max(0, premodCount), - flaggedCount: Math.max(0, flaggedCount), - acceptedCount: Math.max(0, acceptedCount), - rejectedCount: Math.max(0, rejectedCount), - premod, - flagged, - accepted, - rejected - }; - } - } - }); - } - }) -}); diff --git a/client/coral-admin/src/graphql/mutations/rejectUsername.graphql b/client/coral-admin/src/graphql/mutations/rejectUsername.graphql deleted file mode 100644 index c07887649..000000000 --- a/client/coral-admin/src/graphql/mutations/rejectUsername.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation rejectUsername($input: RejectUsernameInput!) { - rejectUsername(input: $input) { - errors { - translation_key - } - } -} diff --git a/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql b/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql deleted file mode 100644 index 7ff6173a8..000000000 --- a/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation setCommentStatus($commentId: ID!, $status: COMMENT_STATUS!){ - setCommentStatus(id: $commentId, status: $status) { - errors { - translation_key - } - } -} diff --git a/client/coral-admin/src/graphql/mutations/setUserStatus.graphql b/client/coral-admin/src/graphql/mutations/setUserStatus.graphql deleted file mode 100644 index 32fcf7e20..000000000 --- a/client/coral-admin/src/graphql/mutations/setUserStatus.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation setUserStatus($userId: ID!, $status: USER_STATUS!) { - setUserStatus(id: $userId, status: $status) { - errors { - translation_key - } - } -} diff --git a/client/coral-admin/src/graphql/mutations/suspendUser.graphql b/client/coral-admin/src/graphql/mutations/suspendUser.graphql deleted file mode 100644 index f6ab2426f..000000000 --- a/client/coral-admin/src/graphql/mutations/suspendUser.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation suspendUser($input: SuspendUserInput!) { - suspendUser(input: $input) { - errors { - translation_key - } - } -} diff --git a/client/coral-admin/src/graphql/queries/assetsQuery.graphql b/client/coral-admin/src/graphql/queries/assetsQuery.graphql deleted file mode 100644 index 37950692d..000000000 --- a/client/coral-admin/src/graphql/queries/assetsQuery.graphql +++ /dev/null @@ -1,6 +0,0 @@ -query Assets { - assets { - id - title - } -} diff --git a/client/coral-admin/src/graphql/queries/getQueueCounts.graphql b/client/coral-admin/src/graphql/queries/getQueueCounts.graphql deleted file mode 100644 index 4a7c00d87..000000000 --- a/client/coral-admin/src/graphql/queries/getQueueCounts.graphql +++ /dev/null @@ -1,22 +0,0 @@ -query Counts ($asset_id: ID) { - allCount: commentCount(query: { - asset_id: $asset_id - }) - acceptedCount: commentCount(query: { - statuses: [ACCEPTED], - asset_id: $asset_id - }) - premodCount: commentCount(query: { - statuses: [PREMOD], - asset_id: $asset_id - }) - rejectedCount: commentCount(query: { - statuses: [REJECTED], - asset_id: $asset_id - }) - flaggedCount: commentCount(query: { - action_type: FLAG, - asset_id: $asset_id, - statuses: [NONE, PREMOD] - }) -} diff --git a/client/coral-admin/src/graphql/queries/index.js b/client/coral-admin/src/graphql/queries/index.js deleted file mode 100644 index ae94d70c3..000000000 --- a/client/coral-admin/src/graphql/queries/index.js +++ /dev/null @@ -1,116 +0,0 @@ -import {graphql} from 'react-apollo'; - -import MOD_QUEUE_QUERY from './modQueueQuery.graphql'; -import MOD_QUEUE_LOAD_MORE from './loadMore.graphql'; -import MOD_USER_FLAGGED_QUERY from './modUserFlaggedQuery.graphql'; -import METRICS from './metricsQuery.graphql'; -import USER_DETAIL from './userDetail.graphql'; -import GET_QUEUE_COUNTS from './getQueueCounts.graphql'; - -export const modQueueQuery = graphql(MOD_QUEUE_QUERY, { - options: ({params: {id = null}}) => { - return { - variables: { - asset_id: id, - sort: 'REVERSE_CHRONOLOGICAL' - } - }; - }, - props: ({ownProps: {params: {id = null}}, data}) => ({ - data, - modQueueResort: modQueueResort(id, data.fetchMore), - loadMore: loadMore(data.fetchMore) - }) -}); - -export const getMetrics = graphql(METRICS, { - options: ({settings: {dashboardWindowStart, dashboardWindowEnd}}) => { - - return { - variables: { - from: dashboardWindowStart, - to: dashboardWindowEnd - } - }; - } -}); - -export const loadMore = (fetchMore) => ({limit = 10, cursor, sort, tab, asset_id}) => { - let variables = { - limit, - cursor, - sort, - asset_id - }; - switch(tab) { - case 'all': - variables.statuses = null; - break; - case 'accepted': - variables.statuses = ['ACCEPTED']; - break; - case 'premod': - variables.statuses = ['PREMOD']; - break; - case 'flagged': - variables.statuses = ['NONE', 'PREMOD']; - variables.action_type = 'FLAG'; - break; - case 'rejected': - variables.statuses = ['REJECTED']; - break; - } - return fetchMore({ - query: MOD_QUEUE_LOAD_MORE, - variables, - updateQuery: (oldData, {fetchMoreResult:{comments}}) => { - return { - ...oldData, - [tab]: [ - ...oldData[tab], - ...comments - ] - }; - } - }); -}; - -export const modUserFlaggedQuery = graphql(MOD_USER_FLAGGED_QUERY, { - options: ({params: {action_type = 'FLAG'}}) => { - return { - variables: { - action_type: action_type - } - }; - } -}); - -export const modQueueResort = (id, fetchMore) => (sort) => { - return fetchMore({ - query: MOD_QUEUE_QUERY, - variables: { - asset_id: id, - sort - }, - updateQuery: (oldData, {fetchMoreResult:{data}}) => data - }); -}; - -export const getUserDetail = graphql(USER_DETAIL, { - options: ({id}) => { - return { - variables: {author_id: id} - }; - } -}); - -export const getQueueCounts = graphql(GET_QUEUE_COUNTS, { - options: ({params: {id = null}}) => { - return { - pollInterval: 5000, - variables: { - asset_id: id - } - }; - } -}); diff --git a/client/coral-admin/src/graphql/queries/loadMore.graphql b/client/coral-admin/src/graphql/queries/loadMore.graphql deleted file mode 100644 index a42c7b2bf..000000000 --- a/client/coral-admin/src/graphql/queries/loadMore.graphql +++ /dev/null @@ -1,13 +0,0 @@ -#import "../fragments/commentView.graphql" - -query LoadMoreModQueue($limit: Int = 10, $cursor: Date, $sort: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) { - comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sort: $sort, action_type: $action_type}) { - ...commentView - action_summaries { - count - ... on FlagActionSummary { - reason - } - } - } -} diff --git a/client/coral-admin/src/graphql/queries/metricsQuery.graphql b/client/coral-admin/src/graphql/queries/metricsQuery.graphql deleted file mode 100644 index 17fac3034..000000000 --- a/client/coral-admin/src/graphql/queries/metricsQuery.graphql +++ /dev/null @@ -1,10 +0,0 @@ -#import "../fragments/assetMetricsView.graphql" - -query Metrics ($from: Date!, $to: Date!) { - assetsByFlag: assetMetrics(from: $from, to: $to, sort: FLAG) { - ...metrics - } - assetsByActivity: assetMetrics(from: $from, to: $to, sort: ACTIVITY) { - ...metrics - } -} diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql deleted file mode 100644 index 6bfcf81ec..000000000 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ /dev/null @@ -1,68 +0,0 @@ -#import "../fragments/commentView.graphql" - -query ModQueue ($asset_id: ID, $sort: SORT_ORDER) { - all: comments(query: { - statuses: [NONE, PREMOD, ACCEPTED, REJECTED], - asset_id: $asset_id, - sort: $sort - }) { - ...commentView - } - accepted: comments(query: { - statuses: [ACCEPTED], - asset_id: $asset_id, - sort: $sort - }) { - ...commentView - } - premod: comments(query: { - statuses: [PREMOD], - asset_id: $asset_id, - sort: $sort - }) { - ...commentView - } - flagged: comments(query: { - action_type: FLAG, - asset_id: $asset_id, - statuses: [NONE, PREMOD], - sort: $sort - }) { - ...commentView - } - rejected: comments(query: { - statuses: [REJECTED], - asset_id: $asset_id, - sort: $sort - }) { - ...commentView - } - assets: assets { - id - title - url - } - allCount: commentCount(query: { - asset_id: $asset_id - }) - acceptedCount: commentCount(query: { - statuses: [ACCEPTED], - asset_id: $asset_id - }) - premodCount: commentCount(query: { - statuses: [PREMOD], - asset_id: $asset_id - }) - rejectedCount: commentCount(query: { - statuses: [REJECTED], - asset_id: $asset_id - }) - flaggedCount: commentCount(query: { - action_type: FLAG, - asset_id: $asset_id, - statuses: [NONE, PREMOD] - }) - settings { - organizationName - } -} diff --git a/client/coral-admin/src/graphql/queries/modUserFlaggedQuery.graphql b/client/coral-admin/src/graphql/queries/modUserFlaggedQuery.graphql deleted file mode 100644 index c99d94583..000000000 --- a/client/coral-admin/src/graphql/queries/modUserFlaggedQuery.graphql +++ /dev/null @@ -1,26 +0,0 @@ -query Users ($action_type: ACTION_TYPE) { - users (query:{action_type: $action_type}){ - id - username - status - roles - actions{ - id - created_at - ... on FlagAction { - reason - message - user { - id - username - } - } - } - action_summaries { - count - ... on FlagActionSummary { - reason - } - } - } -} diff --git a/client/coral-admin/src/graphql/queries/userDetail.graphql b/client/coral-admin/src/graphql/queries/userDetail.graphql deleted file mode 100644 index c375b80ff..000000000 --- a/client/coral-admin/src/graphql/queries/userDetail.graphql +++ /dev/null @@ -1,13 +0,0 @@ -query UserDetail ($author_id: ID!) { - user(id: $author_id) { - id - username - created_at - profiles { - id - provider - } - } - totalComments: commentCount(query: {author_id: $author_id}) - rejectedComments: commentCount(query: {author_id: $author_id, statuses: [REJECTED]}) -} diff --git a/client/coral-admin/src/index.js b/client/coral-admin/src/index.js index 9a532856e..d0dfc5f9a 100644 --- a/client/coral-admin/src/index.js +++ b/client/coral-admin/src/index.js @@ -8,6 +8,10 @@ import store from './services/store'; import App from './components/App'; import 'react-mdl/extra/material.js'; +import './graphql'; +import {loadPluginsTranslations} from 'coral-framework/helpers/plugins'; + +loadPluginsTranslations(); render( diff --git a/client/coral-admin/src/reducers/moderation.js b/client/coral-admin/src/reducers/moderation.js index 4bcd82d47..10cbe0eca 100644 --- a/client/coral-admin/src/reducers/moderation.js +++ b/client/coral-admin/src/reducers/moderation.js @@ -10,6 +10,7 @@ const initialState = fromJS({ userDetailId: null, banDialog: false, shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show', + sortOrder: 'REVERSE_CHRONOLOGICAL', suspendUserDialog: { show: false, userId: null, @@ -64,6 +65,8 @@ export default function moderation (state = initialState, action) { return state.set('userDetailId', action.userId); case actions.HIDE_USER_DETAIL: return state.set('userDetailId', null); + case actions.SET_SORT_ORDER: + return state.set('sortOrder', action.order); default : return state; } diff --git a/client/coral-admin/src/containers/Community/components/ActionButton.js b/client/coral-admin/src/routes/Community/components/ActionButton.js similarity index 84% rename from client/coral-admin/src/containers/Community/components/ActionButton.js rename to client/coral-admin/src/routes/Community/components/ActionButton.js index 34eb852e4..67f16acad 100644 --- a/client/coral-admin/src/containers/Community/components/ActionButton.js +++ b/client/coral-admin/src/routes/Community/components/ActionButton.js @@ -1,8 +1,8 @@ import React from 'react'; -import styles from '../Community.css'; +import styles from './Community.css'; import BanUserButton from './BanUserButton'; import {Button} from 'coral-ui'; -import {menuActionsMap} from '../../../containers/ModerationQueue/helpers/moderationQueueActionsMap'; +import {menuActionsMap} from '../../Moderation/helpers/moderationQueueActionsMap'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-admin/src/containers/Community/components/BanUserButton.css b/client/coral-admin/src/routes/Community/components/BanUserButton.css similarity index 100% rename from client/coral-admin/src/containers/Community/components/BanUserButton.css rename to client/coral-admin/src/routes/Community/components/BanUserButton.css diff --git a/client/coral-admin/src/containers/Community/components/BanUserButton.js b/client/coral-admin/src/routes/Community/components/BanUserButton.js similarity index 100% rename from client/coral-admin/src/containers/Community/components/BanUserButton.js rename to client/coral-admin/src/routes/Community/components/BanUserButton.js diff --git a/client/coral-admin/src/containers/Community/components/BanUserDialog.css b/client/coral-admin/src/routes/Community/components/BanUserDialog.css similarity index 100% rename from client/coral-admin/src/containers/Community/components/BanUserDialog.css rename to client/coral-admin/src/routes/Community/components/BanUserDialog.css diff --git a/client/coral-admin/src/containers/Community/components/BanUserDialog.js b/client/coral-admin/src/routes/Community/components/BanUserDialog.js similarity index 100% rename from client/coral-admin/src/containers/Community/components/BanUserDialog.js rename to client/coral-admin/src/routes/Community/components/BanUserDialog.js diff --git a/client/coral-admin/src/containers/Community/Community.css b/client/coral-admin/src/routes/Community/components/Community.css similarity index 100% rename from client/coral-admin/src/containers/Community/Community.css rename to client/coral-admin/src/routes/Community/components/Community.css diff --git a/client/coral-admin/src/containers/Community/CommunityContainer.js b/client/coral-admin/src/routes/Community/components/Community.js similarity index 54% rename from client/coral-admin/src/containers/Community/CommunityContainer.js rename to client/coral-admin/src/routes/Community/components/Community.js index 7c0051477..f6544b0ac 100644 --- a/client/coral-admin/src/containers/Community/CommunityContainer.js +++ b/client/coral-admin/src/routes/Community/components/Community.js @@ -1,55 +1,26 @@ import React, {Component} from 'react'; -import {connect} from 'react-redux'; -import {compose} from 'react-apollo'; - -import {modUserFlaggedQuery} from 'coral-admin/src/graphql/queries'; -import {banUser, setUserStatus, rejectUsername} from 'coral-admin/src/graphql/mutations'; - -import { - fetchAccounts, - updateSorting, - newPage, - showBanUserDialog, - hideBanUserDialog, - showSuspendUserDialog, - hideSuspendUserDialog -} from '../../actions/community'; - -import CommunityMenu from './components/CommunityMenu'; -import BanUserDialog from './components/BanUserDialog'; -import SuspendUserDialog from './components/SuspendUserDialog'; +import CommunityMenu from './CommunityMenu'; +import BanUserDialog from './BanUserDialog'; +import SuspendUserDialog from './SuspendUserDialog'; import People from './People'; import FlaggedAccounts from './FlaggedAccounts'; -class CommunityContainer extends Component { +export default class Community extends Component { - constructor(props) { - super(props); + state = { + searchValue: '', + timer: null + }; - this.state = { - searchValue: '', - timer: null - }; - - this.onKeyDownHandler = this.onKeyDownHandler.bind(this); - this.onSearchChange = this.onSearchChange.bind(this); - this.onHeaderClickHandler = this.onHeaderClickHandler.bind(this); - this.onNewPageHandler = this.onNewPageHandler.bind(this); - } - - componentWillMount() { - this.props.fetchAccounts({}); - } - - onKeyDownHandler(e) { + onKeyDownHandler = (e) => { if (e.key === 'Enter') { e.preventDefault(); this.search(); } } - onSearchChange(e) { + onSearchChange = (e) => { const value = e.target.value; this.setState((prevState) => { prevState.searchValue = value; @@ -62,6 +33,16 @@ class CommunityContainer extends Component { }); } + onHeaderClickHandler = (sort) => { + this.props.updateSorting(sort); + this.search(); + } + + onNewPageHandler = (page) => { + this.props.newPage(page); + this.search({page}); + } + search(query = {}) { const {community} = this.props; @@ -71,21 +52,10 @@ class CommunityContainer extends Component { asc: community.ascPeople, ...query }); - - } - - onHeaderClickHandler(sort) { - this.props.dispatch(updateSorting(sort)); - this.search(); - } - - onNewPageHandler(page) { - this.props.dispatch(newPage(page)); - this.search({page}); } getTabContent(searchValue, props) { - const {community, data} = props; + const {community, root: {users}} = props; const activeTab = props.route.path === ':id' ? 'flagged' : props.route.path; if (activeTab === 'people') { @@ -108,9 +78,7 @@ class CommunityContainer extends Component { return (
({ - community: state.community.toJS() -}); - -const mapDispatchToProps = (dispatch) => ({ - fetchAccounts: (query) => dispatch(fetchAccounts(query)), - showBanUserDialog: (user) => dispatch(showBanUserDialog(user)), - hideBanUserDialog: () => dispatch(hideBanUserDialog(false)), - showSuspendUserDialog: (user) => dispatch(showSuspendUserDialog(user)), - hideSuspendUserDialog: () => dispatch(hideSuspendUserDialog()) -}); - -export default compose( - connect(mapStateToProps, mapDispatchToProps), - modUserFlaggedQuery, - banUser, - setUserStatus, - rejectUsername -)(CommunityContainer); diff --git a/client/coral-admin/src/containers/Community/CommunityLayout.js b/client/coral-admin/src/routes/Community/components/CommunityLayout.js similarity index 100% rename from client/coral-admin/src/containers/Community/CommunityLayout.js rename to client/coral-admin/src/routes/Community/components/CommunityLayout.js diff --git a/client/coral-admin/src/containers/Community/components/CommunityMenu.js b/client/coral-admin/src/routes/Community/components/CommunityMenu.js similarity index 100% rename from client/coral-admin/src/containers/Community/components/CommunityMenu.js rename to client/coral-admin/src/routes/Community/components/CommunityMenu.js diff --git a/client/coral-admin/src/containers/Community/FlaggedAccounts.js b/client/coral-admin/src/routes/Community/components/FlaggedAccounts.js similarity index 82% rename from client/coral-admin/src/containers/Community/FlaggedAccounts.js rename to client/coral-admin/src/routes/Community/components/FlaggedAccounts.js index 222ace8bb..df6368844 100644 --- a/client/coral-admin/src/containers/Community/FlaggedAccounts.js +++ b/client/coral-admin/src/routes/Community/components/FlaggedAccounts.js @@ -2,20 +2,18 @@ import React from 'react'; import t from 'coral-framework/services/i18n'; import styles from './Community.css'; -import Loading from './Loading'; import EmptyCard from 'coral-admin/src/components/EmptyCard'; -import User from './components/User'; +import User from './User'; const FlaggedAccounts = ({...props}) => { - const {commenters, isFetching} = props; - const hasResults = !isFetching && commenters && !!commenters.length; + const {commenters} = props; + const hasResults = commenters && !!commenters.length; // if (commenter.status === 'PENDING' && commenter.actions.length > 0) { return (
- { isFetching && } { hasResults ? commenters.map((commenter, index) => { diff --git a/client/coral-admin/src/containers/Community/People.js b/client/coral-admin/src/routes/Community/components/People.js similarity index 94% rename from client/coral-admin/src/containers/Community/People.js rename to client/coral-admin/src/routes/Community/components/People.js index 4afbe387f..464f3bc5b 100644 --- a/client/coral-admin/src/containers/Community/People.js +++ b/client/coral-admin/src/routes/Community/components/People.js @@ -1,9 +1,9 @@ import React from 'react'; import styles from './Community.css'; -import Table from './Table'; +import Table from '../containers/Table'; import {Pager, Icon} from 'coral-ui'; -import EmptyCard from '../../components/EmptyCard'; +import EmptyCard from '../../../components/EmptyCard'; import t from 'coral-framework/services/i18n'; const tableHeaders = [ diff --git a/client/coral-admin/src/containers/Community/components/SuspendUserDialog.css b/client/coral-admin/src/routes/Community/components/SuspendUserDialog.css similarity index 100% rename from client/coral-admin/src/containers/Community/components/SuspendUserDialog.css rename to client/coral-admin/src/routes/Community/components/SuspendUserDialog.css diff --git a/client/coral-admin/src/containers/Community/components/SuspendUserDialog.js b/client/coral-admin/src/routes/Community/components/SuspendUserDialog.js similarity index 100% rename from client/coral-admin/src/containers/Community/components/SuspendUserDialog.js rename to client/coral-admin/src/routes/Community/components/SuspendUserDialog.js diff --git a/client/coral-admin/src/routes/Community/components/Table.js b/client/coral-admin/src/routes/Community/components/Table.js new file mode 100644 index 000000000..dac97b1ad --- /dev/null +++ b/client/coral-admin/src/routes/Community/components/Table.js @@ -0,0 +1,54 @@ +import React from 'react'; +import {SelectField, Option} from 'react-mdl-selectfield'; +import styles from '../components/Community.css'; +import t from 'coral-framework/services/i18n'; + +export default ({headers, commenters, onHeaderClickHandler, onRoleChange, onCommenterStatusChange}) => ( + + + + {headers.map((header, i) =>( + + ))} + + + + {commenters.map((row, i)=> ( + + + + + + + ))} + +
onHeaderClickHandler({field: header.field})}> + {header.title} +
+ {row.username} + {row.profiles.map(({id}) => id)} + + {row.created_at} + + onCommenterStatusChange(row.id, status)}> + + + + + onRoleChange(row.id, role)}> + + + + + +
+); diff --git a/client/coral-admin/src/containers/Community/components/User.js b/client/coral-admin/src/routes/Community/components/User.js similarity index 98% rename from client/coral-admin/src/containers/Community/components/User.js rename to client/coral-admin/src/routes/Community/components/User.js index 05224170b..84d30e5f3 100644 --- a/client/coral-admin/src/containers/Community/components/User.js +++ b/client/coral-admin/src/routes/Community/components/User.js @@ -1,5 +1,5 @@ import React from 'react'; -import styles from '../Community.css'; +import styles from './Community.css'; import ActionButton from './ActionButton'; diff --git a/client/coral-admin/src/containers/Community/components/styles.css b/client/coral-admin/src/routes/Community/components/styles.css similarity index 100% rename from client/coral-admin/src/containers/Community/components/styles.css rename to client/coral-admin/src/routes/Community/components/styles.css diff --git a/client/coral-admin/src/routes/Community/containers/Community.js b/client/coral-admin/src/routes/Community/containers/Community.js new file mode 100644 index 000000000..8c5de95d4 --- /dev/null +++ b/client/coral-admin/src/routes/Community/containers/Community.js @@ -0,0 +1,107 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {compose, gql} from 'react-apollo'; +import withQuery from 'coral-framework/hocs/withQuery'; +import {Spinner} from 'coral-ui'; + +import {withSetUserStatus, withRejectUsername} from 'coral-framework/graphql/mutations'; + +import { + fetchAccounts, + updateSorting, + newPage, + showBanUserDialog, + hideBanUserDialog, + showSuspendUserDialog, + hideSuspendUserDialog +} from '../../../actions/community'; + +import Community from '../components/Community'; + +class CommunityContainer extends Component { + + componentWillMount() { + this.props.fetchAccounts({}); + } + + approveUser = ({userId}) => { + return this.props.setUserStatus({userId, status: 'APPROVED'}); + } + + banUser = ({userId}) => { + return this.props.setUserStatus({userId, status: 'BANNED'}); + } + + render() { + if (this.props.data.error) { + return
{this.props.data.error.message}
; + } + + if (!('users' in this.props.root)) { + return
; + } + return ( + + ); + } +} + +export const withCommunityQuery = withQuery(gql` + query CoralAdmin_Community($action_type: ACTION_TYPE) { + users(query:{action_type: $action_type}){ + id + username + status + roles + actions{ + id + created_at + ... on FlagAction { + reason + message + user { + id + username + } + } + } + action_summaries { + count + ... on FlagActionSummary { + reason + } + } + } + } +`, { + options: ({params: {action_type = 'FLAG'}}) => { + return { + variables: { + action_type: action_type + } + }; + } +}); + +const mapStateToProps = (state) => ({ + community: state.community.toJS() +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({ + fetchAccounts, + showBanUserDialog, + hideBanUserDialog, + showSuspendUserDialog, + hideSuspendUserDialog, + updateSorting, + newPage, + }, dispatch); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), + withCommunityQuery, + withSetUserStatus, + withRejectUsername, +)(CommunityContainer); diff --git a/client/coral-admin/src/routes/Community/containers/Table.js b/client/coral-admin/src/routes/Community/containers/Table.js new file mode 100644 index 000000000..499e0718a --- /dev/null +++ b/client/coral-admin/src/routes/Community/containers/Table.js @@ -0,0 +1,37 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {compose} from 'react-apollo'; +import {setRole, setCommenterStatus} from '../../../actions/community'; +import Table from '../components/Table'; + +class TableContainer extends Component { + + constructor (props) { + super(props); + } + + render () { + return ; + } +} + +const mapStateToProps = (state) => ({ + commenters: state.community.get('accounts'), +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({ + setCommenterStatus, + setRole, + }, dispatch); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), +)(TableContainer); + diff --git a/client/coral-admin/src/routes/Community/index.js b/client/coral-admin/src/routes/Community/index.js new file mode 100644 index 000000000..528fe63cd --- /dev/null +++ b/client/coral-admin/src/routes/Community/index.js @@ -0,0 +1,2 @@ +export {default as Community} from './containers/Community'; +export {default as CommunityLayout} from './components/CommunityLayout'; diff --git a/client/coral-admin/src/containers/Configure/Configure.css b/client/coral-admin/src/routes/Configure/components/Configure.css similarity index 100% rename from client/coral-admin/src/containers/Configure/Configure.css rename to client/coral-admin/src/routes/Configure/components/Configure.css diff --git a/client/coral-admin/src/containers/Configure/Configure.js b/client/coral-admin/src/routes/Configure/components/Configure.js similarity index 82% rename from client/coral-admin/src/containers/Configure/Configure.js rename to client/coral-admin/src/routes/Configure/components/Configure.js index 4eb046df4..84e47dfb7 100644 --- a/client/coral-admin/src/containers/Configure/Configure.js +++ b/client/coral-admin/src/routes/Configure/components/Configure.js @@ -1,12 +1,4 @@ import React, {Component} from 'react'; -import {connect} from 'react-redux'; -import { - fetchSettings, - updateSettings, - saveSettingsToServer, - updateWordlist, - updateDomainlist -} from '../../actions/settings'; import {Button, List, Item, Card, Spinner} from 'coral-ui'; import styles from './Configure.css'; @@ -16,45 +8,36 @@ import TechSettings from './TechSettings'; import t from 'coral-framework/services/i18n'; import {can} from 'coral-framework/services/perms'; -class Configure extends Component { - constructor (props) { - super(props); +export default class Configure extends Component { - this.state = { - activeSection: 'stream', - changed: false, - errors: {} - }; - - this.changeSection = this.changeSection.bind(this); - } - - componentWillMount = () => { - this.props.dispatch(fetchSettings()); - } + state = { + activeSection: 'stream', + changed: false, + errors: {} + }; saveSettings = () => { - this.props.dispatch(saveSettingsToServer()); + this.props.saveSettingsToServer(); this.setState({changed: false}); } - changeSection(activeSection) { + changeSection = (activeSection) => { this.setState({activeSection}); } onChangeWordlist = (listName, list) => { this.setState({changed: true}); - this.props.dispatch(updateWordlist(listName, list)); + this.props.updateWordlist(listName, list); } onChangeDomainlist = (listName, list) => { this.setState({changed: true}); - this.props.dispatch(updateDomainlist(listName, list)); + this.props.updateDomainlist(listName, list); } onSettingUpdate = (setting) => { this.setState({changed: true}); - this.props.dispatch(updateSettings(setting)); + this.props.updateSettings(setting); } // Sets an arbitrary error string and a boolean state. @@ -175,9 +158,3 @@ class Configure extends Component { ); } } - -const mapStateToProps = (state) => ({ - auth: state.auth.toJS(), - settings: state.settings.toJS() -}); -export default connect(mapStateToProps)(Configure); diff --git a/client/coral-admin/src/containers/Configure/Domainlist.js b/client/coral-admin/src/routes/Configure/components/Domainlist.js similarity index 100% rename from client/coral-admin/src/containers/Configure/Domainlist.js rename to client/coral-admin/src/routes/Configure/components/Domainlist.js diff --git a/client/coral-admin/src/containers/Configure/EmbedLink.js b/client/coral-admin/src/routes/Configure/components/EmbedLink.js similarity index 99% rename from client/coral-admin/src/containers/Configure/EmbedLink.js rename to client/coral-admin/src/routes/Configure/components/EmbedLink.js index 7ff348b50..1cd48cdc5 100644 --- a/client/coral-admin/src/containers/Configure/EmbedLink.js +++ b/client/coral-admin/src/routes/Configure/components/EmbedLink.js @@ -1,5 +1,4 @@ import React, {Component} from 'react'; - import t from 'coral-framework/services/i18n'; import styles from './Configure.css'; import {Button, Card} from 'coral-ui'; diff --git a/client/coral-admin/src/containers/Configure/ModerationSettings.js b/client/coral-admin/src/routes/Configure/components/ModerationSettings.js similarity index 100% rename from client/coral-admin/src/containers/Configure/ModerationSettings.js rename to client/coral-admin/src/routes/Configure/components/ModerationSettings.js diff --git a/client/coral-admin/src/containers/Configure/StreamSettings.js b/client/coral-admin/src/routes/Configure/components/StreamSettings.js similarity index 100% rename from client/coral-admin/src/containers/Configure/StreamSettings.js rename to client/coral-admin/src/routes/Configure/components/StreamSettings.js diff --git a/client/coral-admin/src/containers/Configure/TechSettings.js b/client/coral-admin/src/routes/Configure/components/TechSettings.js similarity index 100% rename from client/coral-admin/src/containers/Configure/TechSettings.js rename to client/coral-admin/src/routes/Configure/components/TechSettings.js diff --git a/client/coral-admin/src/containers/Configure/Wordlist.js b/client/coral-admin/src/routes/Configure/components/Wordlist.js similarity index 100% rename from client/coral-admin/src/containers/Configure/Wordlist.js rename to client/coral-admin/src/routes/Configure/components/Wordlist.js diff --git a/client/coral-admin/src/routes/Configure/containers/Configure.js b/client/coral-admin/src/routes/Configure/containers/Configure.js new file mode 100644 index 000000000..c68b39e0c --- /dev/null +++ b/client/coral-admin/src/routes/Configure/containers/Configure.js @@ -0,0 +1,42 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {compose} from 'react-apollo'; +import { + fetchSettings, + updateSettings, + saveSettingsToServer, + updateWordlist, + updateDomainlist +} from '../../../actions/settings'; + +import Configure from '../components/Configure'; + +class ConfigureContainer extends Component { + componentWillMount = () => { + this.props.fetchSettings(); + } + + render () { + return ; + } +} + +const mapStateToProps = (state) => ({ + auth: state.auth.toJS(), + settings: state.settings.toJS() +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({ + fetchSettings, + updateSettings, + saveSettingsToServer, + updateWordlist, + updateDomainlist + }, dispatch); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), +)(ConfigureContainer); + diff --git a/client/coral-admin/src/routes/Configure/index.js b/client/coral-admin/src/routes/Configure/index.js new file mode 100644 index 000000000..d548be931 --- /dev/null +++ b/client/coral-admin/src/routes/Configure/index.js @@ -0,0 +1 @@ +export {default} from './containers/Configure'; diff --git a/client/coral-admin/src/containers/Dashboard/ActivityWidget.js b/client/coral-admin/src/routes/Dashboard/components/ActivityWidget.js similarity index 100% rename from client/coral-admin/src/containers/Dashboard/ActivityWidget.js rename to client/coral-admin/src/routes/Dashboard/components/ActivityWidget.js diff --git a/client/coral-admin/src/components/CountdownTimer.js b/client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js similarity index 96% rename from client/coral-admin/src/components/CountdownTimer.js rename to client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js index a1bf32769..9e1995b7f 100644 --- a/client/coral-admin/src/components/CountdownTimer.js +++ b/client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js @@ -1,5 +1,5 @@ import React, {PropTypes} from 'react'; -import styles from 'coral-admin/src/containers/Dashboard/Dashboard.css'; +import styles from './Dashboard.css'; import {Icon} from 'coral-ui'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.css b/client/coral-admin/src/routes/Dashboard/components/Dashboard.css similarity index 100% rename from client/coral-admin/src/containers/Dashboard/Dashboard.css rename to client/coral-admin/src/routes/Dashboard/components/Dashboard.css diff --git a/client/coral-admin/src/routes/Dashboard/components/Dashboard.js b/client/coral-admin/src/routes/Dashboard/components/Dashboard.js new file mode 100644 index 000000000..ba224a3b9 --- /dev/null +++ b/client/coral-admin/src/routes/Dashboard/components/Dashboard.js @@ -0,0 +1,15 @@ +import React from 'react'; +import FlagWidget from './FlagWidget'; +import ActivityWidget from './ActivityWidget'; +import CountdownTimer from './CountdownTimer'; +import styles from './Dashboard.css'; + +export default ({root: {assetsByActivity, assetsByFlag}, reloadData}) => ( +
+ +
+ + +
+
+); diff --git a/client/coral-admin/src/containers/Dashboard/FlagWidget.js b/client/coral-admin/src/routes/Dashboard/components/FlagWidget.js similarity index 100% rename from client/coral-admin/src/containers/Dashboard/FlagWidget.js rename to client/coral-admin/src/routes/Dashboard/components/FlagWidget.js diff --git a/client/coral-admin/src/containers/Dashboard/LikeWidget.js b/client/coral-admin/src/routes/Dashboard/components/LikeWidget.js similarity index 100% rename from client/coral-admin/src/containers/Dashboard/LikeWidget.js rename to client/coral-admin/src/routes/Dashboard/components/LikeWidget.js diff --git a/client/coral-admin/src/containers/Dashboard/MostLikedCommentsWidget.js b/client/coral-admin/src/routes/Dashboard/components/MostLikedCommentsWidget.js similarity index 100% rename from client/coral-admin/src/containers/Dashboard/MostLikedCommentsWidget.js rename to client/coral-admin/src/routes/Dashboard/components/MostLikedCommentsWidget.js diff --git a/client/coral-admin/src/containers/Dashboard/Widget.css b/client/coral-admin/src/routes/Dashboard/components/Widget.css similarity index 100% rename from client/coral-admin/src/containers/Dashboard/Widget.css rename to client/coral-admin/src/routes/Dashboard/components/Widget.css diff --git a/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js b/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js new file mode 100644 index 000000000..ea1fb2b6c --- /dev/null +++ b/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js @@ -0,0 +1,65 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import Dashboard from '../components/Dashboard'; +import {compose, gql} from 'react-apollo'; +import withQuery from 'coral-framework/hocs/withQuery'; + +import {Spinner} from 'coral-ui'; + +class DashboardContainer extends React.Component { + + reloadData = () => { + this.props.data.refetch(); + } + + render () { + if (this.props.data.loading) { + return ; + } + return ; + } +} + +export const witDashboardQuery = withQuery(gql` + query CoralAdmin_Dashboard($from: Date!, $to: Date!) { + assetsByFlag: assetMetrics(from: $from, to: $to, sort: FLAG) { + ...CoralAdmin_Metrics + } + assetsByActivity: assetMetrics(from: $from, to: $to, sort: ACTIVITY) { + ...CoralAdmin_Metrics + } + } + fragment CoralAdmin_Metrics on Asset { + id + title + url + author + created_at + commentCount + action_summaries { + actionCount + actionableItemCount + } + } +`, { + options: ({settings: {dashboardWindowStart, dashboardWindowEnd}}) => { + return { + variables: { + from: dashboardWindowStart, + to: dashboardWindowEnd + } + }; + } +}); + +const mapStateToProps = (state) => { + return { + settings: state.settings.toJS(), + moderation: state.moderation.toJS() + }; +}; + +export default compose( + connect(mapStateToProps), + witDashboardQuery, +)(DashboardContainer); diff --git a/client/coral-admin/src/routes/Dashboard/index.js b/client/coral-admin/src/routes/Dashboard/index.js new file mode 100644 index 000000000..5bbb476e2 --- /dev/null +++ b/client/coral-admin/src/routes/Dashboard/index.js @@ -0,0 +1 @@ +export {default} from './containers/Dashboard.js'; diff --git a/client/coral-admin/src/routes/Install/components/Install.js b/client/coral-admin/src/routes/Install/components/Install.js new file mode 100644 index 000000000..f7069d67c --- /dev/null +++ b/client/coral-admin/src/routes/Install/components/Install.js @@ -0,0 +1,79 @@ +import React, {Component} from 'react'; +import styles from './style.css'; +import {Wizard, WizardNav} from 'coral-ui'; +import Layout from 'coral-admin/src/components/ui/Layout'; + +import InitialStep from './Steps/InitialStep'; +import AddOrganizationName from './Steps/AddOrganizationName'; +import CreateYourAccount from './Steps/CreateYourAccount'; +import PermittedDomainsStep from './Steps/PermittedDomainsStep'; +import FinalStep from './Steps/FinalStep'; + +export default class Install extends Component { + handleDomainsChange = (value) => { + this.props.updatePermittedDomains(value); + }; + + handleSettingsChange = ({currentTarget: {name, value}}) => { + this.props.updateSettingsFormData(name, value); + }; + + handleUserChange = ({currentTarget: {name, value}}) => { + this.props.updateUserFormData(name, value); + }; + + handleSettingsSubmit = (e) => { + e.preventDefault(); + this.props.submitSettings(); + }; + + handleUserSubmit = (e) => { + e.preventDefault(); + this.props.submitUser(); + } + + render() { + const {install} = this.props; + + return ( + +
+ { + !install.alreadyInstalled ? ( +
+

Welcome to the Coral Project

+ { install.step !== 0 ? : null } + + + + + + + +
+ ) : ( +
Talk is already installed
+ ) + } +
+
+ ); + } +} diff --git a/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js b/client/coral-admin/src/routes/Install/components/Steps/AddOrganizationName.js similarity index 77% rename from client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js rename to client/coral-admin/src/routes/Install/components/Steps/AddOrganizationName.js index 294e64f8d..064167dda 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js +++ b/client/coral-admin/src/routes/Install/components/Steps/AddOrganizationName.js @@ -8,18 +8,18 @@ const AddOrganizationName = (props) => { const {handleSettingsChange, handleSettingsSubmit, install} = props; return (
-

{t('ADD_ORGANIZATION.DESCRIPTION')}

+

{t('install.add_organization.description')}

- +
diff --git a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js b/client/coral-admin/src/routes/Install/components/Steps/CreateYourAccount.js similarity index 85% rename from client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js rename to client/coral-admin/src/routes/Install/components/Steps/CreateYourAccount.js index d07b9e9ff..c5d334610 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js +++ b/client/coral-admin/src/routes/Install/components/Steps/CreateYourAccount.js @@ -14,7 +14,7 @@ const InitialStep = (props) => { className={styles.textField} id="email" type="email" - label={t('CREATE.EMAIL')} + label={t('install.create.email')} onChange={handleUserChange} showErrors={install.showErrors} errorMsg={install.errors.email} @@ -25,7 +25,7 @@ const InitialStep = (props) => { className={styles.textField} id="username" type="text" - label={t('CREATE.USERNAME')} + label={t('install.create.username')} onChange={handleUserChange} showErrors={install.showErrors} errorMsg={install.errors.username} @@ -35,7 +35,7 @@ const InitialStep = (props) => { className={styles.textField} id="password" type="password" - label={t('CREATE.PASSWORD')} + label={t('install.create.password')} onChange={handleUserChange} showErrors={install.showErrors} errorMsg={install.errors.password} @@ -45,7 +45,7 @@ const InitialStep = (props) => { className={styles.textField} id="confirmPassword" type="password" - label={t('CREATE.CONFIRM_PASSWORD')} + label={t('install.create.confirm_password')} onChange={handleUserChange} showErrors={install.showErrors} errorMsg={install.errors.confirm_password} @@ -53,7 +53,7 @@ const InitialStep = (props) => { { !props.install.isLoading ? - + : } diff --git a/client/coral-admin/src/containers/Install/components/Steps/FinalStep.js b/client/coral-admin/src/routes/Install/components/Steps/FinalStep.js similarity index 68% rename from client/coral-admin/src/containers/Install/components/Steps/FinalStep.js rename to client/coral-admin/src/routes/Install/components/Steps/FinalStep.js index a09b6ebb1..dbfd33461 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/FinalStep.js +++ b/client/coral-admin/src/routes/Install/components/Steps/FinalStep.js @@ -8,9 +8,9 @@ import t from 'coral-framework/services/i18n'; const InitialStep = () => { return (
-

{t('FINAL.DESCRIPTION')}

- - +

{t('install.final.description')}

+ +
); }; diff --git a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js b/client/coral-admin/src/routes/Install/components/Steps/InitialStep.js similarity index 67% rename from client/coral-admin/src/containers/Install/components/Steps/InitialStep.js rename to client/coral-admin/src/routes/Install/components/Steps/InitialStep.js index 1d8d8b050..aceb0d965 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js +++ b/client/coral-admin/src/routes/Install/components/Steps/InitialStep.js @@ -8,8 +8,8 @@ const InitialStep = (props) => { const {nextStep} = props; return (
-

{t('INITIAL.DESCRIPTION')}

- +

{t('install.initial.description')}

+
); }; diff --git a/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js b/client/coral-admin/src/routes/Install/components/Steps/InviteTeamMembers.js similarity index 100% rename from client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js rename to client/coral-admin/src/routes/Install/components/Steps/InviteTeamMembers.js diff --git a/client/coral-admin/src/containers/Install/components/Steps/PermittedDomainsStep.js b/client/coral-admin/src/routes/Install/components/Steps/PermittedDomainsStep.js similarity index 83% rename from client/coral-admin/src/containers/Install/components/Steps/PermittedDomainsStep.js rename to client/coral-admin/src/routes/Install/components/Steps/PermittedDomainsStep.js index 450aeabd8..fde78779d 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/PermittedDomainsStep.js +++ b/client/coral-admin/src/routes/Install/components/Steps/PermittedDomainsStep.js @@ -10,9 +10,9 @@ const PermittedDomainsStep = (props) => { const domains = install.data.settings.domains.whitelist; return (
-

{t('PERMITTED_DOMAINS.TITLE')}

+

{t('install.permitted_domains.title')}

-

{t('PERMITTED_DOMAINS.DESCRIPTION')}

+

{t('install.permitted_domains.description')}

{ onChange={(tags) => handleDomainsChange(tags)} />
- +
); }; diff --git a/client/coral-admin/src/containers/Install/components/Steps/style.css b/client/coral-admin/src/routes/Install/components/Steps/style.css similarity index 100% rename from client/coral-admin/src/containers/Install/components/Steps/style.css rename to client/coral-admin/src/routes/Install/components/Steps/style.css diff --git a/client/coral-admin/src/containers/Install/style.css b/client/coral-admin/src/routes/Install/components/style.css similarity index 100% rename from client/coral-admin/src/containers/Install/style.css rename to client/coral-admin/src/routes/Install/components/style.css diff --git a/client/coral-admin/src/routes/Install/containers/Install.js b/client/coral-admin/src/routes/Install/containers/Install.js new file mode 100644 index 000000000..5dcb35705 --- /dev/null +++ b/client/coral-admin/src/routes/Install/containers/Install.js @@ -0,0 +1,57 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {compose} from 'react-apollo'; +import Install from '../components/Install'; + +import { + goToStep, + nextStep, + submitUser, + checkInstall, + previousStep, + finishInstall, + submitSettings, + updateUserFormData, + updateSettingsFormData, + updatePermittedDomains +} from '../../../actions/install'; + +class InstallContainer extends Component { + componentDidMount() { + const {checkInstall} = this.props; + checkInstall(() => { + this.context.router.push('/admin'); + }); + } + + render() { + return ; + } +} + +InstallContainer.contextTypes = { + router: React.PropTypes.object +}; + +const mapStateToProps = (state) => ({ + install: state.install.toJS() +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({ + goToStep, + nextStep, + submitUser, + checkInstall, + previousStep, + finishInstall, + submitSettings, + updateUserFormData, + updateSettingsFormData, + updatePermittedDomains + }, dispatch); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), +)(InstallContainer); diff --git a/client/coral-admin/src/routes/Install/index.js b/client/coral-admin/src/routes/Install/index.js new file mode 100644 index 000000000..94c7bf93f --- /dev/null +++ b/client/coral-admin/src/routes/Install/index.js @@ -0,0 +1 @@ +export {default} from './containers/Install.js'; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.css b/client/coral-admin/src/routes/Moderation/components/BanUserDialog.css similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.css rename to client/coral-admin/src/routes/Moderation/components/BanUserDialog.css diff --git a/client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.js b/client/coral-admin/src/routes/Moderation/components/BanUserDialog.js similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.js rename to client/coral-admin/src/routes/Moderation/components/BanUserDialog.js diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.css b/client/coral-admin/src/routes/Moderation/components/Comment.css similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/components/Comment.css rename to client/coral-admin/src/routes/Moderation/components/Comment.css diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js similarity index 89% rename from client/coral-admin/src/containers/ModerationQueue/components/Comment.js rename to client/coral-admin/src/routes/Moderation/components/Comment.js index bf6b050a1..2664fefc5 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -39,8 +39,8 @@ const Comment = ({ } // since words are checked against word boundaries on the backend, - // this should be the behavior on the front end as well. - // currently the highlighter plugin does not support this out of the box. + // should be the behavior on the front end as well. + // currently the highlighter plugin does not support out of the box. const searchWords = [...suspectWords, ...bannedWords] .filter((w) => { return new RegExp(`(^|\\s)${w}(\\s|$)`).test(comment.body); @@ -82,7 +82,12 @@ const Comment = ({ {t('comment.banned_user')} : null} - +
Story: {comment.asset.title} @@ -104,7 +109,12 @@ const Comment = ({ {t('comment.view_context')}

- +
{links ? @@ -135,12 +145,22 @@ const Comment = ({ ); })}
- +
- +
{flagActions && flagActions.length ?
-

{t('community.Flags')} ({actionSummaries.length}):

+

{t('community.flags')} ({actionSummaries.length}):

    {actionSummaries.map((action, i) =>
  • {this.reasonMap(action.reason)} ({action.count})
  • )}
- {showDetail ? t('modqueue.less-detail') : t('modqueue.more_detail')} + {showDetail ? t('modqueue.less_detail') : t('modqueue.more_detail')}
{showDetail && (
diff --git a/client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js b/client/coral-admin/src/routes/Moderation/components/LoadMore.js similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js rename to client/coral-admin/src/routes/Moderation/components/LoadMore.js diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/routes/Moderation/components/Moderation.js similarity index 51% rename from client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js rename to client/coral-admin/src/routes/Moderation/components/Moderation.js index 152121a63..956575ce4 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/routes/Moderation/components/Moderation.js @@ -1,50 +1,24 @@ import React, {Component} from 'react'; -import {connect} from 'react-redux'; -import {bindActionCreators} from 'redux'; -import {compose} from 'react-apollo'; -import * as notification from 'coral-admin/src/services/notification'; import key from 'keymaster'; -import isEqual from 'lodash/isEqual'; -import styles from './components/styles.css'; -import t, {timeago} from 'coral-framework/services/i18n'; +import styles from './styles.css'; -import {modQueueQuery, getQueueCounts} from '../../graphql/queries'; -import {banUser, setCommentStatus, suspendUser} from '../../graphql/mutations'; - -import {fetchSettings} from 'actions/settings'; -import {updateAssets} from 'actions/assets'; -import { - toggleModal, - singleView, - showBanUserDialog, - hideBanUserDialog, - showSuspendUserDialog, - hideSuspendUserDialog, - hideShortcutsNote, - viewUserDetail, - hideUserDetail -} from 'actions/moderation'; - -import {Spinner} from 'coral-ui'; -import BanUserDialog from './components/BanUserDialog'; -import SuspendUserDialog from './components/SuspendUserDialog'; +import BanUserDialog from './BanUserDialog'; +import SuspendUserDialog from './SuspendUserDialog'; import ModerationQueue from './ModerationQueue'; -import ModerationMenu from './components/ModerationMenu'; -import ModerationHeader from './components/ModerationHeader'; -import NotFoundAsset from './components/NotFoundAsset'; -import ModerationKeysModal from '../../components/ModerationKeysModal'; -import UserDetail from './UserDetail'; +import ModerationMenu from './ModerationMenu'; +import ModerationHeader from './ModerationHeader'; +import NotFoundAsset from './NotFoundAsset'; +import ModerationKeysModal from '../../../components/ModerationKeysModal'; +import UserDetail from '../containers/UserDetail'; -class ModerationContainer extends Component { +export default class Moderation extends Component { state = { selectedIndex: 0, - sort: 'REVERSE_CHRONOLOGICAL' } componentWillMount() { const {toggleModal, singleView} = this.props; - this.props.fetchSettings(); key('s', () => singleView()); key('shift+/', () => toggleModal(true)); key('esc', () => toggleModal(false)); @@ -54,6 +28,10 @@ class ModerationContainer extends Component { key('t', this.moderate(true)); } + onClose = () => { + this.toggleModal(false); + } + moderate = (accept) => () => { const {acceptComment, rejectComment} = this.props; const {selectedIndex} = this.state; @@ -69,9 +47,9 @@ class ModerationContainer extends Component { } getComments = () => { - const {data, route} = this.props; + const {root, route} = this.props; const activeTab = route.path === ':id' ? 'premod' : route.path; - return data[activeTab]; + return root[activeTab]; } select = (next) => () => { @@ -92,38 +70,6 @@ class ModerationContainer extends Component { } } - selectSort = (sort) => { - this.setState({sort}); - this.props.modQueueResort(sort); - } - - suspendUser = async (args) => { - this.props.hideSuspendUserDialog(); - try { - const result = await this.props.suspendUser(args); - if (result.data.suspendUser.errors) { - throw result.data.suspendUser.errors; - } - notification.success( - t('suspenduser.notify_suspend_until', - this.props.moderation.suspendUserDialog.username, - timeago(args.until)), - ); - const {commentStatus, commentId} = this.props.moderation.suspendUserDialog; - if (commentStatus !== 'REJECTED') { - return this.props.rejectComment({commentId}) - .then((result) => { - if (result.data.setCommentStatus.errors) { - throw result.data.setCommentStatus.errors; - } - }); - } - } - catch(err) { - notification.showMutationErrors(err); - } - }; - componentWillUnmount() { key.unbind('s'); key.unbind('shift+/'); @@ -145,28 +91,13 @@ class ModerationContainer extends Component { } } - componentWillReceiveProps(nextProps) { - const {updateAssets} = this.props; - if(!isEqual(nextProps.data.assets, this.props.data.assets)) { - updateAssets(nextProps.data.assets); - } - } - render () { - const {data, moderation, settings, assets, onClose, viewUserDetail, hideUserDetail, ...props} = this.props; + const {root, moderation, settings, assets, viewUserDetail, hideUserDetail, ...props} = this.props; const providedAssetId = this.props.params.id; const activeTab = this.props.route.path === ':id' ? 'premod' : this.props.route.path; let asset; - if (!('premodCount' in data)) { - return
; - } - - if (data.error) { - return
Error
; - } - if (providedAssetId) { asset = assets.find((asset) => asset.id === this.props.params.id); @@ -175,23 +106,23 @@ class ModerationContainer extends Component { } } - const comments = data[activeTab]; + const comments = root[activeTab]; let activeTabCount; switch(activeTab) { case 'all': - activeTabCount = data.allCount; + activeTabCount = root.allCount; break; case 'accepted': - activeTabCount = data.acceptedCount; + activeTabCount = root.acceptedCount; break; case 'premod': - activeTabCount = data.premodCount; + activeTabCount = root.premodCount; break; case 'flagged': - activeTabCount = data.flaggedCount; + activeTabCount = root.flaggedCount; break; case 'rejected': - activeTabCount = data.rejectedCount; + activeTabCount = root.rejectedCount; break; } @@ -200,15 +131,17 @@ class ModerationContainer extends Component { + onClose={this.onClose}/> {moderation.userDetailId && ( ({ - moderation: state.moderation.toJS(), - settings: state.settings.toJS(), - auth: state.auth.toJS(), - assets: state.assets.get('assets') -}); - -const mapDispatchToProps = (dispatch) => ({ - onClose: () => dispatch(toggleModal(false)), - hideBanUserDialog: () => dispatch(hideBanUserDialog(false)), - ...bindActionCreators({ - toggleModal, - singleView, - updateAssets, - fetchSettings, - showBanUserDialog, - hideShortcutsNote, - showSuspendUserDialog, - hideSuspendUserDialog, - viewUserDetail, - hideUserDetail, - }, dispatch), -}); - -export default compose( - connect(mapStateToProps, mapDispatchToProps), - setCommentStatus, - getQueueCounts, - banUser, - suspendUser, - modQueueQuery, -)(ModerationContainer); diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js b/client/coral-admin/src/routes/Moderation/components/ModerationHeader.js similarity index 98% rename from client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js rename to client/coral-admin/src/routes/Moderation/components/ModerationHeader.js index cbe8b8cbf..c6909226b 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js +++ b/client/coral-admin/src/routes/Moderation/components/ModerationHeader.js @@ -11,7 +11,7 @@ const ModerationHeader = (props) => ( props.asset ?
{t('modqueue.all_streams')} - + {props.asset.title} diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationLayout.js b/client/coral-admin/src/routes/Moderation/components/ModerationLayout.js similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/ModerationLayout.js rename to client/coral-admin/src/routes/Moderation/components/ModerationLayout.js diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js rename to client/coral-admin/src/routes/Moderation/components/ModerationMenu.js diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js similarity index 89% rename from client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js rename to client/coral-admin/src/routes/Moderation/components/ModerationQueue.js index bdcf82eef..234c9cbcb 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js @@ -1,13 +1,12 @@ import React, {PropTypes} from 'react'; -import Comment from './components/Comment'; -import styles from './components/styles.css'; -import EmptyCard from '../../components/EmptyCard'; -import {actionsMap} from './helpers/moderationQueueActionsMap'; +import Comment from '../containers/Comment'; +import styles from './styles.css'; +import EmptyCard from '../../../components/EmptyCard'; +import {actionsMap} from '../helpers/moderationQueueActionsMap'; +import LoadMore from './LoadMore'; import t from 'coral-framework/services/i18n'; -import LoadMore from './components/LoadMore'; - class ModerationQueue extends React.Component { static propTypes = { @@ -54,6 +53,8 @@ class ModerationQueue extends React.Component { ? comments.map((comment, i) => { const status = comment.action_summaries ? 'FLAGGED' : comment.status; return { @@ -22,13 +21,7 @@ class UserDetail extends React.Component { } render () { - const {data, hideUserDetail} = this.props; - - if (!('user' in data)) { - return null; - } - - const {user, totalComments, rejectedComments} = data; + const {root: {user, totalComments, rejectedComments}, hideUserDetail} = this.props; const localProfile = user.profiles.find((p) => p.provider === 'local'); let profile; if (localProfile) { @@ -47,7 +40,12 @@ class UserDetail extends React.Component {

{user.username}

{profile && this.profile = ref} value={profile} />} - +

Member since {new Date(user.created_at).toLocaleString()}


@@ -69,6 +67,3 @@ class UserDetail extends React.Component { } } -export default compose( - getUserDetail -)(UserDetail); diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/routes/Moderation/components/styles.css similarity index 96% rename from client/coral-admin/src/containers/ModerationQueue/components/styles.css rename to client/coral-admin/src/routes/Moderation/components/styles.css index 52800fe43..bfc79355d 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/routes/Moderation/components/styles.css @@ -18,18 +18,21 @@ .tab { flex: 1; - color: white; + color: #EEEEEE; text-transform: capitalize; - font-weight: 500; + font-weight: 100; font-size: 15px; letter-spacing: 1px; transition: border-bottom 200ms; + padding: 0px 5px; + margin-right: 30px; } .active { color: white; box-sizing: border-box; - border-bottom: solid 5px #F36451; + border-bottom: solid 4px #F36451; + font-weight: 400; } .active > span { @@ -97,7 +100,7 @@ span { flex: 1; color: white; text-transform: capitalize; - font-weight: 500; + font-weight: 400; font-size: 15px; letter-spacing: 1px; transition: opacity 200ms; @@ -371,6 +374,7 @@ span { padding: 10px 15px; box-sizing: border-box; border-radius: 2px; + bor box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); > div { @@ -388,6 +392,7 @@ span { font-size: 13px; letter-spacing: 0.7px; font-weight: 400; + border-bottom: 0px; } label { @@ -421,7 +426,8 @@ span { .tabIcon { position: relative; - top: 7px; + top: 2px; + font-size: 16px; } .username { diff --git a/client/coral-admin/src/routes/Moderation/containers/Comment.js b/client/coral-admin/src/routes/Moderation/containers/Comment.js new file mode 100644 index 000000000..462040903 --- /dev/null +++ b/client/coral-admin/src/routes/Moderation/containers/Comment.js @@ -0,0 +1,56 @@ +import {gql} from 'react-apollo'; +import Comment from '../components/Comment'; +import {getSlotsFragments} from 'coral-framework/helpers/plugins'; +import withFragments from 'coral-framework/hocs/withFragments'; + +const pluginFragments = getSlotsFragments([ + 'adminCommentInfoBar', + 'adminCommentContent', + 'adminSideActions', + 'adminCommentDetailArea', +]); + +export default withFragments({ + root: gql` + fragment CoralAdmin_ModerationComment_root on RootQuery { + __typename + ${pluginFragments.spreads('root')} + } + ${pluginFragments.definitions('root')} + `, + comment: gql` + fragment CoralAdmin_ModerationComment_comment on Comment { + id + body + created_at + status + user { + id + name: username + status + } + asset { + id + title + url + } + action_summaries { + count + ... on FlagActionSummary { + reason + } + } + actions { + ... on FlagAction { + reason + message + user { + username + } + } + } + ${pluginFragments.spreads('comment')} + } + ${pluginFragments.definitions('comment')} + ` +})(Comment); diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js new file mode 100644 index 000000000..63af5a6cd --- /dev/null +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -0,0 +1,304 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {compose, gql} from 'react-apollo'; +import isEqual from 'lodash/isEqual'; +import withQuery from 'coral-framework/hocs/withQuery'; +import {getDefinitionName} from 'coral-framework/utils'; +import * as notification from 'coral-admin/src/services/notification'; +import t, {timeago} from 'coral-framework/services/i18n'; + +import {withSetUserStatus, withSuspendUser, withSetCommentStatus} from 'coral-framework/graphql/mutations'; + +import {fetchSettings} from 'actions/settings'; +import {updateAssets} from 'actions/assets'; +import { + toggleModal, + singleView, + showBanUserDialog, + hideBanUserDialog, + showSuspendUserDialog, + hideSuspendUserDialog, + hideShortcutsNote, + viewUserDetail, + hideUserDetail, + setSortOrder, +} from 'actions/moderation'; + +import {Spinner} from 'coral-ui'; +import Moderation from '../components/Moderation'; +import Comment from './Comment'; + +class ModerationContainer extends Component { + componentWillMount() { + this.props.fetchSettings(); + } + + componentWillReceiveProps(nextProps) { + const {updateAssets} = this.props; + if(!isEqual(nextProps.root.assets, this.props.root.assets)) { + updateAssets(nextProps.root.assets); + } + } + + suspendUser = async (args) => { + this.props.hideSuspendUserDialog(); + try { + const result = await this.props.suspendUser(args); + if (result.data.suspendUser.errors) { + throw result.data.suspendUser.errors; + } + notification.success( + t('suspenduser.notify_suspend_until', + this.props.moderation.suspendUserDialog.username, + timeago(args.until)), + ); + const {commentStatus, commentId} = this.props.moderation.suspendUserDialog; + if (commentStatus !== 'REJECTED') { + return this.props.rejectComment({commentId}) + .then((result) => { + if (result.data.setCommentStatus.errors) { + throw result.data.setCommentStatus.errors; + } + }); + } + } + catch(err) { + notification.showMutationErrors(err); + } + }; + + banUser = ({userId}) => { + return this.props.setUserStatus({userId, status: 'BANNED'}); + } + + acceptComment = ({commentId}) => { + return this.props.setCommentStatus({commentId, status: 'ACCEPTED'}); + } + + rejectComment = ({commentId}) => { + return this.props.setCommentStatus({commentId, status: 'REJECTED'}); + } + + loadMore = ({limit = 10, cursor, sort, tab, asset_id}) => { + let variables = { + limit, + cursor, + sort, + asset_id + }; + switch(tab) { + case 'all': + variables.statuses = null; + break; + case 'accepted': + variables.statuses = ['ACCEPTED']; + break; + case 'premod': + variables.statuses = ['PREMOD']; + break; + case 'flagged': + variables.statuses = ['NONE', 'PREMOD']; + variables.action_type = 'FLAG'; + break; + case 'rejected': + variables.statuses = ['REJECTED']; + break; + } + return this.props.data.fetchMore({ + query: LOAD_MORE_QUERY, + variables, + updateQuery: (oldData, {fetchMoreResult:{comments}}) => { + return { + ...oldData, + [tab]: [ + ...oldData[tab], + ...comments + ] + }; + } + }); + }; + + render () { + const {root, data} = this.props; + + if (data.error) { + return

Error
; + } + + if (!('premodCount' in root)) { + return
; + } + + return ; + } +} + +const LOAD_MORE_QUERY = gql` + query CoralAdmin_Moderation_LoadMore($limit: Int = 10, $cursor: Date, $sort: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) { + comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sort: $sort, action_type: $action_type}) { + ...${getDefinitionName(Comment.fragments.comment)} + action_summaries { + count + ... on FlagActionSummary { + reason + } + } + } + } + ${Comment.fragments.comment} +`; + +const withModQueueQuery = withQuery(gql` + query CoralAdmin_Moderation($asset_id: ID, $sort: SORT_ORDER) { + all: comments(query: { + statuses: [NONE, PREMOD, ACCEPTED, REJECTED], + asset_id: $asset_id, + sort: $sort + }) { + ...${getDefinitionName(Comment.fragments.comment)} + } + accepted: comments(query: { + statuses: [ACCEPTED], + asset_id: $asset_id, + sort: $sort + }) { + ...${getDefinitionName(Comment.fragments.comment)} + } + premod: comments(query: { + statuses: [PREMOD], + asset_id: $asset_id, + sort: $sort + }) { + ...${getDefinitionName(Comment.fragments.comment)} + } + flagged: comments(query: { + action_type: FLAG, + asset_id: $asset_id, + statuses: [NONE, PREMOD], + sort: $sort + }) { + ...${getDefinitionName(Comment.fragments.comment)} + } + rejected: comments(query: { + statuses: [REJECTED], + asset_id: $asset_id, + sort: $sort + }) { + ...${getDefinitionName(Comment.fragments.comment)} + } + assets: assets { + id + title + url + } + allCount: commentCount(query: { + asset_id: $asset_id + }) + acceptedCount: commentCount(query: { + statuses: [ACCEPTED], + asset_id: $asset_id + }) + premodCount: commentCount(query: { + statuses: [PREMOD], + asset_id: $asset_id + }) + rejectedCount: commentCount(query: { + statuses: [REJECTED], + asset_id: $asset_id + }) + flaggedCount: commentCount(query: { + action_type: FLAG, + asset_id: $asset_id, + statuses: [NONE, PREMOD] + }) + settings { + organizationName + } + } + ${Comment.fragments.comment} +`, { + options: ({params: {id = null}, moderation: {sortOrder}}) => { + return { + variables: { + asset_id: id, + sort: sortOrder, + } + }; + }, +}); + +const withQueueCountPolling = withQuery(gql` + query CoralAdmin_ModerationCountPoll($asset_id: ID) { + allCount: commentCount(query: { + asset_id: $asset_id + }) + acceptedCount: commentCount(query: { + statuses: [ACCEPTED], + asset_id: $asset_id + }) + premodCount: commentCount(query: { + statuses: [PREMOD], + asset_id: $asset_id + }) + rejectedCount: commentCount(query: { + statuses: [REJECTED], + asset_id: $asset_id + }) + flaggedCount: commentCount(query: { + action_type: FLAG, + asset_id: $asset_id, + statuses: [NONE, PREMOD] + }) + } +`, { + options: ({params: {id = null}}) => { + return { + pollInterval: 5000, + variables: { + asset_id: id + } + }; + } +}); + +const mapStateToProps = (state) => ({ + moderation: state.moderation.toJS(), + settings: state.settings.toJS(), + auth: state.auth.toJS(), + assets: state.assets.get('assets') +}); + +const mapDispatchToProps = (dispatch) => ({ + ...bindActionCreators({ + toggleModal, + singleView, + updateAssets, + fetchSettings, + showBanUserDialog, + hideBanUserDialog, + hideShortcutsNote, + showSuspendUserDialog, + hideSuspendUserDialog, + viewUserDetail, + hideUserDetail, + setSortOrder, + }, dispatch), +}); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), + withSetCommentStatus, + withSetUserStatus, + withSuspendUser, + withQueueCountPolling, + withModQueueQuery, +)(ModerationContainer); diff --git a/client/coral-admin/src/routes/Moderation/containers/UserDetail.js b/client/coral-admin/src/routes/Moderation/containers/UserDetail.js new file mode 100644 index 000000000..e4f98d6be --- /dev/null +++ b/client/coral-admin/src/routes/Moderation/containers/UserDetail.js @@ -0,0 +1,54 @@ +import React, {PropTypes} from 'react'; +import {compose, gql} from 'react-apollo'; +import UserDetail from '../components/UserDetail'; +import withQuery from 'coral-framework/hocs/withQuery'; +import {getSlotsFragments} from 'coral-framework/helpers/plugins'; + +const pluginFragments = getSlotsFragments([ + 'userProfile', +]); + +class UserDetailContainer extends React.Component { + static propTypes = { + id: PropTypes.string.isRequired, + hideUserDetail: PropTypes.func.isRequired + } + + render () { + if (!('user' in this.props.root)) { + return null; + } + + return ; + } +} + +export const withUserDetailQuery = withQuery(gql` + query CoralAdmin_UserDetail($author_id: ID!) { + user(id: $author_id) { + id + username + created_at + profiles { + id + provider + } + ${pluginFragments.spreads('user')} + } + totalComments: commentCount(query: {author_id: $author_id}) + rejectedComments: commentCount(query: {author_id: $author_id, statuses: [REJECTED]}) + ${pluginFragments.spreads('root')} + } + ${pluginFragments.definitions('user')} + ${pluginFragments.definitions('root')} +`, { + options: ({id}) => { + return { + variables: {author_id: id} + }; + } +}); + +export default compose( + withUserDetailQuery, +)(UserDetailContainer); diff --git a/client/coral-admin/src/containers/ModerationQueue/helpers/moderationQueueActionsMap.js b/client/coral-admin/src/routes/Moderation/helpers/moderationQueueActionsMap.js similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/helpers/moderationQueueActionsMap.js rename to client/coral-admin/src/routes/Moderation/helpers/moderationQueueActionsMap.js diff --git a/client/coral-admin/src/routes/Moderation/index.js b/client/coral-admin/src/routes/Moderation/index.js new file mode 100644 index 000000000..e6bef5ef3 --- /dev/null +++ b/client/coral-admin/src/routes/Moderation/index.js @@ -0,0 +1,2 @@ +export {default as Moderation} from './containers/Moderation'; +export {default as ModerationLayout} from './components/ModerationLayout'; diff --git a/client/coral-admin/src/containers/Stories/Stories.css b/client/coral-admin/src/routes/Stories/components/Stories.css similarity index 100% rename from client/coral-admin/src/containers/Stories/Stories.css rename to client/coral-admin/src/routes/Stories/components/Stories.css diff --git a/client/coral-admin/src/containers/Stories/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js similarity index 91% rename from client/coral-admin/src/containers/Stories/Stories.js rename to client/coral-admin/src/routes/Stories/components/Stories.js index 292eaeaba..7f9098fba 100644 --- a/client/coral-admin/src/containers/Stories/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -1,8 +1,6 @@ import React, {Component} from 'react'; import styles from './Stories.css'; -import {connect} from 'react-redux'; import t from 'coral-framework/services/i18n'; -import {fetchAssets, updateAssetState} from 'coral-admin/src/actions/assets'; import {Link} from 'react-router'; import {Pager, Icon} from 'coral-ui'; @@ -12,7 +10,7 @@ import sortBy from 'lodash/sortBy'; const limit = 25; -class Stories extends Component { +export default class Stories extends Component { state = { search: '', @@ -171,19 +169,3 @@ class Stories extends Component { } } -const mapStateToProps = ({assets}) => { - return { - assets: assets.toJS() - }; -}; - -const mapDispatchToProps = (dispatch) => { - return { - fetchAssets: (...args) => { - dispatch(fetchAssets.apply(this, args)); - }, - updateAssetState: (...args) => dispatch(updateAssetState.apply(this, args)) - }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(Stories); diff --git a/client/coral-admin/src/routes/Stories/containers/Stories.js b/client/coral-admin/src/routes/Stories/containers/Stories.js new file mode 100644 index 000000000..450efcf74 --- /dev/null +++ b/client/coral-admin/src/routes/Stories/containers/Stories.js @@ -0,0 +1,27 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {compose} from 'react-apollo'; +import {fetchAssets, updateAssetState} from 'coral-admin/src/actions/assets'; +import Stories from '../components/Stories'; + +class StoriesContainer extends Component { + render () { + return ; + } +} + +const mapStateToProps = (state) => ({ + assets: state.assets.toJS() +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({ + fetchAssets, + updateAssetState, + }, dispatch); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), +)(StoriesContainer); + diff --git a/client/coral-admin/src/routes/Stories/index.js b/client/coral-admin/src/routes/Stories/index.js new file mode 100644 index 000000000..5b2999b21 --- /dev/null +++ b/client/coral-admin/src/routes/Stories/index.js @@ -0,0 +1 @@ +export {default} from './containers/Stories.js'; diff --git a/client/coral-admin/src/services/PymConnection.js b/client/coral-admin/src/services/PymConnection.js deleted file mode 100644 index ca592b824..000000000 --- a/client/coral-admin/src/services/PymConnection.js +++ /dev/null @@ -1,9 +0,0 @@ -import Pym from '../../node_modules/pym.js'; - -const pym = new Pym.Child({polling: 100}); -export default pym; - -export const link = (url) => (e) => { - e.preventDefault(); - pym.sendMessage('navigate', url); -}; diff --git a/client/coral-admin/src/services/client.js b/client/coral-admin/src/services/client.js index ff8216373..df97066bc 100644 --- a/client/coral-admin/src/services/client.js +++ b/client/coral-admin/src/services/client.js @@ -4,13 +4,17 @@ import fragmentMatcher from './fragmentMatcher'; export const client = new ApolloClient({ fragmentMatcher, + connectToDevTools: true, addTypename: true, queryTransformer: addTypename, dataIdFromObject: (result) => { if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle - return result.__typename + result.id; // eslint-disable-line no-underscore-dangle + return `${result.__typename}_${result.id}`; // eslint-disable-line no-underscore-dangle } return null; }, networkInterface }); + +export default client; + diff --git a/client/coral-admin/src/services/config.js b/client/coral-admin/src/services/config.js deleted file mode 100644 index 05cf0ecb8..000000000 --- a/client/coral-admin/src/services/config.js +++ /dev/null @@ -1,14 +0,0 @@ - -/** - * Just load the root config file and throw an - * error message if not present - */ - -try { - module.exports = require('../../config.json'); -} catch (error) { - const message = `The config.json file under the root directory is missing -or invalid Please add one to use this app. You can use config.sample.json as a guide.`; - window.alert(message); - throw new Error(message); -} diff --git a/client/coral-admin/src/services/notification.js b/client/coral-admin/src/services/notification.js index 644b5c17d..5e8673060 100644 --- a/client/coral-admin/src/services/notification.js +++ b/client/coral-admin/src/services/notification.js @@ -13,13 +13,14 @@ export function info(msg) { return toast(msg, {type: 'info'}); } -export function showMutationErrors(err) { - const errors = Array.isArray(err) ? err : [err]; - errors.forEach((err) => { - console.error(err); - toast( - err.translation_key ? t(`errors.${err.translation_key}`) : err, - {type: 'error'} - ); - }); +export function showMutationErrors(error) { + console.error(error); + if (error.errors) { + error.errors.forEach((err) => { + toast( + err.translation_key ? t(`error.${err.translation_key}`) : err, + {type: 'error'} + ); + }); + } } diff --git a/client/coral-admin/src/services/store.js b/client/coral-admin/src/services/store.js index bf2735e45..2ab9d3750 100644 --- a/client/coral-admin/src/services/store.js +++ b/client/coral-admin/src/services/store.js @@ -14,7 +14,7 @@ if (window.devToolsExtension) { middlewares.push(window.devToolsExtension()); } -export default createStore( +const store = createStore( combineReducers({ ...mainReducer, apollo: client.reducer() @@ -22,3 +22,6 @@ export default createStore( {}, compose(...middlewares) ); + +window.coralStore = store; +export default store; diff --git a/client/coral-admin/src/services/subscriptions.js b/client/coral-admin/src/services/subscriptions.js deleted file mode 100644 index f514ea18b..000000000 --- a/client/coral-admin/src/services/subscriptions.js +++ /dev/null @@ -1,14 +0,0 @@ -import {print} from 'graphql-tag/printer'; - -// quick way to add the subscribe and unsubscribe functions to the network interface -const addGraphQLSubscriptions = (networkInterface, wsClient) => Object.assign(networkInterface, { - subscribe: (request, handler) => wsClient.subscribe({ - query: print(request.query), - variables: request.variables, - }, handler), - unsubscribe: (id) => { - wsClient.unsubscribe(id); - }, -}); - -export default addGraphQLSubscriptions; diff --git a/client/coral-embed-stream/src/AppRouter.js b/client/coral-embed-stream/src/AppRouter.js index e2553889c..6ee63caab 100644 --- a/client/coral-embed-stream/src/AppRouter.js +++ b/client/coral-embed-stream/src/AppRouter.js @@ -2,12 +2,12 @@ import React from 'react'; import {Router, Route, browserHistory} from 'react-router'; import Embed from './containers/Embed'; -import SignInContainer from 'coral-sign-in/containers/SignInContainer'; +import {LoginContainer} from 'coral-sign-in/containers/LoginContainer'; const routes = (
- - + +
); diff --git a/client/coral-embed-stream/src/components/CountdownSeconds.js b/client/coral-embed-stream/src/components/CountdownSeconds.js index ce339867f..d350c803b 100644 --- a/client/coral-embed-stream/src/components/CountdownSeconds.js +++ b/client/coral-embed-stream/src/components/CountdownSeconds.js @@ -36,7 +36,7 @@ export class CountdownSeconds extends React.Component { const secRemaining = msRemaining / 1000; const wholeSecRemaining = Math.floor(secRemaining); const plural = secRemaining !== 1; - const units = t(plural ? 'edit_comment.secondsPlural' : 'edit_comment.second'); + const units = t(plural ? 'edit_comment.seconds_plural' : 'edit_comment.second'); let classFromProp; if (typeof classNameForMsRemaining === 'function') { classFromProp = classNameForMsRemaining(msRemaining); diff --git a/client/coral-embed-stream/src/components/EditableCommentContent.js b/client/coral-embed-stream/src/components/EditableCommentContent.js index 1af4a9fa7..6654b0e13 100644 --- a/client/coral-embed-stream/src/components/EditableCommentContent.js +++ b/client/coral-embed-stream/src/components/EditableCommentContent.js @@ -83,9 +83,9 @@ export class EditableCommentContent extends React.Component { if (error.translation_key) { addNotification('error', t(`error.${error.translation_key}`)); } else if (error.networkError) { - addNotification('error', t('error.networkError')); + addNotification('error', t('error.network_error')); } else { - addNotification('error', t('edit_comment.unexpectedError')); + addNotification('error', t('edit_comment.unexpected_error')); throw error; } } @@ -114,7 +114,7 @@ export class EditableCommentContent extends React.Component { return (comment.body !== originalBody) && !editWindowExpired; }} saveComment={this.editComment} - bodyLabel={t('edit_comment.bodyInputLabel')} + bodyLabel={t('edit_comment.body_input_label')} bodyPlaceholder="" submitText={{t('edit_comment.save_button')}} saveButtonCStyle="green" @@ -126,15 +126,15 @@ export class EditableCommentContent extends React.Component { { editWindowExpired ? - {t('edit_comment.editWindowExpired')} + {t('edit_comment.edit_window_expired')} { typeof this.props.stopEditing === 'function' - ?  {t('edit_comment.editWindowExpiredClose')} + ?  {t('edit_comment.edit_window_expired_close')} : null } : - {t('edit_comment.editWindowTimerPrefix')} + {t('edit_comment.edit_window_timer_prefix')} (remainingMs <= 10 * 1000) ? styles.editWindowAlmostOver : '' } diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index 7030caf74..3c94d5f81 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -1,72 +1,67 @@ import React from 'react'; -import t from 'coral-framework/services/i18n'; +import Stream from '../containers/Stream'; +import Slot from 'coral-framework/components/Slot'; import {can} from 'coral-framework/services/perms'; +import t from 'coral-framework/services/i18n'; import {TabBar, Tab, TabContent, Button} from 'coral-ui'; - -import Stream from '../containers/Stream'; import Count from 'coral-plugin-comment-count/CommentCount'; -import UserBox from 'coral-sign-in/components/UserBox'; import ProfileContainer from 'coral-settings/containers/ProfileContainer'; -import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer'; +import ConfigureStreamContainer + from 'coral-configure/containers/ConfigureStreamContainer'; export default class Embed extends React.Component { changeTab = (tab) => { - switch(tab) { + switch (tab) { case 0: this.props.setActiveTab('stream'); break; case 1: this.props.setActiveTab('profile'); - // TODO: move data fetching to profile container. + // TODO: move data fetching to profile container. this.props.data.refetch(); break; case 2: this.props.setActiveTab('config'); - // TODO: move data fetching to config container. + // TODO: move data fetching to config container. this.props.data.refetch(); break; } - } + }; handleShowProfile = () => this.props.setActiveTab('profile'); - render () { - const {activeTab, logout, viewAllComments, commentId} = this.props; + render() { + const {activeTab, viewAllComments, commentId} = this.props; const {asset: {totalCommentCount}} = this.props.root; - const {loggedIn, user} = this.props.auth; - - const userBox = ; + const {user} = this.props.auth; return (
- + {t('framework.my_profile')} {t('framework.configure_stream')} - { - commentId && - - } + {commentId && + } + - { loggedIn ? userBox : null } - { loggedIn ? userBox : null }
@@ -79,5 +74,5 @@ Embed.propTypes = { data: React.PropTypes.shape({ loading: React.PropTypes.bool, error: React.PropTypes.object - }).isRequired, + }).isRequired }; diff --git a/client/coral-embed-stream/src/components/LoadMore.js b/client/coral-embed-stream/src/components/LoadMore.js index 8715e7902..c9c8a9704 100644 --- a/client/coral-embed-stream/src/components/LoadMore.js +++ b/client/coral-embed-stream/src/components/LoadMore.js @@ -33,7 +33,7 @@ class LoadMore extends React.Component { } if (this.initialState) { - return t('framework.view_all_repliesInitial', count); + return t('framework.view_all_replies_initial', count); } else { return t('framework.view_all_replies', count); } diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index d349d8d6f..68fb3611a 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -1,20 +1,18 @@ import React, {PropTypes} from 'react'; - -import {Button} from 'coral-ui'; import LoadMore from './LoadMore'; import NewCount from './NewCount'; + import Comment from '../containers/Comment'; +import SuspendedAccount from './SuspendedAccount'; +import RestrictedMessageBox + from 'coral-framework/components/RestrictedMessageBox'; +import Slot from 'coral-framework/components/Slot'; import InfoBox from 'coral-plugin-infobox/InfoBox'; +import {can} from 'coral-framework/services/perms'; import {ModerationLink} from 'coral-plugin-moderation'; import CommentBox from 'coral-plugin-commentbox/CommentBox'; import QuestionBox from 'coral-plugin-questionbox/QuestionBox'; import IgnoredCommentTombstone from './IgnoredCommentTombstone'; -import SuspendedAccount from './SuspendedAccount'; -import RestrictedMessageBox - from 'coral-framework/components/RestrictedMessageBox'; -import {can} from 'coral-framework/services/perms'; -import ChangeUsernameContainer - from 'coral-sign-in/containers/ChangeUsernameContainer'; import t, {timeago} from 'coral-framework/services/i18n'; class Stream extends React.Component { @@ -52,7 +50,10 @@ class Stream extends React.Component { : comment; const banned = user && user.status === 'BANNED'; - const temporarilySuspended = user && user.suspension.until && new Date(user.suspension.until) > new Date(); + const temporarilySuspended = + user && + user.suspension.until && + new Date(user.suspension.until) > new Date(); const hasOlderComments = !!(asset && asset.lastComment && @@ -63,10 +64,15 @@ class Stream extends React.Component { ? asset.comments[0].created_at : new Date(Date.now() - 1000 * 60 * 60 * 24 * 7).toISOString(); const commentIsIgnored = (comment) => { - return me && me.ignoredUsers && me.ignoredUsers.find((u) => u.id === comment.user.id); + return ( + me && + me.ignoredUsers && + me.ignoredUsers.find((u) => u.id === comment.user.id) + ); }; return (
+ {open ?
- {!banned && temporarilySuspended && + {!banned && + temporarilySuspended && - { - t('sign_in.temporarily_suspended', - this.props.root.settings.organizationName, - timeago(user.suspension.until), - ) - } - - } + {t( + 'stream.temporarily_suspended', + this.props.root.settings.organizationName, + timeago(user.suspension.until) + )} + } {banned && - } - {loggedIn && !banned && !temporarilySuspended && + />} + {loggedIn && + !banned && + !temporarilySuspended && + !highlightedComment && - } + addNotification={this.props.addNotification} + postComment={this.props.postComment} + appendItemArray={this.props.appendItemArray} + updateItem={this.props.updateItem} + setCommentCountCache={this.props.setCommentCountCache} + commentCountCache={commentCountCache} + assetId={asset.id} + premod={asset.settings.moderation} + isReply={false} + authorId={user.id} + charCountEnable={asset.settings.charCountEnable} + maxCharCount={asset.settings.charCount} + />}
:

{asset.settings.closedMessage}

} - {!loggedIn && - } {loggedIn && - user && - } - {loggedIn && } + } {/* the highlightedComment is isolated after the user followed a permalink */} {highlightedComment @@ -160,41 +159,38 @@ class Stream extends React.Component { setCommentCountCache={this.props.setCommentCountCache} />
- {comments.map( - (comment) => { - return (commentIsIgnored(comment) - ? - : - ); - } - )} + {comments.map((comment) => { + return commentIsIgnored(comment) + ? + : ; + })}
({ updateQueries: { - EmbedQuery: (previousData, {mutationResult}) => { + CoralEmbedStream_Embed: (previousData) => { const ignoredUserId = variables.id; - const response = mutationResult.data.ignoreUser; - if (ignoredUserId && !response.errors) { - const updated = update(previousData, {me: {ignoredUsers: {$push: [{ - id: ignoredUserId, - __typename: 'User', - }]}}}); - return updated; - } - return previousData; + const updated = update(previousData, {me: {ignoredUsers: {$push: [{ + id: ignoredUserId, + __typename: 'User', + }]}}}); + return updated; } } }), StopIgnoringUser: ({variables}) => ({ updateQueries: { - EmbedStreamProfileQuery: (previousData, {mutationResult}) => { + CoralEmbedStream_Profile: (previousData) => { const noLongerIgnoredUserId = variables.id; - const response = mutationResult.data.stopIgnoringUser; - if (noLongerIgnoredUserId && !response.errors) { - // remove noLongerIgnoredUserId from ignoredUsers - const updated = update(previousData, {me: {ignoredUsers: { - $apply: (ignoredUsers) => { - return ignoredUsers.filter((u) => u.id !== noLongerIgnoredUserId); - } - }}}); - return updated; - } - return previousData; + // remove noLongerIgnoredUserId from ignoredUsers + const updated = update(previousData, {me: {ignoredUsers: { + $apply: (ignoredUsers) => { + return ignoredUsers.filter((u) => u.id !== noLongerIgnoredUserId); + } + }}}); + return updated; } } }), @@ -180,7 +133,7 @@ const extension = { } }, updateQueries: { - EmbedQuery: (previousData, {mutationResult: {data: {createComment: {comment}}}}) => { + CoralEmbedStream_Embed: (previousData, {mutationResult: {data: {createComment: {comment}}}}) => { if (previousData.asset.settings.moderation === 'PRE' || comment.status === 'PREMOD' || comment.status === 'REJECTED') { return previousData; } @@ -221,12 +174,7 @@ const extension = { variables: {id, edit}, }) => ({ updateQueries: { - EmbedQuery: (previousData, {mutationResult: {data: {editComment: {comment, errors}}}}) => { - - // @TODO (kiwi) revisit after streamlining error handling - if (errors && errors.length) { - return previousData; - } + CoralEmbedStream_Embed: (previousData, {mutationResult: {data: {editComment: {comment}}}}) => { const {status} = comment; const updateCommentWithEdit = (comment, edit) => { const {body} = edit; diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 0306079f3..06d52649a 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -8,16 +8,18 @@ import './graphql'; import {addExternalConfig} from 'coral-embed-stream/src/actions/config'; import reducers from './reducers'; -import localStore, {injectReducers} from 'coral-framework/services/store'; +import {getStore, injectReducers} from 'coral-framework/services/store'; import AppRouter from './AppRouter'; import {pym} from 'coral-framework'; +import {loadPluginsTranslations} from 'coral-framework/helpers/plugins'; +const store = getStore(); + +loadPluginsTranslations(); injectReducers(reducers); -const store = (window.opener && window.opener.coralStore) ? window.opener.coralStore : localStore; - // Don't run this in the popup. -if (store === localStore) { +if (!window.opener) { store.dispatch(checkLogin()); pym.sendMessage('getConfig'); diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 42a0d7c85..04b2536a3 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -170,6 +170,7 @@ hr { display: flex; align-items: center; justify-content: center; + font-weight: 400; } .hidden { @@ -240,6 +241,8 @@ hr { .coral-plugin-commentcontent-text { margin-bottom: 7px; font-size: 16px; + font-weight: 100; + line-height: 1.3; } .coral-plugin-author-name-text { @@ -319,7 +322,6 @@ button.comment__action-button[disabled], .coral-plugin-pubdate-text { display: inline-block; - margin-left: 5px; } .coral-plugin-permalinks-container { @@ -367,7 +369,7 @@ button.comment__action-button[disabled], .coral-plugin-flags-popup-radio-label { margin:5px; - font-weight: 700; + font-weight: 400; font-size: .9rem; } diff --git a/client/coral-framework/actions/asset.js b/client/coral-framework/actions/asset.js index 2f0523fea..d252bb59f 100644 --- a/client/coral-framework/actions/asset.js +++ b/client/coral-framework/actions/asset.js @@ -1,5 +1,5 @@ import * as actions from '../constants/asset'; -import coralApi from '../helpers/response'; +import coralApi from '../helpers/request'; import {addNotification} from '../actions/notification'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 244afcec6..91f709b35 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -1,13 +1,12 @@ -import {pym} from 'coral-framework'; -import * as Storage from '../helpers/storage'; -import * as actions from '../constants/auth'; -import coralApi, {base} from '../helpers/response'; import jwtDecode from 'jwt-decode'; +import bowser from 'bowser'; +import * as actions from '../constants/auth'; +import * as Storage from '../helpers/storage'; +import coralApi, {base} from '../helpers/request'; import t from 'coral-framework/services/i18n'; -// Dialog Actions -export const showSignInDialog = () => (dispatch) => { +export const showSignInDialog = () => (dispatch, getState) => { const signInPopUp = window.open( '/embed/stream/login', 'Login', @@ -19,6 +18,12 @@ export const showSignInDialog = () => (dispatch) => { let loaded = false; signInPopUp.onload = () => { loaded = true; + + // Fire some actions inside the popups reducer, to initialize required state. + const required = getState().asset.toJS().settings.requireEmailConfirmation; + const redirectUri = getState().auth.toJS().redirectUri; + signInPopUp.coralStore.dispatch(setRequireEmailVerification(required)); + signInPopUp.coralStore.dispatch(setRedirectUri(redirectUri)); }; // Use `onunload` instead of `onbeforeunload` which is not supported in IOS Safari. @@ -119,26 +124,31 @@ export const handleAuthToken = (token) => (dispatch) => { // SIGN IN //============================================================================== -export const fetchSignIn = (formData) => (dispatch) => { - dispatch(signInRequest()); - return coralApi('/auth/local', {method: 'POST', body: formData}) - .then(({token}) => { - dispatch(handleAuthToken(token)); - dispatch(hideSignInDialog()); - }) - .catch((error) => { - if (error.metadata) { +export const fetchSignIn = (formData) => { + return (dispatch) => { + dispatch(signInRequest()); - // the user might not have a valid email. prompt the user user re-request the confirmation email - dispatch( - signInFailure(t('error.email_not_verified', error.metadata)) - ); - } else { + return coralApi('/auth/local', {method: 'POST', body: formData}) + .then(({token}) => { + if (!bowser.safari && !bowser.ios) { + dispatch(handleAuthToken(token)); + } + dispatch(hideSignInDialog()); + }) + .catch((error) => { + if (error.metadata) { - // invalid credentials - dispatch(signInFailure(t('error.email_password'))); - } - }); + // the user might not have a valid email. prompt the user user re-request the confirmation email + dispatch( + signInFailure(t('error.email_not_verified', error.metadata)) + ); + } else { + + // invalid credentials + dispatch(signInFailure(t('error.email_password'))); + } + }); + }; }; //============================================================================== @@ -185,7 +195,7 @@ export const fetchSignUpFacebook = () => (dispatch) => { ); }; -export const facebookCallback = (err, data) => (dispatch, getState) => { +export const facebookCallback = (err, data) => (dispatch) => { if (err) { dispatch(signInFacebookFailure(err)); return; @@ -194,10 +204,6 @@ export const facebookCallback = (err, data) => (dispatch, getState) => { dispatch(handleAuthToken(data.token)); dispatch(signInFacebookSuccess(data.user)); dispatch(hideSignInDialog()); - const {user: {canEditName, status}} = getState().auth.toJS(); - if (canEditName && status !== 'BANNED') { - dispatch(showCreateUsernameDialog()); - } } catch (err) { dispatch(signInFacebookFailure(err)); return; @@ -212,7 +218,8 @@ const signUpRequest = () => ({type: actions.FETCH_SIGNUP_REQUEST}); const signUpSuccess = (user) => ({type: actions.FETCH_SIGNUP_SUCCESS, user}); const signUpFailure = (error) => ({type: actions.FETCH_SIGNUP_FAILURE, error}); -export const fetchSignUp = (formData, redirectUri) => (dispatch) => { +export const fetchSignUp = (formData) => (dispatch, getState) => { + const redirectUri = getState().auth.toJS().redirectUri; dispatch(signUpRequest()); coralApi('/users', { @@ -250,9 +257,9 @@ const forgotPasswordFailure = () => ({ type: actions.FETCH_FORGOT_PASSWORD_FAILURE }); -export const fetchForgotPassword = (email) => (dispatch) => { +export const fetchForgotPassword = (email) => (dispatch, getState) => { dispatch(forgotPasswordRequest(email)); - const redirectUri = pym.parentUrl || location.href; + const redirectUri = getState().auth.toJS().redirectUri; coralApi('/account/password/reset', { method: 'POST', body: {email, loc: redirectUri} @@ -267,7 +274,9 @@ export const fetchForgotPassword = (email) => (dispatch) => { export const logout = () => (dispatch) => { return coralApi('/auth', {method: 'DELETE'}).then(() => { - Storage.removeItem('token'); + if (!bowser.safari && !bowser.ios) { + Storage.removeItem('token'); + } dispatch({type: actions.LOGOUT}); }); }; @@ -290,11 +299,18 @@ export const checkLogin = () => (dispatch) => { coralApi('/auth') .then((result) => { if (!result.user) { - Storage.removeItem('token'); + if (!bowser.safari && !bowser.ios) { + Storage.removeItem('token'); + } throw new Error('Not logged in'); } dispatch(checkLoginSuccess(result.user)); + + // Display create username dialog if necessary. + if (result.user.canEditName && result.user.status !== 'BANNED') { + dispatch(showCreateUsernameDialog()); + } }) .catch((error) => { console.error(error); @@ -321,7 +337,8 @@ const verifyEmailFailure = () => ({ type: actions.VERIFY_EMAIL_FAILURE }); -export const requestConfirmEmail = (email, redirectUri) => (dispatch) => { +export const requestConfirmEmail = (email) => (dispatch, getState) => { + const redirectUri = getState().auth.toJS().redirectUri; dispatch(verifyEmailRequest()); return coralApi('/users/resend-verify', { method: 'POST', @@ -335,5 +352,17 @@ export const requestConfirmEmail = (email, redirectUri) => (dispatch) => { // email might have already been verifyed dispatch(verifyEmailFailure(err)); + throw err; }); }; + +// Login Popup actions. +export const setRequireEmailVerification = (required) => ({ + type: actions.SET_REQUIRE_EMAIL_VERIFICATION, + required, +}); + +export const setRedirectUri = (uri) => ({ + type: actions.SET_REDIRECT_URI, + uri, +}); diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js index 53d59a743..b978f686d 100644 --- a/client/coral-framework/actions/user.js +++ b/client/coral-framework/actions/user.js @@ -1,5 +1,5 @@ import {addNotification} from '../actions/notification'; -import coralApi from '../helpers/response'; +import coralApi from '../helpers/request'; import * as actions from '../constants/auth'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js index 24599ac50..57bde6f3f 100644 --- a/client/coral-framework/constants/auth.js +++ b/client/coral-framework/constants/auth.js @@ -46,3 +46,8 @@ export const VERIFY_EMAIL_REQUEST = 'VERIFY_EMAIL_REQUEST'; export const VERIFY_EMAIL_SUCCESS = 'VERIFY_EMAIL_SUCCESS'; export const VERIFY_EMAIL_FAILURE = 'VERIFY_EMAIL_FAILURE'; export const UPDATE_USERNAME = 'UPDATE_USERNAME'; + +// Login Popup actions. +export const SET_REQUIRE_EMAIL_VERIFICATION = 'SET_REQUIRE_EMAIL_VERIFICATION'; +export const SET_REDIRECT_URI = 'SET_REDIRECT_URI'; + diff --git a/client/coral-framework/graphql/fragments.js b/client/coral-framework/graphql/fragments.js index f4948ed05..9c396cbaa 100644 --- a/client/coral-framework/graphql/fragments.js +++ b/client/coral-framework/graphql/fragments.js @@ -1,2 +1,23 @@ +import {gql} from 'react-apollo'; +import * as mutations from './mutations'; + +function createDefaultResponseFragments() { + const names = Object.keys(mutations).map((key) => key.replace('with', '')); + const result = {}; + names.forEach((name) => { + const response = `${name}Response`; + result[response] = gql` + fragment Coral_${response} on ${response} { + errors { + translation_key + } + } + `; + }); + return result; +} + // fragments defined here are automatically registered. -export default {}; +export default { + ...createDefaultResponseFragments() +}; diff --git a/client/coral-framework/graphql/mutations.js b/client/coral-framework/graphql/mutations.js index 0744df35b..d9c3cfe45 100644 --- a/client/coral-framework/graphql/mutations.js +++ b/client/coral-framework/graphql/mutations.js @@ -1,6 +1,84 @@ import {gql} from 'react-apollo'; import withMutation from '../hocs/withMutation'; +export const withSetCommentStatus = withMutation( + gql` + mutation SetCommentStatus($commentId: ID!, $status: COMMENT_STATUS!){ + setCommentStatus(id: $commentId, status: $status) { + ...SetCommentStatusResponse + } + } + `, { + props: ({mutate}) => ({ + setCommentStatus: ({commentId, status}) => { + return mutate({ + variables: { + commentId, + status, + }, + }); + } + }) + }); + +export const withSuspendUser = withMutation( + gql` + mutation SuspendUser($input: SuspendUserInput!) { + suspendUser(input: $input) { + ...SuspendUserResponse + } + } + `, { + props: ({mutate}) => ({ + suspendUser: (input) => { + return mutate({ + variables: { + input, + }, + }); + } + }) + }); + +export const withRejectUsername = withMutation( + gql` + mutation RejectUsername($input: RejectUsernameInput!) { + rejectUsername(input: $input) { + ...RejectUsernameResponse + } + } + `, { + props: ({mutate}) => ({ + rejectUsername: (input) => { + return mutate({ + variables: { + input, + }, + }); + } + }) + }); + +export const withSetUserStatus = withMutation( + gql` + mutation SetUserStatus($userId: ID!, $status: USER_STATUS!) { + setUserStatus(id: $userId, status: $status) { + ...SetUserStatusResponse + } + } + `, { + props: ({mutate}) => ({ + setUserStatus: ({userId, status}) => { + return mutate({ + variables: { + userId, + status + }, + }); + } + }), + }); + export const withPostComment = withMutation( gql` mutation PostComment($comment: CreateCommentInput!) { diff --git a/client/coral-framework/helpers/plugins.js b/client/coral-framework/helpers/plugins.js index a70c6f6f6..02ee8c3a7 100644 --- a/client/coral-framework/helpers/plugins.js +++ b/client/coral-framework/helpers/plugins.js @@ -6,6 +6,7 @@ import uniq from 'lodash/uniq'; import pick from 'lodash/pick'; import plugins from 'pluginsConfig'; import {getDefinitionName, mergeDocuments} from 'coral-framework/utils'; +import {loadTranslations} from 'coral-framework/services/i18n'; export const pluginReducers = merge( ...plugins @@ -88,9 +89,12 @@ export function getGraphQLExtensions() { .filter((o) => o); } -export function getTranslations() { +function getTranslations() { return plugins .map((o) => o.module.translations) .filter((o) => o); } +export function loadPluginsTranslations() { + getTranslations().forEach((t) => loadTranslations(t)); +} diff --git a/client/coral-framework/helpers/response.js b/client/coral-framework/helpers/request.js similarity index 75% rename from client/coral-framework/helpers/response.js rename to client/coral-framework/helpers/request.js index 794a73e30..0e15003e1 100644 --- a/client/coral-framework/helpers/response.js +++ b/client/coral-framework/helpers/request.js @@ -1,22 +1,26 @@ +import bowser from 'bowser'; import * as Storage from './storage'; +import merge from 'lodash/merge'; const buildOptions = (inputOptions = {}) => { const defaultOptions = { method: 'GET', headers: { Accept: 'application/json', - Authorization: `Bearer ${Storage.getItem('token')}`, - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }, credentials: 'same-origin' }; - let options = Object.assign({}, defaultOptions, inputOptions); - options.headers = Object.assign( - {}, - defaultOptions.headers, - inputOptions.headers - ); + let options = merge({}, defaultOptions, inputOptions); + + if (!bowser.safari && !bowser.ios) { + let authorization = Storage.getItem('token'); + + if (authorization) { + options.headers.Authorization = `Bearer ${authorization}`; + } + } if (options.method.toLowerCase() !== 'get') { options.body = JSON.stringify(options.body); diff --git a/client/coral-framework/hocs/withMutation.js b/client/coral-framework/hocs/withMutation.js index 044bcdfad..5cccaa062 100644 --- a/client/coral-framework/hocs/withMutation.js +++ b/client/coral-framework/hocs/withMutation.js @@ -5,8 +5,26 @@ import uniq from 'lodash/uniq'; import flatten from 'lodash/flatten'; import isEmpty from 'lodash/isEmpty'; import {getMutationOptions, resolveFragments} from 'coral-framework/services/graphqlRegistry'; -import {store} from 'coral-framework/services/store'; -import {getDefinitionName} from '../utils'; +import {getStore} from 'coral-framework/services/store'; +import {getDefinitionName, getResponseErrors} from '../utils'; +import t from 'coral-framework/services/i18n'; + +class ResponseErrors extends Error { + constructor(errors) { + super(`Response Errors ${JSON.stringify(errors)}`); + this.errors = errors.map((e) => new ResponseError(e)); + } +} + +class ResponseError { + constructor(error) { + Object.assign(this, error); + } + + translate(...args) { + return t(`error.${this.translation_key}`, ...args); + } +} /** * Exports a HOC with the same signature as `graphql`, that will @@ -23,7 +41,7 @@ export default (document, config) => (WrappedComponent) => { const callbacks = getMutationOptions(name); const mutate = (base) => { const variables = base.variables || config.options.variables; - const configs = callbacks.map((cb) => cb({variables, state: store.getState()})); + const configs = callbacks.map((cb) => cb({variables, state: getStore().getState()})); const optimisticResponse = merge( base.optimisticResponse || config.options.optimisticResponse, @@ -41,6 +59,11 @@ export default (document, config) => (WrappedComponent) => { .filter((i) => i); const update = (proxy, result) => { + if (getResponseErrors(result)) { + + // Do not run updates when we have mutation errors. + return; + } updateCallbacks.forEach((cb) => cb(proxy, result)); }; @@ -53,7 +76,14 @@ export default (document, config) => (WrappedComponent) => { .reduce((res, map) => { Object.keys(map).forEach((key) => { if (!(key in res)) { - res[key] = map[key]; + res[key] = (prev, result) => { + if (getResponseErrors(result.mutationResult)) { + + // Do not run updates when we have mutation errors. + return prev; + } + return map[key](prev, result); + }; } else { const existing = res[key]; res[key] = (prev, result) => { @@ -75,7 +105,14 @@ export default (document, config) => (WrappedComponent) => { if (isEmpty(wrappedConfig.optimisticResponse)) { delete wrappedConfig.optimisticResponse; } - return data.mutate(wrappedConfig); + return data.mutate(wrappedConfig) + .then((res) => { + const errors = getResponseErrors(res); + if (errors) { + throw new ResponseErrors(errors); + } + return Promise.resolve(res); + }); }; return config.props({...data, mutate}); }; diff --git a/client/coral-framework/hocs/withQuery.js b/client/coral-framework/hocs/withQuery.js index 036f9c149..84182f7dd 100644 --- a/client/coral-framework/hocs/withQuery.js +++ b/client/coral-framework/hocs/withQuery.js @@ -1,7 +1,14 @@ import * as React from 'react'; import {graphql} from 'react-apollo'; import {getQueryOptions, resolveFragments} from 'coral-framework/services/graphqlRegistry'; -import {getDefinitionName, separateDataAndRoot} from '../utils'; +import {getDefinitionName, separateDataAndRoot, getResponseErrors} from '../utils'; + +const withSkipOnErrors = (reducer) => (prev, action, ...rest) => { + if (action.type === 'APOLLO_MUTATION_RESULT' && getResponseErrors(action.result)) { + return prev; + } + return reducer(prev, action, ...rest); +}; /** * Exports a HOC with the same signature as `graphql`, that will @@ -23,10 +30,11 @@ export default (document, config) => (WrappedComponent) => { .concat(...configs.map((cfg) => cfg.reducer)) .filter((i) => i); - const reducer = reducerCallbacks.reduce( - (a, b) => (prev, ...rest) => - b(a(prev, ...rest), ...rest), - ); + const reducer = withSkipOnErrors( + reducerCallbacks.reduce( + (a, b) => (prev, ...rest) => + b(a(prev, ...rest), ...rest), + )); return { ...base, diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index 8fa4ed75b..5a05de28e 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -1,5 +1,6 @@ import {Map, fromJS} from 'immutable'; import * as actions from '../constants/auth'; +import {pym} from 'coral-framework'; const initialState = Map({ isLoading: false, @@ -16,7 +17,9 @@ const initialState = Map({ emailVerificationLoading: false, emailVerificationSuccess: false, successSignUp: false, - fromSignUp: false + fromSignUp: false, + requireEmailConfirmation: false, + redirectUri: pym.parentUrl || location.href, }); const purge = (user) => { @@ -142,6 +145,12 @@ export default function auth (state = initialState, action) { return state .set('emailVerificationSuccess', true) .set('emailVerificationLoading', false); + case actions.SET_REQUIRE_EMAIL_VERIFICATION: + return state + .set('requireEmailConfirmation', action.required); + case actions.SET_REDIRECT_URI: + return state + .set('redirectUri', action.uri); default : return state; } diff --git a/client/coral-framework/services/client.js b/client/coral-framework/services/client.js index 47ea9c352..c77bc9848 100644 --- a/client/coral-framework/services/client.js +++ b/client/coral-framework/services/client.js @@ -13,6 +13,7 @@ import {networkInterface} from './transport'; // ); export const client = new ApolloClient({ connectToDevTools: true, + addTypename: true, queryTransformer: addTypename, dataIdFromObject: (result) => { if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle @@ -24,3 +25,4 @@ export const client = new ApolloClient({ }); export default client; + diff --git a/client/coral-framework/services/i18n.js b/client/coral-framework/services/i18n.js index ecb3ce11c..c78c460e0 100644 --- a/client/coral-framework/services/i18n.js +++ b/client/coral-framework/services/i18n.js @@ -6,7 +6,6 @@ import merge from 'lodash/merge'; import esTA from '../../../node_modules/timeago.js/locales/es'; import en from '../../../locales/en.yml'; import es from '../../../locales/es.yml'; -import * as plugins from '../helpers/plugins'; // Translations are happening at https://translate.lingohub.com/the-coral-project/dashboard @@ -15,7 +14,6 @@ const translations = {...en, ...es}; let lang; let timeagoInstance; -let loadedPluginsTranslations = false; function setLocale(locale) { try { @@ -45,10 +43,6 @@ function init() { timeagoInstance = ta(); } -function loadPluginsTranslations() { - plugins.getTranslations().forEach((t) => loadTranslations(t)); -} - export function loadTranslations(newTranslations) { merge(translations, newTranslations); } @@ -67,10 +61,6 @@ export function timeago(time) { * any extra parameters are optional and replace a variable marked by {0}, {1}, etc in the translation. */ export function t(key, ...replacements) { - if (!loadedPluginsTranslations) { - loadPluginsTranslations(); - loadedPluginsTranslations = true; - } const fullKey = `${lang}.${key}`; if (has(translations, fullKey)) { let translation = get(translations, fullKey); diff --git a/client/coral-framework/services/store.js b/client/coral-framework/services/store.js index 22e126d75..0530d8c28 100644 --- a/client/coral-framework/services/store.js +++ b/client/coral-framework/services/store.js @@ -24,22 +24,29 @@ if (window.devToolsExtension) { middlewares.push(window.devToolsExtension()); } -let storeReducers = { - ...mainReducer, - apollo: client.reducer() -}; - -export const store = createStore( - combineReducers(storeReducers), - {}, - compose(...middlewares) -); - -export default store; - export function injectReducers(reducers) { - storeReducers = {...storeReducers, ...reducers}; - store.replaceReducer(combineReducers(storeReducers)); + const store = getStore(); + store.coralReducers = {...store.coralReducers, ...reducers}; + store.replaceReducer(combineReducers(store.coralReducers)); } -window.coralStore = store; +export function getStore() { + if (window.coralStore) { + return window.coralStore; + } + + const coralReducers = { + ...mainReducer, + apollo: client.reducer() + }; + + window.coralStore = createStore( + combineReducers(coralReducers), + {}, + compose(...middlewares) + ); + + window.coralStore.coralReducers = coralReducers; + + return window.coralStore; +} diff --git a/client/coral-framework/services/transport.js b/client/coral-framework/services/transport.js index e421ca3da..0c430a021 100644 --- a/client/coral-framework/services/transport.js +++ b/client/coral-framework/services/transport.js @@ -1,5 +1,6 @@ import {createNetworkInterface} from 'apollo-client'; import * as Storage from '../helpers/storage'; +import bowser from 'bowser'; //============================================================================== // NETWORK INTERFACE @@ -21,7 +22,11 @@ networkInterface.use([{ if (!req.options.headers) { req.options.headers = {}; // Create the header object if needed. } - req.options.headers['authorization'] = `Bearer ${Storage.getItem('token')}`; + + if (!bowser.safari && !bowser.ios) { + req.options.headers['authorization'] = `Bearer ${Storage.getItem('token')}`; + } + next(); } }]); diff --git a/client/coral-framework/utils/index.js b/client/coral-framework/utils/index.js index 8fb9cbf81..ca2dd5ab2 100644 --- a/client/coral-framework/utils/index.js +++ b/client/coral-framework/utils/index.js @@ -55,11 +55,12 @@ export function separateDataAndRoot( subscribeToMore, updateQuery, variables, + error, ...root, }) { return { data: {fetchMore, loading, networkStatus, refetch, startPolling, - stopPolling, subscribeToMore, updateQuery, variables}, + stopPolling, subscribeToMore, updateQuery, variables, error}, root, }; } @@ -100,3 +101,14 @@ export function mergeDocuments(documents) { const literals = [main, ...substitutions.map(() => '\n')]; return gql.apply(null, [literals, ...substitutions]); } + +export function getResponseErrors(mutationResult) { + const result = []; + Object.keys(mutationResult.data).forEach((response) => { + const errors = mutationResult.data[response].errors; + if (errors && errors.length) { + result.push(...errors); + } + }); + return result.length ? result : false; +} diff --git a/client/coral-plugin-history/Comment.css b/client/coral-plugin-history/Comment.css index bc2134639..4e827cdde 100644 --- a/client/coral-plugin-history/Comment.css +++ b/client/coral-plugin-history/Comment.css @@ -13,10 +13,10 @@ } .assetURL { - font-size: 16px; - color: black; text-decoration: none; font-weight: bold; + font-size: 12px; + color: #2c3e50; } .commentBody { @@ -32,7 +32,7 @@ } li { - margin-bottom: 10px; + margin-bottom: 5px; &:nth-child(1) { color: #5394D7; @@ -46,7 +46,6 @@ i { margin-right: 5px; font-size: 15px; - vertical-align: bottom; } a:hover { diff --git a/client/coral-settings/components/NotLoggedIn.js b/client/coral-settings/components/NotLoggedIn.js index 38bd15977..3531b4a6a 100644 --- a/client/coral-settings/components/NotLoggedIn.js +++ b/client/coral-settings/components/NotLoggedIn.js @@ -6,7 +6,7 @@ import t from 'coral-framework/services/i18n'; export default ({showSignInDialog}) => (
- {t('sign_in')} {t('toAccess')} + {t('settings.sign_in')} {t('settings.to_access')}
{t('from_settings_page')} diff --git a/client/coral-settings/containers/ProfileContainer.js b/client/coral-settings/containers/ProfileContainer.js index 245780f0f..d95a50f13 100644 --- a/client/coral-settings/containers/ProfileContainer.js +++ b/client/coral-settings/containers/ProfileContainer.js @@ -82,7 +82,7 @@ class ProfileContainer extends Component { const withQuery = graphql( gql` - query EmbedStreamProfileQuery { + query CoralEmbedStream_Profile { me { id ignoredUsers { diff --git a/client/coral-sign-in/components/FakeComment.js b/client/coral-sign-in/components/FakeComment.js deleted file mode 100644 index b73532591..000000000 --- a/client/coral-sign-in/components/FakeComment.js +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import styles from 'coral-embed-stream/src/components/Comment.css'; - -import AuthorName from 'coral-plugin-author-name/AuthorName'; -import Content from 'coral-plugin-commentcontent/CommentContent'; -import PubDate from 'coral-plugin-pubdate/PubDate'; -import {ReplyButton} from 'coral-plugin-replies'; -import t from 'coral-framework/services/i18n'; - -class FakeComment extends React.Component { - constructor (props) { - super(props); - } - - render () { - const {username, created_at, body} = this.props; - - return ( -
-
- - - -
-
- -
- {}} - parentCommentId={'commentID'} - currentUserId={{}} - banned={false} - /> -
-
-
- -
-
- -
-
-
- ); - } -} - -export default FakeComment; diff --git a/client/coral-sign-in/components/ForgotContent.js b/client/coral-sign-in/components/ForgotContent.js deleted file mode 100644 index 502335c01..000000000 --- a/client/coral-sign-in/components/ForgotContent.js +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import styles from './styles.css'; -import Button from 'coral-ui/components/Button'; -import t from 'coral-framework/services/i18n'; - -class ForgotContent extends React.Component { - constructor (props) { - super(props); - this.handleSubmit = this.handleSubmit.bind(this); - } - - handleSubmit (e) { - e.preventDefault(); - this.props.fetchForgotPassword(this.emailInput.value); - } - - render () { - const {changeView, auth} = this.props; - const {passwordRequestSuccess, passwordRequestFailure} = auth; - - return ( -
-
-

{t('sign_in.recover_password')}

-
-
-
- - this.emailInput = input} - type="text" - style={{fontSize: 16}} - id="email" - name="email" /> -
- - { - passwordRequestSuccess - ?

{passwordRequestSuccess}

- : null - } - { - passwordRequestFailure - ?

{passwordRequestFailure}

- : null - } - -
- {t('sign_in.need_an_account')} changeView('SIGNUP')}>{t('sign_in.register')} - {t('sign_in.already_have_an_account')} changeView('SIGNIN')}>{t('sign_in.sign_in')} -
-
- ); - } -} - -export default ForgotContent; diff --git a/client/coral-sign-in/components/UserBox.js b/client/coral-sign-in/components/UserBox.js deleted file mode 100644 index 4fe420cfc..000000000 --- a/client/coral-sign-in/components/UserBox.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import styles from './styles.css'; -import t from 'coral-framework/services/i18n'; - -const UserBox = ({className, user, onLogout, onShowProfile}) => ( -
- {t('sign_in.logged_in_as')} - {user.username}. {t('sign_in.not_you')} - {t('sign_in.logout')} -
-); - -export default UserBox; diff --git a/client/coral-sign-in/containers/LoginContainer.js b/client/coral-sign-in/containers/LoginContainer.js new file mode 100644 index 000000000..8d19e5198 --- /dev/null +++ b/client/coral-sign-in/containers/LoginContainer.js @@ -0,0 +1,6 @@ +import React from 'react'; +import Slot from 'coral-framework/components/Slot'; + +export const LoginContainer = () => ( + +); diff --git a/client/coral-ui/components/TextField.css b/client/coral-ui/components/TextField.css index 33b1389e7..5682748be 100644 --- a/client/coral-ui/components/TextField.css +++ b/client/coral-ui/components/TextField.css @@ -18,6 +18,7 @@ box-sizing: border-box; border-radius: 2px; margin: 5px auto; + font-size: 14px; } input.error{ diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..b33f7679f --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,6 @@ +_site/ +.sass-cache/ +.jekyll-metadata +_pdf +.idea/ +.DS_Store diff --git a/docs/.vscode/settings.json b/docs/.vscode/settings.json new file mode 100644 index 000000000..5af001d55 --- /dev/null +++ b/docs/.vscode/settings.json @@ -0,0 +1,6 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "files.associations": { + "*.html": "liquid" + } +} diff --git a/docs/404.md b/docs/404.md new file mode 100644 index 000000000..a7b58c002 --- /dev/null +++ b/docs/404.md @@ -0,0 +1,6 @@ +--- +title: "Page Not Found" +search: exclude +--- + +Sorry, but the page you were trying to view does not exist. Try searching for it or looking at the URL to see if it looks correct. diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100644 index 000000000..b1fa52c47 --- /dev/null +++ b/docs/Dockerfile @@ -0,0 +1,26 @@ +FROM ruby:2.1 +MAINTAINER mrafayaleem@gmail.com + +RUN apt-get clean \ + && mv /var/lib/apt/lists /var/lib/apt/lists.broke \ + && mkdir -p /var/lib/apt/lists/partial + +RUN apt-get update + +RUN apt-get install -y \ + node \ + python-pygments \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/ + +WORKDIR /tmp +ADD Gemfile /tmp/ +ADD Gemfile.lock /tmp/ +RUN bundle install + +VOLUME /src +EXPOSE 4000 + +WORKDIR /src +ENTRYPOINT ["jekyll"] + diff --git a/docs/Gemfile b/docs/Gemfile new file mode 100644 index 000000000..d2e1a4527 --- /dev/null +++ b/docs/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +gem 'github-pages', group: :jekyll_plugins +gem 'wdm', '>= 0.1.0' if Gem.win_platform? diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock new file mode 100644 index 000000000..04ce5e261 --- /dev/null +++ b/docs/Gemfile.lock @@ -0,0 +1,199 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (4.2.8) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + addressable (2.5.1) + public_suffix (~> 2.0, >= 2.0.2) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.12.2) + colorator (1.1.0) + ethon (0.10.1) + ffi (>= 1.3.0) + execjs (2.7.0) + faraday (0.12.1) + multipart-post (>= 1.2, < 3) + ffi (1.9.18) + forwardable-extended (2.6.0) + gemoji (3.0.0) + github-pages (138) + activesupport (= 4.2.8) + github-pages-health-check (= 1.3.3) + jekyll (= 3.4.3) + jekyll-avatar (= 0.4.2) + jekyll-coffeescript (= 1.0.1) + jekyll-default-layout (= 0.1.4) + jekyll-feed (= 0.9.2) + jekyll-gist (= 1.4.0) + jekyll-github-metadata (= 2.3.1) + jekyll-mentions (= 1.2.0) + jekyll-optional-front-matter (= 0.1.2) + jekyll-paginate (= 1.1.0) + jekyll-readme-index (= 0.1.0) + jekyll-redirect-from (= 0.12.1) + jekyll-relative-links (= 0.4.0) + jekyll-sass-converter (= 1.5.0) + jekyll-seo-tag (= 2.2.3) + jekyll-sitemap (= 1.0.0) + jekyll-swiss (= 0.4.0) + jekyll-theme-architect (= 0.0.4) + jekyll-theme-cayman (= 0.0.4) + jekyll-theme-dinky (= 0.0.4) + jekyll-theme-hacker (= 0.0.4) + jekyll-theme-leap-day (= 0.0.4) + jekyll-theme-merlot (= 0.0.4) + jekyll-theme-midnight (= 0.0.4) + jekyll-theme-minimal (= 0.0.4) + jekyll-theme-modernist (= 0.0.4) + jekyll-theme-primer (= 0.1.8) + jekyll-theme-slate (= 0.0.4) + jekyll-theme-tactile (= 0.0.4) + jekyll-theme-time-machine (= 0.0.4) + jekyll-titles-from-headings (= 0.1.5) + jemoji (= 0.8.0) + kramdown (= 1.13.2) + liquid (= 3.0.6) + listen (= 3.0.6) + mercenary (~> 0.3) + minima (= 2.1.1) + rouge (= 1.11.1) + terminal-table (~> 1.4) + github-pages-health-check (1.3.3) + addressable (~> 2.3) + net-dns (~> 0.8) + octokit (~> 4.0) + public_suffix (~> 2.0) + typhoeus (~> 0.7) + html-pipeline (2.6.0) + activesupport (>= 2) + nokogiri (>= 1.4) + i18n (0.8.1) + jekyll (3.4.3) + addressable (~> 2.4) + colorator (~> 1.0) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 1.1) + kramdown (~> 1.3) + liquid (~> 3.0) + mercenary (~> 0.3.3) + pathutil (~> 0.9) + rouge (~> 1.7) + safe_yaml (~> 1.0) + jekyll-avatar (0.4.2) + jekyll (~> 3.0) + jekyll-coffeescript (1.0.1) + coffee-script (~> 2.2) + jekyll-default-layout (0.1.4) + jekyll (~> 3.0) + jekyll-feed (0.9.2) + jekyll (~> 3.3) + jekyll-gist (1.4.0) + octokit (~> 4.2) + jekyll-github-metadata (2.3.1) + jekyll (~> 3.1) + octokit (~> 4.0, != 4.4.0) + jekyll-mentions (1.2.0) + activesupport (~> 4.0) + html-pipeline (~> 2.3) + jekyll (~> 3.0) + jekyll-optional-front-matter (0.1.2) + jekyll (~> 3.0) + jekyll-paginate (1.1.0) + jekyll-readme-index (0.1.0) + jekyll (~> 3.0) + jekyll-redirect-from (0.12.1) + jekyll (~> 3.3) + jekyll-relative-links (0.4.0) + jekyll (~> 3.3) + jekyll-sass-converter (1.5.0) + sass (~> 3.4) + jekyll-seo-tag (2.2.3) + jekyll (~> 3.3) + jekyll-sitemap (1.0.0) + jekyll (~> 3.3) + jekyll-swiss (0.4.0) + jekyll-theme-architect (0.0.4) + jekyll (~> 3.3) + jekyll-theme-cayman (0.0.4) + jekyll (~> 3.3) + jekyll-theme-dinky (0.0.4) + jekyll (~> 3.3) + jekyll-theme-hacker (0.0.4) + jekyll (~> 3.3) + jekyll-theme-leap-day (0.0.4) + jekyll (~> 3.3) + jekyll-theme-merlot (0.0.4) + jekyll (~> 3.3) + jekyll-theme-midnight (0.0.4) + jekyll (~> 3.3) + jekyll-theme-minimal (0.0.4) + jekyll (~> 3.3) + jekyll-theme-modernist (0.0.4) + jekyll (~> 3.3) + jekyll-theme-primer (0.1.8) + jekyll (~> 3.3) + jekyll-theme-slate (0.0.4) + jekyll (~> 3.3) + jekyll-theme-tactile (0.0.4) + jekyll (~> 3.3) + jekyll-theme-time-machine (0.0.4) + jekyll (~> 3.3) + jekyll-titles-from-headings (0.1.5) + jekyll (~> 3.3) + jekyll-watch (1.5.0) + listen (~> 3.0, < 3.1) + jemoji (0.8.0) + activesupport (~> 4.0) + gemoji (~> 3.0) + html-pipeline (~> 2.2) + jekyll (>= 3.0) + kramdown (1.13.2) + liquid (3.0.6) + listen (3.0.6) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9.7) + mercenary (0.3.6) + mini_portile2 (2.1.0) + minima (2.1.1) + jekyll (~> 3.3) + minitest (5.10.2) + multipart-post (2.0.0) + net-dns (0.8.0) + nokogiri (1.6.8.1) + mini_portile2 (~> 2.1.0) + octokit (4.7.0) + sawyer (~> 0.8.0, >= 0.5.3) + pathutil (0.14.0) + forwardable-extended (~> 2.6) + public_suffix (2.0.5) + rb-fsevent (0.9.8) + rb-inotify (0.9.8) + ffi (>= 0.5.0) + rouge (1.11.1) + safe_yaml (1.0.4) + sass (3.4.24) + sawyer (0.8.1) + addressable (>= 2.3.5, < 2.6) + faraday (~> 0.8, < 1.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thread_safe (0.3.6) + typhoeus (0.8.0) + ethon (>= 0.8.0) + tzinfo (1.2.3) + thread_safe (~> 0.1) + unicode-display_width (1.2.1) + +PLATFORMS + ruby + +DEPENDENCIES + github-pages + +BUNDLED WITH + 1.15.0 diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 000000000..96ae10d52 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,102 @@ +repository: coralproject/talk + +output: web +# this property is useful for conditional filtering of content that is separate from the PDF. + +topnav_title: Coral Talk Documentation +# this appears on the top navigation bar next to the home button + +site_title: Coral Talk Documentation +# this appears in the html browser tab for the site title (seen mostly by search engines, not users) + +company_name: The Coral Project +# this appears in the footer + +github_editme_path: +# if you're using Github, provide the basepath to the branch you've created for reviews, following the sample here. if not, leave this value blank. + +disqus_shortname: +# if you're using disqus for comments, add the shortname here. if not, leave this value blank. + +host: 127.0.0.1 +# the preview server used. Leave as is. + +port: 4000 +# the port where the preview is rendered. You can leave this as is unless you have other Jekyll builds using this same port that might cause conflicts. in that case, use another port such as 4006. + +exclude: + - .idea/ + - .gitignore +# these are the files and directories that jekyll will exclude from the build + +feedback_subject_line: + +feedback_email: +# used as a contact email for the Feedback link in the top navigation bar + + feedback_disable: true +# if you uncomment the previous line, the Feedback link gets removed + +# feedback_text: "Need help?" +# if you uncomment the previous line, it changes the Feedback text + +# feedback_link: "http://helpy.io/" +# if you uncomment the previous line, it changes where the feedback link points to + +highlighter: rouge +# library used for syntax highlighting + +markdown: kramdown +kramdown: + input: GFM + auto_ids: true + hard_wrap: false + syntax_highlighter: rouge + +# filter used to process markdown. note that kramdown differs from github-flavored markdown in some subtle ways + +collections: + tooltips: + output: false +# collections are declared here. this renders the content in _tooltips and processes it, but doesn't output it as actual files in the output unless you change output to true + +defaults: + - + scope: + path: "" + type: "pages" + values: + layout: "page" + comments: true + search: true + sidebar: talk_sidebar + - + scope: + path: "" + type: "tooltips" + values: + layout: "page" + comments: true + search: true + tooltip: true + + - + scope: + path: "" + type: "posts" + values: + layout: "post" + comments: true + search: true + sidebar: home_sidebar + +# these are defaults used for the frontmatter for these file types + +sidebars: +- talk_sidebar + +description: "Documentation and guides for Coral Talk." +# the description is used in the feed.xml file + +# needed for sitemap.xml file only +url: https://coralproject.github.io/talk/ diff --git a/docs/_data/alerts.yml b/docs/_data/alerts.yml new file mode 100644 index 000000000..157e1622b --- /dev/null +++ b/docs/_data/alerts.yml @@ -0,0 +1,15 @@ +tip: '
+ + {% assign thisTag = page.tagName %} + {% for page in site.pages %} + {% for tag in page.tags %} + {% if tag == thisTag %} + + + + + + {% endif %} + {% endfor %} + {% endfor %} + + {% assign thisTag = page.tagName %} + {% for post in site.posts %} + {% for tag in post.tags %} + {% if tag == thisTag %} + + + + + + {% endif %} + {% endfor %} + {% endfor %} + + +
TitleTypeExcerpt
{{page.title}}Page{% if page.summary %} {{ page.summary | strip_html | strip_newlines | truncate: 160 }} {% else %} {{ page.content | truncatewords: 50 | strip_html }} {% endif %}
{{post.title}}Post{% if post.summary %} {{ post.summary | strip_html | strip_newlines | truncate: 160 }} {% else %} {{ post.content | truncatewords: 50 | strip_html }} {% endif %}
+ diff --git a/docs/_includes/tip.html b/docs/_includes/tip.html new file mode 100644 index 000000000..faf48afd8 --- /dev/null +++ b/docs/_includes/tip.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/_includes/toc.html b/docs/_includes/toc.html new file mode 100644 index 000000000..067141a6a --- /dev/null +++ b/docs/_includes/toc.html @@ -0,0 +1,21 @@ + + + + +
diff --git a/docs/_includes/topnav.html b/docs/_includes/topnav.html new file mode 100644 index 000000000..256e63458 --- /dev/null +++ b/docs/_includes/topnav.html @@ -0,0 +1,50 @@ + + diff --git a/docs/_includes/warning.html b/docs/_includes/warning.html new file mode 100644 index 000000000..e08268c93 --- /dev/null +++ b/docs/_includes/warning.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html new file mode 100644 index 000000000..0acc2f1db --- /dev/null +++ b/docs/_layouts/default.html @@ -0,0 +1,106 @@ + + + + {% include head.html %} + + + + {% if page.datatable == true %} + + + + + + {% endif %} + + + +{% include topnav.html %} + +
+
 
+ +
+ {% assign content_col_size = "col-md-12" %} + {% unless page.hide_sidebar %} + +
+ {% include sidebar.html %} +
+ {% assign content_col_size = "col-md-9" %} + {% endunless %} + + +
+ {{content}} +
+ +
+ +
+ + +{% if site.google_analytics %} +{% include google_analytics.html %} +{% endif %} + diff --git a/docs/_layouts/default_print.html b/docs/_layouts/default_print.html new file mode 100644 index 000000000..4bf619b47 --- /dev/null +++ b/docs/_layouts/default_print.html @@ -0,0 +1,25 @@ + + + + + {% include head_print.html %} + + + + + + + +
+ +
+ + {{content}} +
+ +
+ + + + + diff --git a/docs/_layouts/page.html b/docs/_layouts/page.html new file mode 100644 index 000000000..7b324738d --- /dev/null +++ b/docs/_layouts/page.html @@ -0,0 +1,66 @@ +--- +layout: default +--- + +
+

{{ page.title }}

+
+ +{% if page.simple_map == true %} + + + +{% include custom/{{page.map_name}}.html %} + +{% elsif page.complex_map == true %} + + + +{% include custom/{{page.map_name}}.html %} + +{% endif %} + +
+ + {% if page.summary %} +
{{page.summary}}
+ {% endif %} + + {% unless page.toc == false %} + {% include toc.html %} + {% endunless %} + + + {% if site.github_editme_path %} + + Edit me + + {% endif %} + + {{content}} + +
+ {% if page.tags != null %} + Tags: + {% assign projectTags = site.data.tags.allowed-tags %} + {% for tag in page.tags %} + {% if projectTags contains tag %} + {{page.tagName}}{{tag}} + {% endif %} + {% endfor %} + {% endif %} +
+ +
+ +{{site.data.alerts.hr_shaded}} + +{% include footer.html %} diff --git a/docs/_layouts/page_print.html b/docs/_layouts/page_print.html new file mode 100644 index 000000000..9e04604a9 --- /dev/null +++ b/docs/_layouts/page_print.html @@ -0,0 +1,15 @@ +--- +layout: default_print +comments: true +--- +
+

{{ page.title }}

+
+ +
+ + {% if page.summary %} +
{{page.summary}}
+ {% endif %} + {{ content }} +
diff --git a/docs/_layouts/post.html b/docs/_layouts/post.html new file mode 100644 index 000000000..0e01ce6d1 --- /dev/null +++ b/docs/_layouts/post.html @@ -0,0 +1,39 @@ +--- +layout: default +--- +
+ +
+

{{ page.title }}

+ + + +
+ +
+ + {% if page.summary %} +
{{page.summary}}
+ {% endif %} + + {{ content }} +
+ + + +
+ +{% include disqus.html %} + +{{site.data.alerts.hr_shaded}} + +{% include footer.html %} diff --git a/docs/architecture.png b/docs/architecture.png deleted file mode 100644 index 0096ccd41..000000000 Binary files a/docs/architecture.png and /dev/null differ diff --git a/docs/architecture.xml b/docs/architecture.xml deleted file mode 100644 index ce2b09a19..000000000 --- a/docs/architecture.xml +++ /dev/null @@ -1 +0,0 @@ -7Vpbc6M2FP41nrYP6wFksP1oO0k7nd1O2nTa7qMMCtZGICpE4vTXVwIJkCVnnQbbWU/z4MDRBen7zk0HRmCVbX9ksNh8ogkio8BLtiNwNQqC6dwTv1Lw3AjCYNoIUoaTRuR3gjv8D1JCNS6tcIJKoyOnlHBcmMKY5jmKuSGDjNEns9s9JeZTC5giS3AXQ2JL/8QJ3zTSWeh18p8QTjf6yb6nWtYwfkgZrXL1vFEA7uu/pjmDei7Vv9zAhD71ROB6BFaMUt5cZdsVIhJaDVsz7mZPa7tuhnJ+yIBJ1Ix4hKRCesn1wvizBkOMELiLm6VYbSGFMaGVmGD5tMEc3RUwlsInoQlCtuEZEXe+uFRTI8bRdu/6/HbXQpkQzRBnz6KLGhBpjdCK1Nw9daQEGvpNn5C56giVIqTtzB0Y4kLh4cYGOKBIhJqoW8r4hqY0h+S6ky5r7pGcwTPBQFvM/5LicajuPuuWXCys1yRvP6sJSg4ZX0iNrmGHZYljLb7BpJ08T+xOQtjr8gVx/qwsDVacClG3g4+UFqpfyRl9QCtKKKv3DLz6r23R9tARbLEpIKMVixVmCkWx4hSpXoHSOgnni1rAEIEcP5pW+RZOw/kxOPV7jHrjaWiQalA6Bt8IqTvU/BeWQ+9EpEb+8UkNTFLHfmTyGrbtt4hhsQXEviETHoDtJpKcgO3AiliLohCCO8REqBkFERErWib4UVym8vL7X0R6MgpWcp/bgqGyHH8pf9AdxeN6fS1FOijEOdCyot7eEAemZojzdZbRC3L+xBXkZt7b0Zx8Pf4fH4I2fVIQABcEMxcEkwEgiCbHdx8vhYTatVyil/AnjtB/Ii/huxTbcg2LJMP5SObIN+JXHmOYWAbN9zkHewLpet6JI/F1dqwdSTCzrShwWFE0hBGF5zWi6cUaUXhGIwotI1pRBmWX62yNkvIdaDmYOWLFsbTcn9vJRyWeGni3jD5i4T7OAUmkE2Bt+L59TPY9YEMCBoCkXW4HySeIiUrIsNj1OQAJDECmDhVxlQ2GwCME53WEl+oHdc3gLCfMwC6T/UzXwta9XyskJI5Dx0OF3tcxY7aTYx/qN6cDGAVwBBKCxWa+kxjeib1biIhtcRMEU8dymkvLuRe6uiOCBKe51GkxvzyFLyVIOIZkoRoynCS12blQ34Pra4qWhxxnpg6kgyGQjo7hfuyi5WW4mDe4DjX0lmIxY0t9sHOYB7ucNi5Njepoff1EjR+0Jqr1o93PYSWAo5TPTJVpg5Sjzn056mRGLDC3I1ZzLD1BxJrYdZz/X14ci9TwRKQC+/hxw2jtNq7zRJ5CSJXivHTlIwzBWHS8YSiptoemJfqFXxOLE1qt6xbf0BV/TyQd9k3g7rkvmJzw3DeZWsCrYpFQNFU+imAmd52vy8JdK1rRLKtyzJ/39f1qtekTzGHa1bgPJK/KyCKWC+0yoY9wjcgtLXFd7AJXa8o5zUQHIhuW7UvsniWp19h2NsXpDtntO21pd4V+GyKVA+epEg+gEMAMkD6w9QEcSx1mdjrbI9eiDWXrujLwrmkbnhLgOHcfixKdYvco+Q0l2C7HaHgTyGEp4N13Dhj4O4adigSIbGjmcxsa3x8AG/2sgXMBqyRxifE+1J8fnaH8GjpKazRP6dV+p3FqrTYNPnAWHl3v7QZR68CCx3C+PXyivyv5RdVSFhU+qPrAQvQg6J53rbuR975JrexpZMOHslZPOYs/Kbb2LLpS/jskD+LfgsWSj5hXjfgKw5TBrBcUmseZS3ghJTjz9v4QLWNfUkiKDXzlNi6p1mNmIbZbb2VvrDTL3L39LrA55nffXoLrfwE= \ No newline at end of file diff --git a/docs/frontend/README.md b/docs/client-architecture.md similarity index 81% rename from docs/frontend/README.md rename to docs/client-architecture.md index 083b776e0..46f593758 100644 --- a/docs/frontend/README.md +++ b/docs/client-architecture.md @@ -1,4 +1,11 @@ -# Frontend Architecture +--- +title: Core Client Architecture +keywords: homepage +sidebar: talk_sidebar +permalink: client-architecture.html +summary: +--- + ## The Stack - [React](#react) - [Redux](#redux) @@ -11,11 +18,11 @@ Here is where our Redux Application, translations, components, and helpers live. ## Presentational and Container Components -We use a common simple pattern called +We use a common simple pattern called __Presentational and Container Components__ -It basically consist in having two types of components: - - Presentational +It basically consist in having two types of components: + - Presentational - Containers ### Presentational Components @@ -36,7 +43,7 @@ It basically consist in having two types of components: How a container looks like: ```js -/* +/* * mapStateToProps * We map the part of the state that we want to use */ @@ -45,7 +52,7 @@ const mapStateToProps = state => ({ auth: state.auth.toJS() }); -/* +/* * mapDispatchToProps * We map the actions that we want to use */ @@ -55,7 +62,7 @@ const mapDispatchToProps = dispatch => ({ }); -/* +/* * connect * We wrap our container in a connect() function */ @@ -86,22 +93,22 @@ More about this architecture: [Presentational and Container Components – Dan Abramov – Medium](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.ai4ih55v3) -## React -## Redux -We use Redux to handle the state container of Talk. +## GraphQL +We use [Apollo](http://www.apollodata.com/) to handle graph requests and handle state. -[How we to use Redux, and how we use it with Talk](https://github.com/coralproject/talk/blob/frontenddocs/docs/frontend/REDUX.md) +## Redux +We use [Redux](http://redux.js.org/) to handle the auth state. ## ImmutableJS -We use Immutable JS to maintain our state immutable. +We use Immutable JS to maintain our state immutable. We found some really good tradeoffs while building Talk. -[How to use ImmutableJS and how we use it with Talk](https://github.com/coralproject/talk/blob/frontenddocs/docs/frontend/IMMUTABLEJS.md) +[How to use ImmutableJS and how we use it with Talk](https://facebook.github.io/immutable-js/docs/#/) ## Test -[How we do testing at Coral with Talk](https://github.com/coralproject/talk/blob/frontenddocs/docs/frontend/DEBUG.md) +[How we do testing at Coral with Talk](/tools.html) ## Lint diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 000000000..8323ca724 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,32 @@ +--- +title: Configuration +keywords: homepage +sidebar: talk_sidebar +permalink: configuration.html +summary: +--- + +The Talk application looks for the following configuration values either as environment variables: + +- `TALK_MONGO_URL` (*required*) - the database connection string for the MongoDB database. +- `TALK_REDIS_URL` (*required*) - the database connection string for the Redis database. +- `TALK_ROOT_URL` (*required*) - root url of the installed application externally +available in the format: `://` without the path. +- `TALK_JWT_SECRET` (*required*) - a long and cryptographical secure random string which will be used to +sign and verify tokens via a `HS256` algorithm. +- `TALK_JWT_EXPIRY` (_optional_) - the expiry duration (`exp`) for the tokens issued for logged in sessions (Default `1 day`) +- `TALK_JWT_ISSUER` (_optional_) - the issuer (`iss`) claim for login JWT tokens (Default `process.env.TALK_ROOT_URL`) +- `TALK_JWT_AUDIENCE` (_optional_) - the audience (`aud`) claim for login JWT tokens (Default `talk`) +- `TALK_SMTP_EMAIL` (*required for email*) - the address to send emails from using the + SMTP provider. +- `TALK_SMTP_USERNAME` (*required for email*) - username of the SMTP provider you are using. +- `TALK_SMTP_PASSWORD` (*required for email*) - password for the SMTP provider you are using. +- `TALK_SMTP_HOST` (*required for email*) - SMTP host url with format `smtp.domain.com`. +- `TALK_SMTP_PORT` (*required for email*) - SMTP port. +- `TALK_INSTALL_LOCK` (_optional for dynamic setup_) - Defaults to `FALSE`. When `TRUE`, disables the dynamic setup endpoint. +- `TALK_RECAPTCHA_SECRET` (*required for reCAPTCHA support*) - server secret used for enabling reCAPTCHA powered logins. If not provided it will instead default to providing only a time based lockout. +- `TALK_RECAPTCHA_PUBLIC` (*required for reCAPTCHA support*) - client secret used for enabling reCAPTCHA powered logins. If not provided it will instead default to providing only a time based lockout. +- `TALK_PLUGINS_JSON` (_optional_) - used to specify the plugin config via the environment + +Refer to the wiki page on [Configuration Loading](https://github.com/coralproject/talk/wiki/Configuration-Loading) for +alternative methods of loading configuration during development. diff --git a/docs/css/bootstrap.min.css b/docs/css/bootstrap.min.css new file mode 100755 index 000000000..7b4112f07 --- /dev/null +++ b/docs/css/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{/*color:#000!important;*/text-shadow:none!important;/*background:0 0!important*/;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none;visibility:hidden}.collapse.in{display:block;visibility:visible}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none;visibility:hidden}.tab-content>.active{display:block;visibility:visible}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important;visibility:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:absolute;top:0;right:0;left:0;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/docs/css/customstyles.css b/docs/css/customstyles.css new file mode 100644 index 000000000..452b23bf1 --- /dev/null +++ b/docs/css/customstyles.css @@ -0,0 +1,1179 @@ +body { + font-size:15px; +} + +.bs-callout { + padding: 20px; + margin: 20px 0; + border: 1px solid #eee; + border-left-width: 5px; + border-radius: 3px; +} +.bs-callout h4 { + margin-top: 0; + margin-bottom: 5px; +} +.bs-callout p:last-child { + margin-bottom: 0; +} +.bs-callout code { + border-radius: 3px; +} +.bs-callout+.bs-callout { + margin-top: -5px; +} +.bs-callout-default { + border-left-color: #777; +} +.bs-callout-default h4 { + color: #777; +} +.bs-callout-primary { + border-left-color: #428bca; +} +.bs-callout-primary h4 { + color: #428bca; +} +.bs-callout-success { + border-left-color: #5cb85c; +} +.bs-callout-success h4 { + color: #5cb85c; +} +.bs-callout-danger { + border-left-color: #d9534f; +} +.bs-callout-danger h4 { + color: #d9534f; +} +.bs-callout-warning { + border-left-color: #f0ad4e; +} +.bs-callout-warning h4 { + color: #f0ad4e; +} +.bs-callout-info { + border-left-color: #5bc0de; +} +.bs-callout-info h4 { + color: #5bc0de; +} + + +.gi-2x{font-size: 2em;} +.gi-3x{font-size: 3em;} +.gi-4x{font-size: 4em;} +.gi-5x{font-size: 5em;} + + + + + +.breadcrumb > .active {color: #777 !important;} + +/* make room for the nav bar */ +h1[id], +h2[id], +h3[id], +h4[id], +h5[id], +h6[id], +dt[id]{ +padding-top: 60px; +margin-top: -40px +} + +body h1 {margin-top:40px;} + +.post-content img { + margin: 12px 0px 3px 0px; + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; +} + +.post-content ol li, .post-content ul li { + margin: 10px 0px; +} + +.pageSummary { + font-size:13px; + display:block; + margin-bottom:15px; + padding-left:20px; +} + +.post-summary { + margin-bottom:12px; +} + +.bs-example{ + margin: 20px; +} + +.breadcrumb li { + color: gray; +} + +table { + background-color: transparent; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777; + text-align: left; +} +th { + text-align: left; +} +table { + max-width: 90%; + margin-bottom: 20px; + border: 1px solid #dedede; +} + +table > thead > tr > th, +table > tbody > tr > th, +table > tfoot > tr > th, +table > thead > tr > td, +table > tbody > tr > td, +table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} +table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; + text-transform: none; + background-color: #777; + color: white; + text-align: left; +} +table > caption + thead > tr:first-child > th, +table > colgroup + thead > tr:first-child > th, +table > thead:first-child > tr:first-child > th, +table > caption + thead > tr:first-child > td, +table > colgroup + thead > tr:first-child > td, +table > thead:first-child > tr:first-child > td { + border-top: 0; +} + +table > tbody > tr:nth-of-type(odd) { + background-color: #f9f9f9; +} + +table col[class*="col-"] { + position: static; + display: table-column; + float: none; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; +} + + +table td:first-child { + width: 150px; + /*font-weight:bold;*/ +} + +table.boldFirstColumn td:first-child { + font-weight: bold; +} + +table tr td { + hyphens: auto; +} + + +p.external a { + text-align:right; + font-size:12px; + color: #0088cc; + display:inline; +} + +#definition-box-container div a.active { + font-weight: bold; +} +p.post-meta {font-size: 80%; color: #777;} + +.entry-date{font-size:14px;font-size:0.875rem;line-height:1.71429;margin-bottom:0;text-transform:uppercase;} + +/* search area */ +#search-demo-container ul#results-container { + list-style: none; + font-size: 12px; + background-color: white; + position: absolute; + top: 40px; /* if you change anything about the nav, you'll prob. need to reset the top and left values here.*/ + left: 20px; + z-index: -1; + width:223px; + border-left: 1px solid #dedede; + box-shadow: 2px 3px 2px #dedede; +} + + +ul#results-container a { + background-color: transparent; +} + +ul#results-container a:hover { + color: black; +} + + +#search-demo-container a:hover { + color: black; +} +#search-input { + padding: .5em; + margin-left:20px; + width:20em; + font-size: 0.8em; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + float: right; + margin-top:10px; +} +/* end search */ + +.filter-options { + margin-bottom: 20px; +} +.filter-options button { + margin: 3px; +} + +div#toc ul li { + margin: 8px 0px 8px 22px; + font-size: 90%; + list-style: square; +} +div#toc ul { + background-color: whitesmoke; + padding: 5px; + border-radius: 5px; + max-width: 300px; + color: gray; +} + +div#toc ul li ul { + padding-left:8px; + +} + +div#toc ul li ul li::before { + content: "– "; +} + +div#toc >ul::before { + content: "Table of Contents"; + font-weight: 500; + color: #555; + text-align:center; + margin-left:auto; + margin-right:auto; + width:70px; + padding-top:20px; + padding-bottom:20px; + padding-left:10px; +} + +li.dropdownActive a { + font-weight: bold; +} + + +.post-content a.fa-rss { + color: orange; +} + + +.navbar-inverse .navbar-nav > li > a { + background-color: transparent; + margin-top:10px; +} + +.post-content .rssfeedLink { + color: #248EC2; +} + +footer { + font-size: smaller; +} + +/* FAQ page */ +#accordion .panel-heading { + font-size: 12px; +} + +a.accordion-toggle, a.accordion-collapsed { + font-size: 14px; + text-decoration: none; +} + +/* navgoco sidebar styles (customized) */ +.nav, .nav ul, .nav li { + list-style: none; +} + +.nav ul { + padding: 0; + /*margin: 0 0 0 18px;*/ + margin:0px; +} + +.nav { + /* padding: 4px;*/ + padding:0px; + margin: 0px; +} + +.nav > li { + margin: 1px 0; +} + +.nav > li li { + margin: 2px 0; +} + +.nav a { + color: #333; + display: block; + outline: none; + /*-webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px;*/ + text-decoration: none; +} + +.nav li > a > span { + float: right; + font-size: 19px; + font-weight: bolder; +} + + +.nav li > a > span:after { + content: '\25be'; +} +.nav li.active > a > span:after { + content: '\25b4'; +} + +.nav a:hover, .nav li.active > a { + background-color: #8D8D8D; + color: #f5f5f5; +} + +.nav > li.active > a { +background-color: #347DBE; +} + +.nav li a { + font-size: 12px; + line-height: 18px; + padding: 2px 10px; + background-color: #f1f1f1; +} + +.nav > li > a { + font-size: 14px; + line-height: 20px; + padding: 4px 10px; +} + +ul#mysidebar { + border-radius:0px; +} + +.nav ul li ul li a { + padding-left:40px; +} + +.nav li.thirdlevel > a { + color: #248EC2; + font-weight:bold; + padding-left:20px; + background-color: whitesmoke !important; +} + + +.nav ul li a { + background-color: #FAFAFA; +} + +.nav li a { + padding-right:10px; +} + +.nav li a:hover { + background-color: #8D8D8D; +} + +.nav ul li a { + border-top:1px solid whitesmoke; + padding-left:10px; +} +/* end sidebar */ + +.navbar-inverse .navbar-nav > .active > a, .navbar-inverse .navbar-nav > .active > a:hover, .navbar-inverse .navbar-nav > .active > a:focus { + border-radius:5px; +} + +.navbar-inverse .navbar-nav>.open>a, .navbar-inverse .navbar-nav>.open>a:focus, .navbar-inverse .navbar-nav>.open>a:hover { + border-radius: 5px; +} + +span.projectTitle { + font-family: Helvetica; + font-weight: bold; +} + +.footer { + text-align: right; +} + +.footerMeta { + background-color: whitesmoke; + padding: 10px; + max-width: 250px; + border-radius: 5px; + margin-top: 50px; + font-style:italic; + font-size:12px; +} + +img.screenshotSmall { + max-width: 300px; +} + + +dl dt p { + margin-left:20px; +} + + +dl dd { + margin-top:10px; + margin-bottom:10px; +} + +dl.dl-horizontal dd { + padding-top: 20px; +} + +figcaption { + + padding-bottom:12px; + padding-top:6px; + max-width: 90%; + margin-bottom:20px; + font-style: italic; + color: gray; + +} + +.testing { + color: orange; +} + +.preference { + color: red; +} + + +table.dataTable thead { + background-color: #444; +} +table td { + hyphens: auto; +} + +section table tr.success { + background-color: #dff0d8 !important; +} + +table tr.info { + background-color: #d9edf7 !important; +} + +section table tr.warning, table tr.testing, table tr.testing > td.sorting_1 { + background-color: #fcf8e3 !important; +} +section table tr.danger, table tr.preference, table tr.preference > td.sorting_1 { + background-color: #f2dede !important; +} + +.orange { + color: orange; +} + +table.profile thead tr th { + background-color: #248ec2; +} + +table.request thead tr th { + background-color: #ED1951; +} + +.audienceLabel { + margin: 10px; + float: right; + border:1px solid #dedede; + padding:7px; +} + +.prefaceAudienceLabel { + color: gray; + text-align: center; + margin:5px; +} +span.myLabel { + padding-left:10px; + padding-right:10px; +} + +button.cursorNorm { + cursor: default; +} + +a.dropdown-toggle, .navbar-inverse .navbar-nav > li > a { + margin-left: 10px; +} + +hr.faded { + border: 0; + height: 1px; + background-image: -webkit-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0)); + background-image: -moz-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0)); + background-image: -ms-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0)); + background-image: -o-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0)); +} + +hr.shaded { + height: 12px; + border: 0; + box-shadow: inset 0 6px 6px -6px rgba(0,0,0,0.5); + margin-top: 70px; + background: white; + width: 100%; + margin-bottom: 10px; +} + +.fa-6x{font-size:900%;} +.fa-7x{font-size:1100%;} +.fa-8x{font-size:1300%;} +.fa-9x{font-size:1500%;} +.fa-10x{font-size:1700%;} + +i.border { + padding: 10px 20px; + background-color: whitesmoke; +} + +a[data-toggle] { + color: #248EC2; +} + +.summary { + font-size:120%; + color: #808080; + margin:20px 0px 20px 0px; + border-left: 5px solid #ED1951; + padding-left: 10px; + +} + +.summary:before { + content: "Summary: "; + font-weight: bold; +} + + +a.fa.fa-envelope-o.mailto { + font-weight: 600; +} + +h3 {color: #ED1951; font-weight:normal; font-size:130%;} +h4 {color: #808080; font-weight:normal; font-size:120%; font-style:italic;} + +.alert, .callout { + overflow: hidden; +} + +.nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus { + background-color: #248ec2; + color: white; +} + +ol li ol li {list-style-type: lower-alpha;} +ol li ul li {list-style-type: disc;} + +li img {clear:both; } + +div#toc ul li ul li { + list-style-type: none; + margin: 5px 0px 0px 0px; +} + +.tab-content { + padding: 15px; + background-color: #FAFAFA; +} + +span.tagTitle {font-weight: 500;} + +li.activeSeries { + font-weight: bold; +} + +.seriesContext .dropdown-menu li.active { + font-weight: bold; + margin-left: 43px; + font-size:18px; +} + +.alert-warning { + color: #444; +} + +div.alert code, h2 code { + background-color: transparent !important; +} +/* without this, the links in these notes aren't visible.*/ +.alert a { + text-decoration: underline; +} + +div.tags {padding: 10px 5px;} + +.tabLabel { + font-weight: normal; +} + +hr { + background: #999; + margin: 30px 0px; + width: 90%; + margin-left: auto; + margin-right: auto; +} + +button.cursorNorm { + cursor: pointer; +} + +h2 { + font-size:24px; + line-height:29px; +} +span.otherProgrammingLanguages { + font-style: normal; +} + +a[data-toggle="tooltip"] { + color: #649345; + font-style: italic; + cursor: default; +} + +.seriesNext, .seriesContext { + margin-top: 15px; + margin-bottom: 15px; +} + +.seriescontext ol li { + list-style-type: upper-roman; +} + +ol.series li { + list-style-type: decimal; + margin-left: 40px; + padding-left: 0px; +} + +.siteTagline { + font-size: 200%; + font-weight: bold; + color: silver; + font-family: monospace; + text-align: center; + line-height: 10px; + margin: 20px 0px; + display: block; +} + +.versionTagline { + text-align: center; + margin-bottom: 20px; + font-family: courier; + color: silver; + color: #444; + display:block; +} + +/* not sure if using this ...*/ +.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form { + border-color: #248ec2 !important; +} + +#mysidebar .nav ul { + background-color: #FAFAFA; +} +.nav ul.series li { + list-style: decimal; + font-size:12px; +} + +.nav ul.series li a:hover { + background-color: gray; +} +.nav ul.series { + padding-left: 30px; +} + +.nav ul.series { + background-color: #FAFAFA; +} + +/* +a.dropdown-toggle.otherProgLangs { + color: #f7e68f !important; +} +*/ + +span.muted {color: #666;} + +table code {background-color: transparent;} + +.highlight .err { + color: #a61717; + background-color: transparent !important; +} + +table p { + margin-top: 12px; + margin-bottom: 12px; +} + +pre, table code { + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + +pre { + margin: 25px 0px; +} + +#json-box-container pre { + margin: 0px; +} + +.video-js { + margin: 30px 0px; +} + +video { + display: block; + margin: 30px 0px; + border: 1px solid #c0c0c0; +} + + +p.required, p.dataType {display: block; color: #c0c0c0; font-size: 80%; margin-left:4px;} + +dd {margin-left:20px;} + +.post-content img.inline { + margin:0px; + margin-bottom:6px; +} +.panel-heading { + font-weight: bold; +} + +.note code, .alert code, .warning code, div#toc code, h2 code, h3 code, h4 code { + color: inherit; + padding: 0px; +} + +.alert { + margin-bottom:10px; + margin-top:10px; +} + +a.accordion-toggle { + font-style: normal; +} + +span.red { + color: red; + font-family: Monaco, Menlo, Consolas, "Courier New", monospace; +} + +h3.codeExplanation { + font-size:18px; + font-style:normal; + color: black; + line-height: 24px; +} + +span.soft { + color: #c0c0c0; +} + +.githubEditButton { + margin-bottom:7px; +} + +.endpoint { + padding: 15px; + background-color: #f0f0f0; + font-family: courier; + font-size: 110%; + margin: 20px 0px; + color: #444; +} + +.parameter { + font-family: courier; + color: red !important; +} + +.formBoundary { + border: 1px solid gray; + padding: 15px; + margin: 15px 0px; + background-color: whitesmoke; +} + +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #444; + } +} + +@media (max-width: 990px) { + #mysidebar { + position: relative; + } +} + +@media (min-width: 1000px) { + + ul#mysidebar { + width: 225px; + } +} + +@media (max-width: 900px) { + + ul#mysidebar { + max-width: 100%; + } +} + +.col-md-9 img { + max-width: 100%; + max-height: 100%; +} + + +.post-content img { + margin: 12px 0px 3px 0px; + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; +} +.col-md-9 img { + max-width: 100%; + max-height: 100%; +} + + +.post-content img { + margin: 12px 0px 3px 0px; + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; +} + +.videoThumbs img { + float: left; + margin:15px 15px 15px 0px; + box-shadow: 2px 2px 1px #f0f0f0; + border: 1px solid #dedede; +} + + +@media only screen and (min-width: 900px), only screen and (min-device-width: 900px) { + .col-md-9 img { + max-width: 700px; + max-height: 700px; + } +} + +*:hover > .anchorjs-link { + transition: color .25s linear; + text-decoration: none; +} + +.kbCaption { + color: white; + background-color: #444; + padding:10px; +} + +/* this part adds an icon after external links, using FontAwesome*/ +a[href^="http://"]:after, a[href^="https://"]:after { + content: "\f08e"; + font-family: FontAwesome; + font-weight: normal; + font-style: normal; + display: inline-block; + text-decoration: none; + padding-left: 3px; +} + +/* Strip the outbound icon when this class is present */ +a[href].noCrossRef::after, +a.no_icon:after + { + content:"" !important; + padding-left: 0; +} + +.btn-default { + margin-bottom: 10px; +} + +/* algolia search */ + +.search { + text-align: left; +} +.search input { + font-size: 20px; + width: 300px; +} +.results { + margin: auto; + text-align: left; +} +.results ul { + list-style-type: none; + padding: 0; +} + +/* algolia */ + +div.results { + position: absolute; + background-color: white; + width: 100%; +} + +.post-meta { + font-size: 14px; + color: #828282; +} + +.post-link { + font-size: 22px; +} + +.post-list p { + margin: 10px 0px; +} + +time { + margin-right: 10px; +} + +p.post-meta time { + margin-right: 0px; +} + +span.label.label-default { + background-color: gray; +} + +span.label.label-primary { + background-color: #f0ad4e; +} +.col-lg-12 .nav li a {background-color: white} + + +.nav li.active > a.subfoldersTitle { + background-color: whitesmoke; + font-weight: bold; + color: black; + } + +a code { + color: #248EC2; +} + +code + a > code { + margin-left: -7px; +} + +table th code { + color: white; +} + +ol li ul li ol li { + list-style: decimal; +} + +ol li ul li ol li ul li{ + list-style: disc; +} + +.post-content table th { + vertical-align: top; +} + +table thead th code.highlighter-rouge { + background-color: transparent; +} + + +.box { + padding: 10px; + border: 1px solid #888; + box-shadow: 2px 2px 4px #dedede; + width: 100px; + height: 80px; + background-color: #f5f5f5; + font-family: Arial; + font-size: 12px; + hyphens: auto; + float: left; + font-size: 12px; +} + +.box:hover { + background-color: #f0f0f0; +} + +#userMap { + overflow-x: auto; + overflow-y: auto; + padding: 20px; + min-width: 770px; +} + +#userMap .active { + background-color: #d6f5d6; + border:1px solid #555; + font-weight: bold; +} + +h2.userMapTitle { + font-family: Arial; +} + +#userMap a:hover { + text-decoration: none; + } + +div.arrow { + max-width: 50px; + margin-left: 15px; + margin-right: 15px; + font-size: 20px; +} + +div.content { + max-width: 110px +} + +#userMap div.arrow, #userMap div.content { + float: left; +} + +.clearfix { + clear: both; +} + + +#userMap div.arrow { + position: relative; + top: 30px; +} + +.box1 { + margin-left:0px; +} + +button.btn.btn-default.btn-lg.modalButton1 { + margin-left: -20px; +} + +div.box.box1 { + margin-left: -20px; +} + +#userMap .btn-lg { + width: 100px; + height: 80px; + +} + +#userMap .complexArrow { + font-size: 22px; + margin: 0px 10px; +} + + +#userMap .btn-lg .active { + background-color: #d6f5d6; +} + +#userMap .btn-lg { + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ + font-size: 14px; + } + +/* + * Let's target IE to respect aspect ratios and sizes for img tags containing SVG files + * + * [1] IE9 + * [2] IE10+ + */ +/* 1 */ +.ie9 img[src$=".svg"] { + width: 100%; +} +/* 2 */ +@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { + img[src$=".svg"] { + width: 100%; + } +} + +h4.panel-title { + padding-top: 0px; + margin-top: 0px; +} diff --git a/docs/css/font-awesome.min.css b/docs/css/font-awesome.min.css new file mode 100644 index 000000000..08e68ec0c --- /dev/null +++ b/docs/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('fonts/fontawesome-webfont.eot?v=4.2.0');src:url('fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'),url('fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'),url('fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'),url('fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"} \ No newline at end of file diff --git a/docs/css/fonts/FontAwesome.otf b/docs/css/fonts/FontAwesome.otf new file mode 100644 index 000000000..f7936cc1e Binary files /dev/null and b/docs/css/fonts/FontAwesome.otf differ diff --git a/docs/css/fonts/fontawesome-webfont.eot b/docs/css/fonts/fontawesome-webfont.eot new file mode 100644 index 000000000..33b2bb800 Binary files /dev/null and b/docs/css/fonts/fontawesome-webfont.eot differ diff --git a/docs/css/fonts/fontawesome-webfont.svg b/docs/css/fonts/fontawesome-webfont.svg new file mode 100644 index 000000000..1ee89d436 --- /dev/null +++ b/docs/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,565 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/css/fonts/fontawesome-webfont.ttf b/docs/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 000000000..ed9372f8e Binary files /dev/null and b/docs/css/fonts/fontawesome-webfont.ttf differ diff --git a/docs/css/fonts/fontawesome-webfont.woff b/docs/css/fonts/fontawesome-webfont.woff new file mode 100644 index 000000000..8b280b98f Binary files /dev/null and b/docs/css/fonts/fontawesome-webfont.woff differ diff --git a/docs/css/fonts/fontawesome-webfont.woff2 b/docs/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 000000000..3311d5851 Binary files /dev/null and b/docs/css/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/css/lavish-bootstrap.css b/docs/css/lavish-bootstrap.css new file mode 100644 index 000000000..c2af93e59 --- /dev/null +++ b/docs/css/lavish-bootstrap.css @@ -0,0 +1,5898 @@ +/* CSS generated by http://lavishbootstrap.com */ +/*! normalize.css v2.1.0 | MIT License | git.io/normalize */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block; +} +audio, +canvas, +video { + display: inline-block; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden] { + display: none; +} +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +body { + margin: 0; +} +a:focus { + outline: thin dotted; +} +a:active, +a:hover { + outline: 0; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} +mark { + background: #ff0; + color: #000; +} +code, +kbd, +pre, +samp { + font-family: monospace, serif; + font-size: 1em; +} +pre { + white-space: pre-wrap; +} +q { + quotes: "\201C" "\201D" "\2018" "\2019"; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 0; +} +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { + border: 0; + padding: 0; +} +button, +input, +select, +textarea { + font-family: inherit; + font-size: 100%; + margin: 0; +} +button, +input { + line-height: normal; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + padding: 0; +} +input[type="search"] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} +textarea { + overflow: auto; + vertical-align: top; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +@media print { + * { + text-shadow: none !important; + color: #000 !important; + background: transparent !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + .ir a:after, + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + @page { + margin: 2cm .5cm; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .table td, + .table th { + background-color: #fff !important; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +*, +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 62.5%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.428571429; + /*color: #77777a; */ + background-color: #ffffff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +button, +input, +select[multiple], +textarea { + background-image: none; +} +a { + color: #248EC2; + text-decoration: none; +} +a:hover, +a:focus { + text-decoration: underline; +} +a:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +img { + vertical-align: middle; +} +.img-responsive { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + padding: 4px; + line-height: 1.428571429; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 4px; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + display: inline-block; + max-width: 100%; + height: auto; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #bac9c2; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0 0 0 0); + border: 0; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16.099999999999998px; + font-weight: 200; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small { + font-size: 85%; +} +cite { + font-style: normal; +} +.text-muted { + color: #f9faf9; +} +.text-primary { + color: #74ab50; +} +.text-warning { + color: #c09853; +} +.text-danger { + color: #b94a48; +} +.text-success { + color: #468847; +} +.text-info { + color: #3a87ad; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 500; + line-height: 1.1; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small { + font-weight: normal; + line-height: 1; + color: #f9faf9; +} +h1, +h2, +h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h4, +h5, +h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +h1 small, +.h1 small { + font-size: 24px; +} +h2 small, +.h2 small { + font-size: 18px; +} +h3 small, +.h3 small, +h4 small, +.h4 small { + font-size: 14px; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #bac9c2; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + list-style: none; +} +.list-inline > li { + display: inline-block; + padding-left: 5px; + padding-right: 5px; +} +dl { + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.428571429; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + clear: left; + text-align: right; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } + .dl-horizontal dd:before, + .dl-horizontal dd:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + + } + .dl-horizontal dd:after { + clear: both; + } + .dl-horizontal dd:before, + .dl-horizontal dd:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + + } + .dl-horizontal dd:after { + clear: both; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #f9faf9; +} +abbr.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + border-left: 5px solid #bac9c2; +} +blockquote p { + font-size: 17.5px; + font-weight: 300; + line-height: 1.25; +} +blockquote p:last-child { + margin-bottom: 0; +} +blockquote small { + display: block; + line-height: 1.428571429; + color: #f9faf9; +} +blockquote small:before { + content: '\2014 \00A0'; +} +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid #bac9c2; + border-left: 0; +} +blockquote.pull-right p, +blockquote.pull-right small { + text-align: right; +} +blockquote.pull-right small:before { + content: ''; +} +blockquote.pull-right small:after { + content: '\00A0 \2014'; +} +q:before, +q:after, +blockquote:before, +blockquote:after { + content: ""; +} +address { + display: block; + margin-bottom: 20px; + font-style: normal; + line-height: 1.428571429; +} +code, +pre { + font-family: Monaco, Menlo, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #444; + background-color: #f0f0f0; + white-space: nowrap; + border-radius: 4px; +} + +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.428571429; + word-break: break-all; + word-wrap: break-word; + color: #77777a; + background-color: #f5f5f5; + border: 1px solid #cccccc; + border-radius: 4px; +} +pre.prettyprint { + margin-bottom: 20px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +.container:before, +.container:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.container:after { + clear: both; +} +.container:before, +.container:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.container:after { + clear: both; +} +.row { + margin-left: -15px; + margin-right: -15px; +} +.row:before, +.row:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.row:after { + clear: both; +} +.row:before, +.row:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.row:after { + clear: both; +} +.col-xs-1, +.col-xs-2, +.col-xs-3, +.col-xs-4, +.col-xs-5, +.col-xs-6, +.col-xs-7, +.col-xs-8, +.col-xs-9, +.col-xs-10, +.col-xs-11, +.col-xs-12, +.col-sm-1, +.col-sm-2, +.col-sm-3, +.col-sm-4, +.col-sm-5, +.col-sm-6, +.col-sm-7, +.col-sm-8, +.col-sm-9, +.col-sm-10, +.col-sm-11, +.col-sm-12, +.col-md-1, +.col-md-2, +.col-md-3, +.col-md-4, +.col-md-5, +.col-md-6, +.col-md-7, +.col-md-8, +.col-md-9, +.col-md-10, +.col-md-11, +.col-md-12, +.col-lg-1, +.col-lg-2, +.col-lg-3, +.col-lg-4, +.col-lg-5, +.col-lg-6, +.col-lg-7, +.col-lg-8, +.col-lg-9, +.col-lg-10, +.col-lg-11, +.col-lg-12 { + position: relative; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; +} +.col-xs-1, +.col-xs-2, +.col-xs-3, +.col-xs-4, +.col-xs-5, +.col-xs-6, +.col-xs-7, +.col-xs-8, +.col-xs-9, +.col-xs-10, +.col-xs-11 { + float: left; +} +.col-xs-1 { + width: 8.333333333333332%; +} +.col-xs-2 { + width: 16.666666666666664%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-4 { + width: 33.33333333333333%; +} +.col-xs-5 { + width: 41.66666666666667%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-7 { + width: 58.333333333333336%; +} +.col-xs-8 { + width: 66.66666666666666%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-10 { + width: 83.33333333333334%; +} +.col-xs-11 { + width: 91.66666666666666%; +} +.col-xs-12 { + width: 100%; +} +@media (min-width: 768px) { + .container { + max-width: 750px; + } + .col-sm-1, + .col-sm-2, + .col-sm-3, + .col-sm-4, + .col-sm-5, + .col-sm-6, + .col-sm-7, + .col-sm-8, + .col-sm-9, + .col-sm-10, + .col-sm-11 { + float: left; + } + .col-sm-1 { + width: 8.333333333333332%; + } + .col-sm-2 { + width: 16.666666666666664%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-4 { + width: 33.33333333333333%; + } + .col-sm-5 { + width: 41.66666666666667%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-7 { + width: 58.333333333333336%; + } + .col-sm-8 { + width: 66.66666666666666%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-10 { + width: 83.33333333333334%; + } + .col-sm-11 { + width: 91.66666666666666%; + } + .col-sm-12 { + width: 100%; + } + .col-sm-push-1 { + left: 8.333333333333332%; + } + .col-sm-push-2 { + left: 16.666666666666664%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-4 { + left: 33.33333333333333%; + } + .col-sm-push-5 { + left: 41.66666666666667%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-7 { + left: 58.333333333333336%; + } + .col-sm-push-8 { + left: 66.66666666666666%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-10 { + left: 83.33333333333334%; + } + .col-sm-push-11 { + left: 91.66666666666666%; + } + .col-sm-pull-1 { + right: 8.333333333333332%; + } + .col-sm-pull-2 { + right: 16.666666666666664%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-4 { + right: 33.33333333333333%; + } + .col-sm-pull-5 { + right: 41.66666666666667%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-7 { + right: 58.333333333333336%; + } + .col-sm-pull-8 { + right: 66.66666666666666%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-10 { + right: 83.33333333333334%; + } + .col-sm-pull-11 { + right: 91.66666666666666%; + } + .col-sm-offset-1 { + margin-left: 8.333333333333332%; + } + .col-sm-offset-2 { + margin-left: 16.666666666666664%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-4 { + margin-left: 33.33333333333333%; + } + .col-sm-offset-5 { + margin-left: 41.66666666666667%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-7 { + margin-left: 58.333333333333336%; + } + .col-sm-offset-8 { + margin-left: 66.66666666666666%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-10 { + margin-left: 83.33333333333334%; + } + .col-sm-offset-11 { + margin-left: 91.66666666666666%; + } +} +@media (min-width: 992px) { + .container { + max-width: 970px; + } + .col-md-1, + .col-md-2, + .col-md-3, + .col-md-4, + .col-md-5, + .col-md-6, + .col-md-7, + .col-md-8, + .col-md-9, + .col-md-10, + .col-md-11 { + float: left; + } + .col-md-1 { + width: 8.333333333333332%; + } + .col-md-2 { + width: 16.666666666666664%; + } + .col-md-3 { + width: 25%; + } + .col-md-4 { + width: 33.33333333333333%; + } + .col-md-5 { + width: 41.66666666666667%; + } + .col-md-6 { + width: 50%; + } + .col-md-7 { + width: 58.333333333333336%; + } + .col-md-8 { + width: 66.66666666666666%; + } + .col-md-9 { + width: 75%; + } + .col-md-10 { + width: 83.33333333333334%; + } + .col-md-11 { + width: 91.66666666666666%; + } + .col-md-12 { + width: 100%; + } + .col-md-push-0 { + left: auto; + } + .col-md-push-1 { + left: 8.333333333333332%; + } + .col-md-push-2 { + left: 16.666666666666664%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-4 { + left: 33.33333333333333%; + } + .col-md-push-5 { + left: 41.66666666666667%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-7 { + left: 58.333333333333336%; + } + .col-md-push-8 { + left: 66.66666666666666%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-10 { + left: 83.33333333333334%; + } + .col-md-push-11 { + left: 91.66666666666666%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-pull-1 { + right: 8.333333333333332%; + } + .col-md-pull-2 { + right: 16.666666666666664%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-4 { + right: 33.33333333333333%; + } + .col-md-pull-5 { + right: 41.66666666666667%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-7 { + right: 58.333333333333336%; + } + .col-md-pull-8 { + right: 66.66666666666666%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-10 { + right: 83.33333333333334%; + } + .col-md-pull-11 { + right: 91.66666666666666%; + } + .col-md-offset-0 { + margin-left: 0; + } + .col-md-offset-1 { + margin-left: 8.333333333333332%; + } + .col-md-offset-2 { + margin-left: 16.666666666666664%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-4 { + margin-left: 33.33333333333333%; + } + .col-md-offset-5 { + margin-left: 41.66666666666667%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-7 { + margin-left: 58.333333333333336%; + } + .col-md-offset-8 { + margin-left: 66.66666666666666%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-10 { + margin-left: 83.33333333333334%; + } + .col-md-offset-11 { + margin-left: 91.66666666666666%; + } +} +@media (min-width: 1200px) { + .container { + max-width: 1170px; + } + .col-lg-1, + .col-lg-2, + .col-lg-3, + .col-lg-4, + .col-lg-5, + .col-lg-6, + .col-lg-7, + .col-lg-8, + .col-lg-9, + .col-lg-10, + .col-lg-11 { + float: left; + } + .col-lg-1 { + width: 8.333333333333332%; + } + .col-lg-2 { + width: 16.666666666666664%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-4 { + width: 33.33333333333333%; + } + .col-lg-5 { + width: 41.66666666666667%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-7 { + width: 58.333333333333336%; + } + .col-lg-8 { + width: 66.66666666666666%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-10 { + width: 83.33333333333334%; + } + .col-lg-11 { + width: 91.66666666666666%; + } + .col-lg-12 { + width: 100%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-push-1 { + left: 8.333333333333332%; + } + .col-lg-push-2 { + left: 16.666666666666664%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-4 { + left: 33.33333333333333%; + } + .col-lg-push-5 { + left: 41.66666666666667%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-7 { + left: 58.333333333333336%; + } + .col-lg-push-8 { + left: 66.66666666666666%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-10 { + left: 83.33333333333334%; + } + .col-lg-push-11 { + left: 91.66666666666666%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-pull-1 { + right: 8.333333333333332%; + } + .col-lg-pull-2 { + right: 16.666666666666664%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-4 { + right: 33.33333333333333%; + } + .col-lg-pull-5 { + right: 41.66666666666667%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-7 { + right: 58.333333333333336%; + } + .col-lg-pull-8 { + right: 66.66666666666666%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-10 { + right: 83.33333333333334%; + } + .col-lg-pull-11 { + right: 91.66666666666666%; + } + .col-lg-offset-0 { + margin-left: 0; + } + .col-lg-offset-1 { + margin-left: 8.333333333333332%; + } + .col-lg-offset-2 { + margin-left: 16.666666666666664%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-4 { + margin-left: 33.33333333333333%; + } + .col-lg-offset-5 { + margin-left: 41.66666666666667%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-7 { + margin-left: 58.333333333333336%; + } + .col-lg-offset-8 { + margin-left: 66.66666666666666%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-10 { + margin-left: 83.33333333333334%; + } + .col-lg-offset-11 { + margin-left: 91.66666666666666%; + } +} +table { + max-width: 100%; + background-color: transparent; +} +th { + text-align: left; +} +.table { + width: 100%; + margin-bottom: 20px; +} +.table thead > tr > th, +.table tbody > tr > th, +.table tfoot > tr > th, +.table thead > tr > td, +.table tbody > tr > td, +.table tfoot > tr > td { + padding: 8px; + line-height: 1.428571429; + vertical-align: top; + border-top: 1px solid #77777a; +} +.table thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #77777a; +} +.table caption + thead tr:first-child th, +.table colgroup + thead tr:first-child th, +.table thead:first-child tr:first-child th, +.table caption + thead tr:first-child td, +.table colgroup + thead tr:first-child td, +.table thead:first-child tr:first-child td { + border-top: 0; +} +.table tbody + tbody { + border-top: 2px solid #77777a; +} +.table .table { + background-color: #ffffff; +} +.table-condensed thead > tr > th, +.table-condensed tbody > tr > th, +.table-condensed tfoot > tr > th, +.table-condensed thead > tr > td, +.table-condensed tbody > tr > td, +.table-condensed tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #77777a; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #77777a; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-child(odd) > td, +.table-striped > tbody > tr:nth-child(odd) > th { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover > td, +.table-hover > tbody > tr:hover > th { + background-color: #f5f5f5; +} +table col[class*="col-"] { + float: none; + display: table-column; +} +table td[class*="col-"], +table th[class*="col-"] { + float: none; + display: table-cell; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; + border-color: #d6e9c6; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td { + background-color: #d0e9c6; + border-color: #c9e2b3; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; + border-color: #eed3d7; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td { + background-color: #ebcccc; + border-color: #e6c1c7; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; + border-color: #fbeed5; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td { + background-color: #faf2cc; + border-color: #f8e5be; +} +@media (max-width: 768px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + overflow-x: scroll; + border: 1px solid #77777a; + } + .table-responsive > .table { + margin-bottom: 0; + background-color: #fff; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > thead > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > thead > tr:last-child > td, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #77777a; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + /* IE8-9 */ + + line-height: normal; +} +input[type="file"] { + display: block; +} +select[multiple], +select[size] { + height: auto; +} +select optgroup { + font-size: inherit; + font-style: inherit; + font-family: inherit; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +input[type="number"]::-webkit-outer-spin-button, +input[type="number"]::-webkit-inner-spin-button { + height: auto; +} +.form-control:-moz-placeholder { + color: #f9faf9; +} +.form-control::-moz-placeholder { + color: #f9faf9; +} +.form-control:-ms-input-placeholder { + color: #f9faf9; +} +.form-control::-webkit-input-placeholder { + color: #f9faf9; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.428571429; + color: #4aadd6; + vertical-align: middle; + background-color: #ffffff; + border: 1px solid #cccccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + cursor: not-allowed; + background-color: #bac9c2; +} +textarea.form-control { + height: auto; +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + display: block; + min-height: 20px; + margin-top: 10px; + margin-bottom: 10px; + padding-left: 20px; + vertical-align: middle; +} +.radio label, +.checkbox label { + display: inline; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + float: left; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + vertical-align: middle; + font-weight: normal; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +.radio[disabled], +.radio-inline[disabled], +.checkbox[disabled], +.checkbox-inline[disabled], +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"], +fieldset[disabled] .radio, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm { + height: auto; +} +.input-lg { + height: 45px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +select.input-lg { + height: 45px; + line-height: 45px; +} +textarea.input-lg { + height: auto; +} +.has-warning .help-block, +.has-warning .control-label { + color: #c09853; +} +.has-warning .form-control { + border-color: #c09853; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-warning .form-control:focus { + border-color: #a47e3c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; +} +.has-warning .input-group-addon { + color: #c09853; + border-color: #c09853; + background-color: #fcf8e3; +} +.has-error .help-block, +.has-error .control-label { + color: #b94a48; +} +.has-error .form-control { + border-color: #b94a48; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-error .form-control:focus { + border-color: #953b39; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; +} +.has-error .input-group-addon { + color: #b94a48; + border-color: #b94a48; + background-color: #f2dede; +} +.has-success .help-block, +.has-success .control-label { + color: #468847; +} +.has-success .form-control { + border-color: #468847; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-success .form-control:focus { + border-color: #356635; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; +} +.has-success .input-group-addon { + color: #468847; + border-color: #468847; + background-color: #dff0d8; +} +.form-control-static { + margin-bottom: 0; + padding-top: 7px; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #b7b7b9; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } +} +.form-horizontal .control-label, +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + margin-top: 0; + margin-bottom: 0; + padding-top: 7px; +} +.form-horizontal .form-group { + margin-left: -15px; + margin-right: -15px; +} +.form-horizontal .form-group:before, +.form-horizontal .form-group:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.form-horizontal .form-group:after { + clear: both; +} +.form-horizontal .form-group:before, +.form-horizontal .form-group:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.form-horizontal .form-group:after { + clear: both; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + text-align: right; + } +} +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.428571429; + text-align: center; + vertical-align: middle; + cursor: pointer; + border: 1px solid transparent; + border-radius: 4px; + white-space: nowrap; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +.btn:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus { + color: #333333; + text-decoration: none; +} +.btn:active, +.btn.active { + outline: 0; + background-image: none; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + pointer-events: none; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-default { + color: #333333; + background-color: #ffffff; + border-color: #cccccc; +} +.btn-default:hover, +.btn-default:focus, +.btn-default:active, +.btn-default.active, +.open .dropdown-toggle.btn-default { + color: #333333; + background-color: #ebebeb; + border-color: #adadad; +} +.btn-default:active, +.btn-default.active, +.open .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #ffffff; + border-color: #cccccc; +} +.btn-primary { + color: #ffffff; + background-color: #74ab50; + border-color: #689a48; +} +.btn-primary:hover, +.btn-primary:focus, +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + color: #ffffff; + background-color: #618f43; + border-color: #4c7034; +} +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #74ab50; + border-color: #689a48; +} +.btn-warning { + color: #ffffff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:hover, +.btn-warning:focus, +.btn-warning:active, +.btn-warning.active, +.open .dropdown-toggle.btn-warning { + color: #ffffff; + background-color: #ed9c28; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-danger { + color: #ffffff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:hover, +.btn-danger:focus, +.btn-danger:active, +.btn-danger.active, +.open .dropdown-toggle.btn-danger { + color: #ffffff; + background-color: #d2322d; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-success { + color: #ffffff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:hover, +.btn-success:focus, +.btn-success:active, +.btn-success.active, +.open .dropdown-toggle.btn-success { + color: #ffffff; + background-color: #47a447; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-info { + color: #ffffff; + background-color: #347DBE; + border-color: #347DBE; +} +.btn-info:hover, +.btn-info:focus, +.btn-info:active, +.btn-info.active, +.open .dropdown-toggle.btn-info { + color: #ffffff; + background-color: #015CAE; + border-color: #015CAE; +} +.btn-info:active, +.btn-info.active, +.open .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-link { + color: #74ab50; + font-weight: normal; + cursor: pointer; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #517738; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #f9faf9; + text-decoration: none; +} +.btn-lg { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +.btn-sm, +.btn-xs { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs { + padding: 1px 5px; +} +.btn-block { + display: block; + width: 100%; + padding-left: 0; + padding-right: 0; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height 0.35s ease; + transition: height 0.35s ease; +} +@font-face { + font-family: 'Glyphicons Halflings'; + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.glyphicon-asterisk:before { + content: "\2a"; +} +.glyphicon-plus:before { + content: "\2b"; +} +.glyphicon-euro:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.glyphicon-briefcase:before { + content: "\1f4bc"; +} +.glyphicon-calendar:before { + content: "\1f4c5"; +} +.glyphicon-pushpin:before { + content: "\1f4cc"; +} +.glyphicon-paperclip:before { + content: "\1f4ce"; +} +.glyphicon-camera:before { + content: "\1f4f7"; +} +.glyphicon-lock:before { + content: "\1f512"; +} +.glyphicon-bell:before { + content: "\1f514"; +} +.glyphicon-bookmark:before { + content: "\1f516"; +} +.glyphicon-fire:before { + content: "\1f525"; +} +.glyphicon-wrench:before { + content: "\1f527"; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px solid #000000; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + border-bottom: 0 dotted; + content: ""; +} +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + font-size: 14px; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-clip: padding-box; +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.428571429; + color: #77777a; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + text-decoration: none; + color: #ffffff; + background-color: #679DCE; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #ffffff; + text-decoration: none; + outline: 0; + background-color: #679DCE; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #f9faf9; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + cursor: not-allowed; +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.428571429; + color: #f9faf9; +} +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + border-top: 0 dotted; + border-bottom: 4px solid #000000; + content: ""; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } +} +.btn-default .caret { + border-top-color: #333333; +} +.btn-primary .caret, +.btn-success .caret, +.btn-warning .caret, +.btn-danger .caret, +.btn-info .caret { + border-top-color: #fff; +} +.dropup .btn-default .caret { + border-bottom-color: #333333; +} +.dropup .btn-primary .caret, +.dropup .btn-success .caret, +.dropup .btn-warning .caret, +.dropup .btn-danger .caret, +.dropup .btn-info .caret { + border-bottom-color: #fff; +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus { + outline: none; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar:before, +.btn-toolbar:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.btn-toolbar:after { + clear: both; +} +.btn-toolbar:before, +.btn-toolbar:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.btn-toolbar:after { + clear: both; +} +.btn-toolbar .btn-group { + float: left; +} +.btn-toolbar > .btn + .btn, +.btn-toolbar > .btn-group + .btn, +.btn-toolbar > .btn + .btn-group, +.btn-toolbar > .btn-group + .btn-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child > .btn:last-child, +.btn-group > .btn-group:first-child > .dropdown-toggle { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.btn-group > .btn-group:last-child > .btn:first-child { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group-xs > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; + padding: 1px 5px; +} +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.btn-group-vertical > .btn-group:after { + clear: both; +} +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.btn-group-vertical > .btn-group:after { + clear: both; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-bottom-left-radius: 4px; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child > .btn:last-child, +.btn-group-vertical > .btn-group:first-child > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child > .btn:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified .btn { + float: none; + display: table-cell; + width: 1%; +} +[data-toggle="buttons"] > .btn > input[type="radio"], +[data-toggle="buttons"] > .btn > input[type="checkbox"] { + display: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group.col { + float: none; + padding-left: 0; + padding-right: 0; +} +.input-group .form-control { + width: 100%; + margin-bottom: 0; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 45px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 45px; + line-height: 45px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + text-align: center; + background-color: #bac9c2; + border: 1px solid #cccccc; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -4px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:active { + z-index: 2; +} +.nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; +} +.nav:before, +.nav:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.nav:after { + clear: both; +} +.nav:before, +.nav:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.nav:after { + clear: both; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #bac9c2; +} +.nav > li.disabled > a { + color: #f9faf9; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #f9faf9; + text-decoration: none; + background-color: transparent; + cursor: not-allowed; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #bac9c2; + border-color: #74ab50; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #dddddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.428571429; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #bac9c2 #bac9c2 #dddddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #4aadd6; + background-color: #ffffff; + border: 1px solid #dddddd; + border-bottom-color: transparent; + cursor: default; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + text-align: center; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } +} +.nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #dddddd; + margin-right: 0; +} +.nav-tabs.nav-justified > .active > a { + border-bottom-color: #ffffff; +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 5px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #ffffff; + background-color: #74ab50; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + text-align: center; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + border-bottom: 1px solid #dddddd; + margin-right: 0; +} +.nav-tabs-justified > .active > a { + border-bottom-color: #ffffff; +} +.tabbable:before, +.tabbable:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.tabbable:after { + clear: both; +} +.tabbable:before, +.tabbable:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.tabbable:after { + clear: both; +} +.tab-content > .tab-pane, +.pill-content > .pill-pane { + display: none; +} +.tab-content > .active, +.pill-content > .active { + display: block; +} +.nav .caret { + border-top-color: #74ab50; + border-bottom-color: #74ab50; +} +.nav a:hover .caret { + border-top-color: #517738; + border-bottom-color: #517738; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.navbar { + position: relative; + z-index: 1000; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +.navbar:before, +.navbar:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.navbar:after { + clear: both; +} +.navbar:before, +.navbar:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.navbar:after { + clear: both; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +.navbar-header:before, +.navbar-header:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.navbar-header:after { + clear: both; +} +.navbar-header:before, +.navbar-header:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.navbar-header:after { + clear: both; +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + max-height: 340px; + overflow-x: visible; + padding-right: 15px; + padding-left: 15px; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + -webkit-overflow-scrolling: touch; +} +.navbar-collapse:before, +.navbar-collapse:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.navbar-collapse:after { + clear: both; +} +.navbar-collapse:before, +.navbar-collapse:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.navbar-collapse:after { + clear: both; +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-collapse .navbar-nav.navbar-left:first-child { + margin-left: -15px; + } + .navbar-collapse .navbar-nav.navbar-right:last-child { + margin-right: -15px; + } + .navbar-collapse .navbar-text:last-child { + margin-right: 0; + } +} +.container > .navbar-header, +.container > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + z-index: 1030; + top: 0; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; +} +.navbar-brand { + float: left; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + margin-right: 15px; + padding: 9px 10px; + margin-top: 8px; + margin-bottom: 8px; + background-color: transparent; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + } +} +.navbar-form { + margin-left: -15px; + margin-right: -15px; + padding: 10px 15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + margin-top: 8px; + margin-bottom: 8px; +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + border: 0; + margin-left: 0; + margin-right: 0; + padding-top: 0; + padding-bottom: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-nav.pull-right > li > .dropdown-menu, +.navbar-nav > li > .dropdown-menu.pull-right { + left: auto; + right: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-text { + float: left; + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + margin-left: 15px; + margin-right: 15px; + } +} +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #777777; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #777777; +} +.navbar-default .navbar-nav > li > a { + color: #777777; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #cccccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #dddddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #dddddd; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #cccccc; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e6e6e6; +} +.navbar-default .navbar-nav > .dropdown > a:hover .caret, +.navbar-default .navbar-nav > .dropdown > a:focus .caret { + border-top-color: #333333; + border-bottom-color: #333333; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + background-color: #e7e7e7; + color: #555555; +} +.navbar-default .navbar-nav > .open > a .caret, +.navbar-default .navbar-nav > .open > a:hover .caret, +.navbar-default .navbar-nav > .open > a:focus .caret { + border-top-color: #555555; + border-bottom-color: #555555; +} +.navbar-default .navbar-nav > .dropdown > a .caret { + border-top-color: #777777; + border-bottom-color: #777777; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #cccccc; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #777777; +} +.navbar-default .navbar-link:hover { + color: #333333; +} +.navbar-inverse { + background-color: #74ab50; + border-color: #5c8840; +} +.navbar-inverse .navbar-brand { + color: #f9faf9; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #ffffff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #f9faf9; +} +.navbar-inverse .navbar-nav > li > a { + color: #f9faf9; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #ffffff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #ffffff; + background-color: #5c8840; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444444; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #333333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #ffffff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #649345; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + background-color: #5c8840; + color: #ffffff; +} +.navbar-inverse .navbar-nav > .dropdown > a:hover .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} +.navbar-inverse .navbar-nav > .dropdown > a .caret { + border-top-color: #f9faf9; + border-bottom-color: #f9faf9; +} +.navbar-inverse .navbar-nav > .open > a .caret, +.navbar-inverse .navbar-nav > .open > a:hover .caret, +.navbar-inverse .navbar-nav > .open > a:focus .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #5c8840; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #f9faf9; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #ffffff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #ffffff; + background-color: #5c8840; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444444; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #f9faf9; +} +.navbar-inverse .navbar-link:hover { + color: #ffffff; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + content: "/\00a0"; + padding: 0 5px; + color: #cccccc; +} +.breadcrumb > .active { + color: #f9faf9; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + line-height: 1.428571429; + text-decoration: none; + background-color: #ffffff; + border: 1px solid #dddddd; + margin-left: -1px; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + background-color: #bac9c2; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 2; + color: #ffffff; + background-color: #74ab50; + border-color: #74ab50; + cursor: default; +} +.pagination > .disabled > span, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #f9faf9; + background-color: #ffffff; + border-color: #dddddd; + cursor: not-allowed; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-bottom-left-radius: 6px; + border-top-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-bottom-right-radius: 6px; + border-top-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + list-style: none; + text-align: center; +} +.pager:before, +.pager:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.pager:after { + clear: both; +} +.pager:before, +.pager:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.pager:after { + clear: both; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #bac9c2; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #f9faf9; + background-color: #ffffff; + cursor: not-allowed; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #ffffff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +.label[href]:hover, +.label[href]:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} + +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #dde3dd; +} +.label-primary { + background-color: #74ab50; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #5c8840; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + color: #ffffff; + line-height: 1; + vertical-align: baseline; + white-space: nowrap; + text-align: center; + background-color: #f9faf9; + border-radius: 10px; +} +.badge:empty { + display: none; +} +a.badge:hover, +a.badge:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} +.btn .badge { + position: relative; + top: -1px; +} +a.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #74ab50; + background-color: #ffffff; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding: 30px; + margin-bottom: 30px; + font-size: 21px; + font-weight: 200; + line-height: 2.1428571435; + color: inherit; + background-color: #bac9c2; +} +.jumbotron h1 { + line-height: 1; + color: inherit; +} +.jumbotron p { + line-height: 1.4; +} +.container .jumbotron { + border-radius: 6px; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron { + padding-left: 60px; + padding-right: 60px; + } + .jumbotron h1 { + font-size: 63px; + } +} +.thumbnail { + padding: 4px; + line-height: 1.428571429; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 4px; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + display: inline-block; + max-width: 100%; + height: auto; + display: block; +} +.thumbnail > img { + display: block; + max-width: 100%; + height: auto; +} +a.thumbnail:hover, +a.thumbnail:focus { + border-color: #74ab50; +} +.thumbnail > img { + margin-left: auto; + margin-right: auto; +} +.thumbnail .caption { + padding: 9px; + color: #77777a; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable { + padding-right: 35px; +} +.alert-dismissable .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + background-color: #dff0d8; + border-color: #d6e9c6; + color: #468847; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #356635; +} +.alert-info { + background-color: #d9edf7; + border-color: #bce8f1; + color: #3a87ad; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #2d6987; +} +.alert-warning { + background-color: #fcf8e3; + border-color: #fbeed5; + color: #c09853; +} +.alert-warning hr { + border-top-color: #f8e5be; +} +.alert-warning .alert-link { + color: #a47e3c; +} +.alert-danger { + background-color: #f2dede; + border-color: #eed3d7; + color: #b94a48; +} +.alert-danger hr { + border-top-color: #e6c1c7; +} +.alert-danger .alert-link { + color: #953b39; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-moz-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-o-keyframes progress-bar-stripes { + from { + background-position: 0 0; + } + to { + background-position: 40px 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + overflow: hidden; + height: 20px; + margin-bottom: 20px; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: 12px; + color: #ffffff; + text-align: center; + background-color: #74ab50; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-transition: width 0.6s ease; + transition: width 0.6s ease; +} +.progress-striped .progress-bar { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 40px 40px; +} +.progress.active .progress-bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -moz-animation: progress-bar-stripes 2s linear infinite; + -ms-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.media, +.media-body { + overflow: hidden; + zoom: 1; +} +.media, +.media .media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media-object { + display: block; +} +.media-heading { + margin: 0 0 5px; +} +.media > .pull-left { + margin-right: 10px; +} +.media > .pull-right { + margin-left: 10px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + margin-bottom: 20px; + padding-left: 0; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #ffffff; + border: 1px solid #dddddd; +} +.list-group-item:first-child { + border-top-right-radius: 4px; + border-top-left-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +a.list-group-item { + color: #555555; +} +a.list-group-item .list-group-item-heading { + color: #333333; +} +a.list-group-item:hover, +a.list-group-item:focus { + text-decoration: none; + background-color: #f5f5f5; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #ffffff; + background-color: #74ab50; + border-color: #74ab50; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #e1edda; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #ffffff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); +} +.panel-body { + padding: 15px; +} +.panel-body:before, +.panel-body:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.panel-body:after { + clear: both; +} +.panel-body:before, +.panel-body:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.panel-body:after { + clear: both; +} +.panel > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item { + border-width: 1px 0; +} +.panel > .list-group .list-group-item:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.panel > .list-group .list-group-item:last-child { + border-bottom: 0; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.panel > .table { + margin-bottom: 0; +} +.panel > .panel-body + .table { + border-top: 1px solid #77777a; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; +} +.panel-title > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #dddddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; + overflow: hidden; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse .panel-body { + border-top: 1px solid #dddddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #dddddd; +} +.panel-default { + border-color: #dddddd; +} +.panel-default > .panel-heading { + color: #77777a; + background-color: #f5f5f5; + border-color: #dddddd; +} +.panel-default > .panel-heading + .panel-collapse .panel-body { + border-top-color: #dddddd; +} +.panel-default > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #dddddd; +} +.panel-primary { + border-color: #74ab50; +} +.panel-primary > .panel-heading { + color: #ffffff; + background-color: #74ab50; + border-color: #74ab50; +} +.panel-primary > .panel-heading + .panel-collapse .panel-body { + border-top-color: #74ab50; +} +.panel-primary > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #74ab50; +} +.panel-success { + border-color: #d6e9c6; +} +.panel-success > .panel-heading { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.panel-success > .panel-heading + .panel-collapse .panel-body { + border-top-color: #d6e9c6; +} +.panel-success > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #d6e9c6; +} +.panel-warning { + border-color: #fbeed5; +} +.panel-warning > .panel-heading { + color: #c09853; + background-color: #fcf8e3; + border-color: #fbeed5; +} +.panel-warning > .panel-heading + .panel-collapse .panel-body { + border-top-color: #fbeed5; +} +.panel-warning > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #fbeed5; +} +.panel-danger { + border-color: #eed3d7; +} +.panel-danger > .panel-heading { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; +} +.panel-danger > .panel-heading + .panel-collapse .panel-body { + border-top-color: #eed3d7; +} +.panel-danger > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #eed3d7; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #bce8f1; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000000; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.2; + filter: alpha(opacity=20); +} +.close:hover, +.close:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.5; + filter: alpha(opacity=50); +} +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} +.modal-open { + overflow: hidden; +} +body.modal-open, +.modal-open .navbar-fixed-top, +.modal-open .navbar-fixed-bottom { + margin-right: 15px; +} +.modal { + display: none; + overflow: auto; + overflow-y: scroll; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; +} +.modal.fade .modal-dialog { + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + transform: translate(0, -25%); + -webkit-transition: -webkit-transform 0.3s ease-out; + -moz-transition: -moz-transform 0.3s ease-out; + -o-transition: -o-transform 0.3s ease-out; + transition: transform 0.3s ease-out; +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-dialog { + margin-left: auto; + margin-right: auto; + width: auto; + padding: 10px; + z-index: 1050; +} +.modal-content { + position: relative; + background-color: #ffffff; + border: 1px solid #999999; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + background-clip: padding-box; + outline: none; +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; + background-color: #000000; +} +.modal-backdrop.fade { + opacity: 0; + filter: alpha(opacity=0); +} +.modal-backdrop.in { + opacity: 0.5; + filter: alpha(opacity=50); +} +.modal-header { + padding: 15px; + border-bottom: 1px solid #e5e5e5; + min-height: 16.428571429px; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.428571429; +} +.modal-body { + position: relative; + padding: 20px; +} +.modal-footer { + margin-top: 15px; + padding: 19px 20px 20px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer:before, +.modal-footer:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.modal-footer:after { + clear: both; +} +.modal-footer:before, +.modal-footer:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.modal-footer:after { + clear: both; +} +.modal-footer .btn + .btn { + margin-left: 5px; + margin-bottom: 0; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +@media screen and (min-width: 768px) { + .modal-dialog { + left: 50%; + right: auto; + width: 600px; + padding-top: 30px; + padding-bottom: 30px; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + } +} +.tooltip { + position: absolute; + z-index: 1030; + display: block; + visibility: visible; + font-size: 12px; + line-height: 1.4; + opacity: 0; + filter: alpha(opacity=0); +} +.tooltip.in { + opacity: 0.9; + filter: alpha(opacity=90); +} +.tooltip.top { + margin-top: -3px; + padding: 5px 0; +} +.tooltip.right { + margin-left: 3px; + padding: 0 5px; +} +.tooltip.bottom { + margin-top: 3px; + padding: 5px 0; +} +.tooltip.left { + margin-left: -3px; + padding: 0 5px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #ffffff; + text-align: center; + text-decoration: none; + background-color: #000000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.top-left .tooltip-arrow { + bottom: 0; + left: 5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.top-right .tooltip-arrow { + bottom: 0; + right: 5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + left: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + right: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1010; + display: none; + max-width: 276px; + padding: 1px; + text-align: left; + background-color: #ffffff; + background-clip: padding-box; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + white-space: normal; +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + margin: 0; + padding: 8px 14px; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover .arrow, +.popover .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover .arrow { + border-width: 11px; +} +.popover .arrow:after { + border-width: 10px; + content: ""; +} +.popover.top .arrow { + left: 50%; + margin-left: -11px; + border-bottom-width: 0; + border-top-color: #999999; + border-top-color: rgba(0, 0, 0, 0.25); + bottom: -11px; +} +.popover.top .arrow:after { + content: " "; + bottom: 1px; + margin-left: -10px; + border-bottom-width: 0; + border-top-color: #ffffff; +} +.popover.right .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-left-width: 0; + border-right-color: #999999; + border-right-color: rgba(0, 0, 0, 0.25); +} +.popover.right .arrow:after { + content: " "; + left: 1px; + bottom: -10px; + border-left-width: 0; + border-right-color: #ffffff; +} +.popover.bottom .arrow { + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999999; + border-bottom-color: rgba(0, 0, 0, 0.25); + top: -11px; +} +.popover.bottom .arrow:after { + content: " "; + top: 1px; + margin-left: -10px; + border-top-width: 0; + border-bottom-color: #ffffff; +} +.popover.left .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999999; + border-left-color: rgba(0, 0, 0, 0.25); +} +.popover.left .arrow:after { + content: " "; + right: 1px; + border-right-width: 0; + border-left-color: #ffffff; + bottom: -10px; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + overflow: hidden; + width: 100%; +} +.carousel-inner > .item { + display: none; + position: relative; + -webkit-transition: 0.6s ease-in-out left; + transition: 0.6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; + line-height: 1; +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 15%; + opacity: 0.5; + filter: alpha(opacity=50); + font-size: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); +} +.carousel-control.left { + background-image: -webkit-gradient(linear, 0% top, 100% top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001))); + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0%), color-stop(rgba(0, 0, 0, 0.0001) 100%)); + background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); +} +.carousel-control.right { + left: auto; + right: 0; + background-image: -webkit-gradient(linear, 0% top, 100% top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5))); + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0%), color-stop(rgba(0, 0, 0, 0.5) 100%)); + background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); +} +.carousel-control:hover, +.carousel-control:focus { + color: #ffffff; + text-decoration: none; + opacity: 0.9; + filter: alpha(opacity=90); +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + left: 50%; + z-index: 5; + display: inline-block; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + margin-left: -10px; + font-family: serif; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + margin-left: -30%; + padding-left: 0; + list-style: none; + text-align: center; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + border: 1px solid #ffffff; + border-radius: 10px; + cursor: pointer; +} +.carousel-indicators .active { + margin: 0; + width: 12px; + height: 12px; + background-color: #ffffff; +} +.carousel-caption { + position: absolute; + left: 15%; + right: 15%; + bottom: 20px; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + margin-left: -15px; + font-size: 30px; + } + .carousel-caption { + left: 20%; + right: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after { + content: " "; + /* 1 */ + + display: table; + /* 2 */ + +} +.clearfix:after { + clear: both; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +@media screen and (max-width: 400px) { + @-ms-viewport { + width: 320px; + } +} +.hidden { + display: none !important; + visibility: hidden !important; +} +.visible-xs { + display: none !important; +} +tr.visible-xs { + display: none !important; +} +th.visible-xs, +td.visible-xs { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-xs.visible-sm { + display: block !important; + } + tr.visible-xs.visible-sm { + display: table-row !important; + } + th.visible-xs.visible-sm, + td.visible-xs.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-xs.visible-md { + display: block !important; + } + tr.visible-xs.visible-md { + display: table-row !important; + } + th.visible-xs.visible-md, + td.visible-xs.visible-md { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-xs.visible-lg { + display: block !important; + } + tr.visible-xs.visible-lg { + display: table-row !important; + } + th.visible-xs.visible-lg, + td.visible-xs.visible-lg { + display: table-cell !important; + } +} +.visible-sm { + display: none !important; +} +tr.visible-sm { + display: none !important; +} +th.visible-sm, +td.visible-sm { + display: none !important; +} +@media (max-width: 767px) { + .visible-sm.visible-xs { + display: block !important; + } + tr.visible-sm.visible-xs { + display: table-row !important; + } + th.visible-sm.visible-xs, + td.visible-sm.visible-xs { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-sm.visible-md { + display: block !important; + } + tr.visible-sm.visible-md { + display: table-row !important; + } + th.visible-sm.visible-md, + td.visible-sm.visible-md { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-sm.visible-lg { + display: block !important; + } + tr.visible-sm.visible-lg { + display: table-row !important; + } + th.visible-sm.visible-lg, + td.visible-sm.visible-lg { + display: table-cell !important; + } +} +.visible-md { + display: none !important; +} +tr.visible-md { + display: none !important; +} +th.visible-md, +td.visible-md { + display: none !important; +} +@media (max-width: 767px) { + .visible-md.visible-xs { + display: block !important; + } + tr.visible-md.visible-xs { + display: table-row !important; + } + th.visible-md.visible-xs, + td.visible-md.visible-xs { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-md.visible-sm { + display: block !important; + } + tr.visible-md.visible-sm { + display: table-row !important; + } + th.visible-md.visible-sm, + td.visible-md.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-md.visible-lg { + display: block !important; + } + tr.visible-md.visible-lg { + display: table-row !important; + } + th.visible-md.visible-lg, + td.visible-md.visible-lg { + display: table-cell !important; + } +} +.visible-lg { + display: none !important; +} +tr.visible-lg { + display: none !important; +} +th.visible-lg, +td.visible-lg { + display: none !important; +} +@media (max-width: 767px) { + .visible-lg.visible-xs { + display: block !important; + } + tr.visible-lg.visible-xs { + display: table-row !important; + } + th.visible-lg.visible-xs, + td.visible-lg.visible-xs { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-lg.visible-sm { + display: block !important; + } + tr.visible-lg.visible-sm { + display: table-row !important; + } + th.visible-lg.visible-sm, + td.visible-lg.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-lg.visible-md { + display: block !important; + } + tr.visible-lg.visible-md { + display: table-row !important; + } + th.visible-lg.visible-md, + td.visible-lg.visible-md { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +.hidden-xs { + display: block !important; +} +tr.hidden-xs { + display: table-row !important; +} +th.hidden-xs, +td.hidden-xs { + display: table-cell !important; +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } + tr.hidden-xs { + display: none !important; + } + th.hidden-xs, + td.hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-xs.hidden-sm { + display: none !important; + } + tr.hidden-xs.hidden-sm { + display: none !important; + } + th.hidden-xs.hidden-sm, + td.hidden-xs.hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-xs.hidden-md { + display: none !important; + } + tr.hidden-xs.hidden-md { + display: none !important; + } + th.hidden-xs.hidden-md, + td.hidden-xs.hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-xs.hidden-lg { + display: none !important; + } + tr.hidden-xs.hidden-lg { + display: none !important; + } + th.hidden-xs.hidden-lg, + td.hidden-xs.hidden-lg { + display: none !important; + } +} +.hidden-sm { + display: block !important; +} +tr.hidden-sm { + display: table-row !important; +} +th.hidden-sm, +td.hidden-sm { + display: table-cell !important; +} +@media (max-width: 767px) { + .hidden-sm.hidden-xs { + display: none !important; + } + tr.hidden-sm.hidden-xs { + display: none !important; + } + th.hidden-sm.hidden-xs, + td.hidden-sm.hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } + tr.hidden-sm { + display: none !important; + } + th.hidden-sm, + td.hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-sm.hidden-md { + display: none !important; + } + tr.hidden-sm.hidden-md { + display: none !important; + } + th.hidden-sm.hidden-md, + td.hidden-sm.hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-sm.hidden-lg { + display: none !important; + } + tr.hidden-sm.hidden-lg { + display: none !important; + } + th.hidden-sm.hidden-lg, + td.hidden-sm.hidden-lg { + display: none !important; + } +} +.hidden-md { + display: block !important; +} +tr.hidden-md { + display: table-row !important; +} +th.hidden-md, +td.hidden-md { + display: table-cell !important; +} +@media (max-width: 767px) { + .hidden-md.hidden-xs { + display: none !important; + } + tr.hidden-md.hidden-xs { + display: none !important; + } + th.hidden-md.hidden-xs, + td.hidden-md.hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-md.hidden-sm { + display: none !important; + } + tr.hidden-md.hidden-sm { + display: none !important; + } + th.hidden-md.hidden-sm, + td.hidden-md.hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } + tr.hidden-md { + display: none !important; + } + th.hidden-md, + td.hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-md.hidden-lg { + display: none !important; + } + tr.hidden-md.hidden-lg { + display: none !important; + } + th.hidden-md.hidden-lg, + td.hidden-md.hidden-lg { + display: none !important; + } +} +.hidden-lg { + display: block !important; +} +tr.hidden-lg { + display: table-row !important; +} +th.hidden-lg, +td.hidden-lg { + display: table-cell !important; +} +@media (max-width: 767px) { + .hidden-lg.hidden-xs { + display: none !important; + } + tr.hidden-lg.hidden-xs { + display: none !important; + } + th.hidden-lg.hidden-xs, + td.hidden-lg.hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-lg.hidden-sm { + display: none !important; + } + tr.hidden-lg.hidden-sm { + display: none !important; + } + th.hidden-lg.hidden-sm, + td.hidden-lg.hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-lg.hidden-md { + display: none !important; + } + tr.hidden-lg.hidden-md { + display: none !important; + } + th.hidden-lg.hidden-md, + td.hidden-lg.hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } + tr.hidden-lg { + display: none !important; + } + th.hidden-lg, + td.hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +tr.visible-print { + display: none !important; +} +th.visible-print, +td.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } + .hidden-print { + display: none !important; + } + tr.hidden-print { + display: none !important; + } + th.hidden-print, + td.hidden-print { + display: none !important; + } +} diff --git a/docs/css/modern-business.css b/docs/css/modern-business.css new file mode 100755 index 000000000..58dc4a2d6 --- /dev/null +++ b/docs/css/modern-business.css @@ -0,0 +1,93 @@ +/*! + * Start Bootstrap - Modern Business HTML Template (http://startbootstrap.com) + * Code licensed under the Apache License v2.0. + * For details, see http://www.apache.org/licenses/LICENSE-2.0. + */ + +/* Global Styles */ + +html, +body { + height: 100%; +} + +body { + padding-top: 50px; /* Required padding for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */ +} + +.img-portfolio { + margin-bottom: 30px; +} + +.img-hover:hover { + opacity: 0.8; +} + +/* Home Page Carousel */ + +header.carousel { + height: 50%; +} + +header.carousel .item, +header.carousel .item.active, +header.carousel .carousel-inner { + height: 100%; +} + +header.carousel .fill { + width: 100%; + height: 100%; + background-position: center; + background-size: cover; +} + +/* 404 Page Styles */ + +.error-404 { + font-size: 100px; +} + +/* Pricing Page Styles */ + +.price { + display: block; + font-size: 50px; + line-height: 50px; +} + +.price sup { + top: -20px; + left: 2px; + font-size: 20px; +} + +.period { + display: block; + font-style: italic; +} + +/* Footer Styles */ + +footer { + margin: 50px 0; +} + +/* Responsive Styles */ + +@media(max-width:991px) { + .client-img, + .img-related { + margin-bottom: 30px; + } +} + +@media(max-width:767px) { + .img-portfolio { + margin-bottom: 15px; + } + + header.carousel .carousel { + height: 70%; + } +} \ No newline at end of file diff --git a/docs/css/printstyles.css b/docs/css/printstyles.css new file mode 100644 index 000000000..5e55e5765 --- /dev/null +++ b/docs/css/printstyles.css @@ -0,0 +1,160 @@ + +/*body.print .container {max-width: 650px;}*/ + +body { + font-size:14px; +} +.nav ul li a {border-top:0px; background-color:transparent; color: #808080; } +#navig a[href] {color: #595959 !important;} +table .table {max-width:650px;} + +#navig li.sectionHead {font-weight: bold; font-size: 18px; color: #595959 !important; } +#navig li {font-weight: normal; } + +#navig a[href]::after { content: leader(".") target-counter(attr(href), page); } + +a[href]::after { + content: " (page " target-counter(attr(href), page) ")" +} + +a[href^="http:"]::after, a[href^="https:"]::after { + content: " (" attr(href) ")"; +} + +a[href] { + color: blue !important; +} +a[href*="mailto"]::after, a[data-toggle="tooltip"]::after, a[href].noCrossRef::after { + content: ""; +} + + +@page { + margin: 60pt 90pt 60pt 90pt; + font-family: sans-serif; + font-style:none; + color: gray; + +} + +.printTitle { + line-height:30pt; + font-size:27pt; + font-weight: bold; + letter-spacing: -.5px; + margin-bottom:25px; +} + +.printSubtitle { + font-size: 19pt; + color: #cccccc !important; + front-family: "Grotesque MT Light"; + line-height: 22pt; + letter-spacing: -.5px; + margin-bottom:20px; +} +.printTitleArea hr { + color: #999999 !important; + height: 2px; + width: 100%; +} + +.printTitleImage { + max-width:300px; + margin-bottom:200px; +} + + +.printTitleImage { + max-width: 250px; +} + +#navig { + /*page-break-before: always;*/ +} + +.copyrightBoilerplate { + page-break-before:always; + font-size:14px; +} + +.lastGeneratedDate { + font-style: italic; + font-size:14px; + color: gray; +} + +.alert a { + text-decoration: none !important; +} + + +body.title { page: title } + +@page title { + @top-left { + content: " "; + } + @top-right { + content: " " + } + @bottom-right { + content: " "; + } + @bottom-left { + content: " "; + } +} + +body.frontmatter { page: frontmatter } +body.frontmatter {counter-reset: page 1} + + +@page frontmatter { + @top-left { + content: prince-script(guideName); + } + @top-right { + content: prince-script(datestamp); + } + @bottom-right { + content: counter(page, lower-roman); + } + @bottom-left { + content: "youremail@domain.com"; } +} + +body.first_page {counter-reset: page 1} + +h1 { string-set: doctitle content() } + +@page { + @top-left { + content: string(doctitle); + font-size: 11px; + font-style: italic; + } + @top-right { + content: prince-script(datestamp); + font-size: 11px; + } + + @bottom-right { + content: "Page " counter(page); + font-size: 11px; + } + @bottom-left { + content: prince-script(guideName); + font-size: 11px; + } +} +.alert { + background-color: #fafafa !important; + border-color: #dedede !important; + color: black; +} + +pre { + background-color: #fafafa; +} + diff --git a/docs/css/syntax.css b/docs/css/syntax.css new file mode 100644 index 000000000..1e651cf79 --- /dev/null +++ b/docs/css/syntax.css @@ -0,0 +1,60 @@ +.highlight { background: #ffffff; } +.highlight .c { color: #999988; font-style: italic } /* Comment */ +.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.highlight .k { font-weight: bold } /* Keyword */ +.highlight .o { font-weight: bold } /* Operator */ +.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ +.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ +.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #aa0000 } /* Generic.Error */ +.highlight .gh { color: #999999 } /* Generic.Heading */ +.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ +.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #555555 } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #aaaaaa } /* Generic.Subheading */ +.highlight .gt { color: #aa0000 } /* Generic.Traceback */ +.highlight .kc { font-weight: bold } /* Keyword.Constant */ +.highlight .kd { font-weight: bold } /* Keyword.Declaration */ +.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #009999 } /* Literal.Number */ +.highlight .s { color: #d14 } /* Literal.String */ +.highlight .na { color: #008080 } /* Name.Attribute */ +.highlight .nb { color: #0086B3 } /* Name.Builtin */ +.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ +.highlight .no { color: #008080 } /* Name.Constant */ +.highlight .ni { color: #800080 } /* Name.Entity */ +.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ +.highlight .nn { color: #555555 } /* Name.Namespace */ +.highlight .nt { color: #000080 } /* Name.Tag */ +.highlight .nv { color: #008080 } /* Name.Variable */ +.highlight .ow { font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #009999 } /* Literal.Number.Float */ +.highlight .mh { color: #009999 } /* Literal.Number.Hex */ +.highlight .mi { color: #009999 } /* Literal.Number.Integer */ +.highlight .mo { color: #009999 } /* Literal.Number.Oct */ +.highlight .sb { color: #d14 } /* Literal.String.Backtick */ +.highlight .sc { color: #d14 } /* Literal.String.Char */ +.highlight .sd { color: #d14 } /* Literal.String.Doc */ +.highlight .s2 { color: #d14 } /* Literal.String.Double */ +.highlight .se { color: #d14 } /* Literal.String.Escape */ +.highlight .sh { color: #d14 } /* Literal.String.Heredoc */ +.highlight .si { color: #d14 } /* Literal.String.Interpol */ +.highlight .sx { color: #d14 } /* Literal.String.Other */ +.highlight .sr { color: #009926 } /* Literal.String.Regex */ +.highlight .s1 { color: #d14 } /* Literal.String.Single */ +.highlight .ss { color: #990073 } /* Literal.String.Symbol */ +.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #008080 } /* Name.Variable.Class */ +.highlight .vg { color: #008080 } /* Name.Variable.Global */ +.highlight .vi { color: #008080 } /* Name.Variable.Instance */ +.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/css/theme-blue.css b/docs/css/theme-blue.css new file mode 100644 index 000000000..64cc6c387 --- /dev/null +++ b/docs/css/theme-blue.css @@ -0,0 +1,103 @@ +.summary { + color: #808080; + border-left: 5px solid #ED1951; + font-size:16px; +} + + +h3 {color: #ED1951; } +h4 {color: #808080; } + +.nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus { + background-color: #248ec2; + color: white; +} + +.nav > li.active > a { + background-color: #347DBE; +} + +.nav > li > a:hover { + background-color: #248ec2; +} + +div.navbar-collapse .dropdown-menu > li > a:hover { + background-color: #347DBE; +} + +.nav li.thirdlevel > a { + background-color: #FAFAFA !important; + color: #248EC2; + font-weight: bold; +} + +a[data-toggle="tooltip"] { + color: #649345; + font-style: italic; + cursor: default; +} + +.navbar-inverse { + background-color: #347DBE; + border-color: #015CAE; +} + +.navbar-inverse .navbar-nav > .open > a, .navbar-inverse .navbar-nav > .open > a:hover, .navbar-inverse .navbar-nav > .open > a:focus { + color: #015CAE; +} + +.navbar-inverse .navbar-nav > .open > a, .navbar-inverse .navbar-nav > .open > a:hover, .navbar-inverse .navbar-nav > .open > a:focus { + background-color: #015CAE; + color: #ffffff; +} + +/* not sure if using this ...*/ +.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form { + border-color: #248ec2 !important; +} + +.btn-primary { + color: #ffffff; + background-color: #347DBE; + border-color: #347DBE; +} + +.navbar-inverse .navbar-nav > .active > a, .navbar-inverse .navbar-nav > .active > a:hover, .navbar-inverse .navbar-nav > .active > a:focus { + background-color: #347DBE; +} + +.btn-primary:hover, +.btn-primary:focus, +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + background-color: #248ec2; + border-color: #347DBE; +} + +.printTitle { + color: #015CAE !important; +} + +body.print h1 {color: #015CAE !important; font-size:28px !important;} +body.print h2 {color: #595959 !important; font-size:20px !important;} +body.print h3 {color: #E50E51 !important; font-size:14px !important;} +body.print h4 {color: #679DCE !important; font-size:14px; font-style: italic !important;} + +.anchorjs-link:hover { + color: #216f9b; +} + +div.sidebarTitle { + color: #015CAE; +} + +li.sidebarTitle { + margin-top:40px; + font-weight:normal; + font-size:130%; + color: #ED1951; + margin-bottom:10px; + margin-left: 5px; + +} \ No newline at end of file diff --git a/docs/css/theme-green.css b/docs/css/theme-green.css new file mode 100644 index 000000000..f6b0d8b1e --- /dev/null +++ b/docs/css/theme-green.css @@ -0,0 +1,99 @@ +.summary { + color: #808080; + border-left: 5px solid #E50E51; + font-size:16px; +} + + +h3 {color: #E50E51; } +h4 {color: #808080; } + +.nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus { + background-color: #248ec2; + color: white; +} + +.nav > li.active > a { + background-color: #72ac4a; +} + +.nav > li > a:hover { + background-color: #72ac4a; +} + +div.navbar-collapse .dropdown-menu > li > a:hover { + background-color: #72ac4a; +} + +.nav li.thirdlevel > a { + background-color: #FAFAFA !important; + color: #72ac4a; + font-weight: bold; +} + +a[data-toggle="tooltip"] { + color: #649345; + font-style: italic; + cursor: default; +} + +.navbar-inverse { + background-color: #72ac4a; + border-color: #5b893c; +} + +.navbar-inverse .navbar-nav > .open > a, .navbar-inverse .navbar-nav > .open > a:hover, .navbar-inverse .navbar-nav > .open > a:focus { + color: #5b893c; +} + +.navbar-inverse .navbar-nav > .open > a, .navbar-inverse .navbar-nav > .open > a:hover, .navbar-inverse .navbar-nav > .open > a:focus { + background-color: #5b893c; + color: #ffffff; +} + +/* not sure if using this ...*/ +.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form { + border-color: #72ac4a !important; +} + +.btn-primary { + color: #ffffff; + background-color: #5b893c; + border-color: #5b893c; +} + +.btn-primary:hover, +.btn-primary:focus, +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + background-color: #72ac4a; + border-color: #5b893c; +} + +.printTitle { + color: #5b893c !important; +} + +body.print h1 {color: #5b893c !important; font-size:28px;} +body.print h2 {color: #595959 !important; font-size:24px;} +body.print h3 {color: #E50E51 !important; font-size:14px;} +body.print h4 {color: #679DCE !important; font-size:14px; font-style: italic;} + +.anchorjs-link:hover { + color: #4f7233; +} + +div.sidebarTitle { + color: #E50E51; +} + +li.sidebarTitle { + margin-top:40px; + font-weight:normal; + font-size:130%; + color: #ED1951; + margin-bottom:10px; + margin-left: 5px; + +} \ No newline at end of file diff --git a/docs/data-model.png b/docs/data-model.png deleted file mode 100644 index 43855b542..000000000 Binary files a/docs/data-model.png and /dev/null differ diff --git a/docs/data-model.xml b/docs/data-model.xml deleted file mode 100644 index 1597bf7d9..000000000 --- a/docs/data-model.xml +++ /dev/null @@ -1 +0,0 @@ -7Zxbb6M4FIB/TV5WmgjMNY+TtLP70JWqrVa781Q54IB3DI7AmTTz69cGc3VI0iYwKEMVVfHh2Bh/to99jsnMWEVvvydwG/5JfURmQPPfZsbDDABnofH/QnDIBZbr5IIgwX4u0ivBC/6BpFDmC3bYR2lDkVFKGN42hR6NY+SxhgwmCd031TaUNO+6hQFSBC8eJKr0H+yzMJe6llbJ/0A4CIs765q8sobetyChu1jebwaMTfaXX45gUZbUT0Po031NZDzOjFVCKcu/RW8rRETTFs2W5/vScbWsd4JidlEGK8/xHZKdfPYVjSKRO68fOxRtku5xRGDMU8uQRYQLdf51Q2P2IpU0nvZCTPwneKA7cf+U8fYoUsuQJvgH14dFZn45YZI+sEVpmJAVJTThgpjm9yozvYjC5G0SlPJsz8Vz6qXoCaasqAolBG5TvM4qJ1QimAQ4XlLGaCSViqf6UrtzBc1YyuZBCUNvnW2sl+T4gEA0Qiw5cBWZARgSthwMjmzzfdWzdFOqhLVeBVwphLI3B2XRFVH+RULtAGwrgPmlNlv+cKzJNWUJ/YZaLI7ggQQHMU8StBEliIbCfBx9lmJGt6KwLfRwHDxlOg9mJflLPq0Q7UPM0AuXizrt+bTCZZSXtyHZAAmx76NYcKYMMrgue9yW4phlLWQt+Ye32UqbWzOLP9eKp/UqzT9CPWErGvPngzjjh3iP2SPRay6EbR2nLekC+0K69g3gOgrcNfUPE97+8FpgOLyGqeCFaYrY6zSC+0TsuAOOYFdFvGPc5k2Me2Wsa+ZwkIty65A9hvnjTYh7RFwsuwZBDBTEfHHLdhPhPglbA661TEMhnFd1mqn7hewOuOIy1RWXlyDIkP8K1R3xRPl2uyZtwEWXqTo9dlt/otw/ZWPAVZelKyiRH6DCWcUfNaQBjSF5rKTLzHWHfNl8DeYwYZ+Fa7Him8mE40iqo9gvNDzCd2rYy4VSRRTzH2LsID1efKFPBceyIk9U9IgjXWwGDC37K68UXkk9uwNvmX9FHeaOVaS/yot5G4gHPwONNxTdJR5qWjv+hAE6AzdBBDL8vXmDY+Rk1mfRKSv3WOk7lZ3EMFtur7wOMlfFnzc1PNTUZF/vvE/pcy0My8KqF3dWv6hXl76uWaf0+Ze8xlXnLdvwsv6sLjFzinzaWl/i88kcpJ2Tw0NtwvJEqcmRKSviE002TghcI7Isnd3v8pxKp72syaxs53qftE67VD9pcwAarf1JFv7Rzlio0M0mRWzWnmfeh8pWd/XqZHTXgLT5YsR4Fgqe334tPHz8LIA1IiSG0Zuxrkxx3V7rTXstLXplrLW+jPUbZnVbzZNfy+WDtOOlUc+uzK0LzbhqxcExK+7ewoq/1/g6RtM46tZpY2o42in9q42poYbFcr/rPRrTfHCdmKuBaZsjnq7Vhc+9WdMzhMR0bbl2E5ExJkSqc+zeLOrZQWQb5pgsKlCR/J3yJmxTmY51nLaoncTt1rEOw7rwXEcZmLjGuwFUT+Xkh77KdwU6BvhPONdhqBbPx+mWwMNrDCM0Ye4P85DnO4Dqh56iDUNAHvKEB1B3GlOwYQjIwx7xsBWUwwUbhnde1D0Xc8deyPQzSjBvOrFQb3o0ysS1Ho2CX92jkVvLm8cl7Jb/3wQ9xSVa68jiTFhnnOFKfd04E/e4St0Et3XUmKqvdkNgENyjnyafRE5sMXUTyOYYxRbTVvcg97brP4tkYerN0TgmPur68hfjkznOzJbjbEy+TdO95bqhdhJA082mzdUcpxC0zfSIzzZ8cJUA1FVC12rx4m5w8bkT9bRvRH2UwLsM1Jsd4aRygjRtqxn6GdUMqW7O7i20cBaQu9BbsfpREVJfcbo7G3aaUBard8a07nOcWxqtEQfrP2Z9nH72o7rzsf3oRwCr/ufP2QsrCvcpenS6Z3SOarMF8+KXgovTmtcsURw1NjgFj67yRXaN+Z8QPHLUXTk7bKegUY94hwwaOeqmnrdh9Dox7pfxkDEj58iPNgjG0yTdJ+FBA0aO6tfepWh67btnxEO+E+yqjqApvD8I5SHfC3Z1dSBP8f0hKPf4YjBPVr/BlW+gq985Mx7/Bw== \ No newline at end of file diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 000000000..3182c3c1c --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,69 @@ +--- +title: Frequently Asked Questions +sidebar: talk_sidebar +permalink: faq.html +summary: +--- + +### How are new stories/assets added to Talk? Is there an API? + +There are three ways that new assets can make their way into Talk: _just in time_, _active_ and manual. + +#### Just in Time asset creation + +Talk ships with a _just in time_ mechanism that works out of the box without integration with any CMS or manual work needed. + +The _just in time_ flow looks like this: + +* Request comes in for a stream on an asset that doesn't yet exist. +* Talk screens the domain against the domain whitelist, fails if doesn't pass. +* Then, concurrently + * Talk creates a new asset record and returns the stream data (which will be empty) + * Schedules a job to scrape the new page and fill in asset information. + +The scraping mechanism utilizes [metascraper](https://www.npmjs.com/package/metascraper) and is queued using the [Que](https://www.npmjs.com/package/kue). If your Talk deployments is configured to run separate job worker cluster, scraping will be performed by them. + +#### Active (or push based) asset creation + +If tighter CMS integration is required to push custom data into assets and/or keep data in sync as changes are made in a CMS an _active_ push based workflow must be implemented. + +This is an ideal candidate for a plugin. If you are interested in working on it, please [contact us](https://coralproject.net/contact.html)! + +#### Manual asset creation + +Sometimes you want to load a lot of assets into the database. The most common use case for this is populating the database during an initial installation. We recommend writing a script that transforms the data from it's source and inserts it into the _assets_ collection. + +For current schema information, please see the [asset model](https://github.com/coralproject/talk/blob/master/models/asset.js). + +### Where are your http API docs? + +Coral relies on GraphQL for the vast majority of it's client <-> server communication. All core queries, mutations and subscriptions are defined along with types and comments in our central [TypeDef](https://github.com/coralproject/talk/blob/master/graph/typeDefs.graphql). For plugin graph api typedefs, see each plugin's `/server/` directory. + +In addition, Talk Server ships with [GraphiQL](https://github.com/graphql/graphiql). GraphiQL provides a full data layer IDE including interactive documentation. The autocompletes and documentation are populated from introspection meaning that Core _and plugin_ apis will be fully explorable. + +To access GraphiQL: + +* [Install Talk](install-source.html). +* Open http://localhost:3000/api/v1/graph/iql in your browser. (Note, your server an port may differ.) + +### Where is documentation for a specific component? + +We strive for clear inline documentation across our codebase, but have gaps. Contributions to documentation would be greatly appreciated and is a great way to start contributing to the project! + +If you are considering changing a core component (aka, one that is not in a plugin), you are entering the realm of a core developer. We strongly ask that you reach out the coral team before forking and changing core code. We are glad to help talk through your product need and come up with a strategy for implementing as a plugin, or working with you to extend the plugin API for your use case. + +### How do I contribue to these docs? + +Contributions to the docs are much appreciated and a great way to get involved in the project. + +Fork the Talk repo, clone it locally (no need to go through the install from source process), then: + +``` +cd docs +docker build --no-cache -t mydocs . +docker run -v "$PWD:/src" -p 4000:4000 mydocs serve -H 0.0.0.0 +``` + +You can edit the files in docs with any editor and view the live updates in a browser by hitting `http://localhost:4000`. + +Once you've made the changes, file a PR back to the `coralproject/talk` repo. diff --git a/docs/feed.xml b/docs/feed.xml new file mode 100644 index 000000000..d8d6ac999 --- /dev/null +++ b/docs/feed.xml @@ -0,0 +1,32 @@ +--- +search: exclude +layout: none +--- + + + + + {{ site.title | xml_escape }} + {{ site.description | xml_escape }} + {{ site.url }}/ + + {{ site.time | date_to_rfc822 }} + {{ site.time | date_to_rfc822 }} + Jekyll v{{ jekyll.version }} + {% for post in site.posts limit:10 %} + + {{ post.title | xml_escape }} + {{ post.content | xml_escape }} + {{ post.date | date_to_rfc822 }} + {{ post.url | prepend: site.url }} + {{ post.url | prepend: site.url }} + {% for tag in post.tags %} + {{ tag | xml_escape }} + {% endfor %} + {% for tag in page.tags %} + {{ cat | xml_escape }} + {% endfor %} + + {% endfor %} + + diff --git a/docs/fonts/FontAwesome.otf b/docs/fonts/FontAwesome.otf new file mode 100644 index 000000000..81c9ad949 Binary files /dev/null and b/docs/fonts/FontAwesome.otf differ diff --git a/docs/fonts/fontawesome-webfont.eot b/docs/fonts/fontawesome-webfont.eot new file mode 100644 index 000000000..84677bc0c Binary files /dev/null and b/docs/fonts/fontawesome-webfont.eot differ diff --git a/docs/fonts/fontawesome-webfont.svg b/docs/fonts/fontawesome-webfont.svg new file mode 100644 index 000000000..d907b25ae --- /dev/null +++ b/docs/fonts/fontawesome-webfont.svg @@ -0,0 +1,520 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/fontawesome-webfont.ttf b/docs/fonts/fontawesome-webfont.ttf new file mode 100644 index 000000000..96a3639cd Binary files /dev/null and b/docs/fonts/fontawesome-webfont.ttf differ diff --git a/docs/fonts/fontawesome-webfont.woff b/docs/fonts/fontawesome-webfont.woff new file mode 100644 index 000000000..628b6a52a Binary files /dev/null and b/docs/fonts/fontawesome-webfont.woff differ diff --git a/docs/fonts/glyphicons-halflings-regular.eot b/docs/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 000000000..b93a4953f Binary files /dev/null and b/docs/fonts/glyphicons-halflings-regular.eot differ diff --git a/docs/fonts/glyphicons-halflings-regular.svg b/docs/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 000000000..94fb5490a --- /dev/null +++ b/docs/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/glyphicons-halflings-regular.ttf b/docs/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 000000000..1413fc609 Binary files /dev/null and b/docs/fonts/glyphicons-halflings-regular.ttf differ diff --git a/docs/fonts/glyphicons-halflings-regular.woff b/docs/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 000000000..9e612858f Binary files /dev/null and b/docs/fonts/glyphicons-halflings-regular.woff differ diff --git a/docs/fonts/glyphicons-halflings-regular.woff2 b/docs/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 000000000..64539b54c Binary files /dev/null and b/docs/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/docs/frontend/DEBUG.md b/docs/frontend/DEBUG.md deleted file mode 100644 index 36a7ac0fa..000000000 --- a/docs/frontend/DEBUG.md +++ /dev/null @@ -1,22 +0,0 @@ -# Debug -How we debug errors at Coral - -## React Debugging -For debugging React - -### React Developer Tools -Another amazing tool for debugging React Applications. You can see where the props are, and much more. - -[React Developer Tools Extension](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) - - -## Redux Debugging -For debugging Redux - -### Redux Devtool Extension -Redux Devtool is an amazing debug tool. You can easily see what' happening with the state, the payloads, and more. - -[Redux Devtool Chrome Extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en) - -[Redux Devtool Github Repo](https://github.com/zalmoxisus/redux-devtools-extension) - diff --git a/docs/frontend/IMMUTABLEJS.md b/docs/frontend/IMMUTABLEJS.md deleted file mode 100644 index 76a9323cf..000000000 --- a/docs/frontend/IMMUTABLEJS.md +++ /dev/null @@ -1,390 +0,0 @@ -# InmutableJS -InmutableJS is a library from Facebook that provides a series of inmutable data structures. They are always immutable. The reference to them can change but the data inside of them cannot which means you can build predictable and reliable state models. -We use ImmutableJS in Talk and it becomes really easy to manage Talk’s application state. [Immutable.js](https://facebook.github.io/immutable-js/) - -More about Immutable Data and React: -[React.js Conf 2015 - Immutable Data and React - YouTube](https://www.youtube.com/watch?v=I7IdS-PbEgI&feature=youtu.be) - -## Why ImmutableJS? -- __Immutable Data is faster__ -* Tracking mutation and Maintaining state is difficult -* Encourages you to think differently about how data flows through your application - -## Getting Started -ImmutableJS API is pretty expense. We will try to cover the basics and more to show its power. - -ImmutableJS provides many Persistent Immutable data structures including: `List()`, `Stack()`, `Map()`, `OrderedMap()`, `Set()`, `OrderedSet()` and `Record()`. - -We will cover the most common data structures. `Map()` , `List()` and `Record()` and also we will describe the behaviour of `Seq()` with `Range()` - - -## Map() -- [Map()](https://facebook.github.io/immutable-js/docs/#/Map) - * Read values - * [get()](https://facebook.github.io/immutable-js/docs/#/Map/get) - * [has()](https://facebook.github.io/immutable-js/docs/#/Map/has) - * [first()](https://facebook.github.io/immutable-js/docs/#/Map/first) - * [last()](https://facebook.github.io/immutable-js/docs/#/Map/last) - * Read deep values - * [getIn()](https://facebook.github.io/immutable-js/docs/#/Map/getIn) - * Change Values - - [set()](https://facebook.github.io/immutable-js/docs/#/Map/set) - * [merge()](https://facebook.github.io/immutable-js/docs/#/Map/merge) - * [update()](https://facebook.github.io/immutable-js/docs/#/Map/update) - * [clear()](https://facebook.github.io/immutable-js/docs/#/Map/clear) - * [delete()](https://facebook.github.io/immutable-js/docs/#/Map/delete) - * Change deep values - * [setIn()](https://facebook.github.io/immutable-js/docs/#/Map/getIn) - * Conversion to JavaScript types - * [toJS()](https://facebook.github.io/immutable-js/docs/#/Map/toJS) - * [toArray()](https://facebook.github.io/immutable-js/docs/#/Map/toArray) - * [toObject](https://facebook.github.io/immutable-js/docs/#/Map/toObject) - * Member - * [size](https://facebook.github.io/immutable-js/docs/#/Map/size) - - -Creates a new Immutable Map. An Object graph. [Map - Immutable.js](https://facebook.github.io/immutable-js/docs/#/Map) - -```js - -const data = { - ‘one’: { - title: ‘One’, - value: 1 - }, - ‘two’: { - title: ‘Two’, - value: 2 - } -} - -let map = Inmutable.Map(data) -``` - -### get() -Returns the value associated with the provided key, Since inmutable data cannot be mutated they create a new reference to the new data. -[get() - Immutable.js](https://facebook.github.io/immutable-js/docs/#/Map/get) - -```js -map.get(‘one’).title -``` - - -```js -let obj = { 1: “one” }; -Object.keys(obj); // [ “1” ] -obj[“1”]; // “one” -obj[1]; // “one” - -let map = Map(obj); -map.get(“1”); // “one” -map.get(1); // undefined -``` - -### getIn() -To get data from a deeply nested structure. -[getIn() - Immutable.js](https://facebook.github.io/immutable-js/docs/#/Map/getIn) - -*With a Map()* -```js - -let map = Inmutable.Map({ - title: ‘Todo One’, - text: ‘Do todo’ - category: { - title: ‘Some category’, - order: 1 - } -}) - -map.getIn([‘category’, ‘title’]) // ‘Some Category’ - -``` - -### length - size -To get the size of a Map() or a List() -```js -map.size -``` - -### set() -```js -map.set(‘three’, {title: ‘three’, value: 3}) -``` - -### delete() -```js -map.delete(‘three’, {title: ‘three’, value: 3}) -``` - -### update() -```js -map.update(‘one’, item => ‘’) -``` - -### clear() -Returns a new Map containing no keys or values. - -```js -map.clear() -``` - -### merge() -Returns a new Map resulting from merging the provided iterables. -```js - -let mapX = Inmutable.Map({a: 10, b: 20, c: 30}) -let mapY = Inmutable.Map({a: 10, b: 20, c: 30}) - -mapX.merge(mapY) // { a: 50, b: 40, c: 30, d: 60 } -``` - -### Querying Methods - -#### has -Returns a boolean if it finds the id key - -```js -map.has(item.id) -``` - -#### first -Returns the first element of a Map -```js -map.first() -``` - -### Iteration Methods -We can use methods like `.filter`, `.map`, `.reduce` . However it’s not recommended to use `.forEach` since it can mutate the data producing side effects. - -#### groupBy - -Returns the first element of a Map -```js -items.groupBy(item => { - return todo.completed -}); -``` - -### Working with Subsets of a Map() - -#### slice() -Returns the last two items of a Map() -slice(, ) -```js -items.slice(items.size-2, todos.size); -``` - -#### takeLast() -Returns the last two items of a Map() -```js -items.takeLast(2); -``` - -#### butLast() -Returns the last item -```js -items.butLast(); -``` - -#### rest() -```js -items.rest(); -``` - -#### skip() -Returns a Map() skipping the first 5 items -```js -items.skip(5); -``` - -#### skipUntil() -Returns a Map() skipping until it finds the value -```js -items.skipUntil(item => item.value === 1); -``` - -#### skipWhile() -Returns a Map() up until it finds 1 included. -```js -items.skipWhile(item => item.value === 1); -``` - -### Equality Methods - -#### is() -```js -let mapX = Inmutable.Map({a: 10, b: 20, c: 30}) -let mapY = Inmutable.Map({a: 10, b: 20, c: 30}) - -Immutable.is(mapX, mapY); // true -``` - -### FromJS - -#### Object to Map() -Creates deeply nested Map() from a plain Javascript Object - -```js -let object = {a: 10, b: 20, c: 30}; - -Immutable.fromJS(object); // Map() -``` - - -#### Array to List() -Creates List() from a JS Array - -```js -let array = [10,20,30]; - -Immutable.fromJS(object); // List() -``` - -#### Usage of the reviver function -The reviver function takes a key and a value. Converting JS to Map() or List() - -```js -let array = [10,20,30]; - -Immutable.fromJS(array, (key, value) => { - return value.toMap(); -}); // Map() -``` - -*Note: the getIn will be index based instead of object based if it comes from an array* - -### List() - -Most of the __Map()__ methods can be used with __List()__ -But there are some differences. - -### Differences between the Immutable Map() and List() -List() have the same methods that a JS Array has. But instead of mutating the array it returns a new one. - -Usually we wouldn’t use the push method in immutable data structures but with Immutable.List()s push methods are safe to be used. - -```js -let list = Immutable.List() -list.push(3) -list.toArray() // [3] -``` - -#### get() and getIn() -The get method with Map() is _key_ based and with List() is _index_ based. - -```js -// get() -let list = Immutable.List(); -list.push(3); -list.get(0); // 3 - -let map = Immutable.Map(); -list.set('active', true); -list.get('active'); // true - -// getIn() -let map = Inmutable.List([10, 20, 30, [40, 50]]) -map.getIn([3, 1]) // 50 -``` - -#### of() -We can create a __List()__ by using the _of_ method - -```js -const items = []; -const list = Immutable.List.of('red', 'green', 'blue'); -``` - -*Using the spread operator:* -```js -const items = ['red', 'green', 'blue']; -const list = Immutable.List.of(...items); -``` - -### Sequences -Represents a sequence of values. [Seq() - Immutable.js](https://facebook.github.io/immutable-js/docs/#/Seq) - -- Sequences are immutable — Once a sequence is created, it cannot be changed. -- Sequences are Lazy - -Creating sequences with _of()_ -```js -let range = [0, 1, 2 ... 999] -let sequence = Immutable.Seq.of(...range) -``` - -For Example: the following performs no work, because the resulting of the sequence values are never iterated: - -```js - -let operations = 0; - -let squared = sequence.map(num => { - operations++; - return num * num; -}) -operations; // 0 - -// Now using the sequence -squared.take(10).toArray(); -operations; // 10 - -``` - -Once the sequence is used, it performs only the work necessary. It will return it only when you ask for them. - -This is really powerful because it doesn’t produce an overflow with infinite an infinite range. - -```js -let squaredRange = Immutable.Range(1, Infinity); - -squaredRange.size; // Infinity - -first1000squared = squaredRange - .take(1000) - .map(n => n * n); - -first1000squared.size; // 1000 -``` - -__Seq()__ allows for the efficient chaining of operations - -```js -let squaredOdds = Immutable.Range(0, Infinity) - .filter(n => n % 2 !== 0) - .map(n => n * n) - .take(1000); - -console.log( - squaredOdds.toArray() -) - -``` - -You can fin this example here: [Sequences - JS Bin](http://jsbin.com/nilekuj/edit?js,console) - -[image:12FACC54-0BAF-4C93-A782-F77DB7CD04D3-813-00001ABD60F45CC4/Screen Shot 2016-12-22 at 8.23.33 AM.png] - -## Memoization with Immutable JS -Immutable JS provides advanced memoization. - -```js -const seq = Immutable.Range(1, Infinity) - .map(n => ({ - value: n - })) - -console.time(‘First Run’); -seq.take(1000); -console.timeEnd(‘First Run’); // First Run: 0.577ms - -console.time(‘Second Run’); -seq.take(1000); -console.timeEnd(‘Second Run’); // Second Run: 0.165ms -``` - - -### Play with Immutable JS -[JS Bin - Collaborative JavaScript Debugging](http://jsbin.com/nilekuj/edit?js,console) - diff --git a/docs/frontend/REDUX.md b/docs/frontend/REDUX.md deleted file mode 100644 index f82d88520..000000000 --- a/docs/frontend/REDUX.md +++ /dev/null @@ -1,223 +0,0 @@ -# Redux -Redux is a predictable state container for JavaScript apps. - -To understand Redux we need to dive into a few concepts. - -- [Actions](#actions) -- [Action Creators](#actions) -- [Action Types](#actions) -- [Reducers](#reducers) -- [Stores](#store) - -## The three principles -These are the three principles to build Redux applications. The following are specified in the Redux Documentation [Three Principles · Redux](http://redux.js.org/docs/introduction/ThreePrinciples.html) - -### Single source of truth -The state of your whole application is stored in an object tree within a single store. We are going to represent the whole state of our application in a single Javascript Object. - -### State is read-only -The only way to change the state is to emit an action, an object describing what happened. - -### Changes are made with pure functions -To specify how the state tree is transformed by actions, you write pure reducers. - -## Actions -Actions describe that something happened in our application. They are payloads of information that send data to your store. __They are the only source of information for the store.__ - -Here is an example: -```js -const ADD_COMMENT = 'ADD_COMMENT'; - -{ - type: ADD_COMMENT, - comment: 'This is my comment.' -} -``` - -Actions are JavaScript objects. Every action must have a `type` property that indicates the type of action being performed. Types should be defined as constants. - -Once an app becomes big enough, you may want to move them into a separate module. We store them in a `contants.js` file. [auth.js Constants](https://github.com/coralproject/talk/blob/153193959cb4dfa5d8feaabb49811325f836ee68/client/coral-framework/constants/auth.js) - -```js -import { ADD_COMMENT, REMOVE_COMMENT } from './constants' -``` - -We can dispatch an action by using `dispatch()`. - -Our actions live within the `coral-framework/actions` folder. [talk/client/coral-framework/actions](https://github.com/coralproject/talk/tree/153193959cb4dfa5d8feaabb49811325f836ee68/client/coral-framework/actions) - -More about Actions: [Actions · Redux](http://redux.js.org/docs/basics/Actions.html) - -### Async Actions -For our async operations we dispatch three actions. - -- `_REQUEST` - -- `_SUCCESS` - -- `_FAILURE` - -#### Request -We use the postfix `_REQUEST` to know that the resource is being requested. - -#### Success -We use the postfix `_SUCCESS` to know that the resource response came back successfully. - -#### Failure -We use the postfix `_FAILURE` to know that the resource request failed. - -## Action Creators -Action Creators are functions that return actions. This makes it easier to use, portable and testable. - -```js -function addComment(comment) { - return { - type: ADD_COMMENT, - comment - } -} -``` - -So we can later trigger those actions by using `dispatch()` - -```js -dispatch(addComment(comment)) -dispatch(removeComment(comment.id)) -``` - - -## Dispatch Function -The `dispatch()` function can be accessed directly from the store as `store.dispatch()`, but more likely you'll access it using a helper like react-redux's`connect()`. - -We use `connect()`in our containers. More about this in Architecture. - -## Reducers -With Actions we describe that something happened in our application. But we don’t specify how our state will be modified with this change. - -In a Reducer we will specify how the state of our application change when an action has been dispatched. - -Here we also will want to specify the `initialState` - -Before building reducers it’s important to that you: - - Don’t mutate the state - - Return the previous state in the default case. - -Here is an example of an auth reducer: -```js -const initialState = { - isLoading: false, - loggedIn: false, - user: null, - error: '' -}; - -function auth (state = initialState, action) { - switch (action.type) { - case actions.CHECK_LOGIN_REQUEST: - return Object.assign({}, state, { - isLoading: true - }); - case actions.CHECK_LOGIN_SUCCESS: - return Object.assign({}, state, { - isLoading: false, - loggedIn: true, - user: action.user, - error: '' - }); - case actions.CHECK_LOGIN_FAILURE: - return Object.assign({}, state, { - isLoading: false, - error: action.error, - loggedIn: false, - user: null - }); - default: - return state - } -} -``` - -Notice that a reducer takes the `state` as first argument and when it’s not defined it returns the `initialState`. As a second argument it takes the `action`. We have our state and we have the action. This is the time to specify how we modify the state. - -### Reducers using ImmutableJS -We are using ImmutableJS to maintain our app state. Here is a guide on how to use ImmutableJS. - -This is how a simplified version of our [auth reducer](https://github.com/coralproject/talk/blob/153193959cb4dfa5d8feaabb49811325f836ee68/client/coral-framework/reducers/auth.js) looks like: -```js -const initialState = Map({ - isLoading: false, - loggedIn: false, - user: null, - error: ‘’ -}); - -function auth (state = initialState, action) { - switch (action.type) { - case CHECK_LOGIN_REQUEST: - return state - .set('isLoading', true); - case CHECK_LOGIN_SUCCESS: - return state - .set('isLoading', false) - .set('loggedIn', true) - .set('user', action.user) - .set('error', ''); - }); - case CHECK_LOGIN_FAILURE: - return state - .set('isLoading', false) - .set('error', action.error) - .set('loggedIn', false) - .set('user', null) - }); - default: - return state - } -} -``` - -Looks cleaner, right? - -It’s pretty easy to follow. Here it says if a `CHECK_LOGIN_REQUEST` action has been dispatched set the `isLoading` from our state to `true`. And we can show a tiny loader to let the user now we are requesting something to the server. - -Our actions live within the `coral-framework/reducers` folder. [talk/client/coral-framework/reducers ](https://github.com/coralproject/talk/tree/153193959cb4dfa5d8feaabb49811325f836ee68/client/coral-framework/reducers) - -More about Reducers: [Reducers · Redux](http://redux.js.org/docs/basics/Reducers.html) - -And the last thing we need to see is the __Store__ - -### Store - The `Store` is what holds the application state. Here we can access and update the state. - -It’s important to note that we will only have a single store in our application called `rootReducer` and we will use reducer composition instead of many stores. - -Here is an example of how create a store with [createStore()](http://redux.js.org/docs/api/createStore.html) using a reducer: -```js -import { createStore } from 'redux' -import authReducer from './auth' -let store = createStore(authReducer) -``` - -We do have a lot of stores so we will need to combine all our reducers with [combineReducers()](http://redux.js.org/docs/api/combineReducers.html) within a single store - -```js -import {combineReducers} from 'redux'; - -import authReducer from './auth' -import configReducer from './config' -import userReducer from './user' - -const rootReducer = combineReducers({ - authReducer, - configReducer, - userReducer - ... -}); -``` - -More about Stores: [Store · Redux](http://redux.js.org/docs/basics/Store.html) - -## Useful Resources -[Redux Documentation · Redux](http://redux.js.org/) -[Getting Started with Redux](https://egghead.io/courses/getting-started-with-redux) -[Usage with React · Redux](http://redux.js.org/docs/basics/UsageWithReact.html) diff --git a/docs/frontend/TEST.md b/docs/frontend/TEST.md deleted file mode 100644 index 0ce5a47bf..000000000 --- a/docs/frontend/TEST.md +++ /dev/null @@ -1,123 +0,0 @@ -# Test -How we do testing at Coral with Talk. - -We use Nightwatch and Selenium for our E2E tests and Enzyme for our React Components. - -## E2E tests -For our E2E Test we use Nightwatch and Selenium. - -#### Selenium Server Setup - -Selenium Server is a Java application which Nightwatch uses to connect to the various browsers. - -You will need to have the Java Development Kit (JDK) installed. -[Java SE Development Kit 8 - Downloads](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) - -The minimum required version is 7. -You can check this by running `java -version` - -#### Folder Structure - -``` -e2e/ - ├── pages - | ├── adminPage.js - | └── embedStreamPage.js - ├── reports - ├── tests - | ├── Admin - | ├── Commenter - | ├── Moderator - | └── Visitor - -``` - -#### Pages -Here we will have all the selectors and commands for a Page - -#### Reports -The folder that Nightwatch will use after running the tests - -#### Tests -Within `tests` folder we have 4 Folders and a couple of files. -`Admin`, `Commenter`, `Moderator`, `Visitor` contains all the group tests based on the user role and their actions. - -## Tests -The `pree2e` script will create 3 users: a Commenter, a Moderator, and an Admin - -* Commenter - * Login - - Post a comment - * Likes a comment - * Flag a comment - * Flag a username - * Gets Permalink - * Visits Permalink - -- Moderator - * Login - -- Admin - * Login - - Approve Comment - - Reject Comment - * Ban User - -- Visitor - * Tries to like a comment - * Tries to flag a comment - - Tries to flag a username - * Signs up - -## Run the tests -Run Talk -`dotenv yarn start` - -Run e2e tests -`yarn e2e` - - -## Advanced Nightwatch and Selenium Settings - -### Adding an Integration Environment -```json -{ - … - “test_settings” : { - “default” : { - “launch_url” : “http://localhost”, - “globals” : { - “myGlobalVar” : “some value”, - “otherGlobal” : “some other value” - } - }, - “integration” : { - “launch_url” : “http://staging.host”, - “globals” : { - “myGlobalVar” : “other value” - } - } - } -} -``` - -`nightwatch —env integration` - -### Chrome Options -[List of Chromium Command Line Switches « Peter Beverloo](http://peter.sh/experiments/chromium-command-line-switches/) - -## Tags -You'll notice that each test file starts with tags. This is useful to selectively target tests to run. - -_i.e nightwatch --tag login will only run login tests tagged with login_ -```js -module.exports { - '@tags': ['login'], - 'Test': browser => { - [...] - } -} -``` - -Source: http://nightwatchjs.org/guide#test-tags - diff --git a/docs/images/communicorn.jpg b/docs/images/communicorn.jpg new file mode 100644 index 000000000..f8189600d Binary files /dev/null and b/docs/images/communicorn.jpg differ diff --git a/docs/images/company_logo.png b/docs/images/company_logo.png new file mode 100644 index 000000000..bbf0e28d9 Binary files /dev/null and b/docs/images/company_logo.png differ diff --git a/docs/images/favicon.ico b/docs/images/favicon.ico new file mode 100644 index 000000000..0d505bed3 Binary files /dev/null and b/docs/images/favicon.ico differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..7673d16eb --- /dev/null +++ b/docs/index.md @@ -0,0 +1,66 @@ +--- +title: Talk +keywords: homepage +sidebar: talk_sidebar +permalink: index.html +summary: +--- + +[![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk) + +Online comments are broken. Our open-source Talk tool rethinks how moderation, comment display, and conversation function, creating the opportunity for safer, smarter discussions around your work. [Read more about Talk here.](https://coralproject.net/products/talk.html) + +Third party licenses are available via the `/client/3rdpartylicenses.txt` +endpoint when the server is running with built assets. + +## Contributing to Talk + +See our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md)! + +## Supported Browsers + +### Web + +- Chrome: latest 2 versions +- Firefox: latest 2 versions, and most recent extended support version, if any +- Safari: latest 2 versions +- Internet Explorer: IE Edge, 11 + +### iOS Devices + +- iPad +- iPad Pro +- iPhone 6 Plus +- iPhone 6 +- iPhone 5 + +### iOS Browsers + +- Chrome for iOS: latest version +- Firefox for iOS: latest version +- Safari for iOS: latest version + +### Android Devices + +- Galaxy S5 +- Nexus 5X +- Nexus 6P + +### Android Browsers + +- Chrome for Android: latest version +- Firefox for Android: latest version + +## License + + Copyright 2017 Mozilla Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + + See the License for the specific language governing permissions and limitations under the License. diff --git a/docs/install-docker.md b/docs/install-docker.md new file mode 100644 index 000000000..2a8659a3c --- /dev/null +++ b/docs/install-docker.md @@ -0,0 +1,147 @@ +--- +title: Installation From Docker +sidebar: talk_sidebar +permalink: install-docker.html +summary: +--- + +We currently support packaging the Talk application via Docker, which automates +the dependency install and asset build process. This is the recommended way to +deploy the application when used in production. + +Available as [coralproject/talk](https://hub.docker.com/r/coralproject/talk/) on Docker Hub. + +Images are tagged using the following notation: + +- `x` (where `x` is the major version number): any minor or patch updates will be included in this. If you're ok getting + new features occasionally and all the bug fixes, this is the tag for you. +- `x.y` (where `y` is the minor version number): any patch updates will be + included with this tag. If you like getting fixes and having features change + only when you want, this is the tag for you. **(recommended)** +- `x.y.z` (where `z` is the patch version): this tag never gets updated, and + essentially freezes your version, this should only be used when you are either + extending Talk or are sure of a specific version you want to freeze. + +We provide tags with `*-onbuild` that can be used for easy plugin integration and +acts as a customization endpoint. Instructions are provided in the `PLUGINS.md` +document as to how to use it. + +### Requirements + +There are some runtime requirements for running Talk for Docker: + +- [Docker](https://www.docker.com/) v1.13.0 or later +- [Docker Compose](https://docs.docker.com/compose/) v1.10.0 or later + +_Please be sure to check the versions of these requirements. Incorrect versions +of these may lead to unexpected errors!_ + +### Installing + +An example docker-compose.yml: + +```yaml +version: '2' +services: + talk: + image: coralproject/talk:1.5 + restart: always + ports: + - "5000:5000" + depends_on: + - mongo + - redis + environment: + - TALK_MONGO_URL=mongodb://mongo/talk + - TALK_REDIS_URL=redis://redis + mongo: + image: mongo:3.2 + restart: always + volumes: + - mongo:/data/db + redis: + image: redis:3.2 + restart: always + volumes: + - redis:/data +volumes: + mongo: + external: false + redis: + external: false +``` + +At this stage, you should refer to the `README.md` for configuration variables +that are specific to your installation. Some pre-defined fields have been filled +in the above example which are consistent with Docker Compose naming conventions +for [Docker Links](https://docs.docker.com/compose/networking/#links). + +### Scaling + +If you are interested in splitting apart services, you can simply adjust the +command being executed in the container to optimize for your use case. An +example would be if you wanted to run the API server and the job processor +on different machines. You can achieve this easily with docker compose: + +```yaml +version: '2' +services: + talk-api: + image: coralproject/talk:1.5 + command: cli serve + restart: always + ports: + - "5000:5000" + depends_on: + - mongo + - redis + environment: + - TALK_MONGO_URL=mongodb://mongo/talk + - TALK_REDIS_URL=redis://redis + talk-jobs: + image: coralproject/talk:1.5 + command: cli jobs process + restart: always + ports: + - "5001:5000" + depends_on: + - mongo + - redis + environment: + - TALK_MONGO_URL=mongodb://mongo/talk + - TALK_REDIS_URL=redis://redis + mongo: + image: mongo:3.2 + restart: always + volumes: + - mongo:/data/db + redis: + image: redis:3.2 + restart: always + volumes: + - redis:/data +volumes: + mongo: + external: false + redis: + external: false +``` + +Note that the only difference is in the `command` key. From this, you are able +to discretely control which modules are running in order to have the maximum +flexibility when managing your application. + +### Running + +If you're using docker compose: + +```bash +# Start the services using compose +docker-compose up -d +``` + +If you're using plain docker: + +```bash +docker run -d -P coralproject/talk:latest +``` diff --git a/docs/install-setup.md b/docs/install-setup.md new file mode 100644 index 000000000..1332dbd97 --- /dev/null +++ b/docs/install-setup.md @@ -0,0 +1,38 @@ +--- +title: Setup +sidebar: talk_sidebar +permalink: install-setup.html +summary: +--- + +Once you've installed Talk (either via Docker or source), you still need to +setup the application. If you are unfamiliar with any terminology used in the +setup process, refer to the `TERMINOLOGY.md` document. + +## Via Web + +If you want to perform your setup via the web, you can navigate to your +installation of Talk at the path `/admin/install`. There you will be asked a +series of questions for your installation. + +## Via CLI + +If you want to perform your setup through the terminal, you can simply run: + +```bash +cli setup +``` + +And follow the instructions to perform initial setup and create your first user +account. + + +# Usage + +After setup is complete, you can then refer to the `/admin/configure` path to +get the embed code that you can copy/paste onto your blog or website in order to +start using Talk. + +_In order for the embed to work correctly, you will need to whitelist the domain +that is allowed to embed your site on the `/admin/configure` page, failure to do +so will result in the comment stream not loading._ diff --git a/docs/install-source.md b/docs/install-source.md new file mode 100644 index 000000000..6199b6ffb --- /dev/null +++ b/docs/install-source.md @@ -0,0 +1,80 @@ +--- +title: Installation From Source +sidebar: talk_sidebar +permalink: install-source.html +summary: +--- + +This provides information on how to setup the application from source. Note that +this is not recommended for production deploys, but will work for development +and testing purposes. + +## Requirements + +There are some runtime requirements for running Talk from source: + +- [Node](https://nodejs.org/) ~7.8 +- [Yarn](https://yarnpkg.com/) ^0.22.0 + +_Please be sure to check the versions of these requirements. Incorrect versions +of these may lead to unexpected errors!_ + +## Installing + +### Download + +It is highly recommended that you download a released version as the code +available in `master` may not be stable. You can download the latest release +from the [releases page](https://github.com/coralproject/talk/releases). + +You can also clone the git repository via: + +```bash +git clone https://github.com/coralproject/talk.git +``` + +### Building + +We now have to install the dependencies and build the static assets. + +```bash +# Install package dependancies +yarn + +# Build static files +yarn build +``` + +After you create/modify the `plugins.json` (refer to `PLUGINS.md` for plugin +docs) file, you can re-run the following to install their dependencies: + +```bash +# Reconcile plugins +./bin/cli plugins reconcile + +# Build static files +yarn build +``` + +## Running + +Refer to the `README.md` file for required configuration variables to add to the +environment. + +You can start the server after configuring the server using the command: + +```bash +yarn start +``` + +This will setup the server to serve everything on a single node.js process and +is designed to be used in production. + +You can see other scripts we've made available by consulting the `package.json` +file under the `scripts` key including: + +- `yarn test` run unit tests +- `yarn e2e` run end to end tests +- `yarn build-watch` watch for changes to client files and build static assets +- `yarn dev-start` watch for changes to server files and reload the server while + also sourcing a `.env` file in your local directory for configuration diff --git a/docs/js/customscripts.js b/docs/js/customscripts.js new file mode 100644 index 000000000..8c9fb4cb9 --- /dev/null +++ b/docs/js/customscripts.js @@ -0,0 +1,55 @@ + +$('#mysidebar').height($(".nav").height()); + + +$( document ).ready(function() { + + //this script says, if the height of the viewport is greater than 800px, then insert affix class, which makes the nav bar float in a fixed + // position as your scroll. if you have a lot of nav items, this height may not work for you. + var h = $(window).height(); + //console.log (h); + if (h > 800) { + $( "#mysidebar" ).attr("class", "nav affix"); + } + // activate tooltips. although this is a bootstrap js function, it must be activated this way in your theme. + $('[data-toggle="tooltip"]').tooltip({ + placement : 'top' + }); + + /** + * AnchorJS + */ + anchors.add('h2,h3,h4,h5'); + +}); + +// needed for nav tabs on pages. See Formatting > Nav tabs for more details. +// script from http://stackoverflow.com/questions/10523433/how-do-i-keep-the-current-tab-active-with-twitter-bootstrap-after-a-page-reload +$(function() { + var json, tabsState; + $('a[data-toggle="pill"], a[data-toggle="tab"]').on('shown.bs.tab', function(e) { + var href, json, parentId, tabsState; + + tabsState = localStorage.getItem("tabs-state"); + json = JSON.parse(tabsState || "{}"); + parentId = $(e.target).parents("ul.nav.nav-pills, ul.nav.nav-tabs").attr("id"); + href = $(e.target).attr('href'); + json[parentId] = href; + + return localStorage.setItem("tabs-state", JSON.stringify(json)); + }); + + tabsState = localStorage.getItem("tabs-state"); + json = JSON.parse(tabsState || "{}"); + + $.each(json, function(containerId, href) { + return $("#" + containerId + " a[href=" + href + "]").tab('show'); + }); + + $("ul.nav.nav-pills, ul.nav.nav-tabs").each(function() { + var $this = $(this); + if (!json[$this.attr("id")]) { + return $this.find("a[data-toggle=tab]:first, a[data-toggle=pill]:first").tab("show"); + } + }); +}); diff --git a/docs/js/jekyll-search.js b/docs/js/jekyll-search.js new file mode 100644 index 000000000..04d6a0d3a --- /dev/null +++ b/docs/js/jekyll-search.js @@ -0,0 +1 @@ +!function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o=0}var self=this;self.matches=function(string,crit){return"string"!=typeof string?!1:(string=string.trim(),doMatch(string,crit))}}module.exports=new LiteralSearchStrategy},{}],4:[function(require,module){module.exports=function(){function findMatches(store,crit,strategy){for(var data=store.get(),i=0;i{title}',noResultsText:"No results found",limit:10,fuzzy:!1};self.init=function(_opt){validateOptions(_opt),assignOptions(_opt),isJSON(opt.dataSource)?initWithJSON(opt.dataSource):initWithURL(opt.dataSource)}}var Searcher=require("./Searcher"),Templater=require("./Templater"),Store=require("./Store"),JSONLoader=require("./JSONLoader"),searcher=new Searcher,templater=new Templater,store=new Store,jsonLoader=new JSONLoader;window.SimpleJekyllSearch=new SimpleJekyllSearch}(window,document)},{"./JSONLoader":1,"./Searcher":4,"./Store":5,"./Templater":6}]},{},[7]); \ No newline at end of file diff --git a/docs/js/jquery.ba-throttle-debounce.min.js b/docs/js/jquery.ba-throttle-debounce.min.js new file mode 100644 index 000000000..07205508e --- /dev/null +++ b/docs/js/jquery.ba-throttle-debounce.min.js @@ -0,0 +1,9 @@ +/* + * jQuery throttle / debounce - v1.1 - 3/7/2010 + * http://benalman.com/projects/jquery-throttle-debounce-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!=="boolean"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this); \ No newline at end of file diff --git a/docs/js/jquery.localScroll.min.js b/docs/js/jquery.localScroll.min.js new file mode 100644 index 000000000..48a6e168f --- /dev/null +++ b/docs/js/jquery.localScroll.min.js @@ -0,0 +1,7 @@ +/** + * Copyright (c) 2007-2014 Ariel Flesler - afleslergmailcom | http://flesler.blogspot.com + * Licensed under MIT + * @author Ariel Flesler + * @version 1.3.5 + */ +;(function(a){if(typeof define==='function'&&define.amd){define(['jquery'],a)}else{a(jQuery)}}(function($){var g=location.href.replace(/#.*/,'');var h=$.localScroll=function(a){$('body').localScroll(a)};h.defaults={duration:1000,axis:'y',event:'click',stop:true,target:window};$.fn.localScroll=function(a){a=$.extend({},h.defaults,a);if(a.hash&&location.hash){if(a.target)window.scrollTo(0,0);scroll(0,location,a)}return a.lazy?this.on(a.event,'a,area',function(e){if(filter.call(this)){scroll(e,this,a)}}):this.find('a,area').filter(filter).bind(a.event,function(e){scroll(e,this,a)}).end().end();function filter(){return!!this.href&&!!this.hash&&this.href.replace(this.hash,'')==g&&(!a.filter||$(this).is(a.filter))}};h.hash=function(){};function scroll(e,a,b){var c=a.hash.slice(1),elem=document.getElementById(c)||document.getElementsByName(c)[0];if(!elem)return;if(e)e.preventDefault();var d=$(b.target);if(b.lock&&d.is(':animated')||b.onBefore&&b.onBefore(e,elem,d)===false)return;if(b.stop)d._scrollable().stop(true);if(b.hash){var f=elem.id===c?'id':'name',$a=$(' ').attr(f,c).css({position:'absolute',top:$(window).scrollTop(),left:$(window).scrollLeft()});elem[f]='';$('body').prepend($a);location.hash=a.hash;$a.remove();elem[f]=c}d.scrollTo(elem,b).trigger('notify.serialScroll',[elem])};return h})); \ No newline at end of file diff --git a/docs/js/jquery.navgoco.min.js b/docs/js/jquery.navgoco.min.js new file mode 100755 index 000000000..4ba447533 --- /dev/null +++ b/docs/js/jquery.navgoco.min.js @@ -0,0 +1,8 @@ +/* + * jQuery Navgoco Menus Plugin v0.2.1 (2014-04-11) + * https://github.com/tefra/navgoco + * + * Copyright (c) 2014 Chris T (@tefra) + * BSD - https://github.com/tefra/navgoco/blob/master/LICENSE-BSD + */ +!function(a){"use strict";var b=function(b,c,d){return this.el=b,this.$el=a(b),this.options=c,this.uuid=this.$el.attr("id")?this.$el.attr("id"):d,this.state={},this.init(),this};b.prototype={init:function(){var b=this;b._load(),b.$el.find("ul").each(function(c){var d=a(this);d.attr("data-index",c),b.options.save&&b.state.hasOwnProperty(c)?(d.parent().addClass(b.options.openClass),d.show()):d.parent().hasClass(b.options.openClass)?(d.show(),b.state[c]=1):d.hide()});var c=a("").prepend(b.options.caretHtml),d=b.$el.find("li > a");b._trigger(c,!1),b._trigger(d,!0),b.$el.find("li:has(ul) > a").prepend(c)},_trigger:function(b,c){var d=this;b.on("click",function(b){b.stopPropagation();var e=c?a(this).next():a(this).parent().next(),f=!1;if(c){var g=a(this).attr("href");f=void 0===g||""===g||"#"===g}if(e=e.length>0?e:!1,d.options.onClickBefore.call(this,b,e),!c||e&&f)b.preventDefault(),d._toggle(e,e.is(":hidden")),d._save();else if(d.options.accordion){var h=d.state=d._parents(a(this));d.$el.find("ul").filter(":visible").each(function(){var b=a(this),c=b.attr("data-index");h.hasOwnProperty(c)||d._toggle(b,!1)}),d._save()}d.options.onClickAfter.call(this,b,e)})},_toggle:function(b,c){var d=this,e=b.attr("data-index"),f=b.parent();if(d.options.onToggleBefore.call(this,b,c),c){if(f.addClass(d.options.openClass),b.slideDown(d.options.slide),d.state[e]=1,d.options.accordion){var g=d.state=d._parents(b);g[e]=d.state[e]=1,d.$el.find("ul").filter(":visible").each(function(){var b=a(this),c=b.attr("data-index");g.hasOwnProperty(c)||d._toggle(b,!1)})}}else f.removeClass(d.options.openClass),b.slideUp(d.options.slide),d.state[e]=0;d.options.onToggleAfter.call(this,b,c)},_parents:function(b,c){var d={},e=b.parent(),f=e.parents("ul");return f.each(function(){var b=a(this),e=b.attr("data-index");return e?void(d[e]=c?b:1):!1}),d},_save:function(){if(this.options.save){var b={};for(var d in this.state)1===this.state[d]&&(b[d]=1);c[this.uuid]=this.state=b,a.cookie(this.options.cookie.name,JSON.stringify(c),this.options.cookie)}},_load:function(){if(this.options.save){if(null===c){var b=a.cookie(this.options.cookie.name);c=b?JSON.parse(b):{}}this.state=c.hasOwnProperty(this.uuid)?c[this.uuid]:{}}},toggle:function(b){var c=this,d=arguments.length;if(1>=d)c.$el.find("ul").each(function(){var d=a(this);c._toggle(d,b)});else{var e,f={},g=Array.prototype.slice.call(arguments,1);d--;for(var h=0;d>h;h++){e=g[h];var i=c.$el.find('ul[data-index="'+e+'"]').first();if(i&&(f[e]=i,b)){var j=c._parents(i,!0);for(var k in j)f.hasOwnProperty(k)||(f[k]=j[k])}}for(e in f)c._toggle(f[e],b)}c._save()},destroy:function(){a.removeData(this.$el),this.$el.find("li:has(ul) > a").unbind("click"),this.$el.find("li:has(ul) > a > span").unbind("click")}},a.fn.navgoco=function(c){if("string"==typeof c&&"_"!==c.charAt(0)&&"init"!==c)var d=!0,e=Array.prototype.slice.call(arguments,1);else c=a.extend({},a.fn.navgoco.defaults,c||{}),a.cookie||(c.save=!1);return this.each(function(f){var g=a(this),h=g.data("navgoco");h||(h=new b(this,d?a.fn.navgoco.defaults:c,f),g.data("navgoco",h)),d&&h[c].apply(h,e)})};var c=null;a.fn.navgoco.defaults={caretHtml:"",accordion:!1,openClass:"open",save:!0,cookie:{name:"navgoco",expires:!1,path:"/"},slide:{duration:400,easing:"swing"},onClickBefore:a.noop,onClickAfter:a.noop,onToggleBefore:a.noop,onToggleAfter:a.noop}}(jQuery); \ No newline at end of file diff --git a/docs/js/jquery.scrollTo.min.js b/docs/js/jquery.scrollTo.min.js new file mode 100644 index 000000000..5f5e01e8e --- /dev/null +++ b/docs/js/jquery.scrollTo.min.js @@ -0,0 +1,7 @@ +/** + * Copyright (c) 2007-2014 Ariel Flesler - afleslergmailcom | http://flesler.blogspot.com + * Licensed under MIT + * @author Ariel Flesler + * @version 1.4.14 + */ +;(function(k){'use strict';k(['jquery'],function($){var j=$.scrollTo=function(a,b,c){return $(window).scrollTo(a,b,c)};j.defaults={axis:'xy',duration:0,limit:!0};j.window=function(a){return $(window)._scrollable()};$.fn._scrollable=function(){return this.map(function(){var a=this,isWin=!a.nodeName||$.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!isWin)return a;var b=(a.contentWindow||a).document||a.ownerDocument||a;return/webkit/i.test(navigator.userAgent)||b.compatMode=='BackCompat'?b.body:b.documentElement})};$.fn.scrollTo=function(f,g,h){if(typeof g=='object'){h=g;g=0}if(typeof h=='function')h={onAfter:h};if(f=='max')f=9e9;h=$.extend({},j.defaults,h);g=g||h.duration;h.queue=h.queue&&h.axis.length>1;if(h.queue)g/=2;h.offset=both(h.offset);h.over=both(h.over);return this._scrollable().each(function(){if(f==null)return;var d=this,$elem=$(d),targ=f,toff,attr={},win=$elem.is('html,body');switch(typeof targ){case'number':case'string':if(/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(targ)){targ=both(targ);break}targ=win?$(targ):$(targ,this);if(!targ.length)return;case'object':if(targ.is||targ.style)toff=(targ=$(targ)).offset()}var e=$.isFunction(h.offset)&&h.offset(d,targ)||h.offset;$.each(h.axis.split(''),function(i,a){var b=a=='x'?'Left':'Top',pos=b.toLowerCase(),key='scroll'+b,old=d[key],max=j.max(d,a);if(toff){attr[key]=toff[pos]+(win?0:old-$elem.offset()[pos]);if(h.margin){attr[key]-=parseInt(targ.css('margin'+b))||0;attr[key]-=parseInt(targ.css('border'+b+'Width'))||0}attr[key]+=e[pos]||0;if(h.over[pos])attr[key]+=targ[a=='x'?'width':'height']()*h.over[pos]}else{var c=targ[pos];attr[key]=c.slice&&c.slice(-1)=='%'?parseFloat(c)/100*max:c}if(h.limit&&/^\d+$/.test(attr[key]))attr[key]=attr[key]<=0?0:Math.min(attr[key],max);if(!i&&h.queue){if(old!=attr[key])animate(h.onAfterFirst);delete attr[key]}});animate(h.onAfter);function animate(a){$elem.animate(attr,g,h.easing,a&&function(){a.call(this,targ,h)})}}).end()};j.max=function(a,b){var c=b=='x'?'Width':'Height',scroll='scroll'+c;if(!$(a).is('html,body'))return a[scroll]-$(a)[c.toLowerCase()]();var d='client'+c,html=a.ownerDocument.documentElement,body=a.ownerDocument.body;return Math.max(html[scroll],body[scroll])-Math.min(html[d],body[d])};function both(a){return $.isFunction(a)||$.isPlainObject(a)?a:{top:a,left:a}}return j})}(typeof define==='function'&&define.amd?define:function(a,b){if(typeof module!=='undefined'&&module.exports){module.exports=b(require('jquery'))}else{b(jQuery)}})); \ No newline at end of file diff --git a/docs/js/jquery.shuffle.min.js b/docs/js/jquery.shuffle.min.js new file mode 100644 index 000000000..d10312719 --- /dev/null +++ b/docs/js/jquery.shuffle.min.js @@ -0,0 +1,1588 @@ +/*! + * Shuffle.js by @Vestride + * Categorize, sort, and filter a responsive grid of items. + * Dependencies: jQuery 1.9+, Modernizr 2.6.2+ + * @license MIT license + * @version 3.0.0 + */ + +/* Modernizr 2.6.2 (Custom Build) | MIT & BSD + * Build: http://modernizr.com/download/#-csstransforms-csstransforms3d-csstransitions-cssclasses-prefixed-teststyles-testprop-testallprops-prefixes-domprefixes + */ +window.Modernizr=function(a,b,c){function z(a){j.cssText=a}function A(a,b){return z(m.join(a+";")+(b||""))}function B(a,b){return typeof a===b}function C(a,b){return!!~(""+a).indexOf(b)}function D(a,b){for(var d in a){var e=a[d];if(!C(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function E(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:B(f,"function")?f.bind(d||b):f}return!1}function F(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+o.join(d+" ")+d).split(" ");return B(b,"string")||B(b,"undefined")?D(e,b):(e=(a+" "+p.join(d+" ")+d).split(" "),E(e,b,c))}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n="Webkit Moz O ms",o=n.split(" "),p=n.toLowerCase().split(" "),q={},r={},s={},t=[],u=t.slice,v,w=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["­",'"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},x={}.hasOwnProperty,y;!B(x,"undefined")&&!B(x.call,"undefined")?y=function(a,b){return x.call(a,b)}:y=function(a,b){return b in a&&B(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e}),q.csstransforms=function(){return!!F("transform")},q.csstransforms3d=function(){var a=!!F("perspective");return a&&"webkitPerspective"in g.style&&w("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},q.csstransitions=function(){return F("transition")};for(var G in q)y(q,G)&&(v=G.toLowerCase(),e[v]=q[G](),t.push((e[v]?"":"no-")+v));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)y(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},z(""),i=k=null,e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.testProp=function(a){return D([a])},e.testAllProps=F,e.testStyles=w,e.prefixed=function(a,b,c){return b?F(a,b,c):F(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document); + +(function (factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery', 'modernizr'], factory); + } else { + window.Shuffle = factory(window.jQuery, window.Modernizr); + } +})(function($, Modernizr, undefined) { + +'use strict'; + + +// Validate Modernizr exists. +// Shuffle requires `csstransitions`, `csstransforms`, `csstransforms3d`, +// and `prefixed` to exist on the Modernizr object. +if (typeof Modernizr !== 'object') { + throw new Error('Shuffle.js requires Modernizr.\n' + + 'http://vestride.github.io/Shuffle/#dependencies'); +} + + +/** + * Returns css prefixed properties like `-webkit-transition` or `box-sizing` + * from `transition` or `boxSizing`, respectively. + * @param {(string|boolean)} prop Property to be prefixed. + * @return {string} The prefixed css property. + */ +function dashify( prop ) { + if (!prop) { + return ''; + } + + // Replace upper case with dash-lowercase, + // then fix ms- prefixes because they're not capitalized. + return prop.replace(/([A-Z])/g, function( str, m1 ) { + return '-' + m1.toLowerCase(); + }).replace(/^ms-/,'-ms-'); +} + +// Constant, prefixed variables. +var TRANSITION = Modernizr.prefixed('transition'); +var TRANSITION_DELAY = Modernizr.prefixed('transitionDelay'); +var TRANSITION_DURATION = Modernizr.prefixed('transitionDuration'); + +// Note(glen): Stock Android 4.1.x browser will fail here because it wrongly +// says it supports non-prefixed transitions. +// https://github.com/Modernizr/Modernizr/issues/897 +var TRANSITIONEND = { + 'WebkitTransition' : 'webkitTransitionEnd', + 'transition' : 'transitionend' +}[ TRANSITION ]; + +var TRANSFORM = Modernizr.prefixed('transform'); +var CSS_TRANSFORM = dashify(TRANSFORM); + +// Constants +var CAN_TRANSITION_TRANSFORMS = Modernizr.csstransforms && Modernizr.csstransitions; +var HAS_TRANSFORMS_3D = Modernizr.csstransforms3d; +var SHUFFLE = 'shuffle'; +var COLUMN_THRESHOLD = 0.3; + +// Configurable. You can change these constants to fit your application. +// The default scale and concealed scale, however, have to be different values. +var ALL_ITEMS = 'all'; +var FILTER_ATTRIBUTE_KEY = 'groups'; +var DEFAULT_SCALE = 1; +var CONCEALED_SCALE = 0.001; + + +// Underscore's throttle function. +function throttle(func, wait, options) { + var context, args, result; + var timeout = null; + var previous = 0; + options = options || {}; + var later = function() { + previous = options.leading === false ? 0 : $.now(); + timeout = null; + result = func.apply(context, args); + context = args = null; + }; + return function() { + var now = $.now(); + if (!previous && options.leading === false) { + previous = now; + } + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; +} + +function each(obj, iterator, context) { + for (var i = 0, length = obj.length; i < length; i++) { + if (iterator.call(context, obj[i], i, obj) === {}) { + return; + } + } +} + +function defer(fn, context, wait) { + return setTimeout( $.proxy( fn, context ), wait ); +} + +function arrayMax( array ) { + return Math.max.apply( Math, array ); +} + +function arrayMin( array ) { + return Math.min.apply( Math, array ); +} + + +/** + * Always returns a numeric value, given a value. + * @param {*} value Possibly numeric value. + * @return {number} `value` or zero if `value` isn't numeric. + * @private + */ +function getNumber(value) { + return $.isNumeric(value) ? value : 0; +} + + +/** + * Represents a coordinate pair. + * @param {number} [x=0] X. + * @param {number} [y=0] Y. + */ +var Point = function(x, y) { + this.x = getNumber( x ); + this.y = getNumber( y ); +}; + + +/** + * Whether two points are equal. + * @param {Point} a Point A. + * @param {Point} b Point B. + * @return {boolean} + */ +Point.equals = function(a, b) { + return a.x === b.x && a.y === b.y; +}; + + +// Used for unique instance variables +var id = 0; +var $window = $( window ); + + +/** + * Categorize, sort, and filter a responsive grid of items. + * + * @param {Element} element An element which is the parent container for the grid items. + * @param {Object} [options=Shuffle.options] Options object. + * @constructor + */ +var Shuffle = function( element, options ) { + options = options || {}; + $.extend( this, Shuffle.options, options, Shuffle.settings ); + + this.$el = $(element); + this.element = element; + this.unique = 'shuffle_' + id++; + + this._fire( Shuffle.EventType.LOADING ); + this._init(); + + // Dispatch the done event asynchronously so that people can bind to it after + // Shuffle has been initialized. + defer(function() { + this.initialized = true; + this._fire( Shuffle.EventType.DONE ); + }, this, 16); +}; + + +/** + * Events the container element emits with the .shuffle namespace. + * For example, "done.shuffle". + * @enum {string} + */ +Shuffle.EventType = { + LOADING: 'loading', + DONE: 'done', + LAYOUT: 'layout', + REMOVED: 'removed' +}; + + +/** @enum {string} */ +Shuffle.ClassName = { + BASE: SHUFFLE, + SHUFFLE_ITEM: 'shuffle-item', + FILTERED: 'filtered', + CONCEALED: 'concealed' +}; + + +// Overrideable options +Shuffle.options = { + group: ALL_ITEMS, // Initial filter group. + speed: 250, // Transition/animation speed (milliseconds). + easing: 'ease-out', // CSS easing function to use. + itemSelector: '', // e.g. '.picture-item'. + sizer: null, // Sizer element. Use an element to determine the size of columns and gutters. + gutterWidth: 0, // A static number or function that tells the plugin how wide the gutters between columns are (in pixels). + columnWidth: 0, // A static number or function that returns a number which tells the plugin how wide the columns are (in pixels). + delimeter: null, // If your group is not json, and is comma delimeted, you could set delimeter to ','. + buffer: 0, // Useful for percentage based heights when they might not always be exactly the same (in pixels). + initialSort: null, // Shuffle can be initialized with a sort object. It is the same object given to the sort method. + throttle: throttle, // By default, shuffle will throttle resize events. This can be changed or removed. + throttleTime: 300, // How often shuffle can be called on resize (in milliseconds). + sequentialFadeDelay: 150, // Delay between each item that fades in when adding items. + supported: CAN_TRANSITION_TRANSFORMS // Whether to use transforms or absolute positioning. +}; + + +// Not overrideable +Shuffle.settings = { + useSizer: false, + itemCss : { // default CSS for each item + position: 'absolute', + top: 0, + left: 0, + visibility: 'visible' + }, + revealAppendedDelay: 300, + lastSort: {}, + lastFilter: ALL_ITEMS, + enabled: true, + destroyed: false, + initialized: false, + _animations: [], + styleQueue: [] +}; + + +// Expose for testing. +Shuffle.Point = Point; + + +/** + * Static methods. + */ + +/** + * If the browser has 3d transforms available, build a string with those, + * otherwise use 2d transforms. + * @param {Point} point X and Y positions. + * @param {number} scale Scale amount. + * @return {string} A normalized string which can be used with the transform style. + * @private + */ +Shuffle._getItemTransformString = function(point, scale) { + if ( HAS_TRANSFORMS_3D ) { + return 'translate3d(' + point.x + 'px, ' + point.y + 'px, 0) scale3d(' + scale + ', ' + scale + ', 1)'; + } else { + return 'translate(' + point.x + 'px, ' + point.y + 'px) scale(' + scale + ')'; + } +}; + + +/** + * Retrieve the computed style for an element, parsed as a float. This should + * not be used for width or height values because jQuery mangles them and they + * are not precise enough. + * @param {Element} element Element to get style for. + * @param {string} style Style property. + * @return {number} The parsed computed value or zero if that fails because IE + * will return 'auto' when the element doesn't have margins instead of + * the computed style. + * @private + */ +Shuffle._getNumberStyle = function( element, style ) { + return Shuffle._getFloat( $( element ).css( style ) ); +}; + + +/** + * Parse a string as an integer. + * @param {string} value String integer. + * @return {number} The string as an integer or zero. + * @private + */ +Shuffle._getInt = function(value) { + return getNumber( parseInt( value, 10 ) ); +}; + +/** + * Parse a string as an float. + * @param {string} value String float. + * @return {number} The string as an float or zero. + * @private + */ +Shuffle._getFloat = function(value) { + return getNumber( parseFloat( value ) ); +}; + + +/** + * Returns the outer width of an element, optionally including its margins. + * The `offsetWidth` property must be used because having a scale transform + * on the element affects the bounding box. Sadly, Firefox doesn't return an + * integer value for offsetWidth (yet). + * @param {Element} element The element. + * @param {boolean} [includeMargins] Whether to include margins. Default is false. + * @return {number} The width. + */ +Shuffle._getOuterWidth = function( element, includeMargins ) { + var width = element.offsetWidth; + + // Use jQuery here because it uses getComputedStyle internally and is + // cross-browser. Using the style property of the element will only work + // if there are inline styles. + if ( includeMargins ) { + var marginLeft = Shuffle._getNumberStyle( element, 'marginLeft'); + var marginRight = Shuffle._getNumberStyle( element, 'marginRight'); + width += marginLeft + marginRight; + } + + return width; +}; + + +/** + * Returns the outer height of an element, optionally including its margins. + * @param {Element} element The element. + * @param {boolean} [includeMargins] Whether to include margins. Default is false. + * @return {number} The height. + */ +Shuffle._getOuterHeight = function( element, includeMargins ) { + var height = element.offsetHeight; + + if ( includeMargins ) { + var marginTop = Shuffle._getNumberStyle( element, 'marginTop'); + var marginBottom = Shuffle._getNumberStyle( element, 'marginBottom'); + height += marginTop + marginBottom; + } + + return height; +}; + + +/** + * Change a property or execute a function which will not have a transition + * @param {Element} element DOM element that won't be transitioned + * @param {Function} callback A function which will be called while transition + * is set to 0ms. + * @param {Object} [context] Optional context for the callback function. + * @private + */ +Shuffle._skipTransition = function( element, callback, context ) { + var duration = element.style[ TRANSITION_DURATION ]; + + // Set the duration to zero so it happens immediately + element.style[ TRANSITION_DURATION ] = '0ms'; // ms needed for firefox! + + callback.call( context ); + + // Force reflow + var reflow = element.offsetWidth; + // Avoid jshint warnings: unused variables and expressions. + reflow = null; + + // Put the duration back + element.style[ TRANSITION_DURATION ] = duration; +}; + + +/** + * Instance methods. + */ + +Shuffle.prototype._init = function() { + this.$items = this._getItems(); + + this.sizer = this._getElementOption( this.sizer ); + + if ( this.sizer ) { + this.useSizer = true; + } + + // Add class and invalidate styles + this.$el.addClass( Shuffle.ClassName.BASE ); + + // Set initial css for each item + this._initItems(); + + // Bind resize events + // http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer + $window.on('resize.' + SHUFFLE + '.' + this.unique, this._getResizeFunction()); + + // Get container css all in one request. Causes reflow + var containerCSS = this.$el.css(['position', 'overflow']); + var containerWidth = Shuffle._getOuterWidth( this.element ); + + // Add styles to the container if it doesn't have them. + this._validateStyles( containerCSS ); + + // We already got the container's width above, no need to cause another reflow getting it again... + // Calculate the number of columns there will be + this._setColumns( containerWidth ); + + // Kick off! + this.shuffle( this.group, this.initialSort ); + + // The shuffle items haven't had transitions set on them yet + // so the user doesn't see the first layout. Set them now that the first layout is done. + if ( this.supported ) { + defer(function() { + this._setTransitions(); + this.element.style[ TRANSITION ] = 'height ' + this.speed + 'ms ' + this.easing; + }, this); + } +}; + + +/** + * Returns a throttled and proxied function for the resize handler. + * @return {Function} + * @private + */ +Shuffle.prototype._getResizeFunction = function() { + var resizeFunction = $.proxy( this._onResize, this ); + return this.throttle ? + this.throttle( resizeFunction, this.throttleTime ) : + resizeFunction; +}; + + +/** + * Retrieve an element from an option. + * @param {string|jQuery|Element} option The option to check. + * @return {?Element} The plain element or null. + * @private + */ +Shuffle.prototype._getElementOption = function( option ) { + // If column width is a string, treat is as a selector and search for the + // sizer element within the outermost container + if ( typeof option === 'string' ) { + return this.$el.find( option )[0] || null; + + // Check for an element + } else if ( option && option.nodeType && option.nodeType === 1 ) { + return option; + + // Check for jQuery object + } else if ( option && option.jquery ) { + return option[0]; + } + + return null; +}; + + +/** + * Ensures the shuffle container has the css styles it needs applied to it. + * @param {Object} styles Key value pairs for position and overflow. + * @private + */ +Shuffle.prototype._validateStyles = function(styles) { + // Position cannot be static. + if ( styles.position === 'static' ) { + this.element.style.position = 'relative'; + } + + // Overflow has to be hidden + if ( styles.overflow !== 'hidden' ) { + this.element.style.overflow = 'hidden'; + } +}; + + +/** + * Filter the elements by a category. + * @param {string} [category] Category to filter by. If it's given, the last + * category will be used to filter the items. + * @param {ArrayLike} [$collection] Optionally filter a collection. Defaults to + * all the items. + * @return {jQuery} Filtered items. + * @private + */ +Shuffle.prototype._filter = function( category, $collection ) { + category = category || this.lastFilter; + $collection = $collection || this.$items; + + var set = this._getFilteredSets( category, $collection ); + + // Individually add/remove concealed/filtered classes + this._toggleFilterClasses( set.filtered, set.concealed ); + + // Save the last filter in case elements are appended. + this.lastFilter = category; + + // This is saved mainly because providing a filter function (like searching) + // will overwrite the `lastFilter` property every time its called. + if ( typeof category === 'string' ) { + this.group = category; + } + + return set.filtered; +}; + + +/** + * Returns an object containing the filtered and concealed elements. + * @param {string|Function} category Category or function to filter by. + * @param {ArrayLike.} $items A collection of items to filter. + * @return {!{filtered: jQuery, concealed: jQuery}} + * @private + */ +Shuffle.prototype._getFilteredSets = function( category, $items ) { + var $filtered = $(); + var $concealed = $(); + + // category === 'all', add filtered class to everything + if ( category === ALL_ITEMS ) { + $filtered = $items; + + // Loop through each item and use provided function to determine + // whether to hide it or not. + } else { + each($items, function( el ) { + var $item = $(el); + if ( this._doesPassFilter( category, $item ) ) { + $filtered = $filtered.add( $item ); + } else { + $concealed = $concealed.add( $item ); + } + }, this); + } + + return { + filtered: $filtered, + concealed: $concealed + }; +}; + + +/** + * Test an item to see if it passes a category. + * @param {string|Function} category Category or function to filter by. + * @param {jQuery} $item A single item, wrapped with jQuery. + * @return {boolean} Whether it passes the category/filter. + * @private + */ +Shuffle.prototype._doesPassFilter = function( category, $item ) { + if ( $.isFunction( category ) ) { + return category.call( $item[0], $item, this ); + + // Check each element's data-groups attribute against the given category. + } else { + var groups = $item.data( FILTER_ATTRIBUTE_KEY ); + var keys = this.delimeter && !$.isArray( groups ) ? + groups.split( this.delimeter ) : + groups; + return $.inArray(category, keys) > -1; + } +}; + + +/** + * Toggles the filtered and concealed class names. + * @param {jQuery} $filtered Filtered set. + * @param {jQuery} $concealed Concealed set. + * @private + */ +Shuffle.prototype._toggleFilterClasses = function( $filtered, $concealed ) { + $filtered + .removeClass( Shuffle.ClassName.CONCEALED ) + .addClass( Shuffle.ClassName.FILTERED ); + $concealed + .removeClass( Shuffle.ClassName.FILTERED ) + .addClass( Shuffle.ClassName.CONCEALED ); +}; + + +/** + * Set the initial css for each item + * @param {jQuery} [$items] Optionally specifiy at set to initialize + */ +Shuffle.prototype._initItems = function( $items ) { + $items = $items || this.$items; + $items.addClass([ + Shuffle.ClassName.SHUFFLE_ITEM, + Shuffle.ClassName.FILTERED + ].join(' ')); + $items.css( this.itemCss ).data('point', new Point()).data('scale', DEFAULT_SCALE); +}; + + +/** + * Updates the filtered item count. + * @private + */ +Shuffle.prototype._updateItemCount = function() { + this.visibleItems = this._getFilteredItems().length; +}; + + +/** + * Sets css transform transition on a an element. + * @param {Element} element Element to set transition on. + * @private + */ +Shuffle.prototype._setTransition = function( element ) { + element.style[ TRANSITION ] = CSS_TRANSFORM + ' ' + this.speed + 'ms ' + + this.easing + ', opacity ' + this.speed + 'ms ' + this.easing; +}; + + +/** + * Sets css transform transition on a group of elements. + * @param {ArrayLike.} $items Elements to set transitions on. + * @private + */ +Shuffle.prototype._setTransitions = function( $items ) { + $items = $items || this.$items; + each($items, function( el ) { + this._setTransition( el ); + }, this); +}; + + +/** + * Sets a transition delay on a collection of elements, making each delay + * greater than the last. + * @param {ArrayLike.} $collection Array to iterate over. + */ +Shuffle.prototype._setSequentialDelay = function( $collection ) { + if ( !this.supported ) { + return; + } + + // $collection can be an array of dom elements or jquery object + each($collection, function( el, i ) { + // This works because the transition-property: transform, opacity; + el.style[ TRANSITION_DELAY ] = '0ms,' + ((i + 1) * this.sequentialFadeDelay) + 'ms'; + }, this); +}; + + +Shuffle.prototype._getItems = function() { + return this.$el.children( this.itemSelector ); +}; + + +Shuffle.prototype._getFilteredItems = function() { + return this.$items.filter('.' + Shuffle.ClassName.FILTERED); +}; + + +Shuffle.prototype._getConcealedItems = function() { + return this.$items.filter('.' + Shuffle.ClassName.CONCEALED); +}; + + +/** + * Returns the column size, based on column width and sizer options. + * @param {number} containerWidth Size of the parent container. + * @param {number} gutterSize Size of the gutters. + * @return {number} + * @private + */ +Shuffle.prototype._getColumnSize = function( containerWidth, gutterSize ) { + var size; + + // If the columnWidth property is a function, then the grid is fluid + if ( $.isFunction( this.columnWidth ) ) { + size = this.columnWidth(containerWidth); + + // columnWidth option isn't a function, are they using a sizing element? + } else if ( this.useSizer ) { + size = Shuffle._getOuterWidth(this.sizer); + + // if not, how about the explicitly set option? + } else if ( this.columnWidth ) { + size = this.columnWidth; + + // or use the size of the first item + } else if ( this.$items.length > 0 ) { + size = Shuffle._getOuterWidth(this.$items[0], true); + + // if there's no items, use size of container + } else { + size = containerWidth; + } + + // Don't let them set a column width of zero. + if ( size === 0 ) { + size = containerWidth; + } + + return size + gutterSize; +}; + + +/** + * Returns the gutter size, based on gutter width and sizer options. + * @param {number} containerWidth Size of the parent container. + * @return {number} + * @private + */ +Shuffle.prototype._getGutterSize = function( containerWidth ) { + var size; + if ( $.isFunction( this.gutterWidth ) ) { + size = this.gutterWidth(containerWidth); + } else if ( this.useSizer ) { + size = Shuffle._getNumberStyle(this.sizer, 'marginLeft'); + } else { + size = this.gutterWidth; + } + + return size; +}; + + +/** + * Calculate the number of columns to be used. Gets css if using sizer element. + * @param {number} [theContainerWidth] Optionally specify a container width if it's already available. + */ +Shuffle.prototype._setColumns = function( theContainerWidth ) { + var containerWidth = theContainerWidth || Shuffle._getOuterWidth( this.element ); + var gutter = this._getGutterSize( containerWidth ); + var columnWidth = this._getColumnSize( containerWidth, gutter ); + var calculatedColumns = (containerWidth + gutter) / columnWidth; + + // Widths given from getComputedStyle are not precise enough... + if ( Math.abs(Math.round(calculatedColumns) - calculatedColumns) < COLUMN_THRESHOLD ) { + // e.g. calculatedColumns = 11.998876 + calculatedColumns = Math.round( calculatedColumns ); + } + + this.cols = Math.max( Math.floor(calculatedColumns), 1 ); + this.containerWidth = containerWidth; + this.colWidth = columnWidth; +}; + +/** + * Adjust the height of the grid + */ +Shuffle.prototype._setContainerSize = function() { + this.$el.css( 'height', this._getContainerSize() ); +}; + + +/** + * Based on the column heights, it returns the biggest one. + * @return {number} + * @private + */ +Shuffle.prototype._getContainerSize = function() { + return arrayMax( this.positions ); +}; + + +/** + * Fire events with .shuffle namespace + */ +Shuffle.prototype._fire = function( name, args ) { + this.$el.trigger( name + '.' + SHUFFLE, args && args.length ? args : [ this ] ); +}; + + +/** + * Zeros out the y columns array, which is used to determine item placement. + * @private + */ +Shuffle.prototype._resetCols = function() { + var i = this.cols; + this.positions = []; + while (i--) { + this.positions.push( 0 ); + } +}; + + +/** + * Loops through each item that should be shown and calculates the x, y position. + * @param {Array.} items Array of items that will be shown/layed out in order in their array. + * Because jQuery collection are always ordered in DOM order, we can't pass a jq collection. + * @param {boolean} [isOnlyPosition=false] If true this will position the items with zero opacity. + */ +Shuffle.prototype._layout = function( items, isOnlyPosition ) { + each(items, function( item ) { + this._layoutItem( item, !!isOnlyPosition ); + }, this); + + // `_layout` always happens after `_shrink`, so it's safe to process the style + // queue here with styles from the shrink method. + this._processStyleQueue(); + + // Adjust the height of the container. + this._setContainerSize(); +}; + + +/** + * Calculates the position of the item and pushes it onto the style queue. + * @param {Element} item Element which is being positioned. + * @param {boolean} isOnlyPosition Whether to position the item, but with zero + * opacity so that it can fade in later. + * @private + */ +Shuffle.prototype._layoutItem = function( item, isOnlyPosition ) { + var $item = $(item); + var itemData = $item.data(); + var currPos = itemData.point; + var currScale = itemData.scale; + var itemSize = { + width: Shuffle._getOuterWidth( item, true ), + height: Shuffle._getOuterHeight( item, true ) + }; + var pos = this._getItemPosition( itemSize ); + + // If the item will not change its position, do not add it to the render + // queue. Transitions don't fire when setting a property to the same value. + if ( Point.equals(currPos, pos) && currScale === DEFAULT_SCALE ) { + return; + } + + // Save data for shrink + itemData.point = pos; + itemData.scale = DEFAULT_SCALE; + + this.styleQueue.push({ + $item: $item, + point: pos, + scale: DEFAULT_SCALE, + opacity: isOnlyPosition ? 0 : 1, + skipTransition: isOnlyPosition, + callfront: function() { + if ( !isOnlyPosition ) { + $item.css( 'visibility', 'visible' ); + } + }, + callback: function() { + if ( isOnlyPosition ) { + $item.css( 'visibility', 'hidden' ); + } + } + }); +}; + + +/** + * Determine the location of the next item, based on its size. + * @param {{width: number, height: number}} itemSize Object with width and height. + * @return {Point} + * @private + */ +Shuffle.prototype._getItemPosition = function( itemSize ) { + var columnSpan = this._getColumnSpan( itemSize.width, this.colWidth, this.cols ); + + var setY = this._getColumnSet( columnSpan, this.cols ); + + // Finds the index of the smallest number in the set. + var shortColumnIndex = this._getShortColumn( setY, this.buffer ); + + // Position the item + var point = new Point( + Math.round( this.colWidth * shortColumnIndex ), + Math.round( setY[shortColumnIndex] )); + + // Update the columns array with the new values for each column. + // e.g. before the update the columns could be [250, 0, 0, 0] for an item + // which spans 2 columns. After it would be [250, itemHeight, itemHeight, 0]. + var setHeight = setY[shortColumnIndex] + itemSize.height; + var setSpan = this.cols + 1 - setY.length; + for ( var i = 0; i < setSpan; i++ ) { + this.positions[ shortColumnIndex + i ] = setHeight; + } + + return point; +}; + + +/** + * Determine the number of columns an items spans. + * @param {number} itemWidth Width of the item. + * @param {number} columnWidth Width of the column (includes gutter). + * @param {number} columns Total number of columns + * @return {number} + * @private + */ +Shuffle.prototype._getColumnSpan = function( itemWidth, columnWidth, columns ) { + var columnSpan = itemWidth / columnWidth; + + // If the difference between the rounded column span number and the + // calculated column span number is really small, round the number to + // make it fit. + if ( Math.abs(Math.round( columnSpan ) - columnSpan ) < COLUMN_THRESHOLD ) { + // e.g. columnSpan = 4.0089945390298745 + columnSpan = Math.round( columnSpan ); + } + + // Ensure the column span is not more than the amount of columns in the whole layout. + return Math.min( Math.ceil( columnSpan ), columns ); +}; + + +/** + * Retrieves the column set to use for placement. + * @param {number} columnSpan The number of columns this current item spans. + * @param {number} columns The total columns in the grid. + * @return {Array.} An array of numbers represeting the column set. + * @private + */ +Shuffle.prototype._getColumnSet = function( columnSpan, columns ) { + // The item spans only one column. + if ( columnSpan === 1 ) { + return this.positions; + + // The item spans more than one column, figure out how many different + // places it could fit horizontally. + // The group count is the number of places within the positions this block + // could fit, ignoring the current positions of items. + // Imagine a 2 column brick as the second item in a 4 column grid with + // 10px height each. Find the places it would fit: + // [10, 0, 0, 0] + // | | | + // * * * + // + // Then take the places which fit and get the bigger of the two: + // max([10, 0]), max([0, 0]), max([0, 0]) = [10, 0, 0] + // + // Next, find the first smallest number (the short column). + // [10, 0, 0] + // | + // * + // + // And that's where it should be placed! + } else { + var groupCount = columns + 1 - columnSpan; + var groupY = []; + + // For how many possible positions for this item there are. + for ( var i = 0; i < groupCount; i++ ) { + // Find the bigger value for each place it could fit. + groupY[i] = arrayMax( this.positions.slice( i, i + columnSpan ) ); + } + + return groupY; + } +}; + + +/** + * Find index of short column, the first from the left where this item will go. + * + * @param {Array.} positions The array to search for the smallest number. + * @param {number} buffer Optional buffer which is very useful when the height + * is a percentage of the width. + * @return {number} Index of the short column. + * @private + */ +Shuffle.prototype._getShortColumn = function( positions, buffer ) { + var minPosition = arrayMin( positions ); + for (var i = 0, len = positions.length; i < len; i++) { + if ( positions[i] >= minPosition - buffer && positions[i] <= minPosition + buffer ) { + return i; + } + } + return 0; +}; + + +/** + * Hides the elements that don't match our filter. + * @param {jQuery} $collection jQuery collection to shrink. + * @private + */ +Shuffle.prototype._shrink = function( $collection ) { + var $concealed = $collection || this._getConcealedItems(); + + each($concealed, function( item ) { + var $item = $(item); + var itemData = $item.data(); + + // Continuing would add a transitionend event listener to the element, but + // that listener would not execute because the transform and opacity would + // stay the same. + if ( itemData.scale === CONCEALED_SCALE ) { + return; + } + + itemData.scale = CONCEALED_SCALE; + + this.styleQueue.push({ + $item: $item, + point: itemData.point, + scale : CONCEALED_SCALE, + opacity: 0, + callback: function() { + $item.css( 'visibility', 'hidden' ); + } + }); + }, this); +}; + + +/** + * Resize handler. + * @private + */ +Shuffle.prototype._onResize = function() { + // If shuffle is disabled, destroyed, don't do anything + if ( !this.enabled || this.destroyed || this.isTransitioning ) { + return; + } + + // Will need to check height in the future if it's layed out horizontaly + var containerWidth = Shuffle._getOuterWidth( this.element ); + + // containerWidth hasn't changed, don't do anything + if ( containerWidth === this.containerWidth ) { + return; + } + + this.update(); +}; + + +/** + * Returns styles for either jQuery animate or transition. + * @param {Object} opts Transition options. + * @return {!Object} Transforms for transitions, left/top for animate. + * @private + */ +Shuffle.prototype._getStylesForTransition = function( opts ) { + var styles = { + opacity: opts.opacity + }; + + if ( this.supported ) { + styles[ TRANSFORM ] = Shuffle._getItemTransformString( opts.point, opts.scale ); + } else { + styles.left = opts.point.x; + styles.top = opts.point.y; + } + + return styles; +}; + + +/** + * Transitions an item in the grid + * + * @param {Object} opts options. + * @param {jQuery} opts.$item jQuery object representing the current item. + * @param {Point} opts.point A point object with the x and y coordinates. + * @param {number} opts.scale Amount to scale the item. + * @param {number} opts.opacity Opacity of the item. + * @param {Function} opts.callback Complete function for the animation. + * @param {Function} opts.callfront Function to call before transitioning. + * @private + */ +Shuffle.prototype._transition = function( opts ) { + var styles = this._getStylesForTransition( opts ); + this._startItemAnimation( opts.$item, styles, opts.callfront || $.noop, opts.callback || $.noop ); +}; + + +Shuffle.prototype._startItemAnimation = function( $item, styles, callfront, callback ) { + // Transition end handler removes its listener. + function handleTransitionEnd( evt ) { + // Make sure this event handler has not bubbled up from a child. + if ( evt.target === evt.currentTarget ) { + $( evt.target ).off( TRANSITIONEND, handleTransitionEnd ); + callback(); + } + } + + callfront(); + + // Transitions are not set until shuffle has loaded to avoid the initial transition. + if ( !this.initialized ) { + $item.css( styles ); + callback(); + return; + } + + // Use CSS Transforms if we have them + if ( this.supported ) { + $item.css( styles ); + $item.on( TRANSITIONEND, handleTransitionEnd ); + + // Use jQuery to animate left/top + } else { + // Save the deferred object which jQuery returns. + var anim = $item.stop( true ).animate( styles, this.speed, 'swing', callback ); + // Push the animation to the list of pending animations. + this._animations.push( anim.promise() ); + } +}; + + +/** + * Execute the styles gathered in the style queue. This applies styles to elements, + * triggering transitions. + * @param {boolean} noLayout Whether to trigger a layout event. + * @private + */ +Shuffle.prototype._processStyleQueue = function( noLayout ) { + var $transitions = $(); + + // Iterate over the queue and keep track of ones that use transitions. + each(this.styleQueue, function( transitionObj ) { + if ( transitionObj.skipTransition ) { + this._styleImmediately( transitionObj ); + } else { + $transitions = $transitions.add( transitionObj.$item ); + this._transition( transitionObj ); + } + }, this); + + + if ( $transitions.length > 0 && this.initialized ) { + // Set flag that shuffle is currently in motion. + this.isTransitioning = true; + + if ( this.supported ) { + this._whenCollectionDone( $transitions, TRANSITIONEND, this._movementFinished ); + + // The _transition function appends a promise to the animations array. + // When they're all complete, do things. + } else { + this._whenAnimationsDone( this._movementFinished ); + } + + // A call to layout happened, but none of the newly filtered items will + // change position. Asynchronously fire the callback here. + } else if ( !noLayout ) { + defer( this._layoutEnd, this ); + } + + // Remove everything in the style queue + this.styleQueue.length = 0; +}; + + +/** + * Apply styles without a transition. + * @param {Object} opts Transitions options object. + * @private + */ +Shuffle.prototype._styleImmediately = function( opts ) { + Shuffle._skipTransition(opts.$item[0], function() { + opts.$item.css( this._getStylesForTransition( opts ) ); + }, this); +}; + +Shuffle.prototype._movementFinished = function() { + this.isTransitioning = false; + this._layoutEnd(); +}; + +Shuffle.prototype._layoutEnd = function() { + this._fire( Shuffle.EventType.LAYOUT ); +}; + +Shuffle.prototype._addItems = function( $newItems, addToEnd, isSequential ) { + // Add classes and set initial positions. + this._initItems( $newItems ); + + // Add transition to each item. + this._setTransitions( $newItems ); + + // Update the list of + this.$items = this._getItems(); + + // Shrink all items (without transitions). + this._shrink( $newItems ); + each(this.styleQueue, function( transitionObj ) { + transitionObj.skipTransition = true; + }); + + // Apply shrink positions, but do not cause a layout event. + this._processStyleQueue( true ); + + if ( addToEnd ) { + this._addItemsToEnd( $newItems, isSequential ); + } else { + this.shuffle( this.lastFilter ); + } +}; + + +Shuffle.prototype._addItemsToEnd = function( $newItems, isSequential ) { + // Get ones that passed the current filter + var $passed = this._filter( null, $newItems ); + var passed = $passed.get(); + + // How many filtered elements? + this._updateItemCount(); + + this._layout( passed, true ); + + if ( isSequential && this.supported ) { + this._setSequentialDelay( passed ); + } + + this._revealAppended( passed ); +}; + + +/** + * Triggers appended elements to fade in. + * @param {ArrayLike.} $newFilteredItems Collection of elements. + * @private + */ +Shuffle.prototype._revealAppended = function( newFilteredItems ) { + defer(function() { + each(newFilteredItems, function( el ) { + var $item = $( el ); + this._transition({ + $item: $item, + opacity: 1, + point: $item.data('point'), + scale: DEFAULT_SCALE + }); + }, this); + + this._whenCollectionDone($(newFilteredItems), TRANSITIONEND, function() { + $(newFilteredItems).css( TRANSITION_DELAY, '0ms' ); + this._movementFinished(); + }); + }, this, this.revealAppendedDelay); +}; + + +/** + * Execute a function when an event has been triggered for every item in a collection. + * @param {jQuery} $collection Collection of elements. + * @param {string} eventName Event to listen for. + * @param {Function} callback Callback to execute when they're done. + * @private + */ +Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callback ) { + var done = 0; + var items = $collection.length; + var self = this; + + function handleEventName( evt ) { + if ( evt.target === evt.currentTarget ) { + $( evt.target ).off( eventName, handleEventName ); + done++; + + // Execute callback if all items have emitted the correct event. + if ( done === items ) { + callback.call( self ); + } + } + } + + // Bind the event to all items. + $collection.on( eventName, handleEventName ); +}; + + +/** + * Execute a callback after jQuery `animate` for a collection has finished. + * @param {Function} callback Callback to execute when they're done. + * @private + */ +Shuffle.prototype._whenAnimationsDone = function( callback ) { + $.when.apply( null, this._animations ).always( $.proxy( function() { + this._animations.length = 0; + callback.call( this ); + }, this )); +}; + + +/** + * Public Methods + */ + +/** + * The magic. This is what makes the plugin 'shuffle' + * @param {string|Function} [category] Category to filter by. Can be a function + * @param {Object} [sortObj] A sort object which can sort the filtered set + */ +Shuffle.prototype.shuffle = function( category, sortObj ) { + if ( !this.enabled || this.isTransitioning ) { + return; + } + + if ( !category ) { + category = ALL_ITEMS; + } + + this._filter( category ); + + // How many filtered elements? + this._updateItemCount(); + + // Shrink each concealed item + this._shrink(); + + // Update transforms on .filtered elements so they will animate to their new positions + this.sort( sortObj ); +}; + + +/** + * Gets the .filtered elements, sorts them, and passes them to layout. + * @param {Object} opts the options object for the sorted plugin + */ +Shuffle.prototype.sort = function( opts ) { + if ( this.enabled && !this.isTransitioning ) { + this._resetCols(); + + var sortOptions = opts || this.lastSort; + var items = this._getFilteredItems().sorted( sortOptions ); + + this._layout( items ); + + this.lastSort = sortOptions; + } +}; + + +/** + * Reposition everything. + * @param {boolean} isOnlyLayout If true, column and gutter widths won't be + * recalculated. + */ +Shuffle.prototype.update = function( isOnlyLayout ) { + if ( this.enabled && !this.isTransitioning ) { + + if ( !isOnlyLayout ) { + // Get updated colCount + this._setColumns(); + } + + // Layout items + this.sort(); + } +}; + + +/** + * Use this instead of `update()` if you don't need the columns and gutters updated + * Maybe an image inside `shuffle` loaded (and now has a height), which means calculations + * could be off. + */ +Shuffle.prototype.layout = function() { + this.update( true ); +}; + + +/** + * New items have been appended to shuffle. Fade them in sequentially + * @param {jQuery} $newItems jQuery collection of new items + * @param {boolean} [addToEnd=false] If true, new items will be added to the end / bottom + * of the items. If not true, items will be mixed in with the current sort order. + * @param {boolean} [isSequential=true] If false, new items won't sequentially fade in + */ +Shuffle.prototype.appended = function( $newItems, addToEnd, isSequential ) { + this._addItems( $newItems, addToEnd === true, isSequential !== false ); +}; + + +/** + * Disables shuffle from updating dimensions and layout on resize + */ +Shuffle.prototype.disable = function() { + this.enabled = false; +}; + + +/** + * Enables shuffle again + * @param {boolean} [isUpdateLayout=true] if undefined, shuffle will update columns and gutters + */ +Shuffle.prototype.enable = function( isUpdateLayout ) { + this.enabled = true; + if ( isUpdateLayout !== false ) { + this.update(); + } +}; + + +/** + * Remove 1 or more shuffle items + * @param {jQuery} $collection A jQuery object containing one or more element in shuffle + * @return {Shuffle} The shuffle object + */ +Shuffle.prototype.remove = function( $collection ) { + + // If this isn't a jquery object, exit + if ( !$collection.length || !$collection.jquery ) { + return; + } + + function handleRemoved() { + // Remove the collection in the callback + $collection.remove(); + + // Update things now that elements have been removed. + this.$items = this._getItems(); + this._updateItemCount(); + + this._fire( Shuffle.EventType.REMOVED, [ $collection, this ] ); + + // Let it get garbage collected + $collection = null; + } + + // Hide collection first. + this._toggleFilterClasses( $(), $collection ); + this._shrink( $collection ); + + this.sort(); + + this.$el.one( Shuffle.EventType.LAYOUT + '.' + SHUFFLE, $.proxy( handleRemoved, this ) ); +}; + + +/** + * Destroys shuffle, removes events, styles, and classes + */ +Shuffle.prototype.destroy = function() { + // If there is more than one shuffle instance on the page, + // removing the resize handler from the window would remove them + // all. This is why a unique value is needed. + $window.off('.' + this.unique); + + // Reset container styles + this.$el + .removeClass( SHUFFLE ) + .removeAttr('style') + .removeData( SHUFFLE ); + + // Reset individual item styles + this.$items + .removeAttr('style') + .removeData('point') + .removeData('scale') + .removeClass([ + Shuffle.ClassName.CONCEALED, + Shuffle.ClassName.FILTERED, + Shuffle.ClassName.SHUFFLE_ITEM + ].join(' ')); + + // Null DOM references + this.$items = null; + this.$el = null; + this.sizer = null; + this.element = null; + + // Set a flag so if a debounced resize has been triggered, + // it can first check if it is actually destroyed and not doing anything + this.destroyed = true; +}; + + +// Plugin definition +$.fn.shuffle = function( opts ) { + var args = Array.prototype.slice.call( arguments, 1 ); + return this.each(function() { + var $this = $( this ); + var shuffle = $this.data( SHUFFLE ); + + // If we don't have a stored shuffle, make a new one and save it + if ( !shuffle ) { + shuffle = new Shuffle( this, opts ); + $this.data( SHUFFLE, shuffle ); + } else if ( typeof opts === 'string' && shuffle[ opts ] ) { + shuffle[ opts ].apply( shuffle, args ); + } + }); +}; + + +// http://stackoverflow.com/a/962890/373422 +function randomize( array ) { + var tmp, current; + var top = array.length; + + if ( !top ) { + return array; + } + + while ( --top ) { + current = Math.floor( Math.random() * (top + 1) ); + tmp = array[ current ]; + array[ current ] = array[ top ]; + array[ top ] = tmp; + } + + return array; +} + + +// You can return `undefined` from the `by` function to revert to DOM order +// This plugin does NOT return a jQuery object. It returns a plain array because +// jQuery sorts everything in DOM order. +$.fn.sorted = function(options) { + var opts = $.extend({}, $.fn.sorted.defaults, options); + var arr = this.get(); + var revert = false; + + if ( !arr.length ) { + return []; + } + + if ( opts.randomize ) { + return randomize( arr ); + } + + // Sort the elements by the opts.by function. + // If we don't have opts.by, default to DOM order + if ( $.isFunction( opts.by ) ) { + arr.sort(function(a, b) { + + // Exit early if we already know we want to revert + if ( revert ) { + return 0; + } + + var valA = opts.by($(a)); + var valB = opts.by($(b)); + + // If both values are undefined, use the DOM order + if ( valA === undefined && valB === undefined ) { + revert = true; + return 0; + } + + if ( valA < valB || valA === 'sortFirst' || valB === 'sortLast' ) { + return -1; + } + + if ( valA > valB || valA === 'sortLast' || valB === 'sortFirst' ) { + return 1; + } + + return 0; + }); + } + + // Revert to the original array if necessary + if ( revert ) { + return this.get(); + } + + if ( opts.reverse ) { + arr.reverse(); + } + + return arr; +}; + + +$.fn.sorted.defaults = { + reverse: false, // Use array.reverse() to reverse the results + by: null, // Sorting function + randomize: false // If true, this will skip the sorting and return a randomized order in the array +}; + +return Shuffle; + +}); \ No newline at end of file diff --git a/docs/js/mydoc_scroll.html b/docs/js/mydoc_scroll.html new file mode 100644 index 000000000..39fa55e99 --- /dev/null +++ b/docs/js/mydoc_scroll.html @@ -0,0 +1,240 @@ +--- +title: Scroll layout +type: scroll +keywords: json, scrolling, scrollto, jquery plugin +tags: special_layouts +last_updated: November 30, 2015 +summary: "This page demonstrates how you the integration of a script called ScrollTo, which is used here to link definitions of a JSON code sample to a list of definitions for that particular term. The scenario here is that the JSON blocks are really long, with extensive nesting and subnesting, which makes it difficult for tables below the JSON to adequately explain the term in a usable way." +--- + +{% if site.output == "pdf" %} +{{site.data.alerts.note}} The content on this page doesn't display well on PDF, but I included it anyway so you could see the problems this layout poses if you're including it in PDF. {{site.data.alerts.end}} +{% endif %} + +{% if site.output == "web" %} + + + + + + + + + + +
+
+ + +
+
+ + +{{site.data.alerts.note}} This was mostly an experiment to see if there was a better way to document a long JSON code example. I haven't actually used this approach in my own documentation.{{site.data.alerts.end}} + +{% endif %} + diff --git a/docs/js/toc.js b/docs/js/toc.js new file mode 100644 index 000000000..a54d172ef --- /dev/null +++ b/docs/js/toc.js @@ -0,0 +1,82 @@ +// https://github.com/ghiculescu/jekyll-table-of-contents +(function($){ + $.fn.toc = function(options) { + var defaults = { + noBackToTopLinks: false, + title: '', + minimumHeaders: 3, + headers: 'h1, h2, h3, h4', + listType: 'ol', // values: [ol|ul] + showEffect: 'show', // values: [show|slideDown|fadeIn|none] + showSpeed: 'slow' // set to 0 to deactivate effect + }, + settings = $.extend(defaults, options); + + var headers = $(settings.headers).filter(function() { + // get all headers with an ID + var previousSiblingName = $(this).prev().attr( "name" ); + if (!this.id && previousSiblingName) { + this.id = $(this).attr( "id", previousSiblingName.replace(/\./g, "-") ); + } + return this.id; + }), output = $(this); + if (!headers.length || headers.length < settings.minimumHeaders || !output.length) { + return; + } + + if (0 === settings.showSpeed) { + settings.showEffect = 'none'; + } + + var render = { + show: function() { output.hide().html(html).show(settings.showSpeed); }, + slideDown: function() { output.hide().html(html).slideDown(settings.showSpeed); }, + fadeIn: function() { output.hide().html(html).fadeIn(settings.showSpeed); }, + none: function() { output.html(html); } + }; + + var get_level = function(ele) { return parseInt(ele.nodeName.replace("H", ""), 10); } + var highest_level = headers.map(function(_, ele) { return get_level(ele); }).get().sort()[0]; + var return_to_top = ' '; + + var level = get_level(headers[0]), + this_level, + html = settings.title + " <"+settings.listType+">"; + headers.on('click', function() { + if (!settings.noBackToTopLinks) { + window.location.hash = this.id; + } + }) + .addClass('clickable-header') + .each(function(_, header) { + this_level = get_level(header); + if (!settings.noBackToTopLinks && this_level === highest_level) { + $(header).addClass('top-level-header').after(return_to_top); + } + if (this_level === level) // same level as before; same indenting + html += "
  • " + header.innerHTML + ""; + else if (this_level <= level){ // higher level than before; end parent ol + for(i = this_level; i < level; i++) { + html += "
  • " + } + html += "
  • " + header.innerHTML + ""; + } + else if (this_level > level) { // lower level than before; expand the previous to contain a ol + for(i = this_level; i > level; i--) { + html += "<"+settings.listType+">
  • " + } + html += "" + header.innerHTML + ""; + } + level = this_level; // update for the next one + }); + html += ""; + if (!settings.noBackToTopLinks) { + $(document).on('click', '.back-to-top', function() { + $(window).scrollTop(0); + window.location.hash = ''; + }); + } + + render[settings.showEffect](); + }; +})(jQuery); \ No newline at end of file diff --git a/docs/licenses/LICENSE b/docs/licenses/LICENSE new file mode 100644 index 000000000..e04b3d02e --- /dev/null +++ b/docs/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Tom Johnson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/licenses/LICENSE-BSD-NAVGOCO.txt b/docs/licenses/LICENSE-BSD-NAVGOCO.txt new file mode 100644 index 000000000..7fdefc390 --- /dev/null +++ b/docs/licenses/LICENSE-BSD-NAVGOCO.txt @@ -0,0 +1,27 @@ +/* This license pertains to the Navgoco jQuery component used for the sidebar. */ + +Copyright (c) 2013, Christodoulos Tsoulloftas, http://www.komposta.net +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/docs/moderation-flow-post.png b/docs/moderation-flow-post.png deleted file mode 100644 index 18860abd3..000000000 Binary files a/docs/moderation-flow-post.png and /dev/null differ diff --git a/docs/moderation-flow-pre.png b/docs/moderation-flow-pre.png deleted file mode 100644 index c2be4cc0c..000000000 Binary files a/docs/moderation-flow-pre.png and /dev/null differ diff --git a/docs/moderation-flow.xml b/docs/moderation-flow.xml deleted file mode 100644 index 08d4a28d0..000000000 --- a/docs/moderation-flow.xml +++ /dev/null @@ -1 +0,0 @@ -7VlNc9sgEP01nmkP9ejD+vAxdpP20M6kk0PbI5GIRIqEilFs99d3EWAJSc4krp24nebgEQ9Y4O1bdqVM/GWx+cBRlX9mKaYTz0k3E//9xPOiuQO/EtgqIPAiBWScpApyW+CG/MIa1POymqR4ZQ0UjFFBKhtMWFniRFgY4pyt7WF3jNqrVijDA+AmQXSIfiWpyBUaB06Lf8Qky83KrqN7blHyI+OsLvV6E8+/a/5Ud4GMLT1+laOUrTuQfznxl5wxoZ6KzRJTSa2hTc272tO72zfHpXjShEDNeEC0xmbLzcbE1pCxzonANxVKZHsN/p74i1wUFFouPGoDmAu82bsLd3c2kAxmBRZ8C0PMBKMXLRfXmFi35LuG/LxDvB/MtdO1w7Od7fbQ8KDPPc6BN6AgYUUht+6FFBZapOQBHjP5+KbE67cGB8OdrgFvsAroFRqL0zPo2QR6Ro9dAr0RAnfgnxAYP4dAVFWcPeD0HFmcOTaNvjt7QRrnz6GR43u4+P4OGt3wJdXouiNXmuLojkkyO7SEP2tmOt6tmjR0AQPcqNq0nS2nysptH6gNcM3xuwJyIUeCsLLjmbo/BbCBGcDUBve4EBwibF+tBGc/8JJRxgEpWSn9e0co7UGIkqyEZgIOxIAvpHsJpLsL3VGQNKX7xHEERfi2IOIRPcxH9HAUOQwTGk4h2esm4yJnGSsRvWzRRZPBsbTg2IzjDRHfJDyNAt38rkcBFXzb7ZNt07kSiIsLWZlIN1C0WpHEwFeE7syXqRmkXQeI7pdm7rEQW10uoVowgNoDfGKsGhUGFCFO87frMUVNG/CSk8edCxSymidmlCnLEM+w6GJDEXBMISAebPt/5NNwEOKfO2HnfKkx4P9Q/HhOPA2sEJqHwxByghOFUDRyo/ZjqlVuK++9VD+uSBNkbjfCpoEVY1aAqYA7TMjxiI7jcWd0uB4rRg32ZLnrFa4ZadLSeCkXxD0Pqs3rSd3Svmcn6mXhmd8zpI48MNSoYXfqpwlkWAEOBHL6siOcvepbhDPgYGnKN+dGcIyKf+lGCkOb7PlISndOlNK9YYV3RVGWkTJrvgk0rK/gMWXwUzLpgVuuekWOC/2eLltMQfLVvJtAfkICmQ7cBS/rFW58IF6mkA56IRyOCHq0jnaOIWj//G79qeNE9s3vx6EBrjEncEoZDk1+OCwfeCP5IDijfBAeng9sO6dLB97sHJQzi2Ld7gtjXxVxeBFhPqN1VTP/L5pnieYJXyJft8g8rjzOqcY8WB5+78PtLDqWPqDZfgVXw9v/NPiXvwE=3VlNc6M4EP01rpo9xIXEh/Ex9iYzh52qbOWwu0cNKJgZQB4hYnt+/TRGAiRBQhLbmZ0cUqgltcTr160nPHPX+f4jJ9vNZxbTbIadeD9z/5xhvFg68L82HBqDjxeNIeFp3JhQZ7hPf1BplPOSKo1pqQ0UjGUi3erGiBUFjYRmI5yznT7sgWX6qluSUMtwH5HMtv6TxmLTWEPf6eyfaJps1MrIkT1fSPQt4awq5Hoz7D4c/5runChfcny5ITHb9Uzuzcxdc8ZE85Tv1zSroVWwNfNuR3rbfXNaiCkTcDPhkWQVVTs+7kscFBa7TSro/ZZEdXsH4Z65q43IM2gheJQOKBd0P7oJ1L4aMIaynAp+gCFyAlboSbbgULZ3HfYokLZND3dXBYTIeCet7+6d4UG+9jAErgVBxPK83joOMlhoFaeP8JjUjx8KuvtD2cFxr8vCDVYBukJjdX4EXR3AFqw+gHgAwNb4FgC9lwBItlvOHmn8K6LoYV+D0cOXhDEYSMUGogdWY9lDJfheMdVxVR6r5zUMQIvtvuvsIG28fDENlTLcsVJc5VDDOREpK3qRqcw5YLP8gK3Z4UgIISBCj1UpOPtG1yxjHCwFK+r4PqRZZphIliYFNCMIIAX7qg5vCmX6WnbkaRxnY+Q4ASMCPa+G6tJygA+noMPCToUYzijZZFxsWMIKkt101tXx4KG1A0cHnO5T8W9tni982fxPjgIk+KHfV7dVZykIF9f1gVpHISNlmUbKfJtmrfsiVoNk5MAi+2s3X6kQB3nKk0owMHUv8Bdj20FewNnpHP/aHnUWd/leY6LFtmQVj6iWUbDZhArN9JKzyR2kwFtCG1qZ/rmXfM7fFQX7b5RFAOJcr6zYD+xMcvzzZNLyeY3TI3DH8lGonyamyjU0dzzcT7a5H7qyfUd5Cu9RR0NPwitn7mA9DedhM602mPMmJIFnJwFyXhpBd6kXQk+B2oufP1AIn9JncrE7lh5Pt70ecHVxMPOteTU5qS9sDT+BoSl9z3DUAGI5OpKpBWASvxSc7yuiA9/QgMrFc+LFQyfIMYQsDNZKBDr3glOS/04VLTCEwXJAGDhnEgbIvrBcpp7ptUyTDnrBOhawCaXJPUVpQp4eChxeqDQFry1NC8OPe77K5NnMOIWI7GtI9L+UiRa/dGZim5l4mIWcZqDbHnXvb0pv+x54m5EkgYhgp62p73pLNhIuGNByZ7slo8X7VD/Hqn6vY5YqcFrRC4YBn1jBJlNwuBwZ346C5SvLWmj4OaPgshX9E1+cOP1KI/GLfnFy9XuRf9EPd3iCcL1MLrVKAulKAqHwhVef59Jv4JvASGW/TPaZFytTDEzNPlMGuaY6OV36YVvrX4Q2cKGGzO9TJ3SUEhm9UBvS9PWFe0ASNHVoXJkaV1B34JZwGmVqruSZtXcyi/yRLZ+BRfidWDT5GnNSrnhPUwUZAXTORhVzJdPFZKoEI1t+M1Wg2f0A2gzvfmR2b34C \ No newline at end of file diff --git a/docs/frontend/PLUGINS.md b/docs/pages/plugins/client.md similarity index 92% rename from docs/frontend/PLUGINS.md rename to docs/pages/plugins/client.md index 0edc4d455..dce2d58af 100644 --- a/docs/frontend/PLUGINS.md +++ b/docs/pages/plugins/client.md @@ -1,14 +1,22 @@ -# Plugins -We can build plugins to extend the functionality of Talk. +--- +title: Talk +keywords: homepage +sidebar: talk_sidebar +permalink: plugins-client.html +summary: +--- +We can build plugins to extend the client side functionality of Talk. -This guide is a walkthrough of our plugin architecture and components that we provide that allow you to build on top of Core coral components without having to understand the concepts there in. It is organized into three sections: +The ultimate goal of our client side plugin architecture is to allow developers +to build on existing concepts without needing to understand core code while +providing complete power and flexibility of javascript, css and html. * [Plugin Architecture](#plugin-architecture) * Using our building block components * [Reactions](#reactions) * [Styling](#styling-plugins) -Advanced users will quickly realize that our plugins have complete access to core code. If you would like to write advanced plugins that reach outside of our published API as described in this document, please see [our notes on experimental pluginss](PLUGINS-experimental.md). +Advanced users will quickly realize that our plugins have complete access to core code. If you would like to write advanced plugins that reach outside of our published API as described in this document, please see [our notes on experimental plugins](/plugins/EXPERIMENTAL.md). Under the hood our plugins are powered by *React*, *Redux* and *GraphQL*. We can also build them with simple vanilla javascript. @@ -119,9 +127,9 @@ export default { ``` In this example we add our reaction component to the `commentReaction` Slot - + #### Our Reaction component: - + ```js import React from 'react'; import {withReaction} from 'coral-plugin-api'; @@ -156,14 +164,14 @@ export default withReaction('love')(LoveButton); ``` -This feature introduces `withReaction` HOC. `withReaction` takes, as argument, a reaction string and it allows our component to receive specific props for handling reactions. +This feature introduces `withReaction` HOC. `withReaction` takes, as argument, a reaction string and it allows our component to receive specific props for handling reactions. * `postReaction` - Posts the reaction - + * `deleteReaction` - Removes the reaction - + * `alreadyReacted` - A function that returns a boolean. - + * `count` - The reaction count diff --git a/docs/plugins-client.md b/docs/plugins-client.md new file mode 100644 index 000000000..4f4e784ec --- /dev/null +++ b/docs/plugins-client.md @@ -0,0 +1,327 @@ +--- +title: Client Plugin API +keywords: homepage +sidebar: talk_sidebar +permalink: plugins-client.html +summary: +--- +We can build plugins to extend the client side functionality of Talk. + +The ultimate goal of our client side plugin architecture is to allow developers +to build on existing concepts without needing to understand core code while +providing complete power and flexibility of javascript, css and html. + +* [Plugin Architecture](#plugin-architecture) +* Using our building block components +* [Reactions](#reactions) +* [Styling](#styling-plugins) + +Advanced users will quickly realize that our plugins have complete access to core code. If you would like to write advanced plugins that reach outside of our published API as described in this document, please see [our notes on experimental plugins](/plugins/EXPERIMENTAL.md). + +Under the hood our plugins are powered by *React*, *Redux* and *GraphQL*. We can also build them with simple vanilla javascript. + +## Plugin Architecture + +The plugins live in the `/plugins` folder. Each plugin must have an `index.js` file and two folders `client` and `server`. + +### The Client Folder +The frontend of our plugin lives inside the `client` folder. The `client` folder must have an `index.js` file that exports the configuration of our plugin. + +``` +my-plugin/ + ├── client/ + │ └── index.js <-- index for client side functionality + ├── server/ + └── index.js <-- base plugin index +``` + +For now our base plugin `index.js` file should look like this: + +```js +export default { + // We will add more here later. +}; +``` + +### Creating a Component + +We can add our components (or any other javascript code) within the `client` folder. + +``` +my-plugin/ + ├── client/ + │ ├── MyComponent.js + │ └── index.js + ├── server/ + └── index.js +``` + +Our component could look like this: + +```js +import React, {Component} from 'react'; + +class MyButton extends Component { + render() { + return ; + } +} + +export default MyButton; +``` + +Here we create a component that renders a `button`. Now that we created our component we need to specify where it should get injected within Talk! + +To tell Talk where that Component should get injected we need to specify which *Slots* to insert it into. + +```js +import React from 'react'; +export default = () => ; +``` + +### Slots +In Talk we have defined specific *Slots* where we can inject components. + +Here is how we specify our slots config in `my-plugin/index.js` + +```js +import MyButton from './MyButton'; + +export default { + slots: { + commentDetail: [MyButton] + } +}; +``` + +Here I’m specifying that the MyComponent Component will take place within the `commentDetail` in Talk. + +`commentDetail` it’s a specific slot in the CommentStream. It means that it will be embedded inside de comment detail. + +Slots properties take an`Array` so we can add as many components as we want. + +## Building Blocks (TBD) + +`Note: the concepts in this section are still to be implemented. Code samples are for discussion and may change.` + +In order to allow you to build more complex plugins, we have wrapped some of our functionality in higher order components that expose a simple api. + +## Reactions + +Reactions provide users the ability to 'like', 'respect', etc... comments. + +Note: some server side work will need to accompany this client side component. See the like and respect plugins as examples. + +### Building Reactions + +#### Our `client/index.js` : + +```js +import LoveButton from './LoveButton'; + +export default { + slots: { + commentReactions: [LoveButton] + } +}; + +``` +In this example we add our reaction component to the `commentReaction` Slot + +#### Our Reaction component: + +```js +import React from 'react'; +import {withReaction} from 'coral-plugin-api'; + +class LoveButton extends React.Component { + handleClick = () => { + const { + postReaction, + deleteReaction, + alreadyReacted + } = this.props; + + if (alreadyReacted()) { + deleteReaction(); + } else { + postReaction(); + } + }; + + render() { + const {count} = this.props; + return ( + + ); + } +} + +export default withReaction('love')(LoveButton); +``` + + +This feature introduces `withReaction` HOC. `withReaction` takes, as argument, a reaction string and it allows our component to receive specific props for handling reactions. + + * `postReaction` - Posts the reaction + + * `deleteReaction` - Removes the reaction + + * `alreadyReacted` - A function that returns a boolean. + + * `count` - The reaction count + + +For full reference: Please, check `coral-plugin-love`: [LoveButton.js](https://github.com/coralproject/talk/blob/master/plugins/coral-plugin-love/client/LoveButton.js) + +### Comment Stream + +Comment streams may be created with filtering and ordering in place: + +* filter by user +* filter by tag +* sort by date ascending / descending + +### Comment Commit hooks + +// docs for the pre/post comment submit commit hooks + +### Mod Queues + +Moderation queues can be added via configuration objects passed in through plugins. + +Basic mod queues will resemble the current moderation queues but can be generated from different lists of comments. + +* filter by user tag +* filter by comment tag +* filter by comment status +* Custom queries (paired with back end plugins that provide queries to get the data) + +#### Advanced mod queues + +Advanced mod queues can be created giving plugin authors the power to create the cards that appear in the queue, create actions and custom buttons, etc... + +### Custom Configuration + +Plugins may rely on configuration options that admins/moderators can set in the Configuration section. + +Basic settings can be added via json configuration in a plugin. + +* Setting headline +* Setting description +* Setting input type +* Default value +* Variable name + +#### Advanced Custom Configuration (low prioritiy) + +Users can inject configuration interfaces that they create into the configuration allowing for more advanced configuration. + + +## Styling Plugins +Talk uses CSS Modules. This basically means that you can also add your CSS Module to your plugin without colliding with the rest of Talk! + +##### My Component +```js +import styles from './style.css'; + +class MyCoralButton extends Component { + render() { + return ; + } +} +```` + +Our `style.css` should could look like this. +```css + +.button { + background: coral; + border-radius: 3px; +} +``` + +## Plugin Hooks +The plugins injected in the CommentBox such as `commentInputDetailArea` will inherit through props tools for handling hooks. + +### Available hook types: +`preSubmit` : To perform actions before submitting the comment. +`postSubmit` : To perform actions after submitting the comment. + +### Register Hooks +`registerHook` is a function that takes: the hook type, a hook function and returns the hook data. + +#### Usage: +```js + this.addCommentTagHook = this.props.registerHook('postSubmit', (data) => { + const {comment} = data.createComment; + this.props.addCommentTag({ + id: comment.id, + tag: 'OFF_TOPIC' + }); + }); +``` + +### Unregister Hooks + +`unregisterHook` will remove the hook. + +```js + this.props.unregisterHook(this.addCommentTagHook); +``` + +### The server folder and the index file +Read more about the `/server` and how to extend Talk here. +[talk/PLUGINS.md at master · coralproject/talk · GitHub](https://github.com/coralproject/talk/blob/master/PLUGINS.md) + +## ESlint and Babel +In talk we use `eslint:recommended` and Babel with the latest ECMAScript Features. But you can use your own! +While building your plugin you need to specify a `.eslintrc.json` file and a`.babelrc` file. + +### `.eslintrc.json` +```json +{ + "env": { + "browser": true, + "es6": true, + "mocha": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + } + }, + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} +``` + +### `.babelrc ` +```json +{ + "presets": [ + "es2015" + ], + "plugins": [ + "add-module-exports", + "transform-class-properties", + "transform-decorators-legacy", + "transform-object-assign", + "transform-object-rest-spread", + "transform-async-to-generator", + "transform-react-jsx" + ] +} +``` diff --git a/docs/frontend/PLUGINS-experimental.md b/docs/plugins-experimental.md similarity index 52% rename from docs/frontend/PLUGINS-experimental.md rename to docs/plugins-experimental.md index 895be5900..766f5f050 100644 --- a/docs/frontend/PLUGINS-experimental.md +++ b/docs/plugins-experimental.md @@ -1,16 +1,27 @@ -# Experimental plugins +--- +title: Experimental Plugin API +keywords: homepage +sidebar: talk_sidebar +permalink: plugins-experimental.html +summary: +--- Talk plugins are, in essence, small programs that hook into the core application in a variety of ways. Ultimately, this code can do anything that javascript is capable of. In addition, plugins can import any core code to hook into talk at any level. -If you want to write plugins that integrate with core code beyond the api described in [PLUGINS.md](PLUGINS.md), please keep the following things in mind: +If you want to write plugins that integrate with core code beyond the api described +in the client api or server api section, please keep the following things in mind: * core code may change and break your plugin -* you may introduce inefficiencies with your plugin that could hurt performance/crash Talk +* you may introduce inefficiencies with your plugin that could hurt performance/ +crash Talk * you may cause bugs in other areas of Talk -If you'd like to build a supported plugin but don't have the hooks you need, please file an issue on this repo and we can discuss deepening the supported plugin api! +If you'd like to build a supported plugin but don't have the hooks you need, +please file an issue on this repo and we can discuss deepening the supported +plugin api! -With that said, here's some of the prime experimental integration points: +With that said, if you are still undeterred, here are some of the prime +experimental integration points: ## Reducers and Actions : Redux @@ -50,53 +61,3 @@ const mapDispatchToProps = dispatch => export default connect(mapStateToProps, mapDispatchToProps)(OffTopicCheckbox); ``` - -## ESlint and Babel -In talk we use `eslint:recommended` and Babel with the latest ECMAScript Features. But you can use your own! -While building your plugin you need to specify a `.eslintrc.json` file and a`.babelrc` file. - -#### `.eslintrc.json` -```json -{ - "env": { - "browser": true, - "es6": true, - "mocha": true - }, - "parserOptions": { - "sourceType": "module", - "ecmaFeatures": { - "experimentalObjectRestSpread": true, - "jsx": true - } - }, - "parser": "babel-eslint", - "plugins": [ - "react" - ], - "rules": { - "react/jsx-uses-react": "error", - "react/jsx-uses-vars": "error", - "no-console": ["warn", { "allow": ["warn", "error"] }] - } -} -```` - - -#### `. babelrc ` -```json -{ - "presets": [ - "es2015" - ], - "plugins": [ - "add-module-exports", - "transform-class-properties", - "transform-decorators-legacy", - "transform-object-assign", - "transform-object-rest-spread", - "transform-async-to-generator", - "transform-react-jsx" - ] -} -```` diff --git a/docs/plugins-server.md b/docs/plugins-server.md new file mode 100644 index 000000000..7b670429f --- /dev/null +++ b/docs/plugins-server.md @@ -0,0 +1,390 @@ +--- +title: Server Plugin API +keywords: homepage +sidebar: talk_sidebar +permalink: plugins-server.html +summary: +--- + +## The Server Folder +The server functionality of our plugin lives inside the `server` folder. The `server` +folder must have an `index.js` file that exports the configuration of our plugin. + +``` +my-plugin/ + ├── client/ + │ └── ... <-- client side plugin files + ├── server/ + │ └── index.js <-- index for server side functionality + │ └── ... <-- other server side plugin files + └── index.js <-- base plugin index +``` + +## Specification + +Each plugin should export a single object with all hooks available on it. + +_**Note: You will have access to the whole core and other plugin's typeDefs, +context, loaders, mutators, resolvers, hooks. This is intentional, as it +encourages composing plugins to merge functionality, like a Slack plugin which +provides a Slack notify context function as well as having the loader for +comments.**_ + +The following are the hooks available: + +### GraphQL hooks + +#### Field: `typeDefs` + +```graphql +enum COLOUR { + RED + BLUE +} + +type Person { + name: String! + colour: COLOUR! +} + +type RootMutation { + createPerson(name: String!): Person +} + +type RootQuery { + people: [Person!] +} + +type Subscription { + leader: Person +} +``` + +Thanks to [gql-merge](https://www.npmjs.com/package/gql-merge) the contents of +`typeDefs` should be a string that will be _merged_ with the existing type +definitions. `enum`'s will be appended to, types will be appended, and new types +will be added. + +#### Field: `context` + +```js +{ + Slack: (context) => ({ + notify: (message) => { + // return a promise after we're done sending notifications. + } + }) +} +``` + +Any property provided here will be added to the context parameter available +inside all resolvers, loaders, mutators, and of course, other context based +plugins. + +The top level item must accept a context for the request which it should use to +configure the context plugin before it would be mounted at `context.plugins`. +This plugin above would mount at: `context.plugins.Slack`, or, if you're using +[object destructuring](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), `{plugins: {Slack}}`. + +#### Field: `loaders` + +```js +(context) => ({ + People: { + load: () => db.people.find({user: context.user}) + } +}) +``` + +Loaders should be provided as a function which returns a map which is used in +the resolvers function. These must return a promise or a value. + +#### Field: `mutators` + +```js +(context) => ({ + People: { + create: (name) => { + return db.people.insert({user: context.user, name}); + } + } +}) +``` + +Mutators should be provided as a function which returns a map which is used in +the resolvers function. These must return a promise or a value. + +#### Field: `resolvers` + +```js +{ + Person: { + name(obj, args, context) { + return obj.name; + }, + colour(obj, args, context) { + // Bill likes the colour red, everyone else likes blue. + return obj.name === 'bill' ? 'RED' : 'BLUE'; + } + }, + RootQuery: { + people(obj, args, {loaders: {People}}) { + return People.load(); + } + }, + RootMutation: { + createPerson(obj, {name}, {mutators: {People}}) { + return People.create(name); + } + } +} +``` + +Should return a resolver map as described in the +[Apollo Docs](http://dev.apollodata.com/tools/graphql-tools/resolvers.html#Resolver-map). + +This will merge with the existing resolvers in core and from previous plugins. + +#### Field: `hooks` + +```js +{ + RootMutation: { + createPerson: { + post: async (obj, args, {plugins: {Slack}}, info, person) { + if (!person) { + return person; + } + + await Slack.notify(`A new person just was created with name ${person.name}`); + + return person; + } + } + } +} +``` + +Hooks here are pretty special, for each resolver field, you can specify a +pre/post hook that will execute pre and post field resolution. + +If your post function accepts four parameters, then it can modify the field +result. It is *required* that the function resolves a promise (or returns) with +the modified value or simply the original if you didn't modify it. + +#### Field: `setupFunctions` + +```js +setupFunctions: { + leader: (options, args) => ({ + leader: { + filter: (person) => person.place === 1 + }, + }), +} +``` + +Setup functions allow you to create filters that control which pubsub.publish() events +send data to the client. If the type in question contains args, clients may subscribe using those arguments to further filter their subscription. + +For more information, see the [Apollo Docs](https://github.com/apollographql/graphql-subscriptions). + +### Routes + +#### Field: `router` + +```js +(router) => { + router.get('/api/v1/people', (req, res) => { + res.json({people: [{name: 'Bob'}]}); + }); +} +``` + +The Router hook allows you to create a function that accepts the base express +router where you can mount any amount of middleware/routes to do any form of +action needed by external applications. + +### Authorization middleware + +The following example creates the requisite callback route and passport +strategy needed to enable Facebook Authorization: + +```js +const authorization = require('middleware/authorization'); + +module.exports = { + router(router) { + router.get('/api/v1/people', authorization.needed('ADMIN'), (req, res) => { + res.json({people: [{name: 'SECRET PEOPLE'}]}); + }); + } +} +``` + +#### Field: `passport` + +```js +const FacebookStrategy = require('passport-facebook').Strategy; +const UsersService = require('services/users'); +const {ValidateUserLogin, HandleAuthPopupCallback} = require('services/passport'); + +module.exports = { + passport(passport) { + passport.use(new FacebookStrategy({ + clientID: process.env.TALK_FACEBOOK_APP_ID, + clientSecret: process.env.TALK_FACEBOOK_APP_SECRET, + callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`, + passReqToCallback: true, + profileFields: ['id', 'displayName', 'picture.type(large)'] + }, async (req, accessToken, refreshToken, profile, done) => { + + let user; + try { + user = await UsersService.findOrCreateExternalUser(profile); + } catch (err) { + return done(err); + } + + return ValidateUserLogin(profile, user, done); + })); + }, + router(router) { + + // Note that we have to import the passport instance here, it is + // instantiated after all the strategies have been mounted. + const {passport} = require('services/passport'); + + /** + * Facebook auth endpoint, this will redirect the user immediatly to facebook + * for authorization. + */ + router.get('/facebook', passport.authenticate('facebook', {display: 'popup', authType: 'rerequest', scope: ['public_profile']})); + + /** + * Facebook callback endpoint, this will send the user a html page designed to + * send back the user credentials upon sucesfull login. + */ + router.get('/facebook/callback', (req, res, next) => { + + // Perform the facebook login flow and pass the data back through the opener. + passport.authenticate('facebook', HandleAuthPopupCallback(req, res, next))(req, res, next); + }); + } +}; +``` + +## Full Example + +Contents of `plugins.json`: + +```json +{ + "server": [ + "people" + ] +} +``` + +Located in `plugins/people/index.js`: + +```js +module.exports = { + typeDefs: ` + enum COLOUR { + RED + BLUE + } + + type Person { + name: String! + colour: COLOUR! + } + + type RootMutation { + createPerson(name: String!): Person + } + + type RootQuery { + people: [Person!] + } + + type Subscription { + leader: Person + } + `, + context: { + Slack: () => ({ + notify: (message) => { + // return a promise after we're done sending notifications. + } + }) + }, + loaders: ({user}) => ({ + People: { + load: () => db.people.find({user}) + } + }), + mutators: ({user}) => ({ + People: { + create: (name) => { + return db.people.insert({user, name}); + } + } + }), + resolvers: { + Person: { + name(obj, args, context) { + return obj.name; + }, + colour(obj, args, context) { + // Bill likes the colour red, everyone else likes blue. + return obj.name === 'bill' ? 'RED' : 'BLUE'; + } + }, + RootQuery: { + people(obj, args, {loaders: {People}}) { + return People.load(); + } + }, + RootMutation: { + createPerson(obj, {name}, {mutators: {People}}) { + return People.create(name); + } + } + }, + hooks: { + RootMutation: { + createPerson: { + post: async (obj, args, {plugins: {Slack}}, info, person) => { + if (!person) { + return person; + } + + await Slack.notify(`A new person just was created with name ${person.name}`); + + return person; + } + } + } + }, + setupFunctions: { + leader: (options, args) => ({ + leader: { + filter: (person) => person.place === 1 + } + } + } +}; + +``` + +## API + +You can access any API available inside the talk directory in a plugin by simply +importing the file relative to the talk project root. An example would be if you +wanted to import the `MetadataService`, you would simply write: + +```javascript +const MetadataService = require('services/metadata'); +``` diff --git a/docs/sitemap.xml b/docs/sitemap.xml new file mode 100644 index 000000000..38a04d6c4 --- /dev/null +++ b/docs/sitemap.xml @@ -0,0 +1,24 @@ +--- +layout: none +search: exclude +--- + + + + {% for post in site.posts %} + {% unless post.search == "exclude" %} + + {{site.url}}{{post.url}} + + {% endunless %} + {% endfor %} + + + {% for page in site.pages %} + {% unless page.search == "exclude" %} + + {{site.url}}{{ page.url}} + + {% endunless %} + {% endfor %} + \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 10e8f34f6..733018af7 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -13,502 +13,6 @@ produces: - application/json paths: - /comments: - get: - tags: - - Comments - parameters: - - name: action_type - in: query - description: Performs a search based on the actions that have been added to it. - type: string - enum: - - rejected - - accepted - - new - responses: - 200: - description: Comments matching the query. - schema: - type: array - items: - $ref: '#/definitions/Comment' - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - post: - tags: - - Comments - parameters: - - name: body - in: body - required: true - schema: - type: object - properties: - body: - type: string - description: The text of the comment to create. - asset_id: - type: string - description: The parent asset of this comment. - parent_id: - type: string - description: The parent comment of this comment (null if the comment is not a reply.) - responses: - 201: - description: The comment that was created. - schema: - $ref: '#/definitions/Comment' - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - - /comments/{comment_id}: - get: - tags: - - Comments - parameters: - - name: comment_id - in: path - description: The id of the comment to retrieve. - type: string - required: true - responses: - 200: - description: The comment was found. - schema: - $ref: '#/definitions/Comment' - 404: - description: The comment was not found. - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - delete: - tags: - - Comments - parameters: - - name: comment_id - in: path - description: The id of the comment to delete. - type: string - required: true - responses: - 204: - description: The comment was deleted. - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - - /comments/{comment_id}/status: - put: - tags: - - Comments - - Moderation - parameters: - - name: comment_id - in: path - description: The id of the comment to retrieve. - type: string - format: uuid - required: true - - name: body - in: body - description: The status to update to. - required: true - schema: - type: object - properties: - status: - type: string - description: The status to update to. - enum: - - new - - flagged - - accepted - - rejected - responses: - 204: - description: The comment status was updated. - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - /comments/{item_id}/actions: - post: - tags: - - Comments - - Actions - parameters: - - name: item_id - in: path - description: The id of the item which is the target of the action. - type: string - required: true - - name: body - in: body - description: The action to add. - required: true - schema: - type: object - properties: - action_type: - type: string - description: The type of action to add - enum: - - like - - flag - metadata: - type: object - description: An arbitrary object describing the action, should be consistent per action type. - responses: - 201: - description: The action created. - schema: - $ref: '#/definitions/Action' - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - /actions/{action_id}: - delete: - tags: - - Actions - parameters: - - name: action_id - in: path - description: The id of the action to delete. - type: string - required: true - responses: - 204: - description: The action was deleted. - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - /auth: - get: - tags: - - Auth - description: Retrieves the current authentication credentials. - responses: - 200: - description: The current user. - schema: - $ref: '#/definitions/User' - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - delete: - tags: - - Auth - description: Logs out the current authenticated user. - responses: - 204: - description: The current user has been logged out. - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - /auth/local: - post: - tags: - - Auth - parameters: - - name: body - in: body - required: true - description: The login credentials. - schema: - type: object - properties: - email: - type: string - description: The email address of the current user. - password: - type: string - description: The password of the current user. - responses: - 200: - description: The user has authenticated sucesfully. - schema: - $ref: '#/definitions/User' - 401: - description: The authentication error. - schema: - $ref: '#/definitions/Error' - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - /auth/facebook: - get: - tags: - - Auth - responses: - 302: - description: Redirects the user to perform external facebook authentication. - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - /auth/facebook/callback: - get: - tags: - - Auth - responses: - 200: - description: Logs in the user after FB Auth. - schema: - $ref: '#/definitions/User' - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - /queue/comments/pending: - get: - tags: - - Comments - - Moderation - responses: - 200: - description: The comments that are not moderated. - schema: - type: object - properties: - comments: - type: array - description: The comments that have yet to be moderated. - items: - $ref: '#/definitions/Comment' - users: - type: array - description: The users authoring these comments. - items: - $ref: '#/definitions/User' - actions: - type: array - description: The actions which have taken place on these comments. - items: - $ref: '#/definitions/Actions' - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - /assets: - get: - parameters: - - name: limit - in: query - type: number - format: int32 - description: Limit the listing results - - name: skip - in: query - type: number - format: int32 - description: Skip the listing results - - name: sort - in: query - enum: - - asc - - desc - type: string - description: Sorting method - - name: field - in: query - type: string - description: Field to sort by. - - name: filter - in: query - type: string - enum: - - open - - closed - description: Comment status to filter by. - - name: search - in: query - type: string - description: String to search by. - responses: - 200: - description: Assets listed. - schema: - type: object - properties: - count: - type: number - description: Total number of assets found. - result: - type: array - items: - $ref: '#/definitions/Asset' - - /assets/{asset_id}: - get: - parameters: - - name: asset_id - in: path - required: true - type: string - format: uuid - responses: - 200: - description: The requested asset. - schema: - $ref: '#/definitions/Asset' - 404: - description: The asset was not found. - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - - - /assets/{asset_id}/scrape: - post: - parameters: - - name: asset_id - in: path - required: true - type: string - format: uuid - responses: - 201: - description: The job that was created. - schema: - $ref: '#/definitions/Job' - 404: - description: The asset was not found. - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - - /assets/{asset_id}/settings: - put: - parameters: - - name: asset_id - in: path - required: true - type: string - format: uuid - - name: body - in: body - required: true - schema: - $ref: '#/definitions/Settings' - responses: - 204: - description: The asset settings were updated. - 404: - description: The asset was not found. - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - - /assets/{asset_id}/status: - put: - parameters: - - name: asset_id - required: true - in: path - type: string - format: uuid - description: The id of the asset to be updated - - name: body - in: body - required: true - schema: - type: object - properties: - closedAt: - type: number - description: The Unix timestamp when the stream will be or was previously closed. - closedMessage: - type: string - description: The message to display to users when the stream is closed. - responses: - 204: - description: Status update successful. - 500: - description: An error has occurred. - schema: - $ref: '#/definitions/Error' - /stream: - get: - tags: - - Actions - - Assets - - Comments - - Users - parameters: - - name: asset_url - in: query - description: The url of the asset for which to get the comment stream. - type: string - format: url - responses: - 200: - description: The comment stream. - schema: - type: object - properties: - asset: - $ref: '#/definitions/Asset' - comments: - type: array - description: All comments for this asset. - items: - $ref: '#/definitions/Comment' - users: - type: array - description: All authors of comments on this asset. - items: - $ref: '#/definitions/User' - actions: - type: array - description: All actions on comments on this asset and their authors. - items: - $ref: '#/definitions/Actions' - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - /settings: - get: - responses: - 200: - description: All global settings. - schema: - $ref: '#/definitions/Settings' - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' - put: - parameters: - - name: body - in: body - required: true - description: Settings to be updated. - schema: - type: object - description: Any allowed setting and value. - responses: - 204: - description: The settings were updated. - 500: - description: An error occured. - schema: - $ref: '#/definitions/Error' /users: get: parameters: diff --git a/docs/tools.md b/docs/tools.md new file mode 100644 index 000000000..51036d96e --- /dev/null +++ b/docs/tools.md @@ -0,0 +1,36 @@ +--- +title: Tooling +keywords: homepage +sidebar: talk_sidebar +permalink: tools.html +summary: +--- + +## Debugging +How we debug errors at Coral + +### React Debugging +For debugging React + +#### React Developer Tools +Another amazing tool for debugging React Applications. You can see where the props are, and much more. + +[React Developer Tools Extension](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) + + +### Redux Debugging +For debugging Redux + +#### Redux Devtool Extension +Redux Devtool is an amazing debug tool. You can easily see what' happening with the state, the payloads, and more. + +[Redux Devtool Chrome Extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en) + +[Redux Devtool Github Repo](https://github.com/zalmoxisus/redux-devtools-extension) + +### GraphQL + +Talk ships with GraphiQL, a live data layer IDE. We strongly recommend using GraphiQL early and often as you work with all things queries, mutations and subscriptions! + +* [Install Talk](install-source.html). +* Open http://localhost:3000/api/v1/graph/iql in your browser. (Note, your server an port may differ.) diff --git a/docs/update.sh b/docs/update.sh new file mode 100755 index 000000000..42a3bece5 --- /dev/null +++ b/docs/update.sh @@ -0,0 +1,4 @@ +git add . +git status +git commit -m "content update" +git push \ No newline at end of file diff --git a/errors.js b/errors.js index 4f26c7bd5..c203b1d25 100644 --- a/errors.js +++ b/errors.js @@ -40,7 +40,7 @@ const ErrPasswordTooShort = new APIError('password must be at least 8 characters }); const ErrMissingEmail = new APIError('email is required', { - translation_key: 'email_required', + translation_key: 'EMAIL_REQUIRED', status: 400 }); @@ -50,7 +50,7 @@ const ErrMissingPassword = new APIError('password is required', { }); const ErrEmailTaken = new APIError('Email address already in use', { - translation_key: 'email_in_use', + translation_key: 'EMAIL_IN_USE', status: 400 }); @@ -146,13 +146,13 @@ const ErrInstallLock = new APIError('install lock active', { // ErrPermissionUpdateUsername is returned when the user does not have permission to update their username. const ErrPermissionUpdateUsername = new APIError('You do not have permission to update your username.', { - translation_key: 'edit_username_not_authorized', + translation_key: 'EDIT_USERNAME_NOT_AUTHORIZED', status: 500 }); // ErrLoginAttemptMaximumExceeded is returned when the login maximum is exceeded. const ErrLoginAttemptMaximumExceeded = new APIError('You have made too many incorrect password attempts.', { - translation_key: 'login_maximum_exceeded', + translation_key: 'LOGIN_MAXIMUM_EXCEEDED', status: 429 }); diff --git a/locales/en.yml b/locales/en.yml index 1c272ad32..f11a38836 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -119,7 +119,7 @@ en: sign_out: "Sign Out" stories: Stories stream_settings: "Stream Settings" - suspect_word_title: "Suspect wåords list" + suspect_word_title: "Suspect words list" suspect_word_text: "Comments which contain these words or phrases (not case-sensitive) will be highlighted in the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list." tech_settings: "Tech Settings" title: "Configure Comment Stream" @@ -151,11 +151,11 @@ en: no_likes: "There have been no likes in the last 5 minutes. All quiet." done: Done edit_comment: - bodyInputLabel: "Edit this comment" + body_input_label: "Edit this comment" save_button: "Save changes" - editWindowExpired: "You can no longer edit this comment. The time window to do so has expired. Why not post another one?" - editWindowExpiredClose: "Close" - editWindowTimerPrefix: "Edit Window: " + edit_window_expired: "You can no longer edit this comment. The time window to do so has expired. Why not post another one?" + edit_window_expired_close: "Close" + edit_window_timer_prefix: "Edit Window: " second: "second" secondsPlural: "seconds" unexpectedError: "Unexpected error while saving changes. Sorry!" @@ -169,27 +169,29 @@ en: embedlink: copy: "Copy to Clipboard" error: - comment_too_short: "Your comment must have something in it" - NOT_AUTHORIZED: "Your username or password is not recognized by our system." + COMMENT_TOO_SHORT: "Your comment must have something in it" + NOT_AUTHORIZED: "You are not authorized to perform this action." NO_SPECIAL_CHARACTERS: "Usernames can contain letters numbers and _ only" PASSWORD_LENGTH: "Password is too short" PROFANITY_ERROR: "Usernames must not contain profanity. Please contact the administrator if you believe this to be in error." USERNAME_IN_USE: "Username already in use" USERNAME_REQUIRED: "Must input a username" - confirm_password: "Passwords don't match. Please check again" - edit_window_ended: "You can no longer edit this comment. The time window to do so has expired." - edit_username_not_authorized: "You do not have permission to update your username." + EDIT_WINDOW_ENDED: "You can no longer edit this comment. The time window to do so has expired." + EDIT_USERNAME_NOT_AUTHORIZED: "You do not have permission to update your username." + EMAIL_IN_USE: "Email address already in use" + EMAIL_REQUIRED: "An email address is required" + LOGIN_MAXIMUM_EXCEEDED: "You have made too many unsuccessful password attempts. Please wait." + PASSWORD_REQUIRED: "Must input a password" + COMMENTING_CLOSED: "Commenting is already closed" + NOT_FOUND: "Resource not found" + INVALID_ASSET_URL: "Assert URL is invalid" email: "Not a valid E-Mail" + confirm_password: "Passwords don't match. Please check again" network_error: "Failed to connect to server. Check your internet connection and try again." - email_in_use: "Email address already in use" email_not_verified: "E-mail address {0} not verified." email_password: "E-mail and/or password combination incorrect." - email_required: "An email address is required" - email_username_in_use: "Email address or username already in use" - login_maximum_exceeded: "You have made too many unsuccessful password attempts. Please wait." organization_name: "Organization name must only contain letters or numbers." password: "Password must be at least 8 characters" - password_required: "Must input a password" username: "Usernames can contain letters numbers and _ only" flag_comment: "Report comment" flag_reason: "Reason for reporting (Optional)" @@ -209,14 +211,14 @@ en: ignored_users: "Ignored users" my_comments: "My Comments" my_profile: "My profile" - new_count: "View 0 new 1" + new_count: "View {0} new {1}" profile: Profile show_all_comments: "Show all comments" success_bio_update: "Your biography has been updated" success_name_update: "Your username has been updated" success_update_settings: "The changes you have made have been applied to the comment stream on this article" - view_all_replies: "view 0 replies" - view_all_replies_initial: "view all 0 replies" + view_all_replies: "view {0} replies" + view_all_replies_initial: "view all {0} replies" view_more_comments: "view more comments" view_reply: "view reply" from_settings_page: "From the Profile Page you can see your comment history." @@ -290,37 +292,8 @@ en: sign_in: "Sign in" to_access: "to access Profile" user_no_comment: "You've never left a comment. Join the conversation!" - sign_in: - email_verify_cta: "Please verify your email address." - request_new_verify_email: "Request another email:" - verify_email: "Thank you for creating an account! We sent an email to the address you provided to verify your account." - verify_email2: "You must verify your account before engaging with the community." - not_you: "Not you?" - logged_in_as: "Logged in as" - facebook_sign_in: "Sign in with Facebook" - facebook_sign_up: "Sign up with Facebook" - logout: "Logout" - sign_in: "Sign in" - sign_in_to_join: "Sign in to join the conversation" - or: "Or" - email: "E-mail Address" - password: "Password" - forgot_your_pass: "Forgot your password?" - need_an_account: "Need an account?" - register: "Register" - sign_up: "Sign Up" - confirm_password: "Confirm Password" - username: "Username" - already_have_an_account: "Already have an account?" - recover_password: "Recover password" - email_in_use: "Email address already in use" - email_or_username_in_use: "Email address or Username already in use" - required_field: "This field is required" - passwords_dont_match: "Passwords don't match." - special_characters: "Usernames can contain letters, numbers and _ only" - sign_in_to_comment: "Sign in to comment" + stream: temporarily_suspended: "In accordance with {0}'s community guidlines, your account has been temporarily suspended. Please rejoin the conversation {1}." - check_the_form: "Invalid Form. Please, check the fields" step_1_header: "Report an issue" step_2_header: "Help us understand" step_3_header: "Thank you for your input" @@ -379,24 +352,24 @@ en: username_offensive: "This username is offensive" view_conversation: "View Conversation" install: - INITIAL: - DESCRIPTION: "Let's set up your Talk community in just a few short steps." - SUBMIT: "Get Started" - ADD_ORGANIZATION: - DESCRIPTION: "Please tell us the name of your organization. This will appear in emails when inviting new team members." - LABEL: "Organization Name" - SAVE: "Save" - CREATE: - EMAIL: "Email address" - USERNAME: "Username" - PASSWORD: "Password" - CONFIRM_PASSWORD: "Confirm Password" - SAVE: "Save" - PERMITTED_DOMAINS: - TITLE: "Permitted domains" - DESCRIPTION: "Enter the domains you would like to permit for Talk, e.g. your local, staging and production environments (ex. localhost:3000, staging.domain.com, domain.com)." - SUBMIT: "Finish install" - FINAL: - DESCRIPTION: "Thanks for installing Talk! We sent an email to verify your email address. While you finish setting up the account, you can start engaging with your readers now." - LAUNCH: "Launch Talk" - CLOSE: "Close this Installer" + initial: + description: "Let's set up your Talk community in just a few short steps." + submit: "Get Started" + add_organization: + description: "Please tell us the name of your organization. This will appear in emails when inviting new team members." + label: "Organization Name" + save: "Save" + create: + email: "Email address" + username: "Username" + password: "Password" + confirm_password: "Confirm Password" + save: "Save" + permitted_domains: + title: "Permitted domains" + description: "Enter the domains you would like to permit for Talk, e.g. your local, staging and production environments (ex. localhost:3000, staging.domain.com, domain.com)." + submit: "Finish install" + final: + description: "Thanks for installing Talk! We sent an email to verify your email address. While you finish setting up the account, you can start engaging with your readers now." + launch: "Launch Talk" + close: "Close this Installer" diff --git a/locales/es.yml b/locales/es.yml index 22a689cd0..b20e4df88 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -124,7 +124,7 @@ es: title: "Configurar los comentarios" weeks: Semanas wordlist: "Palabras Suspendidas" - continue: Continuar + continue: "Continuar" createdisplay: check_the_form: "Formulario Inválido. Por favor verifica los campos" continue: "Continuar con el mismo nombre que aparece en Facebook" @@ -133,82 +133,85 @@ es: fake_comment_date: "hace un minuto" if_you_dont_change_your_name: "Si no modificas tu nombre de usuario en este paso tu nombre de Facebook aparecerá al lado de cada comentario que publiques." required_field: "Campo requerido" - save: Guardar + save: "Guardar" special_characters: "Los nombres sólo pueden contener letras números y _" - username: Nombre + username: "Nombre" write_your_username: "Edita tu nombre" your_username: "Tu nombre aparece en cada comentario que publiques." dashboard: auto_update: "Los datos se actualizan automáticamente cada cinco minutos o cuando refresca el navegador." - comment_count: comentarios - flags: Reportes + comment_count: "comentarios" + flags: "Reportes" most_flags: "Artículos con la mayor cantidad de reportes" most_conversations: "Artículos con las mayores conversaciones" next_update: "{0} minutos hasta la próxima actualización." no_activity: "¡No han habido comentarios en ningún lado en los últimos 5 minutos." no_flags: "¡No ha habido ningún reporte en los últimos 5 minutos! Bravo!" no_likes: "¡No ha habido ningún 'me gusta' en los últimos 5 minutos. Todo tranquilo." - done: Hecho + done: "Hecho" edit_comment: - bodyInputLabel: "Editar este comentario" + body_input_label: "Editar este comentario" save_button: "Guardar cambios" - editWindowExpired: "No se puede editar este comentario. El periodo de edición ya ha concluido. Podrías publicar uno nuevo :-)" - editWindowExpiredClose: "Cerrar" - editWindowTimerPrefix: "Ventana Edición:" + edit_window_expired: "No se puede editar este comentario. El periodo de edición ya ha concluido. Podrías publicar uno nuevo :-)" + edit_window_expired_close: "Cerrar" + edit_window_timer_prefix: "Ventana Edición:" second: "segundo" - secondsPlural: "segundos" - unexpectedError: "Lo siento. Ha habido un error no previsto al guardar los cambios." + seconds_plural: "segundos" + unexpected_error: "Lo siento. Ha habido un error no previsto al guardar los cambios." embedlink: copy: "Copiar al portapapeles" error: - comment_too_short: "Tu comentario debe tener algo escrito" - NOT_AUTHORIZED: "Acción no autorizada." - NO_SPECIAL_CHARACTERS: "Los nombres pueden contener letras números y _" - PASSWORD_LENGTH: "Contraseña es muy corta" - PROFANITY_ERROR: "Los nombres no pueden contener blasfemias. Por favor contacte al o la administradora si cree que esto es un error" - USERNAME_IN_USE: "Este nombre ya está siendo usado." - USERNAME_REQUIRED: "Debe ingresar un nombre" + COMMENT_TOO_SHORT: "Tu comentario debe tener algo escrito" + COMMENTING_CLOSED: "Los comentarios ya estan cerrados" confirm_password: "Las contraseñas no coinciden. Inténtelo nuevamente" edit_window_ended: "No puedes editar este comentario. El tiempo de edición ha expirado." edit_username_not_authorized: "No tiene permiso para editar el nombre de usuario." email: "No es un correo válido" - network_error: "Error al conectar con el servidor. Compruebe su conexión a Internet y vuelva a intentarlo." email_in_use: "Este correo se encuentra en uso" email_not_verified: "Correo {0} no confirmado." email_password: "Correo y/o contraseña incorrecta." email_required: "Se requiere un correo" email_username_in_use: "Correo o nombre en uso." + INVALID_ASSET_URL: "La URL del articulo no es valida" login_maximum_exceeded: "Ha realizado demasiados intentos fallidos de usar la contraseña. Por favor espere." + network_error: "Error al conectar con el servidor. Compruebe su conexión a Internet y vuelva a intentarlo." + NOT_AUTHORIZED: "Acción no autorizada." + NOT_FOUND: "Recurso no encontrado" + NO_SPECIAL_CHARACTERS: "Los nombres pueden contener letras números y _" organization_name: "El nombre de la organización debe contener letras y/o números." password: "La contraseña debe tener por lo menos 8 caracteres" - password_required: "Debe ingresar la contraseña" + PASSWORD_LENGTH: "Contraseña es muy corta" + PASSWORD_REQUIRED: "Debe ingresar la contraseña" + PROFANITY_ERROR: "Los nombres no pueden contener blasfemias. Por favor contacte al o la administradora si cree que esto es un error" username: "Los nombres pueden contener letras números y _" + USERNAME_IN_USE: "Este nombre ya está siendo usado." + USERNAME_REQUIRED: "Debe ingresar un nombre" flag_comment: "Reportar este comentario" flag_reason: "Razón por la que hacer este reporte (Opcional)" flag_username: "Reportar el nombre de usuario" framework: banned_account_msg: "Tu cuenta se encuentra suspendida. Esto significa que no puedes gustar, marcar o escribir comentarios." - comment: comentario + comment: "comentario" comment_is_ignored: "Este comentario está escondido porque has ignorado al usuario." - comments: comentarios + comments: "comentarios" configure_stream: "Configurar Hilo de Comentarios" content_not_available: "Este contenido no se encuentra disponible" edit_name: - button: Enviar + button: "Enviar" error: "Nombres de usuarios pueden solamente incluir letras, números y _" label: "Nuevo Nombre" msg: "Tu cuenta está suspendida porque tu nombre de usuario ha sido considerado no apropiado para el espacio. Para recuperar la cuenta, por favor ingresar un nuevo nombre de usuario. Contáctanos si tienes alguna pregunta." ignored_users: "Usuarios ignorados" my_comments: "Mis Comentarios" my_profile: "Mi perfil" - new_count: "Ver 0 1 nuevo" + new_count: "Ver {0} {1} nuevo" profile: Perfil show_all_comments: "Mostrar todos los comentarios" success_bio_update: "Tu biografia fue actualizada" success_name_update: "Tu nombre de usuario ha sido actualizado" success_update_settings: "La configuración de este articulo fue actualizada" - view_all_replies: "ver 0 respuestas" - view_all_replies_initial: "ver todas las 0 respuestas" + view_all_replies: "ver {0} respuestas" + view_all_replies_initial: "ver todas las {0} respuestas" view_more_comments: "Ver más comentarios" view_reply: "ver respuesta" from_settings_page: "Desde la página de configuración puedes ver tu historia de comentarios." @@ -270,7 +273,7 @@ es: report: Reportar report_notif: "Gracias por reportar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar." report_notif_remove: "Tu reporte ha sido eliminado." - reported: Reportado + reported: "Reportado" set_best: "Etiquetar como el mejor" settings: all_comments: "Todos los comentarios" @@ -315,22 +318,24 @@ es: step_1_header: "Reportar un problema" step_2_header: "Ayúdanos a comprender" step_3_header: "Gracias por tu participación" + stream: + temporarily_suspended: "De acuerdo con la guía de la comunidad de {0}, su cuenta ha sido temporalmente suspendida. Por favor unirse a la conversación {1}." streams: - all: Todos - article: Artículo - closed: Cerrado + all: "Todos" + article: "Artículo" + closed: "Cerrado" empty_result: "Ningún articulo coincide con esta búsqueda. Intente expandir su búsqueda." filter_streams: "Filtrar Hilos" - newest: Más nuevo - oldest: Más antiguo - open: Abierto + newest: "Más nuevo" + oldest: "Más antiguo" + open: "Abierto" pubdate: "Fecha Publicación" - search: Buscar + search: "Buscar" sort_by: "Ordenar por" status: "Estado del Hilo" stream_status: "Estado del Hilo" suspenduser: - bio: bio + bio: "bio" cancel: "Cancelar" days: "{0} días" description_0: "¿Quiere suspender temporalmente a este usuario por su {0}? Hacerlo va a ocultar temporalmente todos sus comentarios hasta que edite su {0}." @@ -347,7 +352,7 @@ es: no_cancel: "No cancelar" notify_suspend_until: "Usuario {0} ha sido temporalmente suspendido. Esta suspension va a terminar automáticamente en {1}." one_hour: "1 hora" - send: Enviar + send: "Enviar" select_duration: "Seleccionar la duración de la suspensión" suspend_user: "Suspender Usuario" title: "Suspender un usuario" diff --git a/package.json b/package.json index a2112bc85..7c0485955 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "talk", - "version": "1.6.0", + "version": "1.7.0", "description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net", "main": "app.js", "scripts": { @@ -54,10 +54,13 @@ "app-module-path": "^2.2.0", "bcrypt": "^1.0.2", "body-parser": "^1.17.1", + "bowser": "^1.7.0", "cli-table": "^0.3.1", "colors": "^1.1.2", "commander": "^2.9.0", + "compression": "^1.6.2", "connect-redis": "^3.1.0", + "cookie-parser": "^1.4.3", "cross-spawn": "^5.1.0", "csurf": "^1.9.0", "dataloader": "^1.3.0", diff --git a/plugins.default.json b/plugins.default.json index b3443af48..3f64a383b 100644 --- a/plugins.default.json +++ b/plugins.default.json @@ -1,9 +1,13 @@ { "server": [ "coral-plugin-respect", - "coral-plugin-facebook-auth" + "coral-plugin-like", + "coral-plugin-facebook-auth", + "coral-plugin-auth" ], "client": [ - "coral-plugin-respect" + "coral-plugin-respect", + "coral-plugin-like", + "coral-plugin-auth" ] } diff --git a/plugins/coral-plugin-auth/client/.babelrc b/plugins/coral-plugin-auth/client/.babelrc new file mode 100644 index 000000000..60be246eb --- /dev/null +++ b/plugins/coral-plugin-auth/client/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + "es2015" + ], + "plugins": [ + "add-module-exports", + "transform-class-properties", + "transform-decorators-legacy", + "transform-object-assign", + "transform-object-rest-spread", + "transform-async-to-generator", + "transform-react-jsx" + ] +} \ No newline at end of file diff --git a/plugins/coral-plugin-auth/client/.eslintrc.json b/plugins/coral-plugin-auth/client/.eslintrc.json new file mode 100644 index 000000000..9fe56bd14 --- /dev/null +++ b/plugins/coral-plugin-auth/client/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es6": true, + "mocha": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + } + }, + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} diff --git a/client/coral-sign-in/containers/ChangeUsernameContainer.js b/plugins/coral-plugin-auth/client/components/ChangeUsername.js similarity index 51% rename from client/coral-sign-in/containers/ChangeUsernameContainer.js rename to plugins/coral-plugin-auth/client/components/ChangeUsername.js index a08359b3f..ed3c5ace0 100644 --- a/client/coral-sign-in/containers/ChangeUsernameContainer.js +++ b/plugins/coral-plugin-auth/client/components/ChangeUsername.js @@ -1,10 +1,9 @@ -import React, {Component} from 'react'; +import React from 'react'; import {connect} from 'react-redux'; - -import validate from 'coral-framework/helpers/validate'; +import {bindActionCreators} from 'redux'; import errorMsj from 'coral-framework/helpers/error'; - -import CreateUsernameDialog from '../components/CreateUsernameDialog'; +import validate from 'coral-framework/helpers/validate'; +import CreateUsernameDialog from './CreateUsernameDialog'; import t from 'coral-framework/services/i18n'; @@ -14,50 +13,57 @@ import { invalidForm, validForm, createUsername -} from '../../coral-framework/actions/auth'; - -class ChangeUsernameContainer extends Component { - initialState = { - formData: { - username: '', - }, - errors: {}, - showErrors: false - }; +} from 'coral-framework/actions/auth'; +class ChangeUsernameContainer extends React.Component { constructor(props) { super(props); - this.initialState.formData.username = props.user.username; - this.state = this.initialState; - this.handleChange = this.handleChange.bind(this); - this.handleSubmitUsername = this.handleSubmitUsername.bind(this); - this.handleClose = this.handleClose.bind(this); - this.addError = this.addError.bind(this); - } - handleChange(e) { - const {name, value} = e.target; - this.setState((state) => ({ - ...state, + this.state = { formData: { - ...state.formData, - [name]: value - } - }), () => { - this.validation(name, value); - }); + username: (props.auth.user && props.auth.user.username) || '' + }, + errors: {}, + showErrors: false + }; } - addError(name, error) { + componentWillReceiveProps(next) { + if (!this.props.auth.showCreateUsernameDialog && next.auth.showCreateUsernameDialog) { + this.setState({ + formData: { + username: (this.props.auth.user && this.props.auth.user.username) || '', + }, + }); + } + } + + handleChange = (e) => { + const {name, value} = e.target; + this.setState( + (state) => ({ + ...state, + formData: { + ...state.formData, + [name]: value + } + }), + () => { + this.validation(name, value); + } + ); + }; + + addError = (name, error) => { return this.setState((state) => ({ errors: { ...state.errors, [name]: error } })); - } + }; - validation(name, value) { + validation = (name, value) => { const {addError} = this; if (!value.length) { @@ -65,37 +71,37 @@ class ChangeUsernameContainer extends Component { } else if (!validate[name](value)) { addError(name, errorMsj[name]); } else { - const { [name]: prop, ...errors } = this.state.errors; // eslint-disable-line + const {[name]: prop, ...errors} = this.state.errors; // eslint-disable-line // Removes Error this.setState((state) => ({...state, errors})); } - } + }; - isCompleted() { + isCompleted = () => { const {formData} = this.state; return !Object.keys(formData).filter((prop) => !formData[prop].length).length; - } + }; - displayErrors(show = true) { + displayErrors = (show = true) => { this.setState({showErrors: show}); - } + }; - handleSubmitUsername(e) { + handleSubmitUsername = (e) => { e.preventDefault(); const {errors} = this.state; const {validForm, invalidForm} = this.props; this.displayErrors(); if (this.isCompleted() && !Object.keys(errors).length) { - this.props.createUsername(this.props.user.id, this.state.formData); + this.props.createUsername(this.props.auth.user.id, this.state.formData); validForm(); } else { invalidForm(t('createdisplay.check_the_form')); } - } + }; - handleClose() { + handleClose = () => { this.props.hideCreateUsernameDialog(); - } + }; render() { const {loggedIn, auth} = this.props; @@ -115,19 +121,22 @@ class ChangeUsernameContainer extends Component { } } -const mapStateToProps = (state) => ({ - auth: state.auth.toJS() +const mapStateToProps = ({auth}) => ({ + auth: auth.toJS() }); -const mapDispatchToProps = (dispatch) => ({ - createUsername: (userid, formData) => dispatch(createUsername(userid, formData)), - showCreateUsernameDialog: () => dispatch(showCreateUsernameDialog()), - hideCreateUsernameDialog: () => dispatch(hideCreateUsernameDialog()), - invalidForm: (error) => dispatch(invalidForm(error)), - validForm: () => dispatch(validForm()) -}); +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + createUsername, + showCreateUsernameDialog, + hideCreateUsernameDialog, + invalidForm, + validForm + }, + dispatch + ); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ChangeUsernameContainer); +export default connect(mapStateToProps, mapDispatchToProps)( + ChangeUsernameContainer +); diff --git a/client/coral-sign-in/components/CreateUsernameDialog.js b/plugins/coral-plugin-auth/client/components/CreateUsernameDialog.js similarity index 60% rename from client/coral-sign-in/components/CreateUsernameDialog.js rename to plugins/coral-plugin-auth/client/components/CreateUsernameDialog.js index c3e640218..96df1e96f 100644 --- a/client/coral-sign-in/components/CreateUsernameDialog.js +++ b/plugins/coral-plugin-auth/client/components/CreateUsernameDialog.js @@ -1,19 +1,23 @@ import React from 'react'; -import TextField from 'coral-ui/components/TextField'; -import Button from 'coral-ui/components/Button'; -import {Dialog, Alert} from 'coral-ui'; -import FakeComment from './FakeComment'; - import styles from './styles.css'; - +import {Dialog, Alert, TextField} from 'coral-ui'; +import {FakeComment} from './FakeComment'; +import Button from 'coral-ui/components/Button'; import t from 'coral-framework/services/i18n'; -const CreateUsernameDialog = ({open, handleClose, formData, handleSubmitUsername, handleChange, ...props}) => { - return ( +const CreateUsernameDialog = ({ + open, + handleClose, + formData, + handleSubmitUsername, + handleChange, + ...props +}) => ( + open={open} + > ×
    @@ -22,17 +26,24 @@ const CreateUsernameDialog = ({open, handleClose, formData, handleSubmitUsername
    -

    {t('createdisplay.your_username')}

    +

    + {t('createdisplay.your_username')} +

    -

    {t('createdisplay.if_you_dont_change_your_name')}

    - { props.auth.error && {props.auth.error} } +

    + {t('createdisplay.if_you_dont_change_your_name')} +

    + {props.auth.error && {props.auth.error}}
    - { props.errors.username && {t('createdisplay.special_characters')} } + {props.errors.username && + + {' '}{t('createdisplay.special_characters')}{' '} + }
    - +
    -
    +
    - ); -}; +); export default CreateUsernameDialog; diff --git a/plugins/coral-plugin-auth/client/components/FakeComment.js b/plugins/coral-plugin-auth/client/components/FakeComment.js new file mode 100644 index 000000000..9900ccdc6 --- /dev/null +++ b/plugins/coral-plugin-auth/client/components/FakeComment.js @@ -0,0 +1,70 @@ +import React from 'react'; +import {ReplyButton} from 'coral-plugin-replies'; +import PubDate from 'coral-plugin-pubdate/PubDate'; +import AuthorName from 'coral-plugin-author-name/AuthorName'; +import Content from 'coral-plugin-commentcontent/CommentContent'; +import styles from 'coral-embed-stream/src/components/Comment.css'; +import t from 'coral-framework/services/i18n'; + +export const FakeComment = ({username, created_at, body}) => ( +
    +
    + + + +
    +
    + +
    + {}} + parentCommentId={'commentID'} + currentUserId={{}} + banned={false} + /> +
    +
    +
    + +
    +
    + +
    +
    +
    +); + diff --git a/plugins/coral-plugin-auth/client/components/ForgotContent.js b/plugins/coral-plugin-auth/client/components/ForgotContent.js new file mode 100644 index 000000000..c0e4205a9 --- /dev/null +++ b/plugins/coral-plugin-auth/client/components/ForgotContent.js @@ -0,0 +1,72 @@ +import React from 'react'; +import styles from './styles.css'; +import Button from 'coral-ui/components/Button'; +import t from 'coral-framework/services/i18n'; + +class ForgotContent extends React.Component { + handleSubmit = (e) => { + e.preventDefault(); + this.props.fetchForgotPassword(this.emailInput.value); + }; + + render() { + const {changeView, auth} = this.props; + const {passwordRequestSuccess, passwordRequestFailure} = auth; + + return ( +
    +
    +

    {t('sign_in.recover_password')}

    +
    +
    +
    + + (this.emailInput = input)} + type="text" + style={{fontSize: 16}} + id="email" + name="email" + /> +
    + + {passwordRequestSuccess + ?

    + {passwordRequestSuccess} +

    + : null} + {passwordRequestFailure + ?

    + {passwordRequestFailure} +

    + : null} +
    +
    + + {t('sign_in.need_an_account')} + {' '} + changeView('SIGNUP')}> + {t('sign_in.register')} + + + + {t('sign_in.already_have_an_account')} + {' '} + changeView('SIGNIN')}> + {t('sign_in.sign_in')} + + +
    +
    + ); + } +} + +export default ForgotContent; diff --git a/client/coral-sign-in/components/SignDialog.js b/plugins/coral-plugin-auth/client/components/SignDialog.js similarity index 68% rename from client/coral-sign-in/components/SignDialog.js rename to plugins/coral-plugin-auth/client/components/SignDialog.js index ff2464f7c..7f05889a0 100644 --- a/client/coral-sign-in/components/SignDialog.js +++ b/plugins/coral-plugin-auth/client/components/SignDialog.js @@ -6,12 +6,9 @@ import SignInContent from './SignInContent'; import SignUpContent from './SignUpContent'; import ForgotContent from './ForgotContent'; -const SignDialog = ({open, view, handleClose, ...props}) => ( - - × +const SignDialog = ({open, view, hideSignInDialog, ...props}) => ( + + × {view === 'SIGNIN' && } {view === 'SIGNUP' && } {view === 'FORGOT' && } diff --git a/plugins/coral-plugin-auth/client/components/SignInButton.js b/plugins/coral-plugin-auth/client/components/SignInButton.js new file mode 100644 index 000000000..881233c83 --- /dev/null +++ b/plugins/coral-plugin-auth/client/components/SignInButton.js @@ -0,0 +1,24 @@ +import React from 'react'; +import {Button} from 'coral-ui'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {showSignInDialog} from 'coral-framework/actions/auth'; + +const SignInButton = ({loggedIn, showSignInDialog}) => ( +
    + {!loggedIn + ? + : null} +
    +); + +const mapStateToProps = ({auth}) => ({ + loggedIn: auth.toJS().loggedIn +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({showSignInDialog}, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(SignInButton); diff --git a/client/coral-sign-in/containers/SignInContainer.js b/plugins/coral-plugin-auth/client/components/SignInContainer.js similarity index 55% rename from client/coral-sign-in/containers/SignInContainer.js rename to plugins/coral-plugin-auth/client/components/SignInContainer.js index 054319d73..6f3c460bb 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/plugins/coral-plugin-auth/client/components/SignInContainer.js @@ -1,11 +1,10 @@ -import React, {Component, PropTypes} from 'react'; +import React from 'react'; import {connect} from 'react-redux'; -import SignDialog from '../components/SignDialog'; -import validate from 'coral-framework/helpers/validate'; -import errorMsj from 'coral-framework/helpers/error'; - -import {pym} from 'coral-framework'; +import SignDialog from './SignDialog'; +import {bindActionCreators} from 'redux'; import t from 'coral-framework/services/i18n'; +import errorMsj from 'coral-framework/helpers/error'; +import validate from 'coral-framework/helpers/validate'; import { changeView, @@ -22,34 +21,22 @@ import { checkLogin } from 'coral-framework/actions/auth'; -class SignInContainer extends Component { - initialState = { - formData: { - email: '', - username: '', - password: '', - confirmPassword: '' - }, - emailToBeResent: '', - errors: {}, - showErrors: false - }; - +class SignInContainer extends React.Component { constructor(props) { super(props); - this.state = this.initialState; - this.addError = this.addError.bind(this); - this.handleAuth = this.handleAuth.bind(this); - this.handleSignUp = this.handleSignUp.bind(this); - this.handleSignIn = this.handleSignIn.bind(this); - this.handleChange = this.handleChange.bind(this); - this.handleChangeEmail = this.handleChangeEmail.bind(this); - this.handleResendVerification = this.handleResendVerification.bind(this); - } - static propTypes = { - requireEmailConfirmation: PropTypes.bool.isRequired - }; + this.state = { + formData: { + email: '', + username: '', + password: '', + confirmPassword: '' + }, + emailToBeResent: '', + errors: {}, + showErrors: false + }; + } componentWillMount() { this.props.checkLogin(); @@ -70,7 +57,7 @@ class SignInContainer extends Component { window.removeEventListener('storage', this.handleAuth); } - handleAuth(e) { + handleAuth = (e) => { // Listening to FB changes // FB localStorage key is 'auth' @@ -80,9 +67,9 @@ class SignInContainer extends Component { const {err, data} = JSON.parse(e.newValue); authCallback(err, data); } - } + }; - handleChange(e) { + handleChange = (e) => { const {name, value} = e.target; this.setState( (state) => ({ @@ -96,39 +83,38 @@ class SignInContainer extends Component { this.validation(name, value); } ); - } + }; - handleChangeEmail(e) { + handleChangeEmail = (e) => { const {value} = e.target; this.setState({emailToBeResent: value}); - } + }; - handleResendVerification(e) { + handleResendVerification = (e) => { e.preventDefault(); this.props .requestConfirmEmail( this.state.emailToBeResent, - pym.parentUrl || location.href ) .then(() => { setTimeout(() => { // allow success UI to be shown for a second, and then close the modal - this.props.handleClose(); + this.props.hideSignInDialog(); }, 2500); }); - } + }; - addError(name, error) { + addError = (name, error) => { return this.setState((state) => ({ errors: { ...state.errors, [name]: error } })); - } + }; - validation(name, value) { + validation = (name, value) => { const {addError} = this; const {formData} = this.state; @@ -146,52 +132,50 @@ class SignInContainer extends Component { // Removes Error this.setState((state) => ({...state, errors})); } - } + }; - isCompleted() { + isCompleted = () => { const {formData} = this.state; return !Object.keys(formData).filter((prop) => !formData[prop].length).length; - } + }; - displayErrors(show = true) { + displayErrors = (show = true) => { this.setState({showErrors: show}); - } + }; - handleSignUp(e) { + handleSignUp = (e) => { e.preventDefault(); const {errors} = this.state; const {fetchSignUp, validForm, invalidForm} = this.props; this.displayErrors(); if (this.isCompleted() && !Object.keys(errors).length) { - fetchSignUp(this.state.formData, pym.parentUrl || location.href); + fetchSignUp(this.state.formData); validForm(); } else { invalidForm(t('sign_in.check_the_form')); } - } + }; - handleSignIn(e) { + handleSignIn = (e) => { e.preventDefault(); this.props.fetchSignIn(this.state.formData); - } + }; render() { - const {auth, requireEmailConfirmation} = this.props; - const {emailVerificationLoading, emailVerificationSuccess} = auth; + const {auth} = this.props; + const {requireEmailConfirmation, emailVerificationLoading, emailVerificationSuccess} = auth; return ( -
    - -
    + ); } } @@ -200,20 +184,23 @@ const mapStateToProps = (state) => ({ auth: state.auth.toJS() }); -const mapDispatchToProps = (dispatch) => ({ - checkLogin: () => dispatch(checkLogin()), - facebookCallback: (err, data) => dispatch(facebookCallback(err, data)), - fetchSignUp: (formData, url) => dispatch(fetchSignUp(formData, url)), - fetchSignIn: (formData) => dispatch(fetchSignIn(formData)), - fetchSignInFacebook: () => dispatch(fetchSignInFacebook()), - fetchSignUpFacebook: () => dispatch(fetchSignUpFacebook()), - fetchForgotPassword: (formData) => dispatch(fetchForgotPassword(formData)), - requestConfirmEmail: (email, url) => - dispatch(requestConfirmEmail(email, url)), - changeView: (view) => dispatch(changeView(view)), - handleClose: () => dispatch(hideSignInDialog()), - invalidForm: (error) => dispatch(invalidForm(error)), - validForm: () => dispatch(validForm()) -}); +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + checkLogin, + facebookCallback, + fetchSignUp, + fetchSignIn, + fetchSignInFacebook, + fetchSignUpFacebook, + fetchForgotPassword, + requestConfirmEmail, + changeView, + hideSignInDialog, + invalidForm, + validForm + }, + dispatch + ); export default connect(mapStateToProps, mapDispatchToProps)(SignInContainer); diff --git a/client/coral-sign-in/components/SignInContent.js b/plugins/coral-plugin-auth/client/components/SignInContent.js similarity index 73% rename from client/coral-sign-in/components/SignInContent.js rename to plugins/coral-plugin-auth/client/components/SignInContent.js index 1349fda6f..dd130d8db 100644 --- a/client/coral-sign-in/components/SignInContent.js +++ b/plugins/coral-plugin-auth/client/components/SignInContent.js @@ -16,17 +16,17 @@ const SignInContent = ({ auth, fetchSignInFacebook }) => { - return (

    - {auth.emailVerificationFailure ? t('sign_in.email_verify_cta') : t('sign_in.sign_in_to_join')} + {auth.emailVerificationFailure + ? t('sign_in.email_verify_cta') + : t('sign_in.sign_in_to_join')}

    - { auth.error && {auth.error} } - { - auth.emailVerificationFailure + {auth.error && {auth.error}} + {auth.emailVerificationFailure ?

    {t('sign_in.request_new_verify_email')}

    - + onChange={handleChangeEmail} + /> + {emailVerificationLoading && } {emailVerificationSuccess && } @@ -68,23 +71,29 @@ const SignInContent = ({ onChange={handleChange} />
    - { - !auth.isLoading ? - - : - - } + {!auth.isLoading + ? + : }
    -
    - } +
  • }
    - changeView('FORGOT')}>{t('sign_in.forgot_your_pass')} + + changeView('FORGOT')}> + {t('sign_in.forgot_your_pass')} + + {t('sign_in.need_an_account')} - changeView('SIGNUP')} id='coralRegister'> + changeView('SIGNUP')} id="coralRegister"> {t('sign_in.register')} diff --git a/client/coral-sign-in/components/SignUpContent.js b/plugins/coral-plugin-auth/client/components/SignUpContent.js similarity index 59% rename from client/coral-sign-in/components/SignUpContent.js rename to plugins/coral-plugin-auth/client/components/SignUpContent.js index 7d3c7a4ca..314e97ec7 100644 --- a/client/coral-sign-in/components/SignUpContent.js +++ b/plugins/coral-plugin-auth/client/components/SignUpContent.js @@ -1,37 +1,23 @@ -import React, {PropTypes} from 'react'; -import {Button, TextField, Spinner, Success, Alert} from 'coral-ui'; import styles from './styles.css'; +import React from 'react'; +import {Button, TextField, Spinner, Success, Alert} from 'coral-ui'; import t from 'coral-framework/services/i18n'; class SignUpContent extends React.Component { - constructor (props) { - super(props); - this.successfulSignup = false; + componentWillReceiveProps(next) { + if ( + !this.props.emailVerificationEnabled && + !this.props.auth.successSignUp && + next.auth.successSignUp + ) { + setTimeout(() => { + this.props.changeView('SIGNIN'); + }, 2000); + } } - static propTypes = { - emailVerificationEnabled: PropTypes.bool.isRequired, - fetchSignUpFacebook: PropTypes.func.isRequired, - changeView: PropTypes.func.isRequired, - handleSignUp: PropTypes.func.isRequired, - showErrors: PropTypes.bool, - errors: PropTypes.shape({ - email: PropTypes.string, - username: PropTypes.string, - password: PropTypes.string, - confirmPassword: PropTypes.string, - }), - formData: PropTypes.shape({ - email: PropTypes.string, - username: PropTypes.string, - password: PropTypes.string, - confirmPassword: PropTypes.string - }) - } - - render () { - + render() { const { handleChange, formData, @@ -41,18 +27,8 @@ class SignUpContent extends React.Component { showErrors, changeView, handleSignUp, - fetchSignUpFacebook} = this.props; - - const beforeSignup = !auth.isLoading && !auth.successSignUp; - const successfulSignup = !auth.isLoading && auth.successSignUp; - - // the first time we render a successfulSignup, trigger a timer - if ((this.successfulSignup ^ successfulSignup) && !emailVerificationEnabled) { - setTimeout(() => { - changeView('SIGNIN'); - }, 1000); - this.successfulSignup = true; - } + fetchSignUpFacebook + } = this.props; return (
    @@ -62,8 +38,8 @@ class SignUpContent extends React.Component {
    - { auth.error && {auth.error} } - { beforeSignup && + {auth.error && {auth.error}} + {!auth.successSignUp &&
    - { auth.isLoading && } + {auth.isLoading && }
    -
    - } - { - successfulSignup && +
    } + {auth.successSignUp &&
    - { - emailVerificationEnabled && -

    {t('sign_in.verify_email')}

    {t('sign_in.verify_email2')}

    - } -
    - } + {emailVerificationEnabled && +

    + {t('sign_in.verify_email')} +
    +
    + {t('sign_in.verify_email2')} +

    } +
    } diff --git a/plugins/coral-plugin-auth/client/components/UserBox.js b/plugins/coral-plugin-auth/client/components/UserBox.js new file mode 100644 index 000000000..91959a35b --- /dev/null +++ b/plugins/coral-plugin-auth/client/components/UserBox.js @@ -0,0 +1,32 @@ +import React from 'react'; +import styles from './styles.css'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import t from 'coral-framework/services/i18n'; +import {logout} from 'coral-framework/actions/auth'; + +const UserBox = ({loggedIn, user, logout, onShowProfile}) => ( +
    + { + loggedIn ? ( +
    + {t('sign_in.logged_in_as')} + {user.username}. {t('sign_in.not_you')} + logout()}> + {t('sign_in.logout')} + +
    + ) : null + } +
    +); + +const mapStateToProps = ({auth}) => ({ + loggedIn: auth.toJS().loggedIn, + user: auth.toJS().user +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({logout}, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(UserBox); diff --git a/client/coral-sign-in/components/styles.css b/plugins/coral-plugin-auth/client/components/styles.css similarity index 100% rename from client/coral-sign-in/components/styles.css rename to plugins/coral-plugin-auth/client/components/styles.css diff --git a/plugins/coral-plugin-auth/client/index.js b/plugins/coral-plugin-auth/client/index.js new file mode 100644 index 000000000..44221652f --- /dev/null +++ b/plugins/coral-plugin-auth/client/index.js @@ -0,0 +1,14 @@ +import UserBox from './components/UserBox'; +import SignInButton from './components/SignInButton'; +import SignInContainer from './components/SignInContainer'; +import ChangeUserNameContainer from './components/ChangeUsername'; +import translations from './translations.yml'; + +export default { + translations, + slots: { + stream: [UserBox, SignInButton, ChangeUserNameContainer], + login: [SignInContainer] + } +}; + diff --git a/plugins/coral-plugin-auth/client/translations.yml b/plugins/coral-plugin-auth/client/translations.yml new file mode 100644 index 000000000..488f505d6 --- /dev/null +++ b/plugins/coral-plugin-auth/client/translations.yml @@ -0,0 +1,91 @@ +en: + sign_in: + email_verify_cta: "Please verify your email address." + request_new_verify_email: "Request another email:" + verify_email: "Thank you for creating an account! We sent an email to the address you provided to verify your account." + verify_email2: "You must verify your account before engaging with the community." + not_you: "Not you?" + logged_in_as: "Logged in as" + facebook_sign_in: "Sign in with Facebook" + facebook_sign_up: "Sign up with Facebook" + logout: "Logout" + sign_in: "Sign in" + sign_in_to_join: "Sign in to join the conversation" + or: "Or" + email: "E-mail Address" + password: "Password" + forgot_your_pass: "Forgot your password?" + need_an_account: "Need an account?" + register: "Register" + sign_up: "Sign Up" + confirm_password: "Confirm Password" + username: "Username" + already_have_an_account: "Already have an account?" + recover_password: "Recover password" + email_in_use: "Email address already in use" + email_or_username_in_use: "Email address or Username already in use" + required_field: "This field is required" + passwords_dont_match: "Passwords don't match." + special_characters: "Usernames can contain letters, numbers and _ only" + sign_in_to_comment: "Sign in to comment" + check_the_form: "Invalid Form. Please, check the fields" + createdisplay: + check_the_form: "Invalid Form. Please check the fields" + continue: "Continue with the same Facebook username" + error_create: "Error when changing username" + fake_comment_body: "This is an example comment. Readers can share their thoughts and opinions with newsrooms in the comments section." + fake_comment_date: "1 minute ago" + if_you_dont_change_your_name: "If you don't change your username at this step your Facebook display name will appear alongside of all your comments." + required_field: "Required field" + save: Save + special_characters: "Usernames can contain letters numbers and _ only" + username: Username + write_your_username: "Edit your username" + your_username: "Your username appears on every comment you post." +es: + sign_in: + email_verify_cta: "Por favor confirme su correo." + request_new_verify_email: "Enviar otro correo:" + verify_email: "¡Gracias por crear una cuenta! Le enviamos un correo a la direcció\ + n que dio para confirmar su cuenta." + verify_email2: "Debe confirmarla antes de poder involucrarse en la comunidad." + not_you: "¿No eres tu?" + logged_in_as: "Entraste como" + facebook_sign_in: "Entrar con Facebook" + facebook_sign_up: "Registrarse con Facebook" + logout: "Salir" + sign_in: "Entrar" + sign_in_to_join: "Entrar para unirte a la conversación" + or: "O" + email: "Dirección de Correo" + password: "Contraseña" + forgot_your_pass: "¿Has olvidado tu contraseña?" + need_an_account: "¿Necesitas una cuenta?" + register: "Registrar" + sign_up: "Registro" + confirm_password: "Confirmar Contraseña" + username: "Nombre" + already_have_an_account: "¿Ya tienes una cuenta?" + recover_password: "Recuperar la contraseña" + email_in_use: "Este correo se encuentra en uso" + email_or_username_in_use: "Este correo ó nombre de usuario se encuentran en uso" + required_field: "Este campo es requerido" + passwords_dont_match: "Las contraseñas no coinciden." + special_characters: "Los nombres pueden contener letras, números y _" + sign_in_to_comment: "Entrar para comentar" + check_the_form: "Formulario Inválido. Por favor, completa los campos" + createdisplay: + check_the_form: "Formulario Inválido. Por favor verifica los campos" + continue: "Continuar con el mismo nombre que aparece en Facebook" + error_create: "Hubo un error al cambiar el nombre de usuario" + fake_comment_body: "Este es un comentario de ejemplo. Las lectoras pueden compartir\ + \ sus ideas y opiniones con los periodistas en la sección de comentarios." + fake_comment_date: "hace un minuto" + if_you_dont_change_your_name: "Si no modificas tu nombre de usuario en este paso\ + \ tu nombre de Facebook aparecerá al lado de cada comentario que publiques." + required_field: "Campo requerido" + save: Guardar + special_characters: "Los nombres sólo pueden contener letras números y _" + username: Nombre + write_your_username: "Edita tu nombre" + your_username: "Tu nombre aparece en cada comentario que publiques." diff --git a/plugins/coral-plugin-auth/index.js b/plugins/coral-plugin-auth/index.js new file mode 100644 index 000000000..85dfb349b --- /dev/null +++ b/plugins/coral-plugin-auth/index.js @@ -0,0 +1,2 @@ +module.exports = {}; + diff --git a/plugins/coral-plugin-like/client/components/LikeButton.js b/plugins/coral-plugin-like/client/components/LikeButton.js index 29ad444ac..7b66a11d4 100644 --- a/plugins/coral-plugin-like/client/components/LikeButton.js +++ b/plugins/coral-plugin-like/client/components/LikeButton.js @@ -1,17 +1,16 @@ -import React, { Component } from 'react'; +import React, {Component} from 'react'; import styles from './style.css'; -import Icon from './Icon'; -import t from 'coral-framework/services/i18n'; import cn from 'classnames'; -import { getMyActionSummary, getTotalActionCount } from 'coral-framework/utils'; +import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils'; +import t from 'coral-framework/services/i18n'; const name = 'coral-plugin-like'; class LikeButton extends Component { handleClick = () => { - const { postLike, showSignInDialog, deleteAction } = this.props; - const { root: { me }, comment } = this.props; + const {postLike, showSignInDialog, deleteAction} = this.props; + const {root: {me}, comment} = this.props; const myLikeActionSummary = getMyActionSummary( 'LikeActionSummary', @@ -40,7 +39,7 @@ class LikeButton extends Component { }; render() { - const { comment } = this.props; + const {comment} = this.props; if (!comment) { return null; @@ -54,7 +53,7 @@ class LikeButton extends Component {
    - ) + ); } } - const mapStateToProps = ({commentBox}) => ({commentBox}); -const mapDispatchToProps = dispatch => +const mapDispatchToProps = (dispatch) => bindActionCreators({addTag, removeTag}, dispatch); export default connect(mapStateToProps, mapDispatchToProps)(OffTopicCheckbox); diff --git a/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js b/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js index 6d2cf6c4f..89a4d03aa 100644 --- a/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js +++ b/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js @@ -4,8 +4,8 @@ import styles from './styles.css'; import t from 'coral-framework/services/i18n'; const isOffTopic = (tags) => { - return !!tags.filter(tag => tag.name === 'OFF_TOPIC').length -} + return !!tags.filter((tag) => tag.name === 'OFF_TOPIC').length; +}; export default (props) => ( diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index 6f610e2b0..392b04e9b 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -22,9 +22,10 @@ router.get('/', (req, res, next) => { /** * This blacklists the token used to authenticate. */ + router.delete('/', HandleLogout); -//============================================================================== +// ============================================================================= // PASSPORT ROUTES //============================================================================== diff --git a/services/passport.js b/services/passport.js index c64bb2334..4f5a8fb8d 100644 --- a/services/passport.js +++ b/services/passport.js @@ -9,6 +9,7 @@ const errors = require('../errors'); const uuid = require('uuid'); const debug = require('debug')('talk:passport'); const {createClient} = require('./redis'); +const bowser = require('bowser'); // Create a redis client to use for authentication. const client = createClient(); @@ -32,6 +33,17 @@ const GenerateToken = (user) => JWT.sign({}, JWT_SECRET, { audience: JWT_AUDIENCE }); +// SetTokenForSafari sends the token in a cookie for Safari clients. +const SetTokenForSafari = (req, res, token) => { + const browser = bowser._detect(req.headers['user-agent']); + if (browser.ios || browser.safari) { + res.cookie('authorization', token, { + httpOnly: true, + expires: new Date(Date.now() + 900000) + }); + } +}; + // HandleGenerateCredentials validates that an authentication scheme did indeed // return a user, if it did, then sign and return the user and token to be used // by the frontend to display and update the UI. @@ -47,6 +59,8 @@ const HandleGenerateCredentials = (req, res, next) => (err, user) => { // Generate the token to re-issue to the frontend. const token = GenerateToken(user); + SetTokenForSafari(req, res, token); + // Send back the details! res.json({user, token}); }; @@ -66,6 +80,8 @@ const HandleAuthPopupCallback = (req, res, next) => (err, user) => { // Generate the token to re-issue to the frontend. const token = GenerateToken(user); + SetTokenForSafari(req, res, token); + // We logged in the user! Let's send back the user data. res.render('auth-callback', {auth: JSON.stringify({err: null, data: {user, token}})}); }; @@ -134,6 +150,7 @@ const HandleLogout = (req, res, next) => { return next(err); } + res.clearCookie('authorization'); res.status(204).end(); }); }; @@ -158,11 +175,24 @@ const CheckBlacklisted = (jwt) => new Promise((resolve, reject) => { const JwtStrategy = require('passport-jwt').Strategy; const ExtractJwt = require('passport-jwt').ExtractJwt; +let cookieExtractor = function(req) { + let token = null; + + if (req && req.cookies) { + token = req.cookies['authorization']; + } + + return token; +}; + // Extract the JWT from the 'Authorization' header with the 'Bearer' scheme. passport.use(new JwtStrategy({ // Prepare the extractor from the header. - jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('Bearer'), + jwtFromRequest: ExtractJwt.fromExtractors([ + cookieExtractor, + ExtractJwt.fromAuthHeaderWithScheme('Bearer') + ]), // Use the secret passed in which is loaded from the environment. This can be // a certificate (loaded) or a HMAC key. diff --git a/services/users.js b/services/users.js index 496db4528..09ddd8ce9 100644 --- a/services/users.js +++ b/services/users.js @@ -381,15 +381,18 @@ module.exports = class UsersService { * @param {Function} done callback after the operation is complete */ static addRoleToUser(id, role) { + const roles = []; // Check to see if the user role is in the allowable set of roles. - if (USER_ROLES.indexOf(role) === -1) { + if (role && USER_ROLES.indexOf(role) === -1) { // User role is not supported! Error out here. return Promise.reject(new Error(`role ${role} is not supported`)); + } else if(role) { + roles.push(role); } - return UserModel.update({id}, {$set: {roles: [role]}}); + return UserModel.update({id}, {$set: {roles}}); } /** @@ -566,10 +569,13 @@ module.exports = class UsersService { // endpoint. return; } - let redirectDomain; try { - redirectDomain = url.parse(loc).hostname; + const {hostname, port} = url.parse(loc); + redirectDomain = hostname; + if (port) { + redirectDomain += `:${port}`; + } } catch (e) { return Promise.reject('redirect location is invalid'); } diff --git a/yarn.lock b/yarn.lock index 79ffe68b2..9855d91e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1262,6 +1262,10 @@ boom@2.x.x: dependencies: hoek "2.x.x" +bowser@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.7.0.tgz#169de4018711f994242bff9a8009e77a1f35e003" + brace-expansion@^1.0.0: version "1.1.7" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59" @@ -1395,6 +1399,10 @@ builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" +bytes@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.3.0.tgz#d5b680a165b6201739acb611542aabc2d8ceb070" + bytes@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" @@ -1815,6 +1823,12 @@ component-emitter@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" +compressible@~2.0.8: + version "2.0.10" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.10.tgz#feda1c7f7617912732b29bf8cf26252a20b9eecd" + dependencies: + mime-db ">= 1.27.0 < 2" + compression-webpack-plugin@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-0.4.0.tgz#811de04215f811ea6a12d4d8aed8457d758f13ac" @@ -1824,6 +1838,17 @@ compression-webpack-plugin@^0.4.0: optionalDependencies: node-zopfli "^2.0.0" +compression@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.6.2.tgz#cceb121ecc9d09c52d7ad0c3350ea93ddd402bc3" + dependencies: + accepts "~1.3.3" + bytes "2.3.0" + compressible "~2.0.8" + debug "~2.2.0" + on-headers "~1.0.1" + vary "~1.1.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1925,6 +1950,13 @@ convert-source-map@^1.1.0: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" +cookie-parser@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.3.tgz#0fe31fa19d000b95f4aadf1f53fdc2b8a203baa5" + dependencies: + cookie "0.3.1" + cookie-signature "1.0.6" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -5143,14 +5175,14 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" +"mime-db@>= 1.27.0 < 2", mime-db@~1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" + mime-db@~1.12.0: version "1.12.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.12.0.tgz#3d0c63180f458eb10d325aaa37d7c58ae312e9d7" -mime-db@~1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" - mime-types@^2.1.10, mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7: version "2.1.15" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed"