Merged in os-107-create-articles-list (pull request #86)

Os 107 create articles list
This commit is contained in:
Ivan Diaz 2016-12-05 01:43:06 +00:00
commit e27e0c66de
48 changed files with 4481 additions and 683 deletions

View File

@ -0,0 +1,21 @@
import API from 'lib-app/api-call';
export default {
initArticles() {
return {
type: 'INIT_ARTICLES',
payload: {}
};
},
retrieveArticles() {
return {
type: 'GET_ARTICLES',
payload: API.call({
path: '/article/get-all',
data: {}
})
};
}
};

View File

@ -0,0 +1,56 @@
import React from 'react';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import ModalContainer from 'app-components/modal-container';
import Header from 'core-components/header';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import Button from 'core-components/button';
class ArticleAddModal extends React.Component {
static propTypes = {
topicId: React.PropTypes.number.isRequired,
topicName: React.PropTypes.string.isRequired,
position: React.PropTypes.number.isRequired
};
render() {
return (
<div className="add-article-modal">
<Header title={i18n('ADD_ARTICLE')} description={i18n('ADD_ARTICLE_DESCRIPTION', {category: this.props.topicName})} />
<Form onSubmit={this.onAddNewArticleFormSubmit.bind(this)}>
<FormField name="title" label={i18n('TITLE')} field="input" fieldProps={{size: 'large'}} validation="TITLE" required/>
<FormField name="content" label={i18n('CONTENT')} field="textarea" validation="TEXT_AREA" required/>
<SubmitButton type="secondary">{i18n('ADD_ARTICLE')}</SubmitButton>
<Button className="add-article-modal__cancel-button" type="link" onClick={(event) => {
event.preventDefault();
ModalContainer.closeModal();
}}>{i18n('CANCEL')}</Button>
</Form>
</div>
);
}
onAddNewArticleFormSubmit(form) {
API.call({
path: '/article/add',
data: {
title: form.title,
content: form.content,
topicId: this.props.topicId,
position: this.props.position
}
}).then(() => {
ModalContainer.closeModal();
if(this.props.onChange) {
this.props.onChange();
}
});
}
}
export default ArticleAddModal;

View File

@ -0,0 +1,7 @@
.article-add-article {
&__cancel-button {
float: right;
margin-top: 15px;
}
}

View File

@ -0,0 +1,88 @@
import React from 'react';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import ArticlesActions from 'actions/articles-actions';
import TopicViewer from 'app-components/topic-viewer';
import ModalContainer from 'app-components/modal-container';
import TopicEditModal from 'app-components/topic-edit-modal';
import Loading from 'core-components/loading';
import Button from 'core-components/button';
import Icon from 'core-components/icon';
class ArticlesList extends React.Component {
static propTypes = {
editable: React.PropTypes.bool,
articlePath: React.PropTypes.string,
loading: React.PropTypes.bool,
topics: React.PropTypes.array,
retrieveOnMount: React.PropTypes.bool
};
static defaultProps = {
editable: true,
retrieveOnMount: true
};
componentDidMount() {
if(this.props.retrieveOnMount) {
this.retrieveArticles();
}
}
render() {
return (this.props.loading) ? <Loading /> : this.renderContent();
}
renderContent() {
return (
<div className="articles-list">
{this.renderTopics()}
{(this.props.editable) ? this.renderAddTopicButton() : null}
</div>
);
}
renderTopics() {
return (
<div className="articles-list__topics">
{this.props.topics.map((topic, index) => {
return (
<div key={index}>
<TopicViewer
{...topic}
editable={this.props.editable}
onChange={this.retrieveArticles.bind(this)}
articlePath={this.props.articlePath} />
<span className="articles-list__topic-separator" />
</div>
);
})}
</div>
);
}
renderAddTopicButton() {
return (
<div className="articles-list__add-topic-button">
<Button onClick={() => ModalContainer.openModal(<TopicEditModal addForm/>)} type="secondary" className="articles-list__add">
<Icon name="plus-circle" size="2x" className="articles-list__add-icon"/> {i18n('ADD_TOPIC')}
</Button>
</div>
);
}
retrieveArticles() {
this.props.dispatch(ArticlesActions.retrieveArticles());
}
}
export default connect((store) => {
return {
topics: store.articles.topics,
loading: store.articles.loading
};
})(ArticlesList);

View File

@ -0,0 +1,22 @@
@import "../scss/vars";
.articles-list {
&__add {
position: relative;
}
&__add-icon {
position: absolute;
left: 10px;
margin-top: -4px;
}
&__topic-separator {
background-color: $grey;
display: block;
height: 1px;
margin: 30px 0;
width: 100%;
}
}

View File

@ -16,6 +16,10 @@ class ModalContainer extends React.Component {
);
}
static closeModal() {
store.dispatch(ModalActions.closeModal());
}
static childContextTypes = {
closeModal: React.PropTypes.func
};

View File

@ -0,0 +1,74 @@
import React from 'react';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import Header from 'core-components/header';
import Button from 'core-components/button';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import IconSelector from 'core-components/icon-selector';
import ColorSelector from 'core-components/color-selector';
class TopicEditModal extends React.Component {
static contextTypes = {
closeModal: React.PropTypes.func
};
static propTypes = {
defaultValues: React.PropTypes.object,
addForm: React.PropTypes.bool,
topicId: React.PropTypes.number
};
state = {
values: this.props.defaultValues || {title: ''}
};
render() {
return (
<div className="topic-edit-modal">
<Header title={i18n((this.props.addForm) ? 'ADD_TOPIC' : 'EDIT_TOPIC')} description={i18n((this.props.addForm) ? 'ADD_TOPIC_DESCRIPTION' : 'EDIT_TOPIC_DESCRIPTION')} />
<Form values={this.state.values} onChange={this.onFormChange.bind(this)} onSubmit={this.onSubmit.bind(this)}>
<FormField name="title" label={i18n('TITLE')} fieldProps={{size: 'large'}} validation="TITLE" required />
<FormField name="icon" className="topic-edit-modal__icon" label={i18n('ICON')} decorator={IconSelector} />
<FormField name="color" className="topic-edit-modal__color" label={i18n('COLOR')} decorator={ColorSelector} />
<SubmitButton className="topic-edit-modal__save-button" type="secondary" size="small">
{i18n('SAVE')}
</SubmitButton>
<Button className="topic-edit-modal__discard-button" onClick={this.onDiscardClick.bind(this)} size="small">
{i18n('CANCEL')}
</Button>
</Form>
</div>
);
}
onSubmit() {
API.call({
path: (this.props.addForm) ? '/article/add-topic' : '/article/edit-topic',
data: {
topicId: this.props.topicId,
name: this.state.values['title'],
icon: this.state.values['icon'],
iconColor: this.state.values['color']
}
}).then(this.context.closeModal);
}
onFormChange(form) {
this.setState({
values: form
});
}
onDiscardClick(event) {
event.preventDefault();
this.context.closeModal();
}
}
export default TopicEditModal;

View File

@ -0,0 +1,19 @@
.topic-edit-modal {
&__icon {
display: inline-block;
float: left;
}
&__color {
margin-left: 60px;
}
&__save-button {
}
&__discard-button {
float: right;
}
}

View File

@ -0,0 +1,248 @@
import React from 'react';
import _ from 'lodash';
import classNames from 'classnames';
import {Link} from 'react-router';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import ModalContainer from 'app-components/modal-container';
import TopicEditModal from 'app-components/topic-edit-modal';
import AreYouSure from 'app-components/are-you-sure';
import ArticleAddModal from 'app-components/article-add-modal';
import Icon from 'core-components/icon';
import Button from 'core-components/button';
class TopicViewer extends React.Component {
static propTypes = {
id: React.PropTypes.number.isRequired,
name: React.PropTypes.string.isRequired,
icon: React.PropTypes.string.isRequired,
iconColor: React.PropTypes.string.isRequired,
articles: React.PropTypes.array.isRequired,
articlePath: React.PropTypes.string,
editable: React.PropTypes.bool
};
static defaultProps = {
articlePath: '/admin/panel/articles/view-article/',
editable: true
};
state = {
articles: this.props.articles,
currentDraggedId: 0
};
render() {
return (
<div className="topic-viewer">
<div className="topic-viewer__header">
<Icon className="topic-viewer__icon" name={this.props.icon} color={this.props.iconColor}/>
<span className="topic-viewer__title">{this.props.name}</span>
{(this.props.editable) ? this.renderEditButton() : null}
{(this.props.editable) ? this.renderDeleteButton() : null}
</div>
<ul className="topic-viewer__list">
{this.state.articles.map(this.renderArticleItem.bind(this))}
{(this.props.editable) ? this.renderAddNewArticle() : null}
</ul>
</div>
);
}
renderEditButton() {
return (
<span onClick={() => {ModalContainer.openModal(this.renderEditModal());}}>
<Icon className="topic-viewer__edit-icon" name="pencil" />
</span>
);
}
renderDeleteButton() {
return (
<span onClick={AreYouSure.openModal.bind(this, i18n('DELETE_TOPIC_DESCRIPTION'), this.onDeleteClick.bind(this))}>
<Icon className="topic-viewer__edit-icon" name="trash" />
</span>
);
}
renderArticleItem(article, index) {
return (
<li className="topic-viewer__list-item" key={index}>
<Link {...this.getArticleLinkProps(article, index)}>
{article.title}
</Link>
</li>
);
}
renderAddNewArticle() {
return (
<li className="topic-viewer__list-item">
<Button type="link" className="topic-viewer__add-item" onClick={() => ModalContainer.openModal(this.renderAddNewArticleModal())}>
{i18n('ADD_NEW_ARTICLE')}
</Button>
</li>
);
}
renderEditModal() {
let props = {
topicId: this.props.id,
defaultValues: {
title: this.props.name,
icon: this.props.icon,
iconColor: this.props.iconColor
}
};
return (
<TopicEditModal {...props} />
);
}
renderAddNewArticleModal() {
let props = {
topicId: this.props.id,
position: this.props.articles.length,
topicName: this.props.name
};
return (
<ArticleAddModal {...props}/>
);
}
getArticleLinkProps(article, index) {
let classes = {
'topic-viewer__list-item-button': true,
'topic-viewer__list-item-hidden': article.hidden
};
let props = {
className: classNames(classes),
to: this.props.articlePath + article.id
};
if(this.props.editable) {
_.extend(props, {
onDragOver: this.onItemDragOver.bind(this, article, index),
onDrop: this.onItemDrop.bind(this, article, index),
onDragStart: () => this.setState({currentDraggedId: article.id}),
onDragEnd: () => {
if(this.state.currentDraggedId) {
this.setState({articles: this.props.articles});
}
}
});
}
return props;
}
onDeleteClick() {
API.call({
path: '/article/delete-topic',
data: {
topicId: this.props.id
}
}).then(this.onChange.bind(this));
}
onItemDragOver(article, index, event) {
event.preventDefault();
if(!article.hidden) {
let articles = [];
let draggedId = this.state.currentDraggedId;
let draggedIndex = _.findIndex(this.props.articles, {id: draggedId});
_.forEach(this.props.articles, (current, currentIndex) => {
if(draggedIndex < index) {
if(current.id !== draggedId) {
articles.push(current);
}
if(index === currentIndex) {
articles.push({
id: article.id,
title: 'X',
hidden: true
});
}
} else {
if(index === currentIndex) {
articles.push({
id: article.id,
title: 'X',
hidden: true
});
}
if(current.id !== draggedId) {
articles.push(current);
}
}
});
this.setState({articles});
}
}
onItemDrop(article, index, event) {
event.stopPropagation();
event.preventDefault();
let articles = [];
let draggedId = this.state.currentDraggedId;
let dragged = _.find(this.props.articles, {id: draggedId});
let draggedIndex = _.findIndex(this.props.articles, {id: draggedId});
_.forEach(this.props.articles, (current) => {
if(current.id !== draggedId) {
if(draggedIndex < index) {
articles.push(current);
if(current.id === article.id) {
articles.push(dragged);
}
} else {
if(current.id === article.id) {
articles.push(dragged);
}
articles.push(current);
}
}
});
if(draggedIndex === index) {
this.setState({articles: this.props.articles, currentDraggedId: 0});
} else {
this.updatePositions(articles.map((article) => article.id));
this.setState({articles, currentDraggedId: 0}, this.onChange.bind(this));
}
}
updatePositions(positions) {
_.forEach(positions, (id, index) => {
if(this.props.articles[index].id !== id) {
API.call({
path: '/article/edit',
data: {
articleId: id,
position: index
}
});
}
});
}
onChange() {
if(this.props.onChange) {
this.props.onChange();
}
}
}
export default TopicViewer;

View File

@ -0,0 +1,62 @@
@import "../scss/vars";
.topic-viewer {
text-align: left;
&__header {
cursor: default;
margin-bottom: 15px;
font-size: $font-size--bg;
&:hover {
.topic-viewer__edit-icon {
display: inline-block;
}
}
}
&__icon {
color: $primary-green;
}
&__title {
font-size: $font-size--md;
margin-left: 15px;
}
&__edit-icon {
color: $grey;
cursor: pointer;
margin-left: 10px;
display: none;
}
&__list {
&-item {
display: inline-block;
width: 50%;
color: $secondary-blue;
margin-bottom: 10px;
&-hidden {
width: 80%;
display: inline-block;
opacity: 0;
}
}
&-item:before {
content: "";
color: $grey;
}
&-item-button {
color: $secondary-blue;
}
}
&__add-item {
color: $dark-grey;
}
}

View File

@ -66,7 +66,7 @@ export default (
<Route path='create-ticket' component={DashboardCreateTicketPage}/>
<Route path='edit-profile' component={DashboardEditProfilePage}/>
<Route path='article' component={DashboardArticlePage}/>
<Route path='article/:articleId' component={DashboardArticlePage}/>
<Route path='ticket/:ticketNumber' component={DashboardTicketPage}/>
</Route>
</Route>
@ -97,7 +97,7 @@ export default (
<Route path="articles">
<IndexRedirect to="list-articles" />
<Route path="list-articles" component={AdminPanelListArticles} />
<Route path="view-article" component={AdminPanelViewArticle} />
<Route path="view-article/:articleId" component={AdminPanelViewArticle} />
</Route>
<Route path="staff">

View File

@ -1,11 +1,18 @@
import React from 'react';
import i18n from 'lib-app/i18n';
import ArticlesList from 'app-components/articles-list';
import Header from 'core-components/header';
class AdminPanelListArticles extends React.Component {
render() {
return (
<div>
/admin/panel/articles/list-articles
<div className="admin-panel-list-articles">
<Header title={i18n('LIST_ARTICLES')} description={i18n('LIST_ARTICLES_DESCRIPTION')}/>
<div className="admin-panel-list-articles__list">
<ArticlesList editable/>
</div>
</div>
);
}

View File

@ -0,0 +1,6 @@
.admin-panel-list-articles {
&__list {
padding: 0 50px;
}
}

View File

@ -1,14 +1,148 @@
import React from 'react';
import _ from 'lodash';
import {connect} from 'react-redux';
import RichTextEditor from 'react-rte-browserify';
import ArticlesActions from 'actions/articles-actions';
import SessionStore from 'lib-app/session-store';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import DateTransformer from 'lib-core/date-transformer';
import Header from 'core-components/header';
import Loading from 'core-components/loading';
import Button from 'core-components/button';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
class AdminPanelViewArticle extends React.Component {
static propTypes = {
topics: React.PropTypes.array,
loading: React.PropTypes.bool
};
static defaultProps = {
topics: [],
loading: true
};
state = {
editable: false
};
componentDidMount() {
if(SessionStore.getItem('topics')) {
this.props.dispatch(ArticlesActions.initArticles());
} else {
this.props.dispatch(ArticlesActions.retrieveArticles());
}
}
render() {
return (
<div>
/admin/panel/articles/view-article
<div className="admin-panel-view-article">
{(this.props.loading) ? <Loading /> : this.renderContent()}
</div>
);
}
renderContent() {
let article = this.findArticle();
return (article) ? this.renderArticle(article) : i18n('ARTICLE_NOT_FOUND');
}
renderArticle(article) {
return (this.state.editable) ? this.renderArticleEdit(article) : this.renderArticlePreview(article);
}
renderArticlePreview(article) {
return (
<div className="admin-panel-view-article__content">
<div className="admin-panel-view-article__edit-button">
<Button size="medium" onClick={this.onEditClick.bind(this, article)}>{i18n('EDIT')}</Button>
</div>
<div className="admin-panel-view-article__article">
<Header title={article.title}/>
<div className="admin-panel-view-article__article-content">
<div dangerouslySetInnerHTML={{__html: article.content}}/>
</div>
<div className="admin-panel-view-article__last-edited">
{i18n('LAST_EDITED_IN', {date: DateTransformer.transformToString(article.lastEdited)})}
</div>
</div>
</div>
);
}
renderArticleEdit() {
return (
<Form values={this.state.form} onChange={(form) => this.setState({form})} onSubmit={this.onFormSubmit.bind(this)}>
<div className="admin-panel-view-article__buttons">
<SubmitButton className="admin-panel-view-article__button" type="secondary" size="medium">{i18n('SAVE')}</SubmitButton>
<Button className="admin-panel-view-article__button" size="medium" onClick={this.onFormCancel.bind(this)}>
{i18n('CANCEL')}
</Button>
</div>
<FormField name="title" label={i18n('TITLE')} />
<FormField name="content" label={i18n('CONTENT')} field="textarea" />
</Form>
);
}
findArticle() {
let article = null;
_.forEach(this.props.topics, (topic) => {
if(!article) {
article = _.find(topic.articles, {id: this.props.params.articleId * 1});
}
});
return article;
}
onEditClick(article) {
this.setState({
editable: true,
form: {
title: article.title,
content: RichTextEditor.createValueFromString(article.content, 'html')
}
});
}
onFormSubmit(form) {
API.call({
path: '/article/edit',
data: {
title: form.title,
content: form.content
}
}).then(() => {
this.props.dispatch(ArticlesActions.retrieveArticles());
this.setState({
editable: false
});
});
}
onFormCancel(event) {
event.preventDefault();
this.setState({
editable: false
});
}
}
export default AdminPanelViewArticle;
export default connect((store) => {
return {
topics: store.articles.topics,
loading: store.articles.loading
};
})(AdminPanelViewArticle);

View File

@ -0,0 +1,22 @@
.admin-panel-view-article {
&__edit-button {
text-align: left;
margin-bottom: 20px;
}
&__last-edited {
font-style: italic;
text-align: right;
margin-top: 20px;
}
&__buttons {
text-align: left;
margin-bottom: 20px;
}
&__button {
margin-right: 20px;
}
}

View File

@ -1,4 +1,5 @@
import React from 'react';
import _ from 'lodash';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
@ -79,7 +80,7 @@ class AdminPanelBanUsers extends React.Component {
onSearch(query) {
this.setState({
filteredEmails: SearchBox.searchQueryInList(this.state.emails, query)
filteredEmails: SearchBox.searchQueryInList(this.state.emails, query, _.startsWith, _.includes)
});
}

View File

@ -107,7 +107,7 @@ let DemoPage = React.createClass({
title: 'Tooltip',
render: (
<div>
<Tooltip content="mensaje mensa jemensajemens ajem ensaje nsaje adicionals" openOnHover={true}>
<Tooltip content="mensaje mensa jemensajemens ajem ensaje nsaje adicionals">
hola
</Tooltip>
</div>

View File

@ -1,14 +1,114 @@
import React from 'react';
import _ from 'lodash';
import {connect} from 'react-redux';
import ArticlesActions from 'actions/articles-actions';
import SessionStore from 'lib-app/session-store';
import i18n from 'lib-app/i18n';
import DateTransformer from 'lib-core/date-transformer';
import Header from 'core-components/header';
import Loading from 'core-components/loading';
import BreadCrumb from 'core-components/breadcrumb';
class DashboardArticlePage extends React.Component {
static propTypes = {
topics: React.PropTypes.array,
loading: React.PropTypes.bool
};
static defaultProps = {
topics: [],
loading: true
};
componentDidMount() {
if(SessionStore.getItem('topics')) {
this.props.dispatch(ArticlesActions.initArticles());
} else {
this.props.dispatch(ArticlesActions.retrieveArticles());
}
}
render() {
return (
<div>
DASHBOARD ARTICLE
<div className="dashboard-article-page">
<div className="dashboard-article-page__breadcrumb">
<BreadCrumb items={this.getBreadCrumbItems()}/>
</div>
{(this.props.loading) ? <Loading /> : this.renderContent()}
</div>
);
}
renderContent() {
let article = this.findArticle();
return (article) ? this.renderArticlePreview(article) : i18n('ARTICLE_NOT_FOUND');
}
renderArticlePreview(article) {
return (
<div className="dashboard-article-page__article">
<Header title={article.title}/>
<div className="dashboard-article-page__article-content">
<div dangerouslySetInnerHTML={{__html: article.content}}/>
</div>
<div className="dashboard-article-page__last-edited">
{i18n('LAST_EDITED_IN', {date: DateTransformer.transformToString(article.lastEdited)})}
</div>
</div>
);
}
findArticle() {
let article = null;
_.forEach(this.props.topics, (topic) => {
if(!article) {
article = _.find(topic.articles, {id: this.props.params.articleId * 1});
}
});
return article;
}
findTopic() {
let topicFound = {};
_.forEach(this.props.topics, (topic) => {
if(_.find(topic.articles, {id: this.props.params.articleId * 1})) {
topicFound = topic;
}
});
return topicFound;
}
getBreadCrumbItems() {
let article = this.findArticle();
let topic = this.findTopic();
let items = [
{content: i18n('ARTICLES'), url: '/dashboard/articles'}
];
if(topic && topic.name) {
items.push({content: topic.name, url: '/dashboard/articles'});
}
if(article && article.title) {
items.push({content: article.title});
}
return items;
}
}
export default DashboardArticlePage;
export default connect((store) => {
return {
topics: store.articles.topics,
loading: store.articles.loading
};
})(DashboardArticlePage);

View File

@ -0,0 +1,8 @@
.dashboard-article-page {
&__last-edited {
font-style: italic;
text-align: right;
margin-top: 20px;
}
}

View File

@ -1,14 +1,109 @@
import React from 'react';
import {connect} from 'react-redux';
import _ from 'lodash';
import {Link} from 'react-router';
import i18n from 'lib-app/i18n';
import ArticlesList from 'app-components/articles-list';
import ArticlesActions from 'actions/articles-actions';
import Header from 'core-components/header';
import SearchBox from 'core-components/search-box';
class DashboardListArticlesPage extends React.Component {
state = {
results: [],
showSearchResults: false
};
componentDidMount() {
this.props.dispatch(ArticlesActions.retrieveArticles());
}
render() {
return (
<div>
DASHBOARD ARTICLES LIST
<div classnames="dashboard-list-articles-page">
<Header title={i18n('LIST_ARTICLES')} description={i18n('LIST_ARTICLES_DESCRIPTION')}/>
<SearchBox className="dashboard-list-articles-page__search-box" onSearch={this.onSearch.bind(this)} searchOnType />
{(!this.state.showSearchResults) ? this.renderArticleList() : this.renderSearchResults()}
</div>
);
}
renderArticleList() {
return (
<ArticlesList editable={false} articlePath="/dashboard/article/" retrieveOnMount={false}/>
);
}
renderSearchResults() {
return (
<div className="dashboard-list-articles-page__search-results">
{(_.isEmpty(this.state.results)) ? i18n('NO_RESULTS') : this.state.results.map(this.renderSearchResultsItem.bind(this))}
</div>
);
}
renderSearchResultsItem(item) {
let content = this.stripHTML(item.content);
content = content.substring(0, 100);
content += '...';
return (
<div className="dashboard-list-articles-page__search-result">
<div className="dashboard-list-articles-page__search-result-title">
<Link to={'/dashboard/article/' + item.id}>{item.title}</Link>
</div>
<div className="dashboard-list-articles-page__search-result-description">{content}</div>
<div className="dashboard-list-articles-page__search-result-topic">{item.topic}</div>
</div>
);
}
onSearch(query) {
this.setState({
results: SearchBox.searchQueryInList(this.getArticles(), query, this.isQueryInTitle.bind(this), this.isQueryInContent.bind(this)),
showSearchResults: query.length
});
}
getArticles() {
let articles = [];
_.forEach(this.props.topics, (topic) => {
_.forEach(topic.articles, (article) => {
articles.push({
id: article.id,
title: article.title,
content: article.content,
topic: topic.name
});
});
});
return articles;
}
isQueryInTitle(article, query) {
return _.includes(article.title.toLowerCase(), query.toLowerCase());
}
isQueryInContent(article, query) {
return _.includes(article.content.toLowerCase(), query.toLowerCase());
}
stripHTML(html){
let tmp = document.createElement('DIV');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || "";
}
}
export default DashboardListArticlesPage;
export default connect((store) => {
return {
topics: store.articles.topics,
loading: store.articles.loading
};
})(DashboardListArticlesPage);

View File

@ -0,0 +1,32 @@
@import "../../../../scss/vars";
.dashboard-list-articles-page {
&__search-results {
}
&__search-box {
margin-bottom: 30px;
}
&__search-result {
margin-bottom: 20px;
text-align: left;
&-title {
}
&-description {
font-size: $font-size--xs;
margin: 5px 0;
}
&-topic {
color: $grey;
font-size: $font-size--sm;
text-transform: uppercase;
}
}
}

View File

@ -0,0 +1,42 @@
import React from 'react';
import {Link} from 'react-router';
class BreadCrumb extends React.Component {
static propTypes = {
items: React.PropTypes.arrayOf(React.PropTypes.shape({
content: React.PropTypes.string.isRequired,
url: React.PropTypes.string
}))
};
render() {
return (
<ol className="breadcrumb">
{this.props.items.map(this.renderItem.bind(this))}
</ol>
);
}
renderItem(item, index) {
return (
<li className="breadcrumb__item" key={index}>
{(item.url) ? this.renderItemLink(item) : item.content}
{(index < this.props.items.length - 1) ? this.renderArrow() : null}
</li>
);
}
renderItemLink(item) {
return (
<Link to={item.url}>{item.content}</Link>
);
}
renderArrow() {
return (
<span className="breadcrumb__arrow">{'>'}</span>
);
}
}
export default BreadCrumb;

View File

@ -0,0 +1,18 @@
@import "../scss/vars";
.breadcrumb {
padding: 0;
text-align: left;
margin-bottom: 20px;
&__item {
color: $grey;
display: inline-block;
text-decoration: none;
}
&__arrow {
color: $grey;
margin: 0 5px;
}
}

View File

@ -0,0 +1,57 @@
import React from 'react';
import Tooltip from 'core-components/tooltip';
const colors = ['#ff6900', '#fcb900', '#7bdcb5', '#00d084', '#8ed1fc', '#0693e3', '#abb8c3', '#eb144c', '#f78da7', '#9900ef'];
class ColorSelector extends React.Component {
static propTypes = {
value: React.PropTypes.string,
onChange: React.PropTypes.func
};
static defaultProps = {
value: '#ff6900'
};
state = {
show: false
};
render() {
return (
<div className="color-selector">
<Tooltip content={this.renderTooltipContent()} show={this.state.show} onToggle={(show) => this.setState({show})}>
<span className="color-selector__current" style={{backgroundColor: this.props.value}} />
</Tooltip>
</div>
)
}
renderTooltipContent() {
return (
<div className="color-selector__tooltip">
{colors.map(this.renderTooltipColor.bind(this))}
</div>
);
}
renderTooltipColor(color) {
return (
<span className="color-selector__tooltip-color" onClick={this.onColorClick.bind(this, color)} style={{backgroundColor: color}}/>
);
}
onColorClick(color) {
this.setState({
show: false
});
if(this.props.onChange) {
this.props.onChange({target: {value: color}});
}
}
}
export default ColorSelector;

View File

@ -0,0 +1,21 @@
.color-selector {
&__current {
cursor: pointer;
display: inline-block;
width: 30px;
height: 30px;
}
&__tooltip {
width: 200px;
}
&__tooltip-color {
cursor: pointer;
display: inline-block;
width: 30px;
height: 30px;
margin: 2px 5px;
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
@import "../scss/vars";
.icon-selector {
&__tooltip {
width: 400px;
height: 200px;
overflow-y: scroll;
}
&__tooltip-icon {
display: inline-block;
cursor: pointer;
background-color: $light-grey;
padding: 6px 3px;
border-radius: 5px;
margin: 5px;
width: 32px;
height: 32px;
text-align: center;
&:hover {
background-color: $medium-grey;
}
}
&__current-icon {
cursor: pointer;
display: inline-block;
text-align: center;
padding: 6px 3px;
border-radius: 5px;
width: 32px;
height: 32px;
background-color: $light-grey;
}
}

View File

@ -5,6 +5,7 @@ class Icon extends React.Component {
static propTypes = {
name: React.PropTypes.string.isRequired,
color: React.PropTypes.string,
size: React.PropTypes.string
};
@ -18,7 +19,7 @@ class Icon extends React.Component {
renderFontIcon() {
return (
<span className={this.getFontIconClass()} aria-hidden="true" />
<span className={this.getFontIconClass()} aria-hidden="true" style={{color: this.props.color}}/>
);
}

View File

@ -1,6 +1,5 @@
import React from 'react';
import classNames from 'classnames';
import _ from 'lodash';
import Input from 'core-components/input';
import Icon from 'core-components/icon';
@ -8,12 +7,12 @@ import keyCode from 'keycode';
class SearchBox extends React.Component {
static searchQueryInList(list, query) {
static searchQueryInList(list, query, startsWith, includes) {
let match = [];
let rest = [];
list.forEach(function (item) {
if(_.startsWith(item, query)) {
if(startsWith(item, query)) {
match.push(item);
} else {
rest.push(item);
@ -21,7 +20,7 @@ class SearchBox extends React.Component {
});
rest.forEach(function (item) {
if(_.includes(item, query)) {
if(includes(item, query)) {
match.push(item);
}
});

View File

@ -6,7 +6,9 @@ class Tooltip extends React.Component {
static propTypes = {
children: React.PropTypes.node,
content: React.PropTypes.node,
openOnHover: React.PropTypes.bool
openOnHover: React.PropTypes.bool,
show: React.PropTypes.bool,
onToggle: React.PropTypes.func
};
state = {
@ -16,7 +18,7 @@ class Tooltip extends React.Component {
render() {
return (
<div {...this.getProps()}>
{(this.state.show) ? this.renderAnimatedMessage() : null}
{(this.getShowValue()) ? this.renderAnimatedMessage() : null}
<div {...this.getChildrenProps()}>
{this.props.children}
</div>
@ -26,7 +28,7 @@ class Tooltip extends React.Component {
renderAnimatedMessage() {
return (
<Motion defaultStyle={{opacity:spring(0)}} style={{opacity:spring(1)}}>
<Motion defaultStyle={{opacity: 0}} style={{opacity: spring(1)}}>
{this.renderMessage.bind(this)}
</Motion>
)
@ -35,10 +37,11 @@ class Tooltip extends React.Component {
renderMessage(animation) {
return (
<div style={animation}>
<span className="tooltip__pointer-shadow"/>
<span className="tooltip__pointer"/>
<div className="tooltip__message">
{this.props.content}
</div>
<span className="tooltip__pointer"/>
</div>
)
}
@ -58,10 +61,10 @@ class Tooltip extends React.Component {
getChildrenProps() {
let props = {};
props.className= 'tooltip__children';
props.className = 'tooltip__children';
if(!this.props.openOnHover) {
props.onClick= this.onClick.bind(this);
props.onClick = this.onClick.bind(this);
}
return props;
@ -71,20 +74,35 @@ class Tooltip extends React.Component {
this.setState({
show: true
});
if(this.props.onToggle) {
this.props.onToggle(true);
}
}
onMouseOut() {
this.setState({
show: false
});
if(this.props.onToggle) {
this.props.onToggle(false);
}
}
onClick() {
if (this.state.show) {
this.setState({show: false});
} else {
this.setState({show: true});
this.setState({
show: !this.getShowValue()
});
if(this.props.onToggle) {
this.props.onToggle(!this.getShowValue());
}
}
getShowValue() {
return (this.props.show !== undefined) ? this.props.show : this.state.show;
}
}
export default Tooltip;

View File

@ -1,3 +1,5 @@
@import "../scss/vars";
.tooltip {
position: relative;
display: inline-block;
@ -10,23 +12,38 @@
position: absolute;
bottom: 100%;
left: -25%;
margin-bottom: 10px;
box-shadow: 0 0 4px #8D8D8D;
margin-bottom: 15px;
margin-left: -10px;
border: 0 solid rgba(0, 0, 0, 0.247059);
box-shadow: rgba(0, 0, 0, 0.247059) 0 -1px 4px;
border-radius: 4px;
min-width: 200px;
background-color: #F7F7F7;
background-color: white;
color: black;
padding: 10px;
z-index: 1000;
z-index: 900;
}
&__pointer {
border: solid transparent;
position: absolute;
border-top-color: #8D8D8D;
border-width: 10px;
top: -10px;
border-top-color: white;
border-width: 13px;
top: -15px;
left: 50%;
margin-left: -8px;
margin-left: -13px;
z-index: 910;
pointer-events: none;
}
&__pointer-shadow {
border: solid transparent;
position: absolute;
border-top-color: rgba(black, 0.1);
border-width: 14px;
top: -15px;
left: 50%;
margin-left: -14px;
pointer-events: none;
}
}

View File

@ -0,0 +1,159 @@
module.exports = [
{
path: '/article/get-all',
time: 100,
response: function () {
return {
status: 'success',
data: [
{
id: 1,
name: 'Membership Settings',
icon: 'user',
iconColor: '#82CA9C',
articles: [
{
id: 1,
title: 'Mannaging apps for your account',
content: 'Curabitur sed dignissim turpis, sed lacinia urna. Vestibulum semper suscipit interdum. Proin sed sem gravida massa tristique rhoncus auctor eu diam. Donec fringilla in ex non dignissim. Praesent sed ultricies eros. Nullam vel augue eget libero volutpat sodales sit amet et orci.',
lastEdited: 20160516,
position: 1
},
{
id: 2,
title: 'How to assign new task and files',
content: 'Aliquam aliquet mi nulla. Nam vel orci diam. Suspendisse euismod orci efficitur nulla mattis eleifend vitae quis neque. Etiam orci dolor, dignissim quis convallis quis, rhoncus eu tellus. Phasellus aliquam ut enim id ultrices. Nunc dolor arcu, viverra vel ullamcorper nec, dignissim a lectus. Praesent fringilla, neque nec suscipit placerat, augue elit suscipit velit, in feugiat leo justo id tortor.',
lastEdited: 20150429,
position: 2
},
{
id: 3,
title: 'Updating your profile picture',
content: 'Aliquam aliquet mi nulla. Nam vel orci diam. Suspendisse euismod orci efficitur nulla mattis eleifend vitae quis neque. Etiam orci dolor, dignissim quis convallis quis, rhoncus eu tellus. Phasellus aliquam ut enim id ultrices. Nunc dolor arcu, viverra vel ullamcorper nec, dignissim a lectus. Praesent fringilla, neque nec suscipit placerat, augue elit suscipit velit, in feugiat leo justo id tortor.',
lastEdited: 20150429,
position: 3
},
{
id: 4,
title: 'Deleting your account',
content: 'Aliquam aliquet mi nulla. Nam vel orci diam. Suspendisse euismod orci efficitur nulla mattis eleifend vitae quis neque. Etiam orci dolor, dignissim quis convallis quis, rhoncus eu tellus. Phasellus aliquam ut enim id ultrices. Nunc dolor arcu, viverra vel ullamcorper nec, dignissim a lectus. Praesent fringilla, neque nec suscipit placerat, augue elit suscipit velit, in feugiat leo justo id tortor.',
lastEdited: 20150929,
position: 4
},
{
id: 5,
title: 'Upload files to your cloud drive',
content: 'Aliquam aliquet mi nulla. Nam vel orci diam. Suspendisse euismod orci efficitur nulla mattis eleifend vitae quis neque. Etiam orci dolor, dignissim quis convallis quis, rhoncus eu tellus. Phasellus aliquam ut enim id ultrices. Nunc dolor arcu, viverra vel ullamcorper nec, dignissim a lectus. Praesent fringilla, neque nec suscipit placerat, augue elit suscipit velit, in feugiat leo justo id tortor.',
lastEdited: 20131229,
position: 5
}
]
},
{
id: 2,
name: 'Billing and plans',
icon: 'credit-card',
iconColor: 'red',
articles: [
{
id: 1,
title: 'Mannaging apps for your account',
content: 'Curabitur sed dignissim turpis, sed lacinia urna. Vestibulum semper suscipit interdum. Proin sed sem gravida massa tristique rhoncus auctor eu diam. Donec fringilla in ex non dignissim. Praesent sed ultricies eros. Nullam vel augue eget libero volutpat sodales sit amet et orci.',
lastEdited: 20160516,
position: 1
},
{
id: 2,
title: 'How to assign new task and files',
content: 'Aliquam aliquet mi nulla. Nam vel orci diam. Suspendisse euismod orci efficitur nulla mattis eleifend vitae quis neque. Etiam orci dolor, dignissim quis convallis quis, rhoncus eu tellus. Phasellus aliquam ut enim id ultrices. Nunc dolor arcu, viverra vel ullamcorper nec, dignissim a lectus. Praesent fringilla, neque nec suscipit placerat, augue elit suscipit velit, in feugiat leo justo id tortor.',
lastEdited: 20150429,
position: 2
},
{
id: 3,
title: 'Updating your profile picture',
content: 'Aliquam aliquet mi nulla. Nam vel orci diam. Suspendisse euismod orci efficitur nulla mattis eleifend vitae quis neque. Etiam orci dolor, dignissim quis convallis quis, rhoncus eu tellus. Phasellus aliquam ut enim id ultrices. Nunc dolor arcu, viverra vel ullamcorper nec, dignissim a lectus. Praesent fringilla, neque nec suscipit placerat, augue elit suscipit velit, in feugiat leo justo id tortor.',
lastEdited: 20150429,
position: 3
},
{
id: 4,
title: 'Deleting your account',
content: 'Aliquam aliquet mi nulla. Nam vel orci diam. Suspendisse euismod orci efficitur nulla mattis eleifend vitae quis neque. Etiam orci dolor, dignissim quis convallis quis, rhoncus eu tellus. Phasellus aliquam ut enim id ultrices. Nunc dolor arcu, viverra vel ullamcorper nec, dignissim a lectus. Praesent fringilla, neque nec suscipit placerat, augue elit suscipit velit, in feugiat leo justo id tortor.',
lastEdited: 20150929,
position: 4
},
{
id: 5,
title: 'Upload files to your cloud drive',
content: 'Aliquam aliquet mi nulla. Nam vel orci diam. Suspendisse euismod orci efficitur nulla mattis eleifend vitae quis neque. Etiam orci dolor, dignissim quis convallis quis, rhoncus eu tellus. Phasellus aliquam ut enim id ultrices. Nunc dolor arcu, viverra vel ullamcorper nec, dignissim a lectus. Praesent fringilla, neque nec suscipit placerat, augue elit suscipit velit, in feugiat leo justo id tortor.',
lastEdited: 20131229,
position: 5
}
]
}
]
};
}
},
{
path: '/article/edit-topic',
time: 100,
response: function () {
return {
status: 'success',
data: {}
};
}
},
{
path: '/article/edit',
time: 100,
response: function () {
return {
status: 'success',
data: {}
};
}
},
{
path: '/article/add',
time: 100,
response: function () {
return {
status: 'success',
data: {}
};
}
},
{
path: '/article/add-topic',
time: 100,
response: function () {
return {
status: 'success',
data: {}
};
}
},
{
path: '/article/delete',
time: 100,
response: function () {
return {
status: 'success',
data: {}
};
}
},
{
path: '/article/delete-topic',
time: 100,
response: function () {
return {
status: 'success',
data: {}
};
}
}
];

View File

@ -42,7 +42,7 @@ export default {
'CUSTOM_RESPONSES': 'Custom Responses',
'LIST_USERS': 'List Users',
'BAN_USERS': 'Ban Users',
'LIST_ARTICLES': 'List Articles',
'LIST_ARTICLES': 'Article List',
'STAFF_MEMBERS': 'Staff Members',
'DEPARTMENTS': 'Departments',
'SYSTEM_PREFERENCES': 'System Preferences',
@ -84,6 +84,16 @@ export default {
'SEARCH_USERS': 'Search users...',
'SEARCH_EMAIL': 'Search email...',
'USER_VIEW_TITLE': 'User #{userId}',
'EDIT_TOPIC': 'Edit Topic',
'ADD_TOPIC': 'Add Topic',
'ICON': 'Icon',
'COLOR': 'Color',
'ADD_NEW_ARTICLE': 'Add new article',
'ADD_ARTICLE': 'Add article',
'LAST_EDITED_IN': 'Last edited in {date}',
'EDIT': 'Edit',
'NO_RESULTS': 'No results',
'DELETE_AND_BAN': 'Delete and ban',
//VIEW DESCRIPTIONS
'CREATE_TICKET_DESCRIPTION': 'This is a form for creating tickets. Fill the form and send us your issues/doubts/suggestions. Our support system will answer it as soon as possible.',
@ -101,6 +111,11 @@ export default {
'LIST_USERS_DESCRIPTION': 'This is the list of users that are registered in this platform. You can search for someone in particular, delete it or ban it.',
'USER_VIEW_DESCRIPTION': 'Here you can find all the information about an user and all the tickets sent by the user. You can also delete or ban it.',
'DELETE_USER_DESCRIPTION': 'The user will not be able to log in aging and all its tickets will be erased. Also, the email can not be used any more.',
'DELETE_TOPIC_DESCRIPTION': 'By deleting the topic, all articles on it will be erased.',
'EDIT_TOPIC_DESCRIPTION': 'Here you can change the name, the icon and the icon color of the topic.',
'ADD_ARTICLE_DESCRIPTION': 'Here you can add an article that will be available for every user. It will be added inside the category {category}.',
'LIST_ARTICLES_DESCRIPTION': 'This is a list of articles that includes information about our services.',
'ADD_TOPIC_DESCRIPTION': 'Here you can add a topic that works as a category for articles.',
//ERRORS
'EMAIL_OR_PASSWORD': 'Email or password invalid',

View File

@ -20,6 +20,7 @@ fixtures.add(require('data/fixtures/user-fixtures'));
fixtures.add(require('data/fixtures/staff-fixtures'));
fixtures.add(require('data/fixtures/ticket-fixtures'));
fixtures.add(require('data/fixtures/system-fixtures'));
fixtures.add(require('data/fixtures/article-fixtures'));
_.each(fixtures.getAll(), function (fixture) {
mockjax({

View File

@ -4,12 +4,14 @@ import { routerReducer } from 'react-router-redux';
import sessionReducer from 'reducers/session-reducer';
import configReducer from 'reducers/config-reducer';
import modalReducer from 'reducers/modal-reducer';
import articlesReducer from 'reducers/articles-reducer';
import adminDataReducer from 'reducers/admin-data-reducer';
export default combineReducers({
session: sessionReducer,
config: configReducer,
modal: modalReducer,
articles: articlesReducer,
adminData: adminDataReducer,
routing: routerReducer
});

View File

@ -0,0 +1,48 @@
import _ from 'lodash';
import Reducer from 'reducers/reducer';
import SessionStore from 'lib-app/session-store';
class ArticlesReducer extends Reducer {
getInitialState() {
return {
retrieved: false,
loading: true,
topics: []
};
}
getTypeHandlers() {
return {
'GET_ARTICLES_FULFILLED': this.onArticlesRetrieved,
'INIT_ARTICLES': this.onInitArticles
};
}
onArticlesRetrieved(state, payload) {
SessionStore.setItem('topics', JSON.stringify(payload.data));
return _.extend({}, state, {
retrieved: true,
loading: false,
topics: payload.data
});
}
onInitArticles(state) {
let topics = SessionStore.getItem('topics');
if(topics) {
topics = JSON.parse(topics);
}
return _.extend({}, state, {
retrieved: !!topics,
loading: false,
topics: topics
});
}
}
export default ArticlesReducer.getInstance();

View File

@ -23,5 +23,6 @@ $half-space: 20px;
$font-size--xs: 11px;
$font-size--sm: 13px;
$font-size--md: 16px;
$font-size--bg: 19px;
$font-size--lg: 24px;
$font-size--xl: 32px;

View File

@ -438,7 +438,7 @@
.#{$fa-css-prefix}-stumbleupon:before { content: $fa-var-stumbleupon; }
.#{$fa-css-prefix}-delicious:before { content: $fa-var-delicious; }
.#{$fa-css-prefix}-digg:before { content: $fa-var-digg; }
.#{$fa-css-prefix}-pied-piper:before { content: $fa-var-pied-piper; }
.#{$fa-css-prefix}-pied-piper-pp:before { content: $fa-var-pied-piper-pp; }
.#{$fa-css-prefix}-pied-piper-alt:before { content: $fa-var-pied-piper-alt; }
.#{$fa-css-prefix}-drupal:before { content: $fa-var-drupal; }
.#{$fa-css-prefix}-joomla:before { content: $fa-var-joomla; }
@ -488,6 +488,7 @@
.#{$fa-css-prefix}-life-ring:before { content: $fa-var-life-ring; }
.#{$fa-css-prefix}-circle-o-notch:before { content: $fa-var-circle-o-notch; }
.#{$fa-css-prefix}-ra:before,
.#{$fa-css-prefix}-resistance:before,
.#{$fa-css-prefix}-rebel:before { content: $fa-var-rebel; }
.#{$fa-css-prefix}-ge:before,
.#{$fa-css-prefix}-empire:before { content: $fa-var-empire; }
@ -604,6 +605,7 @@
.#{$fa-css-prefix}-opencart:before { content: $fa-var-opencart; }
.#{$fa-css-prefix}-expeditedssl:before { content: $fa-var-expeditedssl; }
.#{$fa-css-prefix}-battery-4:before,
.#{$fa-css-prefix}-battery:before,
.#{$fa-css-prefix}-battery-full:before { content: $fa-var-battery-full; }
.#{$fa-css-prefix}-battery-3:before,
.#{$fa-css-prefix}-battery-three-quarters:before { content: $fa-var-battery-three-quarters; }
@ -675,3 +677,113 @@
.#{$fa-css-prefix}-vimeo:before { content: $fa-var-vimeo; }
.#{$fa-css-prefix}-black-tie:before { content: $fa-var-black-tie; }
.#{$fa-css-prefix}-fonticons:before { content: $fa-var-fonticons; }
.#{$fa-css-prefix}-reddit-alien:before { content: $fa-var-reddit-alien; }
.#{$fa-css-prefix}-edge:before { content: $fa-var-edge; }
.#{$fa-css-prefix}-credit-card-alt:before { content: $fa-var-credit-card-alt; }
.#{$fa-css-prefix}-codiepie:before { content: $fa-var-codiepie; }
.#{$fa-css-prefix}-modx:before { content: $fa-var-modx; }
.#{$fa-css-prefix}-fort-awesome:before { content: $fa-var-fort-awesome; }
.#{$fa-css-prefix}-usb:before { content: $fa-var-usb; }
.#{$fa-css-prefix}-product-hunt:before { content: $fa-var-product-hunt; }
.#{$fa-css-prefix}-mixcloud:before { content: $fa-var-mixcloud; }
.#{$fa-css-prefix}-scribd:before { content: $fa-var-scribd; }
.#{$fa-css-prefix}-pause-circle:before { content: $fa-var-pause-circle; }
.#{$fa-css-prefix}-pause-circle-o:before { content: $fa-var-pause-circle-o; }
.#{$fa-css-prefix}-stop-circle:before { content: $fa-var-stop-circle; }
.#{$fa-css-prefix}-stop-circle-o:before { content: $fa-var-stop-circle-o; }
.#{$fa-css-prefix}-shopping-bag:before { content: $fa-var-shopping-bag; }
.#{$fa-css-prefix}-shopping-basket:before { content: $fa-var-shopping-basket; }
.#{$fa-css-prefix}-hashtag:before { content: $fa-var-hashtag; }
.#{$fa-css-prefix}-bluetooth:before { content: $fa-var-bluetooth; }
.#{$fa-css-prefix}-bluetooth-b:before { content: $fa-var-bluetooth-b; }
.#{$fa-css-prefix}-percent:before { content: $fa-var-percent; }
.#{$fa-css-prefix}-gitlab:before { content: $fa-var-gitlab; }
.#{$fa-css-prefix}-wpbeginner:before { content: $fa-var-wpbeginner; }
.#{$fa-css-prefix}-wpforms:before { content: $fa-var-wpforms; }
.#{$fa-css-prefix}-envira:before { content: $fa-var-envira; }
.#{$fa-css-prefix}-universal-access:before { content: $fa-var-universal-access; }
.#{$fa-css-prefix}-wheelchair-alt:before { content: $fa-var-wheelchair-alt; }
.#{$fa-css-prefix}-question-circle-o:before { content: $fa-var-question-circle-o; }
.#{$fa-css-prefix}-blind:before { content: $fa-var-blind; }
.#{$fa-css-prefix}-audio-description:before { content: $fa-var-audio-description; }
.#{$fa-css-prefix}-volume-control-phone:before { content: $fa-var-volume-control-phone; }
.#{$fa-css-prefix}-braille:before { content: $fa-var-braille; }
.#{$fa-css-prefix}-assistive-listening-systems:before { content: $fa-var-assistive-listening-systems; }
.#{$fa-css-prefix}-asl-interpreting:before,
.#{$fa-css-prefix}-american-sign-language-interpreting:before { content: $fa-var-american-sign-language-interpreting; }
.#{$fa-css-prefix}-deafness:before,
.#{$fa-css-prefix}-hard-of-hearing:before,
.#{$fa-css-prefix}-deaf:before { content: $fa-var-deaf; }
.#{$fa-css-prefix}-glide:before { content: $fa-var-glide; }
.#{$fa-css-prefix}-glide-g:before { content: $fa-var-glide-g; }
.#{$fa-css-prefix}-signing:before,
.#{$fa-css-prefix}-sign-language:before { content: $fa-var-sign-language; }
.#{$fa-css-prefix}-low-vision:before { content: $fa-var-low-vision; }
.#{$fa-css-prefix}-viadeo:before { content: $fa-var-viadeo; }
.#{$fa-css-prefix}-viadeo-square:before { content: $fa-var-viadeo-square; }
.#{$fa-css-prefix}-snapchat:before { content: $fa-var-snapchat; }
.#{$fa-css-prefix}-snapchat-ghost:before { content: $fa-var-snapchat-ghost; }
.#{$fa-css-prefix}-snapchat-square:before { content: $fa-var-snapchat-square; }
.#{$fa-css-prefix}-pied-piper:before { content: $fa-var-pied-piper; }
.#{$fa-css-prefix}-first-order:before { content: $fa-var-first-order; }
.#{$fa-css-prefix}-yoast:before { content: $fa-var-yoast; }
.#{$fa-css-prefix}-themeisle:before { content: $fa-var-themeisle; }
.#{$fa-css-prefix}-google-plus-circle:before,
.#{$fa-css-prefix}-google-plus-official:before { content: $fa-var-google-plus-official; }
.#{$fa-css-prefix}-fa:before,
.#{$fa-css-prefix}-font-awesome:before { content: $fa-var-font-awesome; }
.#{$fa-css-prefix}-handshake-o:before { content: $fa-var-handshake-o; }
.#{$fa-css-prefix}-envelope-open:before { content: $fa-var-envelope-open; }
.#{$fa-css-prefix}-envelope-open-o:before { content: $fa-var-envelope-open-o; }
.#{$fa-css-prefix}-linode:before { content: $fa-var-linode; }
.#{$fa-css-prefix}-address-book:before { content: $fa-var-address-book; }
.#{$fa-css-prefix}-address-book-o:before { content: $fa-var-address-book-o; }
.#{$fa-css-prefix}-vcard:before,
.#{$fa-css-prefix}-address-card:before { content: $fa-var-address-card; }
.#{$fa-css-prefix}-vcard-o:before,
.#{$fa-css-prefix}-address-card-o:before { content: $fa-var-address-card-o; }
.#{$fa-css-prefix}-user-circle:before { content: $fa-var-user-circle; }
.#{$fa-css-prefix}-user-circle-o:before { content: $fa-var-user-circle-o; }
.#{$fa-css-prefix}-user-o:before { content: $fa-var-user-o; }
.#{$fa-css-prefix}-id-badge:before { content: $fa-var-id-badge; }
.#{$fa-css-prefix}-drivers-license:before,
.#{$fa-css-prefix}-id-card:before { content: $fa-var-id-card; }
.#{$fa-css-prefix}-drivers-license-o:before,
.#{$fa-css-prefix}-id-card-o:before { content: $fa-var-id-card-o; }
.#{$fa-css-prefix}-quora:before { content: $fa-var-quora; }
.#{$fa-css-prefix}-free-code-camp:before { content: $fa-var-free-code-camp; }
.#{$fa-css-prefix}-telegram:before { content: $fa-var-telegram; }
.#{$fa-css-prefix}-thermometer-4:before,
.#{$fa-css-prefix}-thermometer:before,
.#{$fa-css-prefix}-thermometer-full:before { content: $fa-var-thermometer-full; }
.#{$fa-css-prefix}-thermometer-3:before,
.#{$fa-css-prefix}-thermometer-three-quarters:before { content: $fa-var-thermometer-three-quarters; }
.#{$fa-css-prefix}-thermometer-2:before,
.#{$fa-css-prefix}-thermometer-half:before { content: $fa-var-thermometer-half; }
.#{$fa-css-prefix}-thermometer-1:before,
.#{$fa-css-prefix}-thermometer-quarter:before { content: $fa-var-thermometer-quarter; }
.#{$fa-css-prefix}-thermometer-0:before,
.#{$fa-css-prefix}-thermometer-empty:before { content: $fa-var-thermometer-empty; }
.#{$fa-css-prefix}-shower:before { content: $fa-var-shower; }
.#{$fa-css-prefix}-bathtub:before,
.#{$fa-css-prefix}-s15:before,
.#{$fa-css-prefix}-bath:before { content: $fa-var-bath; }
.#{$fa-css-prefix}-podcast:before { content: $fa-var-podcast; }
.#{$fa-css-prefix}-window-maximize:before { content: $fa-var-window-maximize; }
.#{$fa-css-prefix}-window-minimize:before { content: $fa-var-window-minimize; }
.#{$fa-css-prefix}-window-restore:before { content: $fa-var-window-restore; }
.#{$fa-css-prefix}-times-rectangle:before,
.#{$fa-css-prefix}-window-close:before { content: $fa-var-window-close; }
.#{$fa-css-prefix}-times-rectangle-o:before,
.#{$fa-css-prefix}-window-close-o:before { content: $fa-var-window-close-o; }
.#{$fa-css-prefix}-bandcamp:before { content: $fa-var-bandcamp; }
.#{$fa-css-prefix}-grav:before { content: $fa-var-grav; }
.#{$fa-css-prefix}-etsy:before { content: $fa-var-etsy; }
.#{$fa-css-prefix}-imdb:before { content: $fa-var-imdb; }
.#{$fa-css-prefix}-ravelry:before { content: $fa-var-ravelry; }
.#{$fa-css-prefix}-eercast:before { content: $fa-var-eercast; }
.#{$fa-css-prefix}-microchip:before { content: $fa-var-microchip; }
.#{$fa-css-prefix}-snowflake-o:before { content: $fa-var-snowflake-o; }
.#{$fa-css-prefix}-superpowers:before { content: $fa-var-superpowers; }
.#{$fa-css-prefix}-wpexplorer:before { content: $fa-var-wpexplorer; }
.#{$fa-css-prefix}-meetup:before { content: $fa-var-meetup; }

View File

@ -12,15 +12,49 @@
}
@mixin fa-icon-rotate($degrees, $rotation) {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation});
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})";
-webkit-transform: rotate($degrees);
-ms-transform: rotate($degrees);
transform: rotate($degrees);
}
@mixin fa-icon-flip($horiz, $vert, $rotation) {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation});
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)";
-webkit-transform: scale($horiz, $vert);
-ms-transform: scale($horiz, $vert);
transform: scale($horiz, $vert);
}
// Only display content to screen readers. A la Bootstrap 4.
//
// See: http://a11yproject.com/posts/how-to-hide-content/
@mixin sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
border: 0;
}
// Use in conjunction with .sr-only to only display content when it's focused.
//
// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
//
// Credit: HTML5 Boilerplate
@mixin sr-only-focusable {
&:active,
&:focus {
position: static;
width: auto;
height: auto;
margin: 0;
overflow: visible;
clip: auto;
}
}

View File

@ -0,0 +1,5 @@
// Screen Readers
// -------------------------
.sr-only { @include sr-only(); }
.sr-only-focusable { @include sr-only-focusable(); }

View File

@ -4,14 +4,18 @@
$fa-font-path: "../fonts" !default;
$fa-font-size-base: 14px !default;
$fa-line-height-base: 1 !default;
//$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.4.0/fonts" !default; // for referencing Bootstrap CDN font files directly
//$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.7.0/fonts" !default; // for referencing Bootstrap CDN font files directly
$fa-css-prefix: fa !default;
$fa-version: "4.4.0" !default;
$fa-version: "4.7.0" !default;
$fa-border-color: #eee !default;
$fa-inverse: #fff !default;
$fa-li-width: (30em / 14) !default;
$fa-var-500px: "\f26e";
$fa-var-address-book: "\f2b9";
$fa-var-address-book-o: "\f2ba";
$fa-var-address-card: "\f2bb";
$fa-var-address-card-o: "\f2bc";
$fa-var-adjust: "\f042";
$fa-var-adn: "\f170";
$fa-var-align-center: "\f037";
@ -20,6 +24,7 @@ $fa-var-align-left: "\f036";
$fa-var-align-right: "\f038";
$fa-var-amazon: "\f270";
$fa-var-ambulance: "\f0f9";
$fa-var-american-sign-language-interpreting: "\f2a3";
$fa-var-anchor: "\f13d";
$fa-var-android: "\f17b";
$fa-var-angellist: "\f209";
@ -50,17 +55,24 @@ $fa-var-arrows: "\f047";
$fa-var-arrows-alt: "\f0b2";
$fa-var-arrows-h: "\f07e";
$fa-var-arrows-v: "\f07d";
$fa-var-asl-interpreting: "\f2a3";
$fa-var-assistive-listening-systems: "\f2a2";
$fa-var-asterisk: "\f069";
$fa-var-at: "\f1fa";
$fa-var-audio-description: "\f29e";
$fa-var-automobile: "\f1b9";
$fa-var-backward: "\f04a";
$fa-var-balance-scale: "\f24e";
$fa-var-ban: "\f05e";
$fa-var-bandcamp: "\f2d5";
$fa-var-bank: "\f19c";
$fa-var-bar-chart: "\f080";
$fa-var-bar-chart-o: "\f080";
$fa-var-barcode: "\f02a";
$fa-var-bars: "\f0c9";
$fa-var-bath: "\f2cd";
$fa-var-bathtub: "\f2cd";
$fa-var-battery: "\f240";
$fa-var-battery-0: "\f244";
$fa-var-battery-1: "\f243";
$fa-var-battery-2: "\f242";
@ -86,12 +98,16 @@ $fa-var-bitbucket: "\f171";
$fa-var-bitbucket-square: "\f172";
$fa-var-bitcoin: "\f15a";
$fa-var-black-tie: "\f27e";
$fa-var-blind: "\f29d";
$fa-var-bluetooth: "\f293";
$fa-var-bluetooth-b: "\f294";
$fa-var-bold: "\f032";
$fa-var-bolt: "\f0e7";
$fa-var-bomb: "\f1e2";
$fa-var-book: "\f02d";
$fa-var-bookmark: "\f02e";
$fa-var-bookmark-o: "\f097";
$fa-var-braille: "\f2a1";
$fa-var-briefcase: "\f0b1";
$fa-var-btc: "\f15a";
$fa-var-bug: "\f188";
@ -164,6 +180,7 @@ $fa-var-cny: "\f157";
$fa-var-code: "\f121";
$fa-var-code-fork: "\f126";
$fa-var-codepen: "\f1cb";
$fa-var-codiepie: "\f284";
$fa-var-coffee: "\f0f4";
$fa-var-cog: "\f013";
$fa-var-cogs: "\f085";
@ -182,6 +199,7 @@ $fa-var-copy: "\f0c5";
$fa-var-copyright: "\f1f9";
$fa-var-creative-commons: "\f25e";
$fa-var-credit-card: "\f09d";
$fa-var-credit-card-alt: "\f283";
$fa-var-crop: "\f125";
$fa-var-crosshairs: "\f05b";
$fa-var-css3: "\f13c";
@ -192,6 +210,8 @@ $fa-var-cutlery: "\f0f5";
$fa-var-dashboard: "\f0e4";
$fa-var-dashcube: "\f210";
$fa-var-database: "\f1c0";
$fa-var-deaf: "\f2a4";
$fa-var-deafness: "\f2a4";
$fa-var-dedent: "\f03b";
$fa-var-delicious: "\f1a5";
$fa-var-desktop: "\f108";
@ -202,17 +222,25 @@ $fa-var-dollar: "\f155";
$fa-var-dot-circle-o: "\f192";
$fa-var-download: "\f019";
$fa-var-dribbble: "\f17d";
$fa-var-drivers-license: "\f2c2";
$fa-var-drivers-license-o: "\f2c3";
$fa-var-dropbox: "\f16b";
$fa-var-drupal: "\f1a9";
$fa-var-edge: "\f282";
$fa-var-edit: "\f044";
$fa-var-eercast: "\f2da";
$fa-var-eject: "\f052";
$fa-var-ellipsis-h: "\f141";
$fa-var-ellipsis-v: "\f142";
$fa-var-empire: "\f1d1";
$fa-var-envelope: "\f0e0";
$fa-var-envelope-o: "\f003";
$fa-var-envelope-open: "\f2b6";
$fa-var-envelope-open-o: "\f2b7";
$fa-var-envelope-square: "\f199";
$fa-var-envira: "\f299";
$fa-var-eraser: "\f12d";
$fa-var-etsy: "\f2d7";
$fa-var-eur: "\f153";
$fa-var-euro: "\f153";
$fa-var-exchange: "\f0ec";
@ -226,6 +254,7 @@ $fa-var-external-link-square: "\f14c";
$fa-var-eye: "\f06e";
$fa-var-eye-slash: "\f070";
$fa-var-eyedropper: "\f1fb";
$fa-var-fa: "\f2b4";
$fa-var-facebook: "\f09a";
$fa-var-facebook-f: "\f09a";
$fa-var-facebook-official: "\f230";
@ -260,6 +289,7 @@ $fa-var-filter: "\f0b0";
$fa-var-fire: "\f06d";
$fa-var-fire-extinguisher: "\f134";
$fa-var-firefox: "\f269";
$fa-var-first-order: "\f2b0";
$fa-var-flag: "\f024";
$fa-var-flag-checkered: "\f11e";
$fa-var-flag-o: "\f11d";
@ -272,10 +302,13 @@ $fa-var-folder-o: "\f114";
$fa-var-folder-open: "\f07c";
$fa-var-folder-open-o: "\f115";
$fa-var-font: "\f031";
$fa-var-font-awesome: "\f2b4";
$fa-var-fonticons: "\f280";
$fa-var-fort-awesome: "\f286";
$fa-var-forumbee: "\f211";
$fa-var-forward: "\f04e";
$fa-var-foursquare: "\f180";
$fa-var-free-code-camp: "\f2c5";
$fa-var-frown-o: "\f119";
$fa-var-futbol-o: "\f1e3";
$fa-var-gamepad: "\f11b";
@ -294,15 +327,21 @@ $fa-var-git-square: "\f1d2";
$fa-var-github: "\f09b";
$fa-var-github-alt: "\f113";
$fa-var-github-square: "\f092";
$fa-var-gitlab: "\f296";
$fa-var-gittip: "\f184";
$fa-var-glass: "\f000";
$fa-var-glide: "\f2a5";
$fa-var-glide-g: "\f2a6";
$fa-var-globe: "\f0ac";
$fa-var-google: "\f1a0";
$fa-var-google-plus: "\f0d5";
$fa-var-google-plus-circle: "\f2b3";
$fa-var-google-plus-official: "\f2b3";
$fa-var-google-plus-square: "\f0d4";
$fa-var-google-wallet: "\f1ee";
$fa-var-graduation-cap: "\f19d";
$fa-var-gratipay: "\f184";
$fa-var-grav: "\f2d6";
$fa-var-group: "\f0c0";
$fa-var-h-square: "\f0fd";
$fa-var-hacker-news: "\f1d4";
@ -319,6 +358,9 @@ $fa-var-hand-rock-o: "\f255";
$fa-var-hand-scissors-o: "\f257";
$fa-var-hand-spock-o: "\f259";
$fa-var-hand-stop-o: "\f256";
$fa-var-handshake-o: "\f2b5";
$fa-var-hard-of-hearing: "\f2a4";
$fa-var-hashtag: "\f292";
$fa-var-hdd-o: "\f0a0";
$fa-var-header: "\f1dc";
$fa-var-headphones: "\f025";
@ -340,8 +382,12 @@ $fa-var-hourglass-start: "\f251";
$fa-var-houzz: "\f27c";
$fa-var-html5: "\f13b";
$fa-var-i-cursor: "\f246";
$fa-var-id-badge: "\f2c1";
$fa-var-id-card: "\f2c2";
$fa-var-id-card-o: "\f2c3";
$fa-var-ils: "\f20b";
$fa-var-image: "\f03e";
$fa-var-imdb: "\f2d8";
$fa-var-inbox: "\f01c";
$fa-var-indent: "\f03c";
$fa-var-industry: "\f275";
@ -379,6 +425,7 @@ $fa-var-line-chart: "\f201";
$fa-var-link: "\f0c1";
$fa-var-linkedin: "\f0e1";
$fa-var-linkedin-square: "\f08c";
$fa-var-linode: "\f2b8";
$fa-var-linux: "\f17c";
$fa-var-list: "\f03a";
$fa-var-list-alt: "\f022";
@ -390,6 +437,7 @@ $fa-var-long-arrow-down: "\f175";
$fa-var-long-arrow-left: "\f177";
$fa-var-long-arrow-right: "\f178";
$fa-var-long-arrow-up: "\f176";
$fa-var-low-vision: "\f2a8";
$fa-var-magic: "\f0d0";
$fa-var-magnet: "\f076";
$fa-var-mail-forward: "\f064";
@ -410,16 +458,20 @@ $fa-var-maxcdn: "\f136";
$fa-var-meanpath: "\f20c";
$fa-var-medium: "\f23a";
$fa-var-medkit: "\f0fa";
$fa-var-meetup: "\f2e0";
$fa-var-meh-o: "\f11a";
$fa-var-mercury: "\f223";
$fa-var-microchip: "\f2db";
$fa-var-microphone: "\f130";
$fa-var-microphone-slash: "\f131";
$fa-var-minus: "\f068";
$fa-var-minus-circle: "\f056";
$fa-var-minus-square: "\f146";
$fa-var-minus-square-o: "\f147";
$fa-var-mixcloud: "\f289";
$fa-var-mobile: "\f10b";
$fa-var-mobile-phone: "\f10b";
$fa-var-modx: "\f285";
$fa-var-money: "\f0d6";
$fa-var-moon-o: "\f186";
$fa-var-mortar-board: "\f19d";
@ -446,18 +498,22 @@ $fa-var-paperclip: "\f0c6";
$fa-var-paragraph: "\f1dd";
$fa-var-paste: "\f0ea";
$fa-var-pause: "\f04c";
$fa-var-pause-circle: "\f28b";
$fa-var-pause-circle-o: "\f28c";
$fa-var-paw: "\f1b0";
$fa-var-paypal: "\f1ed";
$fa-var-pencil: "\f040";
$fa-var-pencil-square: "\f14b";
$fa-var-pencil-square-o: "\f044";
$fa-var-percent: "\f295";
$fa-var-phone: "\f095";
$fa-var-phone-square: "\f098";
$fa-var-photo: "\f03e";
$fa-var-picture-o: "\f03e";
$fa-var-pie-chart: "\f200";
$fa-var-pied-piper: "\f1a7";
$fa-var-pied-piper: "\f2ae";
$fa-var-pied-piper-alt: "\f1a8";
$fa-var-pied-piper-pp: "\f1a7";
$fa-var-pinterest: "\f0d2";
$fa-var-pinterest-p: "\f231";
$fa-var-pinterest-square: "\f0d3";
@ -470,20 +526,26 @@ $fa-var-plus: "\f067";
$fa-var-plus-circle: "\f055";
$fa-var-plus-square: "\f0fe";
$fa-var-plus-square-o: "\f196";
$fa-var-podcast: "\f2ce";
$fa-var-power-off: "\f011";
$fa-var-print: "\f02f";
$fa-var-product-hunt: "\f288";
$fa-var-puzzle-piece: "\f12e";
$fa-var-qq: "\f1d6";
$fa-var-qrcode: "\f029";
$fa-var-question: "\f128";
$fa-var-question-circle: "\f059";
$fa-var-question-circle-o: "\f29c";
$fa-var-quora: "\f2c4";
$fa-var-quote-left: "\f10d";
$fa-var-quote-right: "\f10e";
$fa-var-ra: "\f1d0";
$fa-var-random: "\f074";
$fa-var-ravelry: "\f2d9";
$fa-var-rebel: "\f1d0";
$fa-var-recycle: "\f1b8";
$fa-var-reddit: "\f1a1";
$fa-var-reddit-alien: "\f281";
$fa-var-reddit-square: "\f1a2";
$fa-var-refresh: "\f021";
$fa-var-registered: "\f25d";
@ -493,6 +555,7 @@ $fa-var-reorder: "\f0c9";
$fa-var-repeat: "\f01e";
$fa-var-reply: "\f112";
$fa-var-reply-all: "\f122";
$fa-var-resistance: "\f1d0";
$fa-var-retweet: "\f079";
$fa-var-rmb: "\f157";
$fa-var-road: "\f018";
@ -505,9 +568,11 @@ $fa-var-rss-square: "\f143";
$fa-var-rub: "\f158";
$fa-var-ruble: "\f158";
$fa-var-rupee: "\f156";
$fa-var-s15: "\f2cd";
$fa-var-safari: "\f267";
$fa-var-save: "\f0c7";
$fa-var-scissors: "\f0c4";
$fa-var-scribd: "\f28a";
$fa-var-search: "\f002";
$fa-var-search-minus: "\f010";
$fa-var-search-plus: "\f00e";
@ -525,10 +590,15 @@ $fa-var-sheqel: "\f20b";
$fa-var-shield: "\f132";
$fa-var-ship: "\f21a";
$fa-var-shirtsinbulk: "\f214";
$fa-var-shopping-bag: "\f290";
$fa-var-shopping-basket: "\f291";
$fa-var-shopping-cart: "\f07a";
$fa-var-shower: "\f2cc";
$fa-var-sign-in: "\f090";
$fa-var-sign-language: "\f2a7";
$fa-var-sign-out: "\f08b";
$fa-var-signal: "\f012";
$fa-var-signing: "\f2a7";
$fa-var-simplybuilt: "\f215";
$fa-var-sitemap: "\f0e8";
$fa-var-skyatlas: "\f216";
@ -537,6 +607,10 @@ $fa-var-slack: "\f198";
$fa-var-sliders: "\f1de";
$fa-var-slideshare: "\f1e7";
$fa-var-smile-o: "\f118";
$fa-var-snapchat: "\f2ab";
$fa-var-snapchat-ghost: "\f2ac";
$fa-var-snapchat-square: "\f2ad";
$fa-var-snowflake-o: "\f2dc";
$fa-var-soccer-ball-o: "\f1e3";
$fa-var-sort: "\f0dc";
$fa-var-sort-alpha-asc: "\f15d";
@ -572,6 +646,8 @@ $fa-var-stethoscope: "\f0f1";
$fa-var-sticky-note: "\f249";
$fa-var-sticky-note-o: "\f24a";
$fa-var-stop: "\f04d";
$fa-var-stop-circle: "\f28d";
$fa-var-stop-circle-o: "\f28e";
$fa-var-street-view: "\f21d";
$fa-var-strikethrough: "\f0cc";
$fa-var-stumbleupon: "\f1a4";
@ -580,6 +656,7 @@ $fa-var-subscript: "\f12c";
$fa-var-subway: "\f239";
$fa-var-suitcase: "\f0f2";
$fa-var-sun-o: "\f185";
$fa-var-superpowers: "\f2dd";
$fa-var-superscript: "\f12b";
$fa-var-support: "\f1cd";
$fa-var-table: "\f0ce";
@ -589,6 +666,7 @@ $fa-var-tag: "\f02b";
$fa-var-tags: "\f02c";
$fa-var-tasks: "\f0ae";
$fa-var-taxi: "\f1ba";
$fa-var-telegram: "\f2c6";
$fa-var-television: "\f26c";
$fa-var-tencent-weibo: "\f1d5";
$fa-var-terminal: "\f120";
@ -597,6 +675,18 @@ $fa-var-text-width: "\f035";
$fa-var-th: "\f00a";
$fa-var-th-large: "\f009";
$fa-var-th-list: "\f00b";
$fa-var-themeisle: "\f2b2";
$fa-var-thermometer: "\f2c7";
$fa-var-thermometer-0: "\f2cb";
$fa-var-thermometer-1: "\f2ca";
$fa-var-thermometer-2: "\f2c9";
$fa-var-thermometer-3: "\f2c8";
$fa-var-thermometer-4: "\f2c7";
$fa-var-thermometer-empty: "\f2cb";
$fa-var-thermometer-full: "\f2c7";
$fa-var-thermometer-half: "\f2c9";
$fa-var-thermometer-quarter: "\f2ca";
$fa-var-thermometer-three-quarters: "\f2c8";
$fa-var-thumb-tack: "\f08d";
$fa-var-thumbs-down: "\f165";
$fa-var-thumbs-o-down: "\f088";
@ -606,6 +696,8 @@ $fa-var-ticket: "\f145";
$fa-var-times: "\f00d";
$fa-var-times-circle: "\f057";
$fa-var-times-circle-o: "\f05c";
$fa-var-times-rectangle: "\f2d3";
$fa-var-times-rectangle-o: "\f2d4";
$fa-var-tint: "\f043";
$fa-var-toggle-down: "\f150";
$fa-var-toggle-left: "\f191";
@ -636,28 +728,38 @@ $fa-var-twitter-square: "\f081";
$fa-var-umbrella: "\f0e9";
$fa-var-underline: "\f0cd";
$fa-var-undo: "\f0e2";
$fa-var-universal-access: "\f29a";
$fa-var-university: "\f19c";
$fa-var-unlink: "\f127";
$fa-var-unlock: "\f09c";
$fa-var-unlock-alt: "\f13e";
$fa-var-unsorted: "\f0dc";
$fa-var-upload: "\f093";
$fa-var-usb: "\f287";
$fa-var-usd: "\f155";
$fa-var-user: "\f007";
$fa-var-user-circle: "\f2bd";
$fa-var-user-circle-o: "\f2be";
$fa-var-user-md: "\f0f0";
$fa-var-user-o: "\f2c0";
$fa-var-user-plus: "\f234";
$fa-var-user-secret: "\f21b";
$fa-var-user-times: "\f235";
$fa-var-users: "\f0c0";
$fa-var-vcard: "\f2bb";
$fa-var-vcard-o: "\f2bc";
$fa-var-venus: "\f221";
$fa-var-venus-double: "\f226";
$fa-var-venus-mars: "\f228";
$fa-var-viacoin: "\f237";
$fa-var-viadeo: "\f2a9";
$fa-var-viadeo-square: "\f2aa";
$fa-var-video-camera: "\f03d";
$fa-var-vimeo: "\f27d";
$fa-var-vimeo-square: "\f194";
$fa-var-vine: "\f1ca";
$fa-var-vk: "\f189";
$fa-var-volume-control-phone: "\f2a0";
$fa-var-volume-down: "\f027";
$fa-var-volume-off: "\f026";
$fa-var-volume-up: "\f028";
@ -667,11 +769,20 @@ $fa-var-weibo: "\f18a";
$fa-var-weixin: "\f1d7";
$fa-var-whatsapp: "\f232";
$fa-var-wheelchair: "\f193";
$fa-var-wheelchair-alt: "\f29b";
$fa-var-wifi: "\f1eb";
$fa-var-wikipedia-w: "\f266";
$fa-var-window-close: "\f2d3";
$fa-var-window-close-o: "\f2d4";
$fa-var-window-maximize: "\f2d0";
$fa-var-window-minimize: "\f2d1";
$fa-var-window-restore: "\f2d2";
$fa-var-windows: "\f17a";
$fa-var-won: "\f159";
$fa-var-wordpress: "\f19a";
$fa-var-wpbeginner: "\f297";
$fa-var-wpexplorer: "\f2de";
$fa-var-wpforms: "\f298";
$fa-var-wrench: "\f0ad";
$fa-var-xing: "\f168";
$fa-var-xing-square: "\f169";
@ -682,6 +793,7 @@ $fa-var-yc: "\f23b";
$fa-var-yc-square: "\f1d4";
$fa-var-yelp: "\f1e9";
$fa-var-yen: "\f157";
$fa-var-yoast: "\f2b1";
$fa-var-youtube: "\f167";
$fa-var-youtube-play: "\f16a";
$fa-var-youtube-square: "\f166";

View File

@ -1,5 +1,5 @@
/*!
* Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome
* Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/
@ -15,3 +15,4 @@
@import "rotated-flipped";
@import "stacked";
@import "icons";
@import "screen-reader";

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 348 KiB

After

Width:  |  Height:  |  Size: 434 KiB